payify 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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: []