payify 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +7 -0
  2. data/.env.dist +2 -0
  3. data/.rubocop.yml +66 -0
  4. data/Gemfile +28 -0
  5. data/Gemfile.lock +249 -0
  6. data/LICENSE +21 -0
  7. data/README.md +164 -0
  8. data/Rakefile +14 -0
  9. data/app/assets/config/payify_manifest.js +1 -0
  10. data/app/assets/images/payify/.keep +0 -0
  11. data/app/assets/images/payify/screenshot.png +0 -0
  12. data/app/assets/images/payify/vat.png +0 -0
  13. data/app/assets/stylesheets/payify/application.css +15 -0
  14. data/app/controllers/concerns/.keep +0 -0
  15. data/app/controllers/payify/payments_controller.rb +29 -0
  16. data/app/helpers/payify/application_helper.rb +4 -0
  17. data/app/jobs/payify/application_job.rb +4 -0
  18. data/app/mailers/payify/application_mailer.rb +6 -0
  19. data/app/models/concerns/payify/has_payment_concern.rb +28 -0
  20. data/app/models/concerns/payify/stripe_payment_concern.rb +33 -0
  21. data/app/models/payify/application_record.rb +5 -0
  22. data/app/models/payify/payment.rb +35 -0
  23. data/app/serializers/payify/payment_serializer.rb +10 -0
  24. data/app/views/payify/application.html.erb +15 -0
  25. data/app/views/payify/payments/_form.html.erb +46 -0
  26. data/app/views/payify/payments/complete.html.erb +26 -0
  27. data/app/views/payify/payments/new.html.erb +24 -0
  28. data/config/locales/payify.en.yml +7 -0
  29. data/config/routes.rb +8 -0
  30. data/db/migrate/20230611124944_create_payment.rb +15 -0
  31. data/db/migrate/20230617052430_add_stripe_payment_intent_id.rb +6 -0
  32. data/lib/payify/engine.rb +5 -0
  33. data/lib/payify/version.rb +5 -0
  34. data/lib/payify.rb +25 -0
  35. data/lib/stripe/client.rb +31 -0
  36. data/lib/tasks/payify_tasks.rake +4 -0
  37. data/payify.gemspec +27 -0
  38. data/sig/payify.rbs +4 -0
  39. metadata +95 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 65e38363d021bd6956ef7a915c235ecca2f48c3d8175895a671d3a801f323408
