├── .dockerignore
├── .gitattributes
├── .gitignore
├── .ruby-version
├── Dockerfile
├── Gemfile
├── Gemfile.lock
├── LICENSE
├── Procfile.dev
├── README.md
├── Rakefile
├── app
├── assets
│ ├── builds
│ │ └── .keep
│ ├── config
│ │ └── manifest.js
│ ├── images
│ │ ├── .keep
│ │ └── logo.png
│ └── stylesheets
│ │ ├── application.css
│ │ └── application.tailwind.css
├── channels
│ └── application_cable
│ │ ├── channel.rb
│ │ └── connection.rb
├── controllers
│ ├── application_controller.rb
│ ├── checkouts_controller.rb
│ ├── concerns
│ │ └── .keep
│ └── static_controller.rb
├── helpers
│ └── application_helper.rb
├── javascript
│ ├── application.js
│ └── controllers
│ │ ├── application.js
│ │ └── index.js
├── jobs
│ └── application_job.rb
├── mailers
│ └── application_mailer.rb
├── models
│ ├── application_record.rb
│ ├── concerns
│ │ └── .keep
│ └── user.rb
├── services
│ ├── payment_succeeded_handler.rb
│ └── stripe_checkout.rb
└── views
│ ├── layouts
│ ├── application.html.erb
│ ├── common
│ │ ├── _footer.html.erb
│ │ └── _navigation.html.erb
│ ├── mailer.html.erb
│ └── mailer.text.erb
│ └── static
│ └── home.html.erb
├── bin
├── bundle
├── dev
├── docker-entrypoint
├── importmap
├── rails
├── rake
└── setup
├── config.ru
├── config
├── application.rb
├── boot.rb
├── cable.yml
├── credentials.yml.enc
├── credentials
│ └── development.yml.enc
├── database.yml
├── environment.rb
├── environments
│ ├── development.rb
│ ├── production.rb
│ └── test.rb
├── importmap.rb
├── initializers
│ ├── assets.rb
│ ├── content_security_policy.rb
│ ├── filter_parameter_logging.rb
│ ├── inflections.rb
│ ├── pay.rb
│ ├── permissions_policy.rb
│ └── stripe.rb
├── locales
│ └── en.yml
├── puma.rb
├── routes.rb
├── storage.yml
└── tailwind.config.js
├── db
├── migrate
│ ├── 20240513220536_create_users.rb
│ └── 20240513222045_create_pay_tables.pay.rb
├── schema.rb
└── seeds.rb
├── docs
├── demo.png
├── events.png
└── product-catalogue.png
├── lib
├── assets
│ └── .keep
└── tasks
│ └── .keep
├── log
└── .keep
├── public
├── 404.html
├── 422.html
├── 500.html
├── apple-touch-icon-precomposed.png
├── apple-touch-icon.png
├── favicon.ico
└── robots.txt
├── storage
└── .keep
├── test
├── application_system_test_case.rb
├── channels
│ └── application_cable
│ │ └── connection_test.rb
├── controllers
│ └── .keep
├── fixtures
│ ├── files
│ │ └── .keep
│ └── users.yml
├── helpers
│ └── .keep
├── integration
│ └── .keep
├── mailers
│ └── .keep
├── models
│ ├── .keep
│ └── user_test.rb
├── system
│ └── .keep
└── test_helper.rb
├── tmp
├── .keep
├── pids
│ └── .keep
└── storage
│ └── .keep
└── vendor
├── .keep
└── javascript
└── .keep
/.dockerignore:
--------------------------------------------------------------------------------
1 | # See https://docs.docker.com/engine/reference/builder/#dockerignore-file for more about ignoring files.
2 |
3 | # Ignore git directory.
4 | /.git/
5 |
6 | # Ignore bundler config.
7 | /.bundle
8 |
9 | # Ignore all environment files (except templates).
10 | /.env*
11 | !/.env*.erb
12 |
13 | # Ignore all default key files.
14 | /config/master.key
15 | /config/credentials/*.key
16 |
17 | # Ignore all logfiles and tempfiles.
18 | /log/*
19 | /tmp/*
20 | !/log/.keep
21 | !/tmp/.keep
22 |
23 | # Ignore pidfiles, but keep the directory.
24 | /tmp/pids/*
25 | !/tmp/pids/.keep
26 |
27 | # Ignore storage (uploaded files in development and any SQLite databases).
28 | /storage/*
29 | !/storage/.keep
30 | /tmp/storage/*
31 | !/tmp/storage/.keep
32 |
33 | # Ignore assets.
34 | /node_modules/
35 | /app/assets/builds/*
36 | !/app/assets/builds/.keep
37 | /public/assets
38 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # See https://git-scm.com/docs/gitattributes for more about git attribute files.
2 |
3 | # Mark the database schema as having been generated.
4 | db/schema.rb linguist-generated
5 |
6 | # Mark any vendored files as having been vendored.
7 | vendor/* linguist-vendored
8 | config/credentials/*.yml.enc diff=rails_credentials
9 | config/credentials.yml.enc diff=rails_credentials
10 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files for more about ignoring files.
2 | #
3 | # If you find yourself ignoring temporary files generated by your text editor
4 | # or operating system, you probably want to add a global ignore instead:
5 | # git config --global core.excludesfile '~/.gitignore_global'
6 |
7 | # Ignore bundler config.
8 | /.bundle
9 |
10 | # Ignore all environment files (except templates).
11 | /.env*
12 | !/.env*.erb
13 |
14 | # Ignore all logfiles and tempfiles.
15 | /log/*
16 | /tmp/*
17 | !/log/.keep
18 | !/tmp/.keep
19 |
20 | # Ignore pidfiles, but keep the directory.
21 | /tmp/pids/*
22 | !/tmp/pids/
23 | !/tmp/pids/.keep
24 |
25 | # Ignore storage (uploaded files in development and any SQLite databases).
26 | /storage/*
27 | !/storage/.keep
28 | /tmp/storage/*
29 | !/tmp/storage/
30 | !/tmp/storage/.keep
31 |
32 | /public/assets
33 |
34 | # Ignore master key for decrypting credentials and more.
35 | /config/master.key
36 |
37 | /app/assets/builds/*
38 | !/app/assets/builds/.keep
39 |
40 | /config/credentials/development.key
41 |
--------------------------------------------------------------------------------
/.ruby-version:
--------------------------------------------------------------------------------
1 | 3.3.0
2 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | # syntax = docker/dockerfile:1
2 |
3 | # Make sure RUBY_VERSION matches the Ruby version in .ruby-version and Gemfile
4 | ARG RUBY_VERSION=3.3.0
5 | FROM registry.docker.com/library/ruby:$RUBY_VERSION-slim as base
6 |
7 | # Rails app lives here
8 | WORKDIR /rails
9 |
10 | # Set production environment
11 | ENV RAILS_ENV="production" \
12 | BUNDLE_DEPLOYMENT="1" \
13 | BUNDLE_PATH="/usr/local/bundle" \
14 | BUNDLE_WITHOUT="development"
15 |
16 |
17 | # Throw-away build stage to reduce size of final image
18 | FROM base as build
19 |
20 | # Install packages needed to build gems
21 | RUN apt-get update -qq && \
22 | apt-get install --no-install-recommends -y build-essential git libvips pkg-config
23 |
24 | # Install application gems
25 | COPY Gemfile Gemfile.lock ./
26 | RUN bundle install && \
27 | rm -rf ~/.bundle/ "${BUNDLE_PATH}"/ruby/*/cache "${BUNDLE_PATH}"/ruby/*/bundler/gems/*/.git && \
28 | bundle exec bootsnap precompile --gemfile
29 |
30 | # Copy application code
31 | COPY . .
32 |
33 | # Precompile bootsnap code for faster boot times
34 | RUN bundle exec bootsnap precompile app/ lib/
35 |
36 | # Precompiling assets for production without requiring secret RAILS_MASTER_KEY
37 | RUN SECRET_KEY_BASE_DUMMY=1 ./bin/rails assets:precompile
38 |
39 |
40 | # Final stage for app image
41 | FROM base
42 |
43 | # Install packages needed for deployment
44 | RUN apt-get update -qq && \
45 | apt-get install --no-install-recommends -y curl libsqlite3-0 libvips && \
46 | rm -rf /var/lib/apt/lists /var/cache/apt/archives
47 |
48 | # Copy built artifacts: gems, application
49 | COPY --from=build /usr/local/bundle /usr/local/bundle
50 | COPY --from=build /rails /rails
51 |
52 | # Run and own only the runtime files as a non-root user for security
53 | RUN useradd rails --create-home --shell /bin/bash && \
54 | chown -R rails:rails db log storage tmp
55 | USER rails:rails
56 |
57 | # Entrypoint prepares the database.
58 | ENTRYPOINT ["/rails/bin/docker-entrypoint"]
59 |
60 | # Start the server by default, this can be overwritten at runtime
61 | EXPOSE 3000
62 | CMD ["./bin/rails", "server"]
63 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source "https://rubygems.org"
2 |
3 | ruby "3.3.0"
4 |
5 | gem "rails", "~> 7.1.3", ">= 7.1.3.2"
6 | gem "sprockets-rails"
7 | gem "sqlite3", "~> 1.4"
8 | gem "puma", ">= 5.0"
9 | gem "importmap-rails"
10 | gem "turbo-rails"
11 | gem "stimulus-rails"
12 | gem "tailwindcss-rails"
13 | gem "jbuilder"
14 | gem "redis", ">= 4.0.1"
15 |
16 | # Windows does not include zoneinfo files, so bundle the tzinfo-data gem
17 | gem "tzinfo-data", platforms: %i[windows jruby]
18 |
19 | gem "bootsnap", require: false
20 | group :development, :test do
21 | gem "debug", platforms: %i[mri windows]
22 | end
23 |
24 | group :development do
25 | gem "web-console"
26 | gem "standard"
27 | end
28 |
29 | group :test do
30 | gem "capybara"
31 | gem "selenium-webdriver"
32 | end
33 |
34 | ###################################################################
35 | ## payments ✓
36 | ####################################################################
37 | gem "pay", "~> 7.1.1"
38 | gem "receipts", "~> 2.0"
39 | gem "stripe", "~> 10.12"
40 | gem "pg", "~> 1.5"
41 | gem "faker", "~> 3.3"
42 |
--------------------------------------------------------------------------------
/Gemfile.lock:
--------------------------------------------------------------------------------
1 | GEM
2 | remote: https://rubygems.org/
3 | specs:
4 | actioncable (7.1.3.2)
5 | actionpack (= 7.1.3.2)
6 | activesupport (= 7.1.3.2)
7 | nio4r (~> 2.0)
8 | websocket-driver (>= 0.6.1)
9 | zeitwerk (~> 2.6)
10 | actionmailbox (7.1.3.2)
11 | actionpack (= 7.1.3.2)
12 | activejob (= 7.1.3.2)
13 | activerecord (= 7.1.3.2)
14 | activestorage (= 7.1.3.2)
15 | activesupport (= 7.1.3.2)
16 | mail (>= 2.7.1)
17 | net-imap
18 | net-pop
19 | net-smtp
20 | actionmailer (7.1.3.2)
21 | actionpack (= 7.1.3.2)
22 | actionview (= 7.1.3.2)
23 | activejob (= 7.1.3.2)
24 | activesupport (= 7.1.3.2)
25 | mail (~> 2.5, >= 2.5.4)
26 | net-imap
27 | net-pop
28 | net-smtp
29 | rails-dom-testing (~> 2.2)
30 | actionpack (7.1.3.2)
31 | actionview (= 7.1.3.2)
32 | activesupport (= 7.1.3.2)
33 | nokogiri (>= 1.8.5)
34 | racc
35 | rack (>= 2.2.4)
36 | rack-session (>= 1.0.1)
37 | rack-test (>= 0.6.3)
38 | rails-dom-testing (~> 2.2)
39 | rails-html-sanitizer (~> 1.6)
40 | actiontext (7.1.3.2)
41 | actionpack (= 7.1.3.2)
42 | activerecord (= 7.1.3.2)
43 | activestorage (= 7.1.3.2)
44 | activesupport (= 7.1.3.2)
45 | globalid (>= 0.6.0)
46 | nokogiri (>= 1.8.5)
47 | actionview (7.1.3.2)
48 | activesupport (= 7.1.3.2)
49 | builder (~> 3.1)
50 | erubi (~> 1.11)
51 | rails-dom-testing (~> 2.2)
52 | rails-html-sanitizer (~> 1.6)
53 | activejob (7.1.3.2)
54 | activesupport (= 7.1.3.2)
55 | globalid (>= 0.3.6)
56 | activemodel (7.1.3.2)
57 | activesupport (= 7.1.3.2)
58 | activerecord (7.1.3.2)
59 | activemodel (= 7.1.3.2)
60 | activesupport (= 7.1.3.2)
61 | timeout (>= 0.4.0)
62 | activestorage (7.1.3.2)
63 | actionpack (= 7.1.3.2)
64 | activejob (= 7.1.3.2)
65 | activerecord (= 7.1.3.2)
66 | activesupport (= 7.1.3.2)
67 | marcel (~> 1.0)
68 | activesupport (7.1.3.2)
69 | base64
70 | bigdecimal
71 | concurrent-ruby (~> 1.0, >= 1.0.2)
72 | connection_pool (>= 2.2.5)
73 | drb
74 | i18n (>= 1.6, < 2)
75 | minitest (>= 5.1)
76 | mutex_m
77 | tzinfo (~> 2.0)
78 | addressable (2.8.6)
79 | public_suffix (>= 2.0.2, < 6.0)
80 | ast (2.4.2)
81 | base64 (0.2.0)
82 | bigdecimal (3.1.7)
83 | bindex (0.8.1)
84 | bootsnap (1.18.3)
85 | msgpack (~> 1.2)
86 | builder (3.2.4)
87 | capybara (3.40.0)
88 | addressable
89 | matrix
90 | mini_mime (>= 0.1.3)
91 | nokogiri (~> 1.11)
92 | rack (>= 1.6.0)
93 | rack-test (>= 0.6.3)
94 | regexp_parser (>= 1.5, < 3.0)
95 | xpath (~> 3.2)
96 | concurrent-ruby (1.2.3)
97 | connection_pool (2.4.1)
98 | crass (1.0.6)
99 | date (3.3.4)
100 | debug (1.9.1)
101 | irb (~> 1.10)
102 | reline (>= 0.3.8)
103 | drb (2.2.1)
104 | erubi (1.12.0)
105 | faker (3.3.1)
106 | i18n (>= 1.8.11, < 2)
107 | globalid (1.2.1)
108 | activesupport (>= 6.1)
109 | i18n (1.14.4)
110 | concurrent-ruby (~> 1.0)
111 | importmap-rails (2.0.1)
112 | actionpack (>= 6.0.0)
113 | activesupport (>= 6.0.0)
114 | railties (>= 6.0.0)
115 | io-console (0.7.2)
116 | irb (1.12.0)
117 | rdoc
118 | reline (>= 0.4.2)
119 | jbuilder (2.11.5)
120 | actionview (>= 5.0.0)
121 | activesupport (>= 5.0.0)
122 | json (2.7.2)
123 | language_server-protocol (3.17.0.3)
124 | lint_roller (1.1.0)
125 | loofah (2.22.0)
126 | crass (~> 1.0.2)
127 | nokogiri (>= 1.12.0)
128 | mail (2.8.1)
129 | mini_mime (>= 0.1.1)
130 | net-imap
131 | net-pop
132 | net-smtp
133 | marcel (1.0.4)
134 | matrix (0.4.2)
135 | mini_mime (1.1.5)
136 | minitest (5.22.3)
137 | msgpack (1.7.2)
138 | mutex_m (0.2.0)
139 | net-imap (0.4.10)
140 | date
141 | net-protocol
142 | net-pop (0.1.2)
143 | net-protocol
144 | net-protocol (0.2.2)
145 | timeout
146 | net-smtp (0.4.0.1)
147 | net-protocol
148 | nio4r (2.7.1)
149 | nokogiri (1.16.3-aarch64-linux)
150 | racc (~> 1.4)
151 | nokogiri (1.16.3-arm-linux)
152 | racc (~> 1.4)
153 | nokogiri (1.16.3-arm64-darwin)
154 | racc (~> 1.4)
155 | nokogiri (1.16.3-x86-linux)
156 | racc (~> 1.4)
157 | nokogiri (1.16.3-x86_64-darwin)
158 | racc (~> 1.4)
159 | nokogiri (1.16.3-x86_64-linux)
160 | racc (~> 1.4)
161 | parallel (1.24.0)
162 | parser (3.3.1.0)
163 | ast (~> 2.4.1)
164 | racc
165 | pay (7.1.1)
166 | rails (>= 6.0.0)
167 | pdf-core (0.10.0)
168 | pg (1.5.6)
169 | prawn (2.5.0)
170 | matrix (~> 0.4)
171 | pdf-core (~> 0.10.0)
172 | ttfunk (~> 1.8)
173 | prawn-table (0.2.2)
174 | prawn (>= 1.3.0, < 3.0.0)
175 | psych (5.1.2)
176 | stringio
177 | public_suffix (5.0.4)
178 | puma (6.4.2)
179 | nio4r (~> 2.0)
180 | racc (1.7.3)
181 | rack (3.0.10)
182 | rack-session (2.0.0)
183 | rack (>= 3.0.0)
184 | rack-test (2.1.0)
185 | rack (>= 1.3)
186 | rackup (2.1.0)
187 | rack (>= 3)
188 | webrick (~> 1.8)
189 | rails (7.1.3.2)
190 | actioncable (= 7.1.3.2)
191 | actionmailbox (= 7.1.3.2)
192 | actionmailer (= 7.1.3.2)
193 | actionpack (= 7.1.3.2)
194 | actiontext (= 7.1.3.2)
195 | actionview (= 7.1.3.2)
196 | activejob (= 7.1.3.2)
197 | activemodel (= 7.1.3.2)
198 | activerecord (= 7.1.3.2)
199 | activestorage (= 7.1.3.2)
200 | activesupport (= 7.1.3.2)
201 | bundler (>= 1.15.0)
202 | railties (= 7.1.3.2)
203 | rails-dom-testing (2.2.0)
204 | activesupport (>= 5.0.0)
205 | minitest
206 | nokogiri (>= 1.6)
207 | rails-html-sanitizer (1.6.0)
208 | loofah (~> 2.21)
209 | nokogiri (~> 1.14)
210 | railties (7.1.3.2)
211 | actionpack (= 7.1.3.2)
212 | activesupport (= 7.1.3.2)
213 | irb
214 | rackup (>= 1.0.0)
215 | rake (>= 12.2)
216 | thor (~> 1.0, >= 1.2.2)
217 | zeitwerk (~> 2.6)
218 | rainbow (3.1.1)
219 | rake (13.1.0)
220 | rdoc (6.6.3.1)
221 | psych (>= 4.0.0)
222 | receipts (2.4.0)
223 | prawn (>= 1.3.0, < 3.0.0)
224 | prawn-table (~> 0.2.1)
225 | redis (5.1.0)
226 | redis-client (>= 0.17.0)
227 | redis-client (0.21.1)
228 | connection_pool
229 | regexp_parser (2.9.0)
230 | reline (0.4.3)
231 | io-console (~> 0.5)
232 | rexml (3.2.6)
233 | rubocop (1.62.1)
234 | json (~> 2.3)
235 | language_server-protocol (>= 3.17.0)
236 | parallel (~> 1.10)
237 | parser (>= 3.3.0.2)
238 | rainbow (>= 2.2.2, < 4.0)
239 | regexp_parser (>= 1.8, < 3.0)
240 | rexml (>= 3.2.5, < 4.0)
241 | rubocop-ast (>= 1.31.1, < 2.0)
242 | ruby-progressbar (~> 1.7)
243 | unicode-display_width (>= 2.4.0, < 3.0)
244 | rubocop-ast (1.31.3)
245 | parser (>= 3.3.1.0)
246 | rubocop-performance (1.20.2)
247 | rubocop (>= 1.48.1, < 2.0)
248 | rubocop-ast (>= 1.30.0, < 2.0)
249 | ruby-progressbar (1.13.0)
250 | rubyzip (2.3.2)
251 | selenium-webdriver (4.18.1)
252 | base64 (~> 0.2)
253 | rexml (~> 3.2, >= 3.2.5)
254 | rubyzip (>= 1.2.2, < 3.0)
255 | websocket (~> 1.0)
256 | sprockets (4.2.1)
257 | concurrent-ruby (~> 1.0)
258 | rack (>= 2.2.4, < 4)
259 | sprockets-rails (3.4.2)
260 | actionpack (>= 5.2)
261 | activesupport (>= 5.2)
262 | sprockets (>= 3.0.0)
263 | sqlite3 (1.7.3-aarch64-linux)
264 | sqlite3 (1.7.3-arm-linux)
265 | sqlite3 (1.7.3-arm64-darwin)
266 | sqlite3 (1.7.3-x86-linux)
267 | sqlite3 (1.7.3-x86_64-darwin)
268 | sqlite3 (1.7.3-x86_64-linux)
269 | standard (1.35.1)
270 | language_server-protocol (~> 3.17.0.2)
271 | lint_roller (~> 1.0)
272 | rubocop (~> 1.62.0)
273 | standard-custom (~> 1.0.0)
274 | standard-performance (~> 1.3)
275 | standard-custom (1.0.2)
276 | lint_roller (~> 1.0)
277 | rubocop (~> 1.50)
278 | standard-performance (1.3.1)
279 | lint_roller (~> 1.1)
280 | rubocop-performance (~> 1.20.2)
281 | stimulus-rails (1.3.3)
282 | railties (>= 6.0.0)
283 | stringio (3.1.0)
284 | stripe (10.15.0)
285 | tailwindcss-rails (2.3.0)
286 | railties (>= 6.0.0)
287 | tailwindcss-rails (2.3.0-aarch64-linux)
288 | railties (>= 6.0.0)
289 | tailwindcss-rails (2.3.0-arm-linux)
290 | railties (>= 6.0.0)
291 | tailwindcss-rails (2.3.0-arm64-darwin)
292 | railties (>= 6.0.0)
293 | tailwindcss-rails (2.3.0-x86_64-darwin)
294 | railties (>= 6.0.0)
295 | tailwindcss-rails (2.3.0-x86_64-linux)
296 | railties (>= 6.0.0)
297 | thor (1.3.1)
298 | timeout (0.4.1)
299 | ttfunk (1.8.0)
300 | bigdecimal (~> 3.1)
301 | turbo-rails (2.0.5)
302 | actionpack (>= 6.0.0)
303 | activejob (>= 6.0.0)
304 | railties (>= 6.0.0)
305 | tzinfo (2.0.6)
306 | concurrent-ruby (~> 1.0)
307 | unicode-display_width (2.5.0)
308 | web-console (4.2.1)
309 | actionview (>= 6.0.0)
310 | activemodel (>= 6.0.0)
311 | bindex (>= 0.4.0)
312 | railties (>= 6.0.0)
313 | webrick (1.8.1)
314 | websocket (1.2.10)
315 | websocket-driver (0.7.6)
316 | websocket-extensions (>= 0.1.0)
317 | websocket-extensions (0.1.5)
318 | xpath (3.2.0)
319 | nokogiri (~> 1.8)
320 | zeitwerk (2.6.13)
321 |
322 | PLATFORMS
323 | aarch64-linux
324 | arm-linux
325 | arm64-darwin
326 | x86-linux
327 | x86_64-darwin
328 | x86_64-linux
329 |
330 | DEPENDENCIES
331 | bootsnap
332 | capybara
333 | debug
334 | faker (~> 3.3)
335 | importmap-rails
336 | jbuilder
337 | pay (~> 7.1.1)
338 | pg (~> 1.5)
339 | puma (>= 5.0)
340 | rails (~> 7.1.3, >= 7.1.3.2)
341 | receipts (~> 2.0)
342 | redis (>= 4.0.1)
343 | selenium-webdriver
344 | sprockets-rails
345 | sqlite3 (~> 1.4)
346 | standard
347 | stimulus-rails
348 | stripe (~> 10.12)
349 | tailwindcss-rails
350 | turbo-rails
351 | tzinfo-data
352 | web-console
353 |
354 | RUBY VERSION
355 | ruby 3.3.0p0
356 |
357 | BUNDLED WITH
358 | 2.5.3
359 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 Shey Sewani
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Procfile.dev:
--------------------------------------------------------------------------------
1 | web: bin/rails server
2 | css: bin/rails tailwindcss:watch
3 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # rails-pay-checkout-demo
2 |
3 | Stripe integration with Rails and the Pay gem for Subscription Billing.
4 |
5 | 
6 |
7 | ## Table of Contents
8 |
9 | - [Running The App](#running-the-app)
10 | - [Stripe Checkout](#stripe-checkout)
11 | - [The sequence of events for Stripe Checkout are](#the-sequence-of-events-for-stripe-checkout-are)
12 | - [Products and Prices](#products-and-prices)
13 | - [The Pay Integration](#the-pay-integration)
14 | - [Set up your payment processor credentials and initializers](#set-up-your-payment-processor-credentials-and-initializers)
15 | - [Stripe Credentials](#stripe-credentials)
16 | - [Initializers](#initializers)
17 | - [Add the `pay_customer` Class Method to the User Model](#add-the-pay_customer-class-method-to-the-user-model)
18 | - [Request a Checkout URL and Redirect the User](#request-a-checkout-url-and-redirect-the-user)
19 | - [Checkout URL Generator](#checkout-url-generator)
20 | - [Handle Stripe Events](#handle-stripe-events)
21 | - [Stripe CLI](#stripe-cli)
22 | - [The Payment Succeed Event](#the-payment-succeed-event)
23 | - [Appendix](#appendix)
24 | - [Relevant Files](#relevant-files)
25 | - [Related Articles](#related-articles)
26 |
27 | ## Running the App
28 |
29 | ```bash
30 | bin/setup # Installs gems, creates the database, and runs seeds
31 | bin/dev # Starts the Rails server with foreman (Procfile.dev)
32 | ```
33 |
34 | Note: You’ll need the Stripe CLI running for webhooks to be received
35 |
36 | ```bash
37 | $ stripe listen --forward-to localhost:3000/pay/webhooks/stripe
38 | ```
39 |
40 | ## Stripe Checkout
41 |
42 | **Stripe Checkout** Stripe Checkout is a checkout flow where Stripe hosts the payments page that collects the credit card details.
43 |
44 | ### The sequence of events for Stripe Checkout are:
45 |
46 | 1. A checkout session is created.
47 | 1. The user is redirected to Stripe's payment page.
48 | 1. The user completed a payment on Stripe's page.
49 | 1. The user is redirected back to the app.
50 | 1. The app receives a "payment success" webhook from Stripe.
51 |
52 | ### Products and Prices
53 |
54 | [Products](https://dashboard.stripe.com/products) and prices are used manage subscription billing. In this app, each plan is its own product, and each product has a single [price](app/controllers/checkouts_controller.rb).
55 |
56 | 
57 |
58 | ## The Pay Integration
59 |
60 | To complete the integration
61 |
62 | 1. Set up your payment processor credentials and [initializers](#initializers).
63 | 1. Add the `pay_customer` the [User](#pay_customer) Model.
64 | 1. Request a Checkout URL and [Redirect](#redirect) the User
65 | 1. Handle Stripe [Events](#events)
66 |
67 | ### Set up your payment processor credentials and initializers {#initializers}
68 |
69 | #### Stripe Credentials
70 |
71 | Follow Pay's [configuration instructions](https://github.com/pay-rails/pay/blob/main/docs/2_configuration.md#configuring-pay) to set up your Stripe credentials.
72 |
73 | #### Initializers
74 |
75 | 1. Create or update the [stripe.rb](config/initializers/stripe.rb) initializer.
76 | 1. Create the [pay.rb](config/initializers/pay.rb) initializer.
77 |
78 | ### Add the `pay_customer` Class Method to the User Model
79 |
80 | 1. **Generate the Pay Models**:
81 |
82 | - Pay is already installed. For a fresh app, run `bin/rails pay:install:migrations` to create the necessary Pay models.
83 |
84 | 2. **Update the User Model**:
85 | - Add `pay_customer` to the User model:
86 | ```ruby
87 | class User < ApplicationRecord
88 | pay_customer
89 | end
90 | ```
91 |
92 | Including `pay_customer` in the User model establishes an internal association between the User and the `Pay::Customer` model. This association uses the `owner_type` and `owner_id` fields.
93 |
94 | `payment_processor` is the entry point to Pay's functionality. By including pay_customer in the User model, the payment_processor method becomes available on all User instances, providing access to customer, subscription, and charge models.
95 |
96 | ### Request a Checkout URL and Redirect the User
97 |
98 | Using Pay, request a checkout URL from Stripe and then redirect the user to that URL. Once the transaction is complete, Stripe will return the user to the URL defined by `success_URL`, along with a `session_id`.
99 |
100 | #### Checkout URL Generator
101 |
102 | ```ruby
103 | def checkout
104 | user.payment_processor.checkout(
105 | mode: "subscription",
106 | line_items: stripe_price_id,
107 | success_url: success_url,
108 | billing_address_collection: "auto",
109 | allow_promotion_codes: false,
110 | )
111 | end
112 | ```
113 |
114 | ### Handle Stripe Events
115 |
116 | #### Stripe CLI
117 |
118 | The Pay Gem requires Stripe Webhooks to function properly. When building the Pay integration locally, you'll need to set up the [Stripe CLI](https://docs.stripe.com/stripe-cli) and have it running to forward the events to your app.
119 |
120 | ```bash
121 | $ stripe listen --forward-to localhost:3000/pay/webhooks/stripe
122 | ```
123 |
124 | Update your stripe credentials to include the webhook signing secreted generated by the CLI.
125 |
126 | 
127 |
128 | #### The Payment Succeed Event
129 |
130 | When Stripe processes a payment successfully, it triggers the invoice.payment_succeeded [event] (https://github.com/pay-rails/pay/tree/main/test/pay/stripe/webhooks). Use this event to initiate other workflows in Rails. For instance, access a paid feature or a custom receipt.
131 |
132 | ```ruby
133 | class PaymentSucceededHandler
134 | def call(event)
135 | pay_charge = Pay::Stripe::Charge.sync(
136 | event.data.object.charge,
137 | stripe_account: event.try(:account)
138 | )
139 | return if pay_charge.nil?
140 |
141 | adjust_user(pay_charge)
142 | end
143 |
144 | def adjust_user(pay_charge)
145 | user = pay_charge.customer.owner
146 | user.update!(updated_at: Time.zone.now)
147 | end
148 | end
149 | ```
150 |
151 | ## Appendix
152 |
153 | ### Relevant Files
154 |
155 | 1. [user.rb](app/models/user.rb)
156 | 1. [app/controllers/static_controller.rb](app/controllers/static_controller.rb)
157 | 1. [app/controllers/checkouts_controller.rb](app/controllers/checkouts_controller.rb?#L23)
158 | 1. [config/environments/development.rb](config/environments/development.rb?#L78)
159 | 1. [app/services/stripe_checkout.rb](app/services/stripe_checkout.rb)
160 | 1. [app/services/payment_succeded_handler.rb](app/services/payment_succeded_handler.rb)
161 | 1. [config/initializers/pay.rb](config/initializers/pay.rb)
162 | 1. [config/initializers/stripe.rb](config/initializers/stripe.rb)
163 |
164 | ### Related Articles
165 |
166 | 1. [Stripe Checkout](https://github.com/pay-rails/pay/blob/main/docs/stripe/8_stripe_checkout.md)
167 | 1. [Routes and Webhooks](https://github.com/pay-rails/pay/blob/main/docs/7_webhooks.md)
168 | 1. [Stripe Webhooks](https://github.com/pay-rails/pay/blob/main/docs/stripe/5_webhooks.md)
169 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | # Add your own tasks in files placed in lib/tasks ending in .rake,
2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
3 |
4 | require_relative "config/application"
5 |
6 | Rails.application.load_tasks
7 |
--------------------------------------------------------------------------------
/app/assets/builds/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shey/rails-pay-checkout-demo/b4d6d981ec77522487fc14c26191775ba9fbc6bc/app/assets/builds/.keep
--------------------------------------------------------------------------------
/app/assets/config/manifest.js:
--------------------------------------------------------------------------------
1 | //= link_tree ../images
2 | //= link_directory ../stylesheets .css
3 | //= link_tree ../../javascript .js
4 | //= link_tree ../../../vendor/javascript .js
5 | //= link_tree ../builds
6 |
--------------------------------------------------------------------------------
/app/assets/images/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shey/rails-pay-checkout-demo/b4d6d981ec77522487fc14c26191775ba9fbc6bc/app/assets/images/.keep
--------------------------------------------------------------------------------
/app/assets/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shey/rails-pay-checkout-demo/b4d6d981ec77522487fc14c26191775ba9fbc6bc/app/assets/images/logo.png
--------------------------------------------------------------------------------
/app/assets/stylesheets/application.css:
--------------------------------------------------------------------------------
1 | /*
2 | * This is a manifest file that'll be compiled into application.css, which will include all the files
3 | * listed below.
4 | *
5 | * Any CSS (and SCSS, if configured) file within this directory, lib/assets/stylesheets, or any plugin's
6 | * vendor/assets/stylesheets directory can be referenced here using a relative path.
7 | *
8 | * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9 | * compiled file so the styles you add here take precedence over styles defined in any other CSS
10 | * files in this directory. Styles in this file should be added after the last require_* statement.
11 | * It is generally better to create a new file per style scope.
12 | *
13 | *= require_tree .
14 | *= require_self
15 | */
16 | body {
17 | font-family: 'Montserrat', sans-serif;
18 | }
19 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/application.tailwind.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | /*
6 |
7 | @layer components {
8 | .btn-primary {
9 | @apply py-2 px-4 bg-blue-200;
10 | }
11 | }
12 |
13 | */
14 |
--------------------------------------------------------------------------------
/app/channels/application_cable/channel.rb:
--------------------------------------------------------------------------------
1 | module ApplicationCable
2 | class Channel < ActionCable::Channel::Base
3 | end
4 | end
5 |
--------------------------------------------------------------------------------
/app/channels/application_cable/connection.rb:
--------------------------------------------------------------------------------
1 | module ApplicationCable
2 | class Connection < ActionCable::Connection::Base
3 | end
4 | end
5 |
--------------------------------------------------------------------------------
/app/controllers/application_controller.rb:
--------------------------------------------------------------------------------
1 | class ApplicationController < ActionController::Base
2 | end
3 |
--------------------------------------------------------------------------------
/app/controllers/checkouts_controller.rb:
--------------------------------------------------------------------------------
1 | class CheckoutsController < ApplicationController
2 | before_action :set_price_id, only: %i[show]
3 |
4 | def show
5 | redirect_to checkout_url, allow_other_host: true, status: :see_other
6 | end
7 |
8 | private
9 |
10 | def checkout_url
11 | StripeCheckout.new(user, @stripe_price_id).url
12 | end
13 |
14 | def user
15 | User.first
16 | end
17 |
18 | def set_price_id
19 | @stripe_price_id = price_id_map[plan_name]
20 | end
21 |
22 | def plan_name
23 | params[:plan].to_sym
24 | end
25 |
26 | def price_id_map
27 | {
28 | enterprise: "price_1Ow66SAWnWpxHPD5qEZoUi4O",
29 | professional: "price_1Ow66SAWnWpxHPD5K270wTji",
30 | hobby: "price_1Ow66RAWnWpxHPD5xV9xUCyD"
31 | }
32 | end
33 | end
34 |
--------------------------------------------------------------------------------
/app/controllers/concerns/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shey/rails-pay-checkout-demo/b4d6d981ec77522487fc14c26191775ba9fbc6bc/app/controllers/concerns/.keep
--------------------------------------------------------------------------------
/app/controllers/static_controller.rb:
--------------------------------------------------------------------------------
1 | class StaticController < ApplicationController
2 | before_action :set_user, only: %i[home]
3 | before_action :set_subscription, only: %i[home]
4 |
5 | def home
6 | if @subscription.present?
7 | processor_subscription = Stripe::Subscription.retrieve(@subscription.processor_id)
8 | @price = Stripe::Price.retrieve(processor_subscription.items.data.first.price.id)
9 | end
10 | end
11 |
12 | private
13 |
14 | def set_user
15 | @user = User.first
16 | end
17 |
18 | def set_subscription
19 | @subscription = @user.payment_processor.subscriptions.first
20 | end
21 | end
22 |
--------------------------------------------------------------------------------
/app/helpers/application_helper.rb:
--------------------------------------------------------------------------------
1 | module ApplicationHelper
2 | end
3 |
--------------------------------------------------------------------------------
/app/javascript/application.js:
--------------------------------------------------------------------------------
1 | // Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails
2 | import "@hotwired/turbo-rails"
3 | import "controllers"
4 |
--------------------------------------------------------------------------------
/app/javascript/controllers/application.js:
--------------------------------------------------------------------------------
1 | import { Application } from "@hotwired/stimulus"
2 |
3 | const application = Application.start()
4 |
5 | // Configure Stimulus development experience
6 | application.debug = false
7 | window.Stimulus = application
8 |
9 | export { application }
10 |
--------------------------------------------------------------------------------
/app/javascript/controllers/index.js:
--------------------------------------------------------------------------------
1 | // Import and register all your controllers from the importmap under controllers/*
2 |
3 | import { application } from "controllers/application"
4 |
5 | // Eager load all controllers defined in the import map under controllers/**/*_controller
6 | import { eagerLoadControllersFrom } from "@hotwired/stimulus-loading"
7 | eagerLoadControllersFrom("controllers", application)
8 |
9 | // Lazy load controllers as they appear in the DOM (remember not to preload controllers in import map!)
10 | // import { lazyLoadControllersFrom } from "@hotwired/stimulus-loading"
11 | // lazyLoadControllersFrom("controllers", application)
12 |
--------------------------------------------------------------------------------
/app/jobs/application_job.rb:
--------------------------------------------------------------------------------
1 | class ApplicationJob < ActiveJob::Base
2 | # Automatically retry jobs that encountered a deadlock
3 | # retry_on ActiveRecord::Deadlocked
4 |
5 | # Most jobs are safe to ignore if the underlying records are no longer available
6 | # discard_on ActiveJob::DeserializationError
7 | end
8 |
--------------------------------------------------------------------------------
/app/mailers/application_mailer.rb:
--------------------------------------------------------------------------------
1 | class ApplicationMailer < ActionMailer::Base
2 | default from: "from@example.com"
3 | layout "mailer"
4 | end
5 |
--------------------------------------------------------------------------------
/app/models/application_record.rb:
--------------------------------------------------------------------------------
1 | class ApplicationRecord < ActiveRecord::Base
2 | primary_abstract_class
3 | end
4 |
--------------------------------------------------------------------------------
/app/models/concerns/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shey/rails-pay-checkout-demo/b4d6d981ec77522487fc14c26191775ba9fbc6bc/app/models/concerns/.keep
--------------------------------------------------------------------------------
/app/models/user.rb:
--------------------------------------------------------------------------------
1 | class User < ApplicationRecord
2 | ################################
3 | ## Stripe/Payments
4 | ################################
5 | pay_customer default_payment_processor: :stripe
6 | end
7 |
--------------------------------------------------------------------------------
/app/services/payment_succeeded_handler.rb:
--------------------------------------------------------------------------------
1 | # app/services/payment_succeeded_handler.rb
2 | class PaymentSucceededHandler
3 | def call(event)
4 | pay_charge = Pay::Stripe::Charge.sync(
5 | event.data.object.charge,
6 | stripe_account: event.try(:account)
7 | )
8 | return if pay_charge.nil?
9 |
10 | adjust_user(pay_charge)
11 | end
12 |
13 | def adjust_user(pay_charge)
14 | user = pay_charge.customer.owner
15 | user.update!(updated_at: Time.zone.now)
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/app/services/stripe_checkout.rb:
--------------------------------------------------------------------------------
1 | class StripeCheckout
2 | include Rails.application.routes.url_helpers
3 |
4 | attr_reader :user, :stripe_price_id
5 |
6 | def initialize(user, stripe_price_id)
7 | @user = user
8 | @stripe_price_id = stripe_price_id
9 | end
10 |
11 | def url
12 | checkout.url
13 | end
14 |
15 | private
16 |
17 | def success_url
18 | root_url
19 | end
20 |
21 | def checkout
22 | user.payment_processor.checkout(
23 | mode: "subscription",
24 | line_items: stripe_price_id,
25 | success_url: success_url,
26 | billing_address_collection: "auto",
27 | allow_promotion_codes: false
28 | )
29 | end
30 | end
31 |
--------------------------------------------------------------------------------
/app/views/layouts/application.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Subscription Billing With Stripe and the Pay Gem
5 |
6 | <%= csrf_meta_tags %>
7 | <%= csp_meta_tag %>
8 | <%= stylesheet_link_tag "tailwind", "inter-font", "data-turbo-track": "reload" %>
9 | <%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
10 | <%= javascript_importmap_tags %>
11 |
12 |
13 |
14 | <%= render "layouts/common/navigation" %>
15 |
16 | <%= yield %>
17 |
18 | <%= render "layouts/common/footer"%>
19 |
20 |
21 |
--------------------------------------------------------------------------------
/app/views/layouts/common/_footer.html.erb:
--------------------------------------------------------------------------------
1 |
3 |
--------------------------------------------------------------------------------
/app/views/layouts/common/_navigation.html.erb:
--------------------------------------------------------------------------------
1 |
3 |
--------------------------------------------------------------------------------
/app/views/layouts/mailer.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
9 |
10 |
11 | <%= yield %>
12 |
13 |
14 |
--------------------------------------------------------------------------------
/app/views/layouts/mailer.text.erb:
--------------------------------------------------------------------------------
1 | <%= yield %>
2 |
--------------------------------------------------------------------------------
/app/views/static/home.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
Pick Your Plan
5 |
6 |
7 |
8 |
9 |
Hobby
10 |
Perfect for personal projects and small-scale apps.
11 | <%= link_to 'Get Started', checkouts_path(plan: 'hobby'), class: 'bg-blue-500 hover:bg-pink-500 text-white py-2 px-6 rounded-full transition-colors duration-300', data: { turbo: false } %>
12 |
13 |
14 |
15 |
Professional
16 |
Ideal for freelancers and growing businesses.
17 | <%= link_to 'Get Started', checkouts_path(plan: 'professional'), class: 'bg-green-500 hover:bg-pink-500 text-white py-2 px-6 rounded-full transition-colors duration-300', data: { turbo: false } %>
18 |
19 |
20 |
21 |
Enterprise
22 |
Best for large organizations with advanced needs.
23 | <%= link_to 'Get Started', checkouts_path(plan: 'enterprise'), class: 'bg-purple-500 hover:bg-pink-500 text-white py-2 px-6 rounded-full transition-colors duration-300', data: { turbo: false } %>
24 |
25 |
26 |
27 |
28 |
29 |
30 |
Your Subscription Details
31 |
32 |
33 |
34 |
User
35 | <% if @subscription.present? %>
36 |
37 | You are currently subscribed to the
38 | <%= @subscription.name %> plan.
39 |
40 |
41 |
42 | Renewal Date:
43 | <%= @subscription.current_period_end.strftime('%B %d, %Y') %>
44 |
45 |
46 | Price:
47 |
48 | <%= number_to_currency(@price.unit_amount / 100.0, unit: '$', precision: 2) %>/month
49 |
50 |
51 |
52 | <% else %>
53 |
You are not currently subscribed to any plan.
54 | <% end %>
55 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/bin/bundle:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | #
5 | # This file was generated by Bundler.
6 | #
7 | # The application 'bundle' is installed as part of a gem, and
8 | # this file is here to facilitate running it.
9 | #
10 |
11 | require "rubygems"
12 |
13 | m = Module.new do
14 | module_function
15 |
16 | def invoked_as_script?
17 | File.expand_path($0) == File.expand_path(__FILE__)
18 | end
19 |
20 | def env_var_version
21 | ENV["BUNDLER_VERSION"]
22 | end
23 |
24 | def cli_arg_version
25 | return unless invoked_as_script? # don't want to hijack other binstubs
26 | return unless "update".start_with?(ARGV.first || " ") # must be running `bundle update`
27 | bundler_version = nil
28 | update_index = nil
29 | ARGV.each_with_index do |a, i|
30 | if update_index && update_index.succ == i && a.match?(Gem::Version::ANCHORED_VERSION_PATTERN)
31 | bundler_version = a
32 | end
33 | next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/
34 | bundler_version = $1
35 | update_index = i
36 | end
37 | bundler_version
38 | end
39 |
40 | def gemfile
41 | gemfile = ENV["BUNDLE_GEMFILE"]
42 | return gemfile if gemfile && !gemfile.empty?
43 |
44 | File.expand_path("../Gemfile", __dir__)
45 | end
46 |
47 | def lockfile
48 | lockfile =
49 | case File.basename(gemfile)
50 | when "gems.rb" then gemfile.sub(/\.rb$/, ".locked")
51 | else "#{gemfile}.lock"
52 | end
53 | File.expand_path(lockfile)
54 | end
55 |
56 | def lockfile_version
57 | return unless File.file?(lockfile)
58 | lockfile_contents = File.read(lockfile)
59 | return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/
60 | Regexp.last_match(1)
61 | end
62 |
63 | def bundler_requirement
64 | @bundler_requirement ||=
65 | env_var_version ||
66 | cli_arg_version ||
67 | bundler_requirement_for(lockfile_version)
68 | end
69 |
70 | def bundler_requirement_for(version)
71 | return "#{Gem::Requirement.default}.a" unless version
72 |
73 | bundler_gem_version = Gem::Version.new(version)
74 |
75 | bundler_gem_version.approximate_recommendation
76 | end
77 |
78 | def load_bundler!
79 | ENV["BUNDLE_GEMFILE"] ||= gemfile
80 |
81 | activate_bundler
82 | end
83 |
84 | def activate_bundler
85 | gem_error = activation_error_handling do
86 | gem "bundler", bundler_requirement
87 | end
88 | return if gem_error.nil?
89 | require_error = activation_error_handling do
90 | require "bundler/version"
91 | end
92 | return if require_error.nil? && Gem::Requirement.new(bundler_requirement).satisfied_by?(Gem::Version.new(Bundler::VERSION))
93 | warn "Activating bundler (#{bundler_requirement}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_requirement}'`"
94 | exit 42
95 | end
96 |
97 | def activation_error_handling
98 | yield
99 | nil
100 | rescue StandardError, LoadError => e
101 | e
102 | end
103 | end
104 |
105 | m.load_bundler!
106 |
107 | if m.invoked_as_script?
108 | load Gem.bin_path("bundler", "bundle")
109 | end
110 |
--------------------------------------------------------------------------------
/bin/dev:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | if ! gem list foreman -i --silent; then
4 | echo "Installing foreman..."
5 | gem install foreman
6 | fi
7 |
8 | # Default to port 3000 if not specified
9 | export PORT="${PORT:-3000}"
10 |
11 | # Let the debug gem allow remote connections,
12 | # but avoid loading until `debugger` is called
13 | export RUBY_DEBUG_OPEN="true"
14 | export RUBY_DEBUG_LAZY="true"
15 |
16 | exec foreman start -f Procfile.dev "$@"
17 |
--------------------------------------------------------------------------------
/bin/docker-entrypoint:
--------------------------------------------------------------------------------
1 | #!/bin/bash -e
2 |
3 | # If running the rails server then create or migrate existing database
4 | if [ "${1}" == "./bin/rails" ] && [ "${2}" == "server" ]; then
5 | ./bin/rails db:prepare
6 | fi
7 |
8 | exec "${@}"
9 |
--------------------------------------------------------------------------------
/bin/importmap:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | require_relative "../config/application"
4 | require "importmap/commands"
5 |
--------------------------------------------------------------------------------
/bin/rails:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | APP_PATH = File.expand_path("../config/application", __dir__)
3 | require_relative "../config/boot"
4 | require "rails/commands"
5 |
--------------------------------------------------------------------------------
/bin/rake:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | require_relative "../config/boot"
3 | require "rake"
4 | Rake.application.run
5 |
--------------------------------------------------------------------------------
/bin/setup:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | require "fileutils"
3 |
4 | # path to your application root.
5 | APP_ROOT = File.expand_path("..", __dir__)
6 |
7 | def system!(*args)
8 | system(*args, exception: true)
9 | end
10 |
11 | FileUtils.chdir APP_ROOT do
12 | # This script is a way to set up or update your development environment automatically.
13 | # This script is idempotent, so that you can run it at any time and get an expectable outcome.
14 | # Add necessary setup steps to this file.
15 |
16 | puts "== Installing dependencies =="
17 | system! "gem install bundler --conservative"
18 | system("bundle check") || system!("bundle install")
19 |
20 | # puts "\n== Copying sample files =="
21 | # unless File.exist?("config/database.yml")
22 | # FileUtils.cp "config/database.yml.sample", "config/database.yml"
23 | # end
24 |
25 | puts "\n== Preparing database =="
26 | system! "bin/rails db:prepare"
27 |
28 | puts "\n== Removing old logs and tempfiles =="
29 | system! "bin/rails log:clear tmp:clear"
30 |
31 | puts "\n== Restarting application server =="
32 | system! "bin/rails restart"
33 | end
34 |
--------------------------------------------------------------------------------
/config.ru:
--------------------------------------------------------------------------------
1 | # This file is used by Rack-based servers to start the application.
2 |
3 | require_relative "config/environment"
4 |
5 | run Rails.application
6 | Rails.application.load_server
7 |
--------------------------------------------------------------------------------
/config/application.rb:
--------------------------------------------------------------------------------
1 | require_relative "boot"
2 |
3 | require "rails/all"
4 |
5 | # Require the gems listed in Gemfile, including any gems
6 | # you've limited to :test, :development, or :production.
7 | Bundler.require(*Rails.groups)
8 |
9 | module TorontoRails
10 | class Application < Rails::Application
11 | # Initialize configuration defaults for originally generated Rails version.
12 | config.load_defaults 7.1
13 |
14 | # Please, add to the `ignore` list any other `lib` subdirectories that do
15 | # not contain `.rb` files, or that should not be reloaded or eager loaded.
16 | # Common ones are `templates`, `generators`, or `middleware`, for example.
17 | config.autoload_lib(ignore: %w(assets tasks))
18 |
19 | # Configuration for the application, engines, and railties goes here.
20 | #
21 | # These settings can be overridden in specific environments using the files
22 | # in config/environments, which are processed later.
23 | #
24 | # config.time_zone = "Central Time (US & Canada)"
25 | # config.eager_load_paths << Rails.root.join("extras")
26 | end
27 | end
28 |
--------------------------------------------------------------------------------
/config/boot.rb:
--------------------------------------------------------------------------------
1 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
2 |
3 | require "bundler/setup" # Set up gems listed in the Gemfile.
4 | require "bootsnap/setup" # Speed up boot time by caching expensive operations.
5 |
--------------------------------------------------------------------------------
/config/cable.yml:
--------------------------------------------------------------------------------
1 | development:
2 | adapter: redis
3 | url: redis://localhost:6379/1
4 |
5 | test:
6 | adapter: test
7 |
8 | production:
9 | adapter: redis
10 | url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %>
11 | channel_prefix: toronto_rails_production
12 |
--------------------------------------------------------------------------------
/config/credentials.yml.enc:
--------------------------------------------------------------------------------
1 | dlMSGC85wHabCks/0kF2akP5r1FAksnWHJ2Ef3cF/koJ9VkbBnqXTQihRqeJyu5sFKOcsKKy8+A1pmNWrz4KgVD5K/kklqXzGgWPAt//u6auV8QW7nlpWDPe0ce9AHyXezUktch0FAxU0fW+qOq1BbHxXri5nYEFM7F9TiNacL9z+TlEZjJq3B4oerzv2pRdQrpkvpfpau146WFjJ29xjiYNdz/7VblNiXTLkZrULCWd7H0jJGAuA9UwAuUCROQjRaM+Iji5cV+uTpnUU2Szpwp/6eIx6XaBeLO7SE929YISPxPe1/VpLgOx6gcbE+LniBeyHlqetPdwD1p9ELX55Rx07bKEZEou4b/2CnpPFrzHUMsKZiMWXUNzQzkqXlxGlvAmCjd7EUg2xb6gmuk200mNT6xQ--PfIEs73hYgK84RZe--tum57tIGb+BSWSHJOJwI3w==
--------------------------------------------------------------------------------
/config/credentials/development.yml.enc:
--------------------------------------------------------------------------------
1 | 1QOs7HeKWV9ivR1Pil1QzYia0wQTOaDdEJ3bs6xHNjc7sLCzNN06fyT2jgXTty/T4GAONVnwc0YCX1bs4tXDfvLaY4GOWLY2iZFYSu0w2mS0pDZQF5tn7naK6AlXom5Whh2HZxdDuhtIDc4n0O2nOlTxpUgR0XC3bJRCIOZcC5Q3FGhZL8ka7xG593/ypyR4Ry2JMOAjiAUt/SrvOin/SSWvKuumh7/gPJotcMhW6gSSIhcgRxI69s5KE9pIZ+8rs10oFhkuyMLEsyw=--MRtwANIzz7b4RntD--hGmNSjjCL8zv93aSgB2mAg==
--------------------------------------------------------------------------------
/config/database.yml:
--------------------------------------------------------------------------------
1 | # config/database.yml
2 | default: &default
3 | adapter: postgresql
4 | encoding: unicode
5 | pool: <%= ENV.fetch("RAILS_MAX_THREADS", "5") %>
6 |
7 | development:
8 | <<: *default
9 | database: pay_demo_development
10 |
--------------------------------------------------------------------------------
/config/environment.rb:
--------------------------------------------------------------------------------
1 | # Load the Rails application.
2 | require_relative "application"
3 |
4 | # Initialize the Rails application.
5 | Rails.application.initialize!
6 |
--------------------------------------------------------------------------------
/config/environments/development.rb:
--------------------------------------------------------------------------------
1 | require "active_support/core_ext/integer/time"
2 |
3 | Rails.application.configure do
4 | # Settings specified here will take precedence over those in config/application.rb.
5 |
6 | # In the development environment your application's code is reloaded any time
7 | # it changes. This slows down response time but is perfect for development
8 | # since you don't have to restart the web server when you make code changes.
9 | config.enable_reloading = true
10 |
11 | # Do not eager load code on boot.
12 | config.eager_load = false
13 |
14 | # Show full error reports.
15 | config.consider_all_requests_local = true
16 |
17 | # Enable server timing
18 | config.server_timing = true
19 |
20 | # Enable/disable caching. By default caching is disabled.
21 | # Run rails dev:cache to toggle caching.
22 | if Rails.root.join("tmp/caching-dev.txt").exist?
23 | config.action_controller.perform_caching = true
24 | config.action_controller.enable_fragment_cache_logging = true
25 |
26 | config.cache_store = :memory_store
27 | config.public_file_server.headers = {
28 | "Cache-Control" => "public, max-age=#{2.days.to_i}"
29 | }
30 | else
31 | config.action_controller.perform_caching = false
32 |
33 | config.cache_store = :null_store
34 | end
35 |
36 | # Store uploaded files on the local file system (see config/storage.yml for options).
37 | config.active_storage.service = :local
38 |
39 | # Don't care if the mailer can't send.
40 | config.action_mailer.raise_delivery_errors = false
41 |
42 | config.action_mailer.perform_caching = false
43 |
44 | # Print deprecation notices to the Rails logger.
45 | config.active_support.deprecation = :log
46 |
47 | # Raise exceptions for disallowed deprecations.
48 | config.active_support.disallowed_deprecation = :raise
49 |
50 | # Tell Active Support which deprecation messages to disallow.
51 | config.active_support.disallowed_deprecation_warnings = []
52 |
53 | # Raise an error on page load if there are pending migrations.
54 | config.active_record.migration_error = :page_load
55 |
56 | # Highlight code that triggered database queries in logs.
57 | config.active_record.verbose_query_logs = true
58 |
59 | # Highlight code that enqueued background job in logs.
60 | config.active_job.verbose_enqueue_logs = true
61 |
62 | # Suppress logger output for asset requests.
63 | config.assets.quiet = true
64 |
65 | # Raises error for missing translations.
66 | # config.i18n.raise_on_missing_translations = true
67 |
68 | # Annotate rendered view with file names.
69 | # config.action_view.annotate_rendered_view_with_filenames = true
70 |
71 | # Uncomment if you wish to allow Action Cable access from any origin.
72 | # config.action_cable.disable_request_forgery_protection = true
73 |
74 | # Raise error when a before_action's only/except options reference missing actions
75 | config.action_controller.raise_on_missing_callback_actions = true
76 |
77 | # Configure host for URL helpers for strpie
78 | Rails.application.routes.default_url_options = { host: "localhost", port: 3000}
79 | end
80 |
--------------------------------------------------------------------------------
/config/environments/production.rb:
--------------------------------------------------------------------------------
1 | require "active_support/core_ext/integer/time"
2 |
3 | Rails.application.configure do
4 | # Settings specified here will take precedence over those in config/application.rb.
5 |
6 | # Code is not reloaded between requests.
7 | config.enable_reloading = false
8 |
9 | # Eager load code on boot. This eager loads most of Rails and
10 | # your application in memory, allowing both threaded web servers
11 | # and those relying on copy on write to perform better.
12 | # Rake tasks automatically ignore this option for performance.
13 | config.eager_load = true
14 |
15 | # Full error reports are disabled and caching is turned on.
16 | config.consider_all_requests_local = false
17 | config.action_controller.perform_caching = true
18 |
19 | # Ensures that a master key has been made available in ENV["RAILS_MASTER_KEY"], config/master.key, or an environment
20 | # key such as config/credentials/production.key. This key is used to decrypt credentials (and other encrypted files).
21 | # config.require_master_key = true
22 |
23 | # Disable serving static files from `public/`, relying on NGINX/Apache to do so instead.
24 | # config.public_file_server.enabled = false
25 |
26 | # Compress CSS using a preprocessor.
27 | # config.assets.css_compressor = :sass
28 |
29 | # Do not fall back to assets pipeline if a precompiled asset is missed.
30 | config.assets.compile = false
31 |
32 | # Enable serving of images, stylesheets, and JavaScripts from an asset server.
33 | # config.asset_host = "http://assets.example.com"
34 |
35 | # Specifies the header that your server uses for sending files.
36 | # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for Apache
37 | # config.action_dispatch.x_sendfile_header = "X-Accel-Redirect" # for NGINX
38 |
39 | # Store uploaded files on the local file system (see config/storage.yml for options).
40 | config.active_storage.service = :local
41 |
42 | # Mount Action Cable outside main process or domain.
43 | # config.action_cable.mount_path = nil
44 | # config.action_cable.url = "wss://example.com/cable"
45 | # config.action_cable.allowed_request_origins = [ "http://example.com", /http:\/\/example.*/ ]
46 |
47 | # Assume all access to the app is happening through a SSL-terminating reverse proxy.
48 | # Can be used together with config.force_ssl for Strict-Transport-Security and secure cookies.
49 | # config.assume_ssl = true
50 |
51 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
52 | config.force_ssl = true
53 |
54 | # Log to STDOUT by default
55 | config.logger = ActiveSupport::Logger.new(STDOUT)
56 | .tap { |logger| logger.formatter = ::Logger::Formatter.new }
57 | .then { |logger| ActiveSupport::TaggedLogging.new(logger) }
58 |
59 | # Prepend all log lines with the following tags.
60 | config.log_tags = [ :request_id ]
61 |
62 | # "info" includes generic and useful information about system operation, but avoids logging too much
63 | # information to avoid inadvertent exposure of personally identifiable information (PII). If you
64 | # want to log everything, set the level to "debug".
65 | config.log_level = ENV.fetch("RAILS_LOG_LEVEL", "info")
66 |
67 | # Use a different cache store in production.
68 | # config.cache_store = :mem_cache_store
69 |
70 | # Use a real queuing backend for Active Job (and separate queues per environment).
71 | # config.active_job.queue_adapter = :resque
72 | # config.active_job.queue_name_prefix = "toronto_rails_production"
73 |
74 | config.action_mailer.perform_caching = false
75 |
76 | # Ignore bad email addresses and do not raise email delivery errors.
77 | # Set this to true and configure the email server for immediate delivery to raise delivery errors.
78 | # config.action_mailer.raise_delivery_errors = false
79 |
80 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to
81 | # the I18n.default_locale when a translation cannot be found).
82 | config.i18n.fallbacks = true
83 |
84 | # Don't log any deprecations.
85 | config.active_support.report_deprecations = false
86 |
87 | # Do not dump schema after migrations.
88 | config.active_record.dump_schema_after_migration = false
89 |
90 | # Enable DNS rebinding protection and other `Host` header attacks.
91 | # config.hosts = [
92 | # "example.com", # Allow requests from example.com
93 | # /.*\.example\.com/ # Allow requests from subdomains like `www.example.com`
94 | # ]
95 | # Skip DNS rebinding protection for the default health check endpoint.
96 | # config.host_authorization = { exclude: ->(request) { request.path == "/up" } }
97 |
98 | # Use the env to read in secrets
99 | config.require_master_key = false
100 | config.secret_key_base = ENV["SECRET_KEY_BASE"]
101 | end
102 |
--------------------------------------------------------------------------------
/config/environments/test.rb:
--------------------------------------------------------------------------------
1 | require "active_support/core_ext/integer/time"
2 |
3 | # The test environment is used exclusively to run your application's
4 | # test suite. You never need to work with it otherwise. Remember that
5 | # your test database is "scratch space" for the test suite and is wiped
6 | # and recreated between test runs. Don't rely on the data there!
7 |
8 | Rails.application.configure do
9 | # Settings specified here will take precedence over those in config/application.rb.
10 |
11 | # While tests run files are not watched, reloading is not necessary.
12 | config.enable_reloading = false
13 |
14 | # Eager loading loads your entire application. When running a single test locally,
15 | # this is usually not necessary, and can slow down your test suite. However, it's
16 | # recommended that you enable it in continuous integration systems to ensure eager
17 | # loading is working properly before deploying your code.
18 | config.eager_load = ENV["CI"].present?
19 |
20 | # Configure public file server for tests with Cache-Control for performance.
21 | config.public_file_server.enabled = true
22 | config.public_file_server.headers = {
23 | "Cache-Control" => "public, max-age=#{1.hour.to_i}"
24 | }
25 |
26 | # Show full error reports and disable caching.
27 | config.consider_all_requests_local = true
28 | config.action_controller.perform_caching = false
29 | config.cache_store = :null_store
30 |
31 | # Render exception templates for rescuable exceptions and raise for other exceptions.
32 | config.action_dispatch.show_exceptions = :rescuable
33 |
34 | # Disable request forgery protection in test environment.
35 | config.action_controller.allow_forgery_protection = false
36 |
37 | # Store uploaded files on the local file system in a temporary directory.
38 | config.active_storage.service = :test
39 |
40 | config.action_mailer.perform_caching = false
41 |
42 | # Tell Action Mailer not to deliver emails to the real world.
43 | # The :test delivery method accumulates sent emails in the
44 | # ActionMailer::Base.deliveries array.
45 | config.action_mailer.delivery_method = :test
46 |
47 | # Print deprecation notices to the stderr.
48 | config.active_support.deprecation = :stderr
49 |
50 | # Raise exceptions for disallowed deprecations.
51 | config.active_support.disallowed_deprecation = :raise
52 |
53 | # Tell Active Support which deprecation messages to disallow.
54 | config.active_support.disallowed_deprecation_warnings = []
55 |
56 | # Raises error for missing translations.
57 | # config.i18n.raise_on_missing_translations = true
58 |
59 | # Annotate rendered view with file names.
60 | # config.action_view.annotate_rendered_view_with_filenames = true
61 |
62 | # Raise error when a before_action's only/except options reference missing actions
63 | config.action_controller.raise_on_missing_callback_actions = true
64 | end
65 |
--------------------------------------------------------------------------------
/config/importmap.rb:
--------------------------------------------------------------------------------
1 | # Pin npm packages by running ./bin/importmap
2 |
3 | pin "application"
4 | pin "@hotwired/turbo-rails", to: "turbo.min.js"
5 | pin "@hotwired/stimulus", to: "stimulus.min.js"
6 | pin "@hotwired/stimulus-loading", to: "stimulus-loading.js"
7 | pin_all_from "app/javascript/controllers", under: "controllers"
8 |
--------------------------------------------------------------------------------
/config/initializers/assets.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Version of your assets, change this if you want to expire all your assets.
4 | Rails.application.config.assets.version = "1.1"
5 |
6 | # Add additional assets to the asset load path.
7 | # Rails.application.config.assets.paths << Emoji.images_path
8 |
9 | # Precompile additional assets.
10 | # application.js, application.css, and all non-JS/CSS in the app/assets
11 | # folder are already added.
12 | # Rails.application.config.assets.precompile += %w( admin.js admin.css )
13 |
--------------------------------------------------------------------------------
/config/initializers/content_security_policy.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Define an application-wide content security policy.
4 | # See the Securing Rails Applications Guide for more information:
5 | # https://guides.rubyonrails.org/security.html#content-security-policy-header
6 |
7 | # Rails.application.configure do
8 | # config.content_security_policy do |policy|
9 | # policy.default_src :self, :https
10 | # policy.font_src :self, :https, :data
11 | # policy.img_src :self, :https, :data
12 | # policy.object_src :none
13 | # policy.script_src :self, :https
14 | # policy.style_src :self, :https
15 | # # Specify URI for violation reports
16 | # # policy.report_uri "/csp-violation-report-endpoint"
17 | # end
18 | #
19 | # # Generate session nonces for permitted importmap, inline scripts, and inline styles.
20 | # config.content_security_policy_nonce_generator = ->(request) { request.session.id.to_s }
21 | # config.content_security_policy_nonce_directives = %w(script-src style-src)
22 | #
23 | # # Report violations without enforcing the policy.
24 | # # config.content_security_policy_report_only = true
25 | # end
26 |
--------------------------------------------------------------------------------
/config/initializers/filter_parameter_logging.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Configure parameters to be partially matched (e.g. passw matches password) and filtered from the log file.
4 | # Use this to limit dissemination of sensitive information.
5 | # See the ActiveSupport::ParameterFilter documentation for supported notations and behaviors.
6 | Rails.application.config.filter_parameters += [
7 | :passw, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn
8 | ]
9 |
--------------------------------------------------------------------------------
/config/initializers/inflections.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Add new inflection rules using the following format. Inflections
4 | # are locale specific, and you may define rules for as many different
5 | # locales as you wish. All of these examples are active by default:
6 | # ActiveSupport::Inflector.inflections(:en) do |inflect|
7 | # inflect.plural /^(ox)$/i, "\\1en"
8 | # inflect.singular /^(ox)en/i, "\\1"
9 | # inflect.irregular "person", "people"
10 | # inflect.uncountable %w( fish sheep )
11 | # end
12 |
13 | # These inflection rules are supported but not enabled by default:
14 | # ActiveSupport::Inflector.inflections(:en) do |inflect|
15 | # inflect.acronym "RESTful"
16 | # end
17 |
--------------------------------------------------------------------------------
/config/initializers/pay.rb:
--------------------------------------------------------------------------------
1 | Pay.setup do |config|
2 | config.support_email = "happy@acme.test"
3 | config.application_name = "demo"
4 | config.business_name = "demo"
5 | end
6 |
7 | ActiveSupport.on_load(:pay) do
8 | Pay::Webhooks.delegator.subscribe "stripe.invoice.payment_succeeded", PaymentSucceededHandler.new
9 | end
10 |
--------------------------------------------------------------------------------
/config/initializers/permissions_policy.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Define an application-wide HTTP permissions policy. For further
4 | # information see: https://developers.google.com/web/updates/2018/06/feature-policy
5 |
6 | # Rails.application.config.permissions_policy do |policy|
7 | # policy.camera :none
8 | # policy.gyroscope :none
9 | # policy.microphone :none
10 | # policy.usb :none
11 | # policy.fullscreen :self
12 | # policy.payment :self, "https://secure.example.com"
13 | # end
14 |
--------------------------------------------------------------------------------
/config/initializers/stripe.rb:
--------------------------------------------------------------------------------
1 | Stripe.api_version = "2023-10-16"
2 | Stripe.max_network_retries = 1
3 | Stripe.enable_telemetry = false
4 |
--------------------------------------------------------------------------------
/config/locales/en.yml:
--------------------------------------------------------------------------------
1 | # Files in the config/locales directory are used for internationalization and
2 | # are automatically loaded by Rails. If you want to use locales other than
3 | # English, add the necessary files in this directory.
4 | #
5 | # To use the locales, use `I18n.t`:
6 | #
7 | # I18n.t "hello"
8 | #
9 | # In views, this is aliased to just `t`:
10 | #
11 | # <%= t("hello") %>
12 | #
13 | # To use a different locale, set it with `I18n.locale`:
14 | #
15 | # I18n.locale = :es
16 | #
17 | # This would use the information in config/locales/es.yml.
18 | #
19 | # To learn more about the API, please read the Rails Internationalization guide
20 | # at https://guides.rubyonrails.org/i18n.html.
21 | #
22 | # Be aware that YAML interprets the following case-insensitive strings as
23 | # booleans: `true`, `false`, `on`, `off`, `yes`, `no`. Therefore, these strings
24 | # must be quoted to be interpreted as strings. For example:
25 | #
26 | # en:
27 | # "yes": yup
28 | # enabled: "ON"
29 |
30 | en:
31 | hello: "Hello world"
32 |
--------------------------------------------------------------------------------
/config/puma.rb:
--------------------------------------------------------------------------------
1 | # This configuration file will be evaluated by Puma. The top-level methods that
2 | # are invoked here are part of Puma's configuration DSL. For more information
3 | # about methods provided by the DSL, see https://puma.io/puma/Puma/DSL.html.
4 |
5 | # Puma can serve each request in a thread from an internal thread pool.
6 | # The `threads` method setting takes two numbers: a minimum and maximum.
7 | # Any libraries that use thread pools should be configured to match
8 | # the maximum value specified for Puma. Default is set to 5 threads for minimum
9 | # and maximum; this matches the default thread size of Active Record.
10 | max_threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }
11 | min_threads_count = ENV.fetch("RAILS_MIN_THREADS") { max_threads_count }
12 | threads min_threads_count, max_threads_count
13 |
14 | # Specifies that the worker count should equal the number of processors in production.
15 | if ENV["RAILS_ENV"] == "production"
16 | require "concurrent-ruby"
17 | worker_count = Integer(ENV.fetch("WEB_CONCURRENCY") { Concurrent.physical_processor_count })
18 | workers worker_count if worker_count > 1
19 | end
20 |
21 | # Specifies the `worker_timeout` threshold that Puma will use to wait before
22 | # terminating a worker in development environments.
23 | worker_timeout 3600 if ENV.fetch("RAILS_ENV", "development") == "development"
24 |
25 | # Specifies the `port` that Puma will listen on to receive requests; default is 3000.
26 | port ENV.fetch("PORT") { 3000 }
27 |
28 | # Specifies the `environment` that Puma will run in.
29 | environment ENV.fetch("RAILS_ENV") { "development" }
30 |
31 | # Specifies the `pidfile` that Puma will use.
32 | pidfile ENV.fetch("PIDFILE") { "tmp/pids/server.pid" }
33 |
34 | # Allow puma to be restarted by `bin/rails restart` command.
35 | plugin :tmp_restart
36 |
--------------------------------------------------------------------------------
/config/routes.rb:
--------------------------------------------------------------------------------
1 | Rails.application.routes.draw do
2 | get 'up' => 'rails/health#show', as: :rails_health_check
3 | root 'static#home'
4 | get "checkouts" => "checkouts#show"
5 | end
6 |
--------------------------------------------------------------------------------
/config/storage.yml:
--------------------------------------------------------------------------------
1 | test:
2 | service: Disk
3 | root: <%= Rails.root.join("tmp/storage") %>
4 |
5 | local:
6 | service: Disk
7 | root: <%= Rails.root.join("storage") %>
8 |
9 | # Use bin/rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key)
10 | # amazon:
11 | # service: S3
12 | # access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %>
13 | # secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %>
14 | # region: us-east-1
15 | # bucket: your_own_bucket-<%= Rails.env %>
16 |
17 | # Remember not to checkin your GCS keyfile to a repository
18 | # google:
19 | # service: GCS
20 | # project: your_project
21 | # credentials: <%= Rails.root.join("path/to/gcs.keyfile") %>
22 | # bucket: your_own_bucket-<%= Rails.env %>
23 |
24 | # Use bin/rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key)
25 | # microsoft:
26 | # service: AzureStorage
27 | # storage_account_name: your_account_name
28 | # storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %>
29 | # container: your_container_name-<%= Rails.env %>
30 |
31 | # mirror:
32 | # service: Mirror
33 | # primary: local
34 | # mirrors: [ amazon, google, microsoft ]
35 |
--------------------------------------------------------------------------------
/config/tailwind.config.js:
--------------------------------------------------------------------------------
1 | const defaultTheme = require('tailwindcss/defaultTheme')
2 |
3 | module.exports = {
4 | content: [
5 | './public/*.html',
6 | './app/helpers/**/*.rb',
7 | './app/javascript/**/*.js',
8 | './app/views/**/*.{erb,haml,html,slim}'
9 | ],
10 | theme: {
11 | extend: {
12 | fontFamily: {
13 | sans: ['Inter var', ...defaultTheme.fontFamily.sans],
14 | },
15 | },
16 | },
17 | plugins: [
18 | require('@tailwindcss/forms'),
19 | require('@tailwindcss/aspect-ratio'),
20 | require('@tailwindcss/typography'),
21 | require('@tailwindcss/container-queries'),
22 | ]
23 | }
24 |
--------------------------------------------------------------------------------
/db/migrate/20240513220536_create_users.rb:
--------------------------------------------------------------------------------
1 | class CreateUsers < ActiveRecord::Migration[7.1]
2 | def change
3 | create_table :users do |t|
4 | t.string :name
5 | t.string :email
6 |
7 | t.timestamps
8 | end
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/db/migrate/20240513222045_create_pay_tables.pay.rb:
--------------------------------------------------------------------------------
1 | # This migration comes from pay (originally 1)
2 | class CreatePayTables < ActiveRecord::Migration[6.0]
3 | def change
4 | primary_key_type, foreign_key_type = primary_and_foreign_key_types
5 |
6 | create_table :pay_customers, id: primary_key_type do |t|
7 | t.belongs_to :owner, polymorphic: true, index: false, type: foreign_key_type
8 | t.string :processor, null: false
9 | t.string :processor_id
10 | t.boolean :default
11 | t.public_send Pay::Adapter.json_column_type, :data
12 | t.string :stripe_account
13 | t.datetime :deleted_at
14 | t.timestamps
15 | end
16 | add_index :pay_customers, [:owner_type, :owner_id, :deleted_at], name: :pay_customer_owner_index, unique: true
17 | add_index :pay_customers, [:processor, :processor_id], unique: true
18 |
19 | create_table :pay_merchants, id: primary_key_type do |t|
20 | t.belongs_to :owner, polymorphic: true, index: false, type: foreign_key_type
21 | t.string :processor, null: false
22 | t.string :processor_id
23 | t.boolean :default
24 | t.public_send Pay::Adapter.json_column_type, :data
25 | t.timestamps
26 | end
27 | add_index :pay_merchants, [:owner_type, :owner_id, :processor]
28 |
29 | create_table :pay_payment_methods, id: primary_key_type do |t|
30 | t.belongs_to :customer, foreign_key: {to_table: :pay_customers}, null: false, index: false, type: foreign_key_type
31 | t.string :processor_id, null: false
32 | t.boolean :default
33 | t.string :type
34 | t.public_send Pay::Adapter.json_column_type, :data
35 | t.string :stripe_account
36 | t.timestamps
37 | end
38 | add_index :pay_payment_methods, [:customer_id, :processor_id], unique: true
39 |
40 | create_table :pay_subscriptions, id: primary_key_type do |t|
41 | t.belongs_to :customer, foreign_key: {to_table: :pay_customers}, null: false, index: false, type: foreign_key_type
42 | t.string :name, null: false
43 | t.string :processor_id, null: false
44 | t.string :processor_plan, null: false
45 | t.integer :quantity, default: 1, null: false
46 | t.string :status, null: false
47 | t.datetime :current_period_start
48 | t.datetime :current_period_end
49 | t.datetime :trial_ends_at
50 | t.datetime :ends_at
51 | t.boolean :metered
52 | t.string :pause_behavior
53 | t.datetime :pause_starts_at
54 | t.datetime :pause_resumes_at
55 | t.decimal :application_fee_percent, precision: 8, scale: 2
56 | t.public_send Pay::Adapter.json_column_type, :metadata
57 | t.public_send Pay::Adapter.json_column_type, :data
58 | t.string :stripe_account
59 | t.string :payment_method_id
60 | t.timestamps
61 | end
62 | add_index :pay_subscriptions, [:customer_id, :processor_id], unique: true
63 | add_index :pay_subscriptions, [:metered]
64 | add_index :pay_subscriptions, [:pause_starts_at]
65 |
66 | create_table :pay_charges, id: primary_key_type do |t|
67 | t.belongs_to :customer, foreign_key: {to_table: :pay_customers}, null: false, index: false, type: foreign_key_type
68 | t.belongs_to :subscription, foreign_key: {to_table: :pay_subscriptions}, null: true, type: foreign_key_type
69 | t.string :processor_id, null: false
70 | t.integer :amount, null: false
71 | t.string :currency
72 | t.integer :application_fee_amount
73 | t.integer :amount_refunded
74 | t.public_send Pay::Adapter.json_column_type, :metadata
75 | t.public_send Pay::Adapter.json_column_type, :data
76 | t.string :stripe_account
77 | t.timestamps
78 | end
79 | add_index :pay_charges, [:customer_id, :processor_id], unique: true
80 |
81 | create_table :pay_webhooks, id: primary_key_type do |t|
82 | t.string :processor
83 | t.string :event_type
84 | t.public_send Pay::Adapter.json_column_type, :event
85 | t.timestamps
86 | end
87 | end
88 |
89 | private
90 |
91 | def primary_and_foreign_key_types
92 | config = Rails.configuration.generators
93 | setting = config.options[config.orm][:primary_key_type]
94 | primary_key_type = setting || :primary_key
95 | foreign_key_type = setting || :bigint
96 | [primary_key_type, foreign_key_type]
97 | end
98 | end
99 |
--------------------------------------------------------------------------------
/db/schema.rb:
--------------------------------------------------------------------------------
1 | # This file is auto-generated from the current state of the database. Instead
2 | # of editing this file, please use the migrations feature of Active Record to
3 | # incrementally modify your database, and then regenerate this schema definition.
4 | #
5 | # This file is the source Rails uses to define your schema when running `bin/rails
6 | # db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to
7 | # be faster and is potentially less error prone than running all of your
8 | # migrations from scratch. Old migrations may fail to apply correctly if those
9 | # migrations use external dependencies or application code.
10 | #
11 | # It's strongly recommended that you check this file into your version control system.
12 |
13 | ActiveRecord::Schema[7.1].define(version: 2024_05_13_222045) do
14 | # These are extensions that must be enabled in order to support this database
15 | enable_extension "plpgsql"
16 |
17 | create_table "pay_charges", force: :cascade do |t|
18 | t.bigint "customer_id", null: false
19 | t.bigint "subscription_id"
20 | t.string "processor_id", null: false
21 | t.integer "amount", null: false
22 | t.string "currency"
23 | t.integer "application_fee_amount"
24 | t.integer "amount_refunded"
25 | t.json "metadata"
26 | t.json "data"
27 | t.string "stripe_account"
28 | t.datetime "created_at", null: false
29 | t.datetime "updated_at", null: false
30 | t.index ["customer_id", "processor_id"], name: "index_pay_charges_on_customer_id_and_processor_id", unique: true
31 | t.index ["subscription_id"], name: "index_pay_charges_on_subscription_id"
32 | end
33 |
34 | create_table "pay_customers", force: :cascade do |t|
35 | t.string "owner_type"
36 | t.bigint "owner_id"
37 | t.string "processor", null: false
38 | t.string "processor_id"
39 | t.boolean "default"
40 | t.json "data"
41 | t.string "stripe_account"
42 | t.datetime "deleted_at", precision: nil
43 | t.datetime "created_at", null: false
44 | t.datetime "updated_at", null: false
45 | t.index ["owner_type", "owner_id", "deleted_at"], name: "pay_customer_owner_index", unique: true
46 | t.index ["processor", "processor_id"], name: "index_pay_customers_on_processor_and_processor_id", unique: true
47 | end
48 |
49 | create_table "pay_merchants", force: :cascade do |t|
50 | t.string "owner_type"
51 | t.bigint "owner_id"
52 | t.string "processor", null: false
53 | t.string "processor_id"
54 | t.boolean "default"
55 | t.json "data"
56 | t.datetime "created_at", null: false
57 | t.datetime "updated_at", null: false
58 | t.index ["owner_type", "owner_id", "processor"], name: "index_pay_merchants_on_owner_type_and_owner_id_and_processor"
59 | end
60 |
61 | create_table "pay_payment_methods", force: :cascade do |t|
62 | t.bigint "customer_id", null: false
63 | t.string "processor_id", null: false
64 | t.boolean "default"
65 | t.string "type"
66 | t.json "data"
67 | t.string "stripe_account"
68 | t.datetime "created_at", null: false
69 | t.datetime "updated_at", null: false
70 | t.index ["customer_id", "processor_id"], name: "index_pay_payment_methods_on_customer_id_and_processor_id", unique: true
71 | end
72 |
73 | create_table "pay_subscriptions", force: :cascade do |t|
74 | t.bigint "customer_id", null: false
75 | t.string "name", null: false
76 | t.string "processor_id", null: false
77 | t.string "processor_plan", null: false
78 | t.integer "quantity", default: 1, null: false
79 | t.string "status", null: false
80 | t.datetime "current_period_start", precision: nil
81 | t.datetime "current_period_end", precision: nil
82 | t.datetime "trial_ends_at", precision: nil
83 | t.datetime "ends_at", precision: nil
84 | t.boolean "metered"
85 | t.string "pause_behavior"
86 | t.datetime "pause_starts_at", precision: nil
87 | t.datetime "pause_resumes_at", precision: nil
88 | t.decimal "application_fee_percent", precision: 8, scale: 2
89 | t.json "metadata"
90 | t.json "data"
91 | t.string "stripe_account"
92 | t.string "payment_method_id"
93 | t.datetime "created_at", null: false
94 | t.datetime "updated_at", null: false
95 | t.index ["customer_id", "processor_id"], name: "index_pay_subscriptions_on_customer_id_and_processor_id", unique: true
96 | t.index ["metered"], name: "index_pay_subscriptions_on_metered"
97 | t.index ["pause_starts_at"], name: "index_pay_subscriptions_on_pause_starts_at"
98 | end
99 |
100 | create_table "pay_webhooks", force: :cascade do |t|
101 | t.string "processor"
102 | t.string "event_type"
103 | t.json "event"
104 | t.datetime "created_at", null: false
105 | t.datetime "updated_at", null: false
106 | end
107 |
108 | create_table "users", force: :cascade do |t|
109 | t.string "name"
110 | t.string "email"
111 | t.datetime "created_at", null: false
112 | t.datetime "updated_at", null: false
113 | end
114 |
115 | add_foreign_key "pay_charges", "pay_customers", column: "customer_id"
116 | add_foreign_key "pay_charges", "pay_subscriptions", column: "subscription_id"
117 | add_foreign_key "pay_payment_methods", "pay_customers", column: "customer_id"
118 | add_foreign_key "pay_subscriptions", "pay_customers", column: "customer_id"
119 | end
120 |
--------------------------------------------------------------------------------
/db/seeds.rb:
--------------------------------------------------------------------------------
1 | User.create!(
2 | name: Faker::Name.name,
3 | email: Faker::Internet.email
4 | )
5 |
--------------------------------------------------------------------------------
/docs/demo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shey/rails-pay-checkout-demo/b4d6d981ec77522487fc14c26191775ba9fbc6bc/docs/demo.png
--------------------------------------------------------------------------------
/docs/events.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shey/rails-pay-checkout-demo/b4d6d981ec77522487fc14c26191775ba9fbc6bc/docs/events.png
--------------------------------------------------------------------------------
/docs/product-catalogue.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shey/rails-pay-checkout-demo/b4d6d981ec77522487fc14c26191775ba9fbc6bc/docs/product-catalogue.png
--------------------------------------------------------------------------------
/lib/assets/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shey/rails-pay-checkout-demo/b4d6d981ec77522487fc14c26191775ba9fbc6bc/lib/assets/.keep
--------------------------------------------------------------------------------
/lib/tasks/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shey/rails-pay-checkout-demo/b4d6d981ec77522487fc14c26191775ba9fbc6bc/lib/tasks/.keep
--------------------------------------------------------------------------------
/log/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shey/rails-pay-checkout-demo/b4d6d981ec77522487fc14c26191775ba9fbc6bc/log/.keep
--------------------------------------------------------------------------------
/public/404.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The page you were looking for doesn't exist (404)
5 |
6 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
The page you were looking for doesn't exist.
62 |
You may have mistyped the address or the page may have moved.
63 |
64 |
If you are the application owner check the logs for more information.
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/public/422.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The change you wanted was rejected (422)
5 |
6 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
The change you wanted was rejected.
62 |
Maybe you tried to change something you didn't have access to.
63 |
64 |
If you are the application owner check the logs for more information.
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/public/500.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | We're sorry, but something went wrong (500)
5 |
6 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
We're sorry, but something went wrong.
62 |
63 |
If you are the application owner check the logs for more information.
64 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/public/apple-touch-icon-precomposed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shey/rails-pay-checkout-demo/b4d6d981ec77522487fc14c26191775ba9fbc6bc/public/apple-touch-icon-precomposed.png
--------------------------------------------------------------------------------
/public/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shey/rails-pay-checkout-demo/b4d6d981ec77522487fc14c26191775ba9fbc6bc/public/apple-touch-icon.png
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shey/rails-pay-checkout-demo/b4d6d981ec77522487fc14c26191775ba9fbc6bc/public/favicon.ico
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # See https://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file
2 |
--------------------------------------------------------------------------------
/storage/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shey/rails-pay-checkout-demo/b4d6d981ec77522487fc14c26191775ba9fbc6bc/storage/.keep
--------------------------------------------------------------------------------
/test/application_system_test_case.rb:
--------------------------------------------------------------------------------
1 | require "test_helper"
2 |
3 | class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
4 | driven_by :selenium, using: :chrome, screen_size: [1400, 1400]
5 | end
6 |
--------------------------------------------------------------------------------
/test/channels/application_cable/connection_test.rb:
--------------------------------------------------------------------------------
1 | require "test_helper"
2 |
3 | module ApplicationCable
4 | class ConnectionTest < ActionCable::Connection::TestCase
5 | # test "connects with cookies" do
6 | # cookies.signed[:user_id] = 42
7 | #
8 | # connect
9 | #
10 | # assert_equal connection.user_id, "42"
11 | # end
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/test/controllers/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shey/rails-pay-checkout-demo/b4d6d981ec77522487fc14c26191775ba9fbc6bc/test/controllers/.keep
--------------------------------------------------------------------------------
/test/fixtures/files/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shey/rails-pay-checkout-demo/b4d6d981ec77522487fc14c26191775ba9fbc6bc/test/fixtures/files/.keep
--------------------------------------------------------------------------------
/test/fixtures/users.yml:
--------------------------------------------------------------------------------
1 | # Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
2 |
3 | one:
4 | name: MyString
5 | email: MyString
6 |
7 | two:
8 | name: MyString
9 | email: MyString
10 |
--------------------------------------------------------------------------------
/test/helpers/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shey/rails-pay-checkout-demo/b4d6d981ec77522487fc14c26191775ba9fbc6bc/test/helpers/.keep
--------------------------------------------------------------------------------
/test/integration/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shey/rails-pay-checkout-demo/b4d6d981ec77522487fc14c26191775ba9fbc6bc/test/integration/.keep
--------------------------------------------------------------------------------
/test/mailers/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shey/rails-pay-checkout-demo/b4d6d981ec77522487fc14c26191775ba9fbc6bc/test/mailers/.keep
--------------------------------------------------------------------------------
/test/models/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shey/rails-pay-checkout-demo/b4d6d981ec77522487fc14c26191775ba9fbc6bc/test/models/.keep
--------------------------------------------------------------------------------
/test/models/user_test.rb:
--------------------------------------------------------------------------------
1 | require "test_helper"
2 |
3 | class UserTest < ActiveSupport::TestCase
4 | # test "the truth" do
5 | # assert true
6 | # end
7 | end
8 |
--------------------------------------------------------------------------------
/test/system/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shey/rails-pay-checkout-demo/b4d6d981ec77522487fc14c26191775ba9fbc6bc/test/system/.keep
--------------------------------------------------------------------------------
/test/test_helper.rb:
--------------------------------------------------------------------------------
1 | ENV["RAILS_ENV"] ||= "test"
2 | require_relative "../config/environment"
3 | require "rails/test_help"
4 |
5 | module ActiveSupport
6 | class TestCase
7 | # Run tests in parallel with specified workers
8 | parallelize(workers: :number_of_processors)
9 |
10 | # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order.
11 | fixtures :all
12 |
13 | # Add more helper methods to be used by all tests here...
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/tmp/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shey/rails-pay-checkout-demo/b4d6d981ec77522487fc14c26191775ba9fbc6bc/tmp/.keep
--------------------------------------------------------------------------------
/tmp/pids/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shey/rails-pay-checkout-demo/b4d6d981ec77522487fc14c26191775ba9fbc6bc/tmp/pids/.keep
--------------------------------------------------------------------------------
/tmp/storage/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shey/rails-pay-checkout-demo/b4d6d981ec77522487fc14c26191775ba9fbc6bc/tmp/storage/.keep
--------------------------------------------------------------------------------
/vendor/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shey/rails-pay-checkout-demo/b4d6d981ec77522487fc14c26191775ba9fbc6bc/vendor/.keep
--------------------------------------------------------------------------------
/vendor/javascript/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shey/rails-pay-checkout-demo/b4d6d981ec77522487fc14c26191775ba9fbc6bc/vendor/javascript/.keep
--------------------------------------------------------------------------------