4
+ data.tar.gz: 6123dd61b176a86860396f93e3f50605169551056a278928d333dc6571c46990
5
+ SHA512:
6
+ metadata.gz: 572d1098b2a7305ac4bad08eec7560422ec9a9661d352bf78536ac9a3e8315e36984b1ab8d362a28bab8a1db18731c924d1a068f83aa81130600044404dd2da9
7
+ data.tar.gz: 2b7f1129bf709e830c305b43c7ca03f80ebf48c04a676f1eeab765ecfbf4450f7057dc9ce22a26e212fd2ed65d256ecaa45d738b412a09daa5145b5dcc7a078c
data/.env.dist ADDED
@@ -0,0 +1,2 @@
1
+ STRIPE_API_KEY=""
2
+ STRIPE_PUBLISHABLE_KEY=""
data/.rubocop.yml ADDED
@@ -0,0 +1,66 @@
1
+ AllCops:
2
+ Exclude:
3
+ - '**/bin/**/*'
4
+ - '**/test/**/*'
5
+ - '**/db/**/*'
6
+ - '**/config/**/*'
7
+ Security/YAMLLoad:
8
+ Enabled: false
9
+ Style/EmptyMethod:
10
+ EnforcedStyle: expanded
11
+ Metrics/AbcSize:
12
+ Max: 26
13
+ Metrics/CyclomaticComplexity:
14
+ Max: 12
15
+ Metrics/PerceivedComplexity:
16
+ Max: 12
17
+ Layout/LineLength:
18
+ Max: 160
19
+ Layout/FirstHashElementIndentation:
20
+ Enabled: false
21
+ Layout/ArgumentAlignment:
22
+ Enabled: false
23
+ Layout/HashAlignment:
24
+ Enabled: false
25
+ Metrics/MethodLength:
26
+ Max: 20
27
+ Metrics/ClassLength:
28
+ Max: 300
29
+ Metrics/BlockLength:
30
+ Max: 2000
31
+ Metrics/ModuleLength:
32
+ Max: 300
33
+ Style/Documentation:
34
+ Enabled: false
35
+ Style/FrozenStringLiteralComment:
36
+ Enabled: false
37
+ Style/OptionalBooleanParameter:
38
+ Enabled: false
39
+ Style/MethodDefParentheses:
40
+ Enabled: true
41
+ Style/StringLiterals:
42
+ EnforcedStyle: double_quotes
43
+ Style/RescueStandardError:
44
+ EnforcedStyle: implicit
45
+ Naming/VariableNumber:
46
+ CheckSymbols: false
47
+ Style/VariableNumber:
48
+ Enabled: false
49
+ Style/HashSyntax:
50
+ EnforcedShorthandSyntax: never
51
+ Gemspec/RequiredRubyVersion:
52
+ Enabled: false
53
+ Style/WordArray:
54
+ Enabled: false
55
+ Style/SymbolArray:
56
+ Enabled: false
57
+ Lint/Debugger:
58
+ Enabled: false
59
+ Layout/MultilineMethodCallIndentation:
60
+ Enabled: false
61
+ Style/TrailingCommaInArguments:
62
+ EnforcedStyleForMultiline: comma
63
+ Layout/ArrayAlignment:
64
+ EnforcedStyle: with_fixed_indentation
65
+ Style/ClassVars:
66
+ Enabled: false
data/Gemfile ADDED
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ # Specify your gem's dependencies in payify.gemspec
6
+ gemspec
7
+
8
+ gem "puma"
9
+ gem "rails", "~> 7.0.4"
10
+ gem "sprockets-rails"
11
+ gem "sqlite3"
12
+
13
+ group :development, :test do
14
+ gem "rails-controller-testing"
15
+ gem "rspec-rails"
16
+ gem "rubocop", "~> 1.21"
17
+
18
+ gem "factory_bot_rails"
19
+ gem "faker"
20
+ end
21
+
22
+ gem "rake", "~> 13.0.6"
23
+
24
+ gem "active_model_serializers"
25
+ gem "dotenv-rails", "~> 2.8.1"
26
+ gem "money", "~> 6.16"
27
+ gem "stripe", "~> 8.5.0"
28
+ gem "timecop", "~> 0.9.6"
data/Gemfile.lock ADDED
@@ -0,0 +1,249 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ payify (0.1.0)
5
+ rails (>= 7.0)
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ actioncable (7.0.5)
11
+ actionpack (= 7.0.5)
12
+ activesupport (= 7.0.5)
13
+ nio4r (~> 2.0)
14
+ websocket-driver (>= 0.6.1)
15
+ actionmailbox (7.0.5)
16
+ actionpack (= 7.0.5)
17
+ activejob (= 7.0.5)
18
+ activerecord (= 7.0.5)
19
+ activestorage (= 7.0.5)
20
+ activesupport (= 7.0.5)
21
+ mail (>= 2.7.1)
22
+ net-imap
23
+ net-pop
24
+ net-smtp
25
+ actionmailer (7.0.5)
26
+ actionpack (= 7.0.5)
27
+ actionview (= 7.0.5)
28
+ activejob (= 7.0.5)
29
+ activesupport (= 7.0.5)
30
+ mail (~> 2.5, >= 2.5.4)
31
+ net-imap
32
+ net-pop
33
+ net-smtp
34
+ rails-dom-testing (~> 2.0)
35
+ actionpack (7.0.5)
36
+ actionview (= 7.0.5)
37
+ activesupport (= 7.0.5)
38
+ rack (~> 2.0, >= 2.2.4)
39
+ rack-test (>= 0.6.3)
40
+ rails-dom-testing (~> 2.0)
41
+ rails-html-sanitizer (~> 1.0, >= 1.2.0)
42
+ actiontext (7.0.5)
43
+ actionpack (= 7.0.5)
44
+ activerecord (= 7.0.5)
45
+ activestorage (= 7.0.5)
46
+ activesupport (= 7.0.5)
47
+ globalid (>= 0.6.0)
48
+ nokogiri (>= 1.8.5)
49
+ actionview (7.0.5)
50
+ activesupport (= 7.0.5)
51
+ builder (~> 3.1)
52
+ erubi (~> 1.4)
53
+ rails-dom-testing (~> 2.0)
54
+ rails-html-sanitizer (~> 1.1, >= 1.2.0)
55
+ active_model_serializers (0.10.13)
56
+ actionpack (>= 4.1, < 7.1)
57
+ activemodel (>= 4.1, < 7.1)
58
+ case_transform (>= 0.2)
59
+ jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
60
+ activejob (7.0.5)
61
+ activesupport (= 7.0.5)
62
+ globalid (>= 0.3.6)
63
+ activemodel (7.0.5)
64
+ activesupport (= 7.0.5)
65
+ activerecord (7.0.5)
66
+ activemodel (= 7.0.5)
67
+ activesupport (= 7.0.5)
68
+ activestorage (7.0.5)
69
+ actionpack (= 7.0.5)
70
+ activejob (= 7.0.5)
71
+ activerecord (= 7.0.5)
72
+ activesupport (= 7.0.5)
73
+ marcel (~> 1.0)
74
+ mini_mime (>= 1.1.0)
75
+ activesupport (7.0.5)
76
+ concurrent-ruby (~> 1.0, >= 1.0.2)
77
+ i18n (>= 1.6, < 2)
78
+ minitest (>= 5.1)
79
+ tzinfo (~> 2.0)
80
+ ast (2.4.2)
81
+ builder (3.2.4)
82
+ case_transform (0.2)
83
+ activesupport
84
+ concurrent-ruby (1.2.2)
85
+ crass (1.0.6)
86
+ date (3.3.3)
87
+ diff-lcs (1.5.0)
88
+ dotenv (2.8.1)
89
+ dotenv-rails (2.8.1)
90
+ dotenv (= 2.8.1)
91
+ railties (>= 3.2)
92
+ erubi (1.12.0)
93
+ factory_bot (6.2.1)
94
+ activesupport (>= 5.0.0)
95
+ factory_bot_rails (6.2.0)
96
+ factory_bot (~> 6.2.0)
97
+ railties (>= 5.0.0)
98
+ faker (3.2.0)
99
+ i18n (>= 1.8.11, < 2)
100
+ globalid (1.1.0)
101
+ activesupport (>= 5.0)
102
+ i18n (1.14.1)
103
+ concurrent-ruby (~> 1.0)
104
+ json (2.6.3)
105
+ jsonapi-renderer (0.2.2)
106
+ loofah (2.21.3)
107
+ crass (~> 1.0.2)
108
+ nokogiri (>= 1.12.0)
109
+ mail (2.8.1)
110
+ mini_mime (>= 0.1.1)
111
+ net-imap
112
+ net-pop
113
+ net-smtp
114
+ marcel (1.0.2)
115
+ method_source (1.0.0)
116
+ mini_mime (1.1.2)
117
+ minitest (5.18.0)
118
+ money (6.16.0)
119
+ i18n (>= 0.6.4, <= 2)
120
+ net-imap (0.3.4)
121
+ date
122
+ net-protocol
123
+ net-pop (0.1.2)
124
+ net-protocol
125
+ net-protocol (0.2.1)
126
+ timeout
127
+ net-smtp (0.3.3)
128
+ net-protocol
129
+ nio4r (2.5.9)
130
+ nokogiri (1.15.2-x86_64-linux)
131
+ racc (~> 1.4)
132
+ parallel (1.23.0)
133
+ parser (3.2.2.3)
134
+ ast (~> 2.4.1)
135
+ racc
136
+ puma (6.3.0)
137
+ nio4r (~> 2.0)
138
+ racc (1.7.0)
139
+ rack (2.2.7)
140
+ rack-test (2.1.0)
141
+ rack (>= 1.3)
142
+ rails (7.0.5)
143
+ actioncable (= 7.0.5)
144
+ actionmailbox (= 7.0.5)
145
+ actionmailer (= 7.0.5)
146
+ actionpack (= 7.0.5)
147
+ actiontext (= 7.0.5)
148
+ actionview (= 7.0.5)
149
+ activejob (= 7.0.5)
150
+ activemodel (= 7.0.5)
151
+ activerecord (= 7.0.5)
152
+ activestorage (= 7.0.5)
153
+ activesupport (= 7.0.5)
154
+ bundler (>= 1.15.0)
155
+ railties (= 7.0.5)
156
+ rails-controller-testing (1.0.5)
157
+ actionpack (>= 5.0.1.rc1)
158
+ actionview (>= 5.0.1.rc1)
159
+ activesupport (>= 5.0.1.rc1)
160
+ rails-dom-testing (2.0.3)
161
+ activesupport (>= 4.2.0)
162
+ nokogiri (>= 1.6)
163
+ rails-html-sanitizer (1.6.0)
164
+ loofah (~> 2.21)
165
+ nokogiri (~> 1.14)
166
+ railties (7.0.5)
167
+ actionpack (= 7.0.5)
168
+ activesupport (= 7.0.5)
169
+ method_source
170
+ rake (>= 12.2)
171
+ thor (~> 1.0)
172
+ zeitwerk (~> 2.5)
173
+ rainbow (3.1.1)
174
+ rake (13.0.6)
175
+ regexp_parser (2.8.1)
176
+ rexml (3.2.5)
177
+ rspec-core (3.12.2)
178
+ rspec-support (~> 3.12.0)
179
+ rspec-expectations (3.12.3)
180
+ diff-lcs (>= 1.2.0, < 2.0)
181
+ rspec-support (~> 3.12.0)
182
+ rspec-mocks (3.12.5)
183
+ diff-lcs (>= 1.2.0, < 2.0)
184
+ rspec-support (~> 3.12.0)
185
+ rspec-rails (6.0.3)
186
+ actionpack (>= 6.1)
187
+ activesupport (>= 6.1)
188
+ railties (>= 6.1)
189
+ rspec-core (~> 3.12)
190
+ rspec-expectations (~> 3.12)
191
+ rspec-mocks (~> 3.12)
192
+ rspec-support (~> 3.12)
193
+ rspec-support (3.12.0)
194
+ rubocop (1.52.0)
195
+ json (~> 2.3)
196
+ parallel (~> 1.10)
197
+ parser (>= 3.2.0.0)
198
+ rainbow (>= 2.2.2, < 4.0)
199
+ regexp_parser (>= 1.8, < 3.0)
200
+ rexml (>= 3.2.5, < 4.0)
201
+ rubocop-ast (>= 1.28.0, < 2.0)
202
+ ruby-progressbar (~> 1.7)
203
+ unicode-display_width (>= 2.4.0, < 3.0)
204
+ rubocop-ast (1.29.0)
205
+ parser (>= 3.2.1.0)
206
+ ruby-progressbar (1.13.0)
207
+ sprockets (4.2.0)
208
+ concurrent-ruby (~> 1.0)
209
+ rack (>= 2.2.4, < 4)
210
+ sprockets-rails (3.4.2)
211
+ actionpack (>= 5.2)
212
+ activesupport (>= 5.2)
213
+ sprockets (>= 3.0.0)
214
+ sqlite3 (1.6.3-x86_64-linux)
215
+ stripe (8.5.0)
216
+ thor (1.2.2)
217
+ timecop (0.9.6)
218
+ timeout (0.3.2)
219
+ tzinfo (2.0.6)
220
+ concurrent-ruby (~> 1.0)
221
+ unicode-display_width (2.4.2)
222
+ websocket-driver (0.7.5)
223
+ websocket-extensions (>= 0.1.0)
224
+ websocket-extensions (0.1.5)
225
+ zeitwerk (2.6.8)
226
+
227
+ PLATFORMS
228
+ x86_64-linux
229
+
230
+ DEPENDENCIES
231
+ active_model_serializers
232
+ dotenv-rails (~> 2.8.1)
233
+ factory_bot_rails
234
+ faker
235
+ money (~> 6.16)
236
+ payify!
237
+ puma
238
+ rails (~> 7.0.4)
239
+ rails-controller-testing
240
+ rake (~> 13.0.6)
241
+ rspec-rails
242
+ rubocop (~> 1.21)
243
+ sprockets-rails
244
+ sqlite3
245
+ stripe (~> 8.5.0)
246
+ timecop (~> 0.9.6)
247
+
248
+ BUNDLED WITH
249
+ 2.4.12
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023 Nathan Lopez
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.
data/README.md ADDED
@@ -0,0 +1,164 @@
1
+ # Payify
2
+
3
+ PayifyRails is a Ruby gem that simplifies payment integration into Ruby on Rails projects. It allows to easily add payment functionality to your models. For example, by applying the HasPaymentConcern to a reservation model, you can manage the payment process for reservations seamlessly.
4
+
5
+ ![Screenshot](./app/assets/images/payify/screenshot.png)
6
+
7
+ ## Installation
8
+
9
+ To install the gem add it into a Gemfile (Bundler):
10
+
11
+ ```ruby
12
+ gem 'payify', git: 'https://github.com/andrewdsilva/payify'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ ```
18
+ bundle install
19
+ ```
20
+
21
+ ## Features ✅
22
+
23
+ - Includes a Payment model
24
+ - Provides concerns to easily add a payment system to your model
25
+ - Enables payment with Stripe
26
+ - Offers a payment form to integrate into your application
27
+ - Allows payment status to be checked via an api route
28
+ - Allows configuration of payment (currency, VAT)
29
+ - Provides a user interface for payment processing
30
+ - Enables management of payment status (pending, paid)
31
+
32
+ ## Configuration
33
+
34
+ For now, Payify uses Stripe as the payment gateway. You can configure the currency using an initializer.
35
+
36
+ ```ruby
37
+ # initializers/payify.rb
38
+ Payify.setup do |config|
39
+ config.currency = "usd"
40
+ config.default_tax_rates_id = "eur"
41
+ end
42
+ ```
43
+
44
+ To handle VAT or different tax rates on your payments, you need to create tax rates on Stripe and define the default_tax_rates_id or, alternatively, define the tax_rates_id method on your payment-related models. Leave it empty if you don't manage any taxes.
45
+
46
+ You can set your Stripe API credentials using environment variables. (Secret key, Publishable key)
47
+
48
+ ```ruby
49
+ # .env
50
+ STRIPE_API_KEY="..."
51
+ STRIPE_PUBLISHABLE_KEY="..."
52
+ ```
53
+
54
+ ## Usage
55
+
56
+ To enable payment functionality for a model, simply include the HasPayment concern:
57
+
58
+ ```ruby
59
+ class Reservation < ApplicationRecord
60
+ include Payify::HasPaymentConcern
61
+
62
+ def ammount_to_pay
63
+ self.price
64
+ end
65
+
66
+ # Optional : Override the default tax rates id
67
+ def tax_rates_id
68
+ 'txr_1234567890'
69
+ end
70
+ end
71
+ ```
72
+
73
+ When you want to request a payment for a model on which you added the concern, you just need to call the create_payment method.
74
+
75
+ ```ruby
76
+ reservation_1.create_payment
77
+ ```
78
+
79
+ Then you can find the id of the new pending payment with payment.id.
80
+
81
+ ```ruby
82
+ reservation_1.payment.id
83
+ ```
84
+
85
+ Now you just have to redirect the user to `/payments/:id/new` or include the payment form in your page.
86
+
87
+ ```ruby
88
+ # reservation/show.html.erb
89
+ <%= render "payify/payments/form", payment: @payment %>
90
+ ```
91
+
92
+ After completing the payment process, the user will be redirected to:
93
+
94
+ ```
95
+ /payments/:id/complete
96
+ ```
97
+
98
+ The application will then verify the payment status with Stripe. You can do it manually calling the following method:
99
+
100
+ ```ruby
101
+ @payment.stripe_confirm_payment
102
+ ```
103
+
104
+ If the payment has been successfully processed, a confirmation message will be displayed to the user. The payment method `paid?` will return `true`.
105
+
106
+ To customize the page that displays the payment status, you can create the following file:
107
+
108
+ ```ruby
109
+ # views/payify/payments/complete.html.erb
110
+
111
+ <% if @payment.paid? %>
112
+ <div class="alert alert-success" role="alert">
113
+ <%= I18n.t("payments.complete.paid") %>
114
+ </div>
115
+ <% else %>
116
+ <div class="alert alert-danger" role="alert">
117
+ <%= I18n.t("payments.complete.pending") %>
118
+ </div>
119
+ <% end %>
120
+ ```
121
+
122
+ ## API
123
+
124
+ If you prefer using the Payify API, after creating the payment object, you can initialize a new Stripe payment by making a request to: `/payments/:id/new.json`
125
+
126
+ This request will return a JSON response containing the `stripe_payment_intent_id` and `stripe_client_secret` required to process a payment using Stripe.
127
+
128
+ After making the payment, you can make a request to the following endpoint to update the payment status and retrieve its current state:
129
+
130
+ ```
131
+ /payments/:id/complete.json
132
+ ```
133
+
134
+ ## Status
135
+
136
+ You can access the payment status using `@payment.status`. The possible statuses are:
137
+
138
+ - `pending`: The payment is still being processed.
139
+ - `paid`: The payment has been successfully completed.
140
+ - `failed`: The payment has failed.
141
+
142
+ ## Tests
143
+
144
+ To run the tests, execute `rspec` command.
145
+
146
+ You can test the payment process in your web browser by following these steps:
147
+
148
+ 1. Navigate to the `test/dummy` directory.
149
+ 2. Run the following commands:
150
+ - `rails db:create`
151
+ - `rails db:migrate`
152
+ - `rails db:seed`
153
+ - `rails s`
154
+ 3. Open your browser and go to: [http://localhost:3000/](http://localhost:3000/).
155
+
156
+ Each time you run the seed command, your test database will be reset with a reservation that has a payment with an ID of 1. This allows you to test the payment process at the following address: [http://localhost:3000/payments/1/new](http://localhost:3000/payments/1/new).
157
+
158
+ ## Contributing
159
+
160
+ Bug reports and pull requests are welcome on GitHub at https://github.com/andrewdsilva/payify.
161
+
162
+ ## License
163
+
164
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,14 @@
1
+ require "bundler/setup"
2
+
3
+ APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
4
+ load "rails/tasks/engine.rake"
5
+
6
+ load "rails/tasks/statistics.rake"
7
+
8
+ require "bundler/gem_tasks"
9
+
10
+ require "rubocop/rake_task"
11
+
12
+ RuboCop::RakeTask.new
13
+
14
+ task default: :rubocop
@@ -0,0 +1 @@
1
+ //= link_directory ../stylesheets/payify .css
File without changes
Binary file
@@ -0,0 +1,15 @@
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 file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6
+ * or any plugin's 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/SCSS
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
+ */
File without changes
@@ -0,0 +1,29 @@
1
+ module Payify
2
+ class PaymentsController < ActionController::Base
3
+ before_action :set_object, only: %i[new complete]
4
+
5
+ def new
6
+ @payment.stripe_init_intent
7
+
8
+ respond_to do |format|
9
+ format.html
10
+ format.json { render json: @payment }
11
+ end
12
+ end
13
+
14
+ def complete
15
+ @payment.stripe_confirm_payment unless @payment.paid?
16
+
17
+ respond_to do |format|
18
+ format.html
19
+ format.json { render json: @payment }
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def set_object
26
+ @payment = Payment.find(params[:id])
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,4 @@
1
+ module Payify
2
+ module ApplicationHelper
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module Payify
2
+ class ApplicationJob < ActiveJob::Base
3
+ end
4
+ end
@@ -0,0 +1,6 @@
1
+ module Payify
2
+ class ApplicationMailer < ActionMailer::Base
3
+ default from: "from@example.com"
4
+ layout "mailer"
5
+ end
6
+ end
@@ -0,0 +1,28 @@
1
+ module Payify
2
+ module HasPaymentConcern
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ has_one :payment, as: :model, dependent: :destroy, class_name: "Payify::Payment"
7
+ end
8
+
9
+ def create_payment
10
+ build_payment(amount: amount_to_pay)
11
+
12
+ payment.save
13
+ payment
14
+ end
15
+
16
+ def cancel_payment
17
+ payment.destroy if payment.present?
18
+ end
19
+
20
+ def amount_to_pay
21
+ raise NotImplementedError, "The 'amount_to_pay' method must be implemented in the including model."
22
+ end
23
+
24
+ def tax_rates_id
25
+ nil
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,33 @@
1
+ require "stripe/client"
2
+
3
+ module Payify
4
+ module StripePaymentConcern
5
+ extend ActiveSupport::Concern
6
+
7
+ def stripe_client
8
+ @stripe_client ||= Stripe::Client.new
9
+ end
10
+
11
+ def stripe_init_intent
12
+ return unless stripe_payment_inent_id.nil? && !paid?
13
+
14
+ intent = stripe_client.create_payment_intent(amount, tax_id)
15
+
16
+ update_attribute(:stripe_payment_inent_id, intent.id)
17
+ update_attribute(:stripe_client_secret, intent.client_secret)
18
+ end
19
+
20
+ def stripe_confirm_payment
21
+ intent = stripe_client.find_intent(stripe_payment_inent_id)
22
+
23
+ return unless intent["status"] == "succeeded"
24
+
25
+ update_attribute(:paid_at, Time.now)
26
+ update_attribute(:status, Payify::Payment.statuses[:paid])
27
+ end
28
+
29
+ def tax_id
30
+ model&.tax_rates_id || Payify.default_tax_rates_id
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,5 @@
1
+ module Payify
2
+ class ApplicationRecord < ActiveRecord::Base
3
+ self.abstract_class = true
4
+ end
5
+ end
@@ -0,0 +1,35 @@
1
+ module Payify
2
+ class Payment < ApplicationRecord
3
+ include ::Payify::StripePaymentConcern
4
+
5
+ enum status: { pending: 0, paid: 1, failed: 2 }
6
+ enum payment_method: { stripe: 0 }
7
+
8
+ belongs_to :model, polymorphic: true, optional: true
9
+
10
+ validates :amount, presence: true, numericality: { greater_than_or_equal_to: 0 }
11
+ validates :status, presence: true
12
+
13
+ before_destroy :cannot_destroy_if_paid
14
+
15
+ def make_payment(transaction_id)
16
+ self.transaction_id = transaction_id
17
+ self.paid_at = Time.current
18
+ self.status = :paid
19
+ end
20
+
21
+ def can_destroy?
22
+ !paid?
23
+ end
24
+
25
+ def paid?
26
+ paid_at.present?
27
+ end
28
+
29
+ private
30
+
31
+ def cannot_destroy_if_paid
32
+ raise ActiveRecord::RecordNotDestroyed if paid?
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,10 @@
1
+ module Payify
2
+ class PaymentSerializer < ActiveModel::Serializer
3
+ attributes :id, :amount, :status, :payment_method, :transaction_id, :paid_at
4
+
5
+ attributes :stripe_payment_inent_id, :stripe_client_secret
6
+
7
+ attribute(:paid) { object.paid? }
8
+ attribute(:can_destroy) { object.can_destroy? }
9
+ end
10
+ end
@@ -0,0 +1,15 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Payify</title>
5
+ <%= csrf_meta_tags %>
6
+ <%= csp_meta_tag %>
7
+
8
+ <%= stylesheet_link_tag "payify/application", media: "all" %>
9
+ </head>
10
+ <body>
11
+
12
+ <%= yield %>
13
+
14
+ </body>
15
+ </html>
@@ -0,0 +1,46 @@
1
+ <script src="https://js.stripe.com/v3/"></script>
2
+
3
+ <%= form_with(id: "payment-form", url: complete_payment_path, method: "post") do |form| %>
4
+ <div id="payment-element">
5
+ </div>
6
+
7
+ <div id="error-message">
8
+ </div>
9
+
10
+ <%= form.submit I18n.t("payments.submit"), id: "submit", class: "btn btn-primary w-100 mt-4" %>
11
+ <% end %>
12
+
13
+ <script>
14
+ const stripe = Stripe("<%= Payify.stripe_publishable_key %>");
15
+
16
+ const options = {
17
+ clientSecret: "<%= payment.stripe_client_secret %>",
18
+ };
19
+
20
+ const elements = stripe.elements(options);
21
+
22
+ const paymentElement = elements.create("payment");
23
+ paymentElement.mount("#payment-element");
24
+
25
+ const form = document.getElementById("payment-form");
26
+
27
+ form.addEventListener("submit", async (event) => {
28
+ event.preventDefault();
29
+
30
+ const {error} = await stripe.confirmPayment({
31
+ elements,
32
+ confirmParams: {
33
+ return_url: "<%= request.base_url %>/payments/<%= payment.id %>/complete",
34
+ },
35
+ });
36
+
37
+ if (error) {
38
+ const messageContainer = document.querySelector("#error-message");
39
+ messageContainer.textContent = error.message;
40
+ } else {
41
+ // Your customer will be redirected to your `return_url`. For some payment
42
+ // methods like iDEAL, your customer will be redirected to an intermediate
43
+ // site first to authorize the payment, then redirected to the `return_url`.
44
+ }
45
+ });
46
+ </script>
@@ -0,0 +1,26 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+
7
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
8
+
9
+ <title>Payment</title>
10
+ </head>
11
+ <body>
12
+
13
+ <div class="container py-5 my-5" style="max-width: 500px;">
14
+ <% if @payment.paid? %>
15
+ <div class="alert alert-success" role="alert">
16
+ <%= I18n.t("payments.complete.paid") %>
17
+ </div>
18
+ <% else %>
19
+ <div class="alert alert-danger" role="alert">
20
+ <%= I18n.t("payments.complete.pending") %>
21
+ </div>
22
+ <% end %>
23
+ </div>
24
+
25
+ </body>
26
+ </html>
@@ -0,0 +1,24 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+
7
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
8
+
9
+ <title>Payment</title>
10
+ </head>
11
+ <body>
12
+
13
+ <div class="container py-5 my-5" style="max-width: 500px;">
14
+ <% if @payment.paid? %>
15
+ <div class="alert alert-info" role="alert">
16
+ <%= I18n.t("payments.already_paid") %>
17
+ </div>
18
+ <% else %>
19
+ <%= render "form", payment: @payment %>
20
+ <% end %>
21
+ </div>
22
+
23
+ </body>
24
+ </html>
@@ -0,0 +1,7 @@
1
+ en:
2
+ payments:
3
+ submit: Submit
4
+ already_paid: Payment has already been completed.
5
+ complete:
6
+ pending: Payment pending.
7
+ paid: Successful payment!
data/config/routes.rb ADDED
@@ -0,0 +1,8 @@
1
+ ::Payify::Engine.routes.draw do
2
+ resources :payments do
3
+ member do
4
+ get "new"
5
+ get "complete", as: "complete"
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,15 @@
1
+ class CreatePayment < ActiveRecord::Migration[7.0]
2
+ def change
3
+ create_table :payify_payments do |t|
4
+ t.decimal :amount, precision: 10, scale: 2, null: false
5
+ t.integer :status, default: 0, null: false
6
+ t.integer :payment_method
7
+ t.string :transaction_id
8
+ t.datetime :paid_at
9
+ t.string :model_type
10
+ t.integer :model_id
11
+
12
+ t.timestamps
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,6 @@
1
+ class AddStripePaymentIntentId < ActiveRecord::Migration[7.0]
2
+ def change
3
+ add_column :payify_payments, :stripe_payment_inent_id, :string
4
+ add_column :payify_payments, :stripe_client_secret, :string
5
+ end
6
+ end
@@ -0,0 +1,5 @@
1
+ module Payify
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace Payify
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Payify
4
+ VERSION = "0.1.0"
5
+ end
data/lib/payify.rb ADDED
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "payify/version"
4
+ require "payify/engine"
5
+ require "dotenv/load"
6
+
7
+ module Payify
8
+ class Error < StandardError; end
9
+
10
+ mattr_accessor :currency
11
+ @@currency = "usd"
12
+
13
+ mattr_accessor :default_tax_rates_id
14
+ @@default_tax_rates_id = nil
15
+
16
+ mattr_accessor :stripe_api_key
17
+ @@stripe_api_key = ENV["STRIPE_API_KEY"]
18
+
19
+ mattr_accessor :stripe_publishable_key
20
+ @@stripe_publishable_key = ENV["STRIPE_PUBLISHABLE_KEY"]
21
+
22
+ def self.setup
23
+ yield self
24
+ end
25
+ end
@@ -0,0 +1,31 @@
1
+ module Stripe
2
+ class Client
3
+ def initialize
4
+ stripe
5
+
6
+ Stripe.api_key = Payify.stripe_api_key
7
+ end
8
+
9
+ def stripe
10
+ @stripe ||= Stripe::StripeClient.new
11
+ end
12
+
13
+ def create_payment_intent(amount, tax_id = nil, object_invoice = "")
14
+ infos = {
15
+ amount: (amount * 100).to_i,
16
+ currency: Payify.currency,
17
+ description: object_invoice,
18
+ setup_future_usage: "off_session",
19
+ metadata: {
20
+ "tax_id": tax_id
21
+ }
22
+ }
23
+
24
+ Stripe::PaymentIntent.create(infos)
25
+ end
26
+
27
+ def find_intent(payment_intent_id)
28
+ Stripe::PaymentIntent.retrieve(payment_intent_id)
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :payify do
3
+ # # Task goes here
4
+ # end
data/payify.gemspec ADDED
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/payify/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "payify"
7
+ spec.version = Payify::VERSION
8
+ spec.authors = ["Nathan Lopez"]
9
+ spec.email = ["nathan.lopez042@gmail.com"]
10
+
11
+ spec.summary = "Payment integration for Ruby on Rails projects."
12
+ spec.description = "Payify is a Ruby gem that simplifies payment integration into Ruby on Rails projects."
13
+ spec.homepage = "https://github.com/andrewdsilva/payify"
14
+ spec.license = "MIT"
15
+ spec.required_ruby_version = ">= 2.6.0"
16
+
17
+ spec.files = Dir.chdir(__dir__) do
18
+ `git ls-files -z`.split("\x0").reject do |f|
19
+ (File.expand_path(f) == __FILE__) || f.start_with?(*%w[bin/ test/ spec/ features/ .git .circleci appveyor])
20
+ end
21
+ end
22
+ spec.bindir = "exe"
23
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
24
+ spec.require_paths = ["lib"]
25
+
26
+ spec.add_dependency "rails", "~> 7.0"
27
+ end
data/sig/payify.rbs ADDED
@@ -0,0 +1,4 @@
1
+ module Payify
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
metadata ADDED
@@ -0,0 +1,95 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: payify
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Nathan Lopez
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2023-06-18 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '7.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '7.0'
27
+ description: Payify is a Ruby gem that simplifies payment integration into Ruby on
28
+ Rails projects.
29
+ email:
30
+ - nathan.lopez042@gmail.com
31
+ executables: []
32
+ extensions: []
33
+ extra_rdoc_files: []
34
+ files:
35
+ - ".env.dist"
36
+ - ".rubocop.yml"
37
+ - Gemfile
38
+ - Gemfile.lock
39
+ - LICENSE
40
+ - README.md
41
+ - Rakefile
42
+ - app/assets/config/payify_manifest.js
43
+ - app/assets/images/payify/.keep
44
+ - app/assets/images/payify/screenshot.png
45
+ - app/assets/images/payify/vat.png
46
+ - app/assets/stylesheets/payify/application.css
47
+ - app/controllers/concerns/.keep
48
+ - app/controllers/payify/payments_controller.rb
49
+ - app/helpers/payify/application_helper.rb
50
+ - app/jobs/payify/application_job.rb
51
+ - app/mailers/payify/application_mailer.rb
52
+ - app/models/concerns/payify/has_payment_concern.rb
53
+ - app/models/concerns/payify/stripe_payment_concern.rb
54
+ - app/models/payify/application_record.rb
55
+ - app/models/payify/payment.rb
56
+ - app/serializers/payify/payment_serializer.rb
57
+ - app/views/payify/application.html.erb
58
+ - app/views/payify/payments/_form.html.erb
59
+ - app/views/payify/payments/complete.html.erb
60
+ - app/views/payify/payments/new.html.erb
61
+ - config/locales/payify.en.yml
62
+ - config/routes.rb
63
+ - db/migrate/20230611124944_create_payment.rb
64
+ - db/migrate/20230617052430_add_stripe_payment_intent_id.rb
65
+ - lib/payify.rb
66
+ - lib/payify/engine.rb
67
+ - lib/payify/version.rb
68
+ - lib/stripe/client.rb
69
+ - lib/tasks/payify_tasks.rake
70
+ - payify.gemspec
71
+ - sig/payify.rbs
72
+ homepage: https://github.com/andrewdsilva/payify
73
+ licenses:
74
+ - MIT
75
+ metadata: {}
76
+ post_install_message:
77
+ rdoc_options: []
78
+ require_paths:
79
+ - lib
80
+ required_ruby_version: !ruby/object:Gem::Requirement
81
+ requirements:
82
+ - - ">="
83
+ - !ruby/object:Gem::Version
84
+ version: 2.6.0
85
+ required_rubygems_version: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ requirements: []
91
+ rubygems_version: 3.4.14
92
+ signing_key:
93
+ specification_version: 4
94
+ summary: Payment integration for Ruby on Rails projects.
95
+ test_files: []