solidus_stripe 4.4.1 → 5.0.0.alpha.1

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 (153) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +92 -52
  3. data/.gitignore +3 -0
  4. data/.rubocop.yml +94 -4
  5. data/.yardopts +1 -0
  6. data/Gemfile +10 -30
  7. data/LICENSE +2 -2
  8. data/Procfile.dev +3 -0
  9. data/README.md +145 -215
  10. data/Rakefile +5 -44
  11. data/app/assets/javascripts/spree/backend/solidus_stripe.js +2 -0
  12. data/app/assets/stylesheets/spree/backend/solidus_stripe.css +4 -0
  13. data/app/controllers/solidus_stripe/intents_controller.rb +36 -52
  14. data/app/controllers/solidus_stripe/webhooks_controller.rb +28 -0
  15. data/app/models/concerns/solidus_stripe/log_entries.rb +31 -0
  16. data/app/models/solidus_stripe/customer.rb +21 -0
  17. data/app/models/solidus_stripe/gateway.rb +231 -0
  18. data/app/models/solidus_stripe/payment_intent.rb +111 -0
  19. data/app/models/solidus_stripe/payment_method.rb +106 -0
  20. data/app/models/solidus_stripe/payment_source.rb +31 -0
  21. data/app/models/solidus_stripe/slug_entry.rb +20 -0
  22. data/app/models/solidus_stripe.rb +7 -0
  23. data/app/subscribers/solidus_stripe/webhook/charge_subscriber.rb +28 -0
  24. data/app/subscribers/solidus_stripe/webhook/payment_intent_subscriber.rb +112 -0
  25. data/app/views/spree/admin/payments/source_forms/_stripe.html.erb +29 -0
  26. data/app/views/spree/admin/payments/source_forms/existing_payment/_stripe.html.erb +14 -0
  27. data/app/views/spree/admin/payments/source_forms/existing_payment/stripe/_card.html.erb +8 -0
  28. data/app/views/spree/admin/payments/source_forms/existing_payment/stripe/_default.html.erb +7 -0
  29. data/app/views/spree/admin/payments/source_views/_stripe.html.erb +15 -0
  30. data/app/views/spree/api/payments/source_views/_stripe.json.jbuilder +8 -0
  31. data/bin/dev +13 -0
  32. data/bin/dummy-app +29 -0
  33. data/bin/rails +38 -3
  34. data/bin/rails-dummy-app +3 -0
  35. data/bin/rails-engine +1 -11
  36. data/bin/rails-new +55 -0
  37. data/bin/rails-sandbox +1 -14
  38. data/bin/rspec +10 -0
  39. data/bin/sandbox +12 -74
  40. data/bin/setup +1 -0
  41. data/bin/update-migrations +56 -0
  42. data/codecov.yml +12 -0
  43. data/config/locales/en.yml +16 -1
  44. data/config/routes.rb +5 -11
  45. data/coverage.rb +42 -0
  46. data/db/migrate/20230109183332_create_solidus_stripe_payment_sources.rb +10 -0
  47. data/db/migrate/20230303154931_create_solidus_stripe_setup_intent.rb +10 -0
  48. data/db/migrate/20230306105520_create_solidus_stripe_payment_intents.rb +10 -0
  49. data/db/migrate/20230308122414_create_solidus_stripe_webhook_endpoints.rb +10 -0
  50. data/db/migrate/20230310152615_add_payment_method_reference_to_stripe_intents.rb +6 -0
  51. data/db/migrate/20230310171444_normalize_stripe_intent_id_attributes.rb +6 -0
  52. data/db/migrate/20230313150008_create_solidus_stripe_customers.rb +16 -0
  53. data/db/migrate/20230323154931_drop_solidus_stripe_setup_intent.rb +13 -0
  54. data/db/migrate/20230403094916_rename_webhook_endpoint_to_payment_method_slug_entries.rb +5 -0
  55. data/db/seeds.rb +6 -24
  56. data/lib/generators/solidus_stripe/install/install_generator.rb +121 -14
  57. data/lib/generators/solidus_stripe/install/templates/app/assets/stylesheets/spree/frontend/solidus_stripe.css +13 -0
  58. data/lib/generators/solidus_stripe/install/templates/app/javascript/controllers/solidus_stripe_confirm_controller.js +39 -0
  59. data/lib/generators/solidus_stripe/install/templates/app/javascript/controllers/solidus_stripe_payment_controller.js +89 -0
  60. data/lib/generators/solidus_stripe/install/templates/app/views/checkouts/existing_payment/_stripe.html.erb +16 -0
  61. data/lib/generators/solidus_stripe/install/templates/app/views/checkouts/existing_payment/stripe/_card.html.erb +8 -0
  62. data/lib/generators/solidus_stripe/install/templates/app/views/checkouts/existing_payment/stripe/_default.html.erb +7 -0
  63. data/lib/generators/solidus_stripe/install/templates/app/views/checkouts/payment/_stripe.html.erb +39 -0
  64. data/lib/generators/solidus_stripe/install/templates/app/views/orders/payment_info/_stripe.html.erb +20 -0
  65. data/lib/generators/solidus_stripe/install/templates/config/initializers/solidus_stripe.rb +31 -0
  66. data/lib/solidus_stripe/configuration.rb +24 -3
  67. data/lib/solidus_stripe/engine.rb +19 -6
  68. data/lib/solidus_stripe/money_to_stripe_amount_converter.rb +109 -0
  69. data/lib/solidus_stripe/refunds_synchronizer.rb +96 -0
  70. data/lib/solidus_stripe/seeds.rb +19 -0
  71. data/lib/solidus_stripe/testing_support/factories.rb +153 -0
  72. data/lib/solidus_stripe/version.rb +1 -1
  73. data/lib/solidus_stripe/webhook/event.rb +90 -0
  74. data/lib/solidus_stripe.rb +0 -2
  75. data/solidus_stripe.gemspec +29 -6
  76. data/spec/lib/solidus_stripe/configuration_spec.rb +21 -0
  77. data/spec/lib/solidus_stripe/money_to_stripe_amount_converter_spec.rb +133 -0
  78. data/spec/lib/solidus_stripe/refunds_synchronizer_spec.rb +238 -0
  79. data/spec/lib/solidus_stripe/seeds_spec.rb +43 -0
  80. data/spec/lib/solidus_stripe/webhook/event_spec.rb +134 -0
  81. data/spec/models/concerns/solidus_stripe/log_entries_spec.rb +54 -0
  82. data/spec/models/solidus_stripe/customer_spec.rb +47 -0
  83. data/spec/models/solidus_stripe/gateway_spec.rb +283 -0
  84. data/spec/models/solidus_stripe/payment_intent_spec.rb +17 -0
  85. data/spec/models/solidus_stripe/payment_method_spec.rb +137 -0
  86. data/spec/models/solidus_stripe/payment_source_spec.rb +25 -0
  87. data/spec/requests/solidus_stripe/intents_controller_spec.rb +29 -0
  88. data/spec/requests/solidus_stripe/webhooks_controller/charge/refunded_spec.rb +31 -0
  89. data/spec/requests/solidus_stripe/webhooks_controller/payment_intent/canceled_spec.rb +23 -0
  90. data/spec/requests/solidus_stripe/webhooks_controller/payment_intent/payment_failed_spec.rb +23 -0
  91. data/spec/requests/solidus_stripe/webhooks_controller/payment_intent/succeeded_spec.rb +29 -0
  92. data/spec/requests/solidus_stripe/webhooks_controller_spec.rb +52 -0
  93. data/spec/solidus_stripe_spec_helper.rb +10 -0
  94. data/spec/subscribers/solidus_stripe/webhook/charge_subscriber_spec.rb +33 -0
  95. data/spec/subscribers/solidus_stripe/webhook/payment_intent_subscriber_spec.rb +297 -0
  96. data/spec/support/solidus_stripe/backend_test_helper.rb +210 -0
  97. data/spec/support/solidus_stripe/checkout_test_helper.rb +339 -0
  98. data/spec/support/solidus_stripe/factories.rb +5 -0
  99. data/spec/support/solidus_stripe/webhook/data_fixtures.rb +106 -0
  100. data/spec/support/solidus_stripe/webhook/event_with_context_factory.rb +82 -0
  101. data/spec/support/solidus_stripe/webhook/request_helper.rb +32 -0
  102. data/spec/system/backend/solidus_stripe/orders/payments_spec.rb +119 -0
  103. data/spec/system/frontend/.keep +0 -0
  104. data/spec/system/frontend/solidus_stripe/checkout_spec.rb +187 -0
  105. data/tmp/.keep +0 -0
  106. metadata +202 -78
  107. data/.rubocop_todo.yml +0 -298
  108. data/.travis.yml +0 -28
  109. data/app/assets/javascripts/spree/frontend/solidus_stripe/stripe-cart-page-checkout.js +0 -122
  110. data/app/assets/javascripts/spree/frontend/solidus_stripe/stripe-elements.js +0 -148
  111. data/app/assets/javascripts/spree/frontend/solidus_stripe/stripe-init.js +0 -20
  112. data/app/assets/javascripts/spree/frontend/solidus_stripe/stripe-payment-intents.js +0 -84
  113. data/app/assets/javascripts/spree/frontend/solidus_stripe/stripe-payment-request-button-shared.js +0 -160
  114. data/app/assets/javascripts/spree/frontend/solidus_stripe/stripe-payment.js +0 -16
  115. data/app/assets/javascripts/spree/frontend/solidus_stripe.js +0 -6
  116. data/app/controllers/solidus_stripe/payment_request_controller.rb +0 -52
  117. data/app/controllers/spree/stripe_controller.rb +0 -13
  118. data/app/decorators/models/spree/order_update_attributes_decorator.rb +0 -39
  119. data/app/decorators/models/spree/payment_decorator.rb +0 -11
  120. data/app/decorators/models/spree/refund_decorator.rb +0 -9
  121. data/app/models/solidus_stripe/address_from_params_service.rb +0 -72
  122. data/app/models/solidus_stripe/create_intents_payment_service.rb +0 -114
  123. data/app/models/solidus_stripe/prepare_order_for_payment_service.rb +0 -46
  124. data/app/models/solidus_stripe/shipping_rates_service.rb +0 -46
  125. data/app/models/spree/payment_method/stripe_credit_card.rb +0 -230
  126. data/bin/r +0 -13
  127. data/bin/sandbox_rails +0 -18
  128. data/db/migrate/20181010123508_update_stripe_payment_method_type_to_credit_card.rb +0 -21
  129. data/lib/assets/stylesheets/spree/frontend/solidus_stripe.scss +0 -11
  130. data/lib/solidus_stripe/testing_support/card_input_helper.rb +0 -34
  131. data/lib/tasks/solidus_stripe/db/seed.rake +0 -14
  132. data/lib/views/api/spree/api/payments/source_views/_stripe.json.jbuilder +0 -3
  133. data/lib/views/backend/spree/admin/log_entries/_stripe.html.erb +0 -28
  134. data/lib/views/backend/spree/admin/payments/source_forms/_stripe.html.erb +0 -1
  135. data/lib/views/backend/spree/admin/payments/source_views/_stripe.html.erb +0 -1
  136. data/lib/views/frontend/spree/checkout/existing_payment/_stripe.html.erb +0 -1
  137. data/lib/views/frontend/spree/checkout/payment/_stripe.html.erb +0 -8
  138. data/lib/views/frontend/spree/checkout/payment/v2/_javascript.html.erb +0 -78
  139. data/lib/views/frontend/spree/checkout/payment/v3/_elements.html.erb +0 -1
  140. data/lib/views/frontend/spree/checkout/payment/v3/_form_elements.html.erb +0 -40
  141. data/lib/views/frontend/spree/checkout/payment/v3/_intents.html.erb +0 -1
  142. data/lib/views/frontend/spree/checkout/payment/v3/_stripe.html.erb +0 -2
  143. data/lib/views/frontend/spree/orders/_stripe_payment_request_button.html.erb +0 -14
  144. data/spec/features/stripe_checkout_spec.rb +0 -486
  145. data/spec/models/solidus_stripe/address_from_params_service_spec.rb +0 -87
  146. data/spec/models/solidus_stripe/create_intents_payment_service_spec.rb +0 -127
  147. data/spec/models/solidus_stripe/prepare_order_for_payment_service_spec.rb +0 -65
  148. data/spec/models/solidus_stripe/shipping_rates_service_spec.rb +0 -54
  149. data/spec/models/spree/payment_method/stripe_credit_card_spec.rb +0 -354
  150. data/spec/requests/payment_requests_spec.rb +0 -152
  151. data/spec/solidus_frontend_app_template.rb +0 -17
  152. data/spec/spec_helper.rb +0 -37
  153. data/spec/support/solidus_address_helper.rb +0 -15
@@ -0,0 +1,283 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'solidus_stripe_spec_helper'
4
+
5
+ RSpec.describe SolidusStripe::Gateway do
6
+ describe '#authorize' do
7
+ it 'confirms the Stripe payment' do
8
+ stripe_payment_method = Stripe::PaymentMethod.construct_from(id: "pm_123")
9
+ stripe_customer = Stripe::Customer.construct_from(id: 'cus_123')
10
+ stripe_payment_intent = Stripe::PaymentIntent.construct_from(id: "pi_123")
11
+
12
+ payment_method = build(:stripe_payment_method)
13
+ gateway = payment_method.gateway
14
+ order = create(:order_with_stripe_payment,
15
+ amount: 123.45,
16
+ payment_method: payment_method,
17
+ stripe_payment_method_id: stripe_payment_method.id)
18
+ payment = order.payments.last
19
+
20
+ allow(Stripe::Customer).to receive(:create).and_return(stripe_customer)
21
+ [:create, :update, :retrieve].each do |method|
22
+ allow(Stripe::PaymentIntent).to receive(method).and_return(stripe_payment_intent)
23
+ end
24
+ allow(Stripe::PaymentIntent).to receive(:confirm).with(stripe_payment_intent.id).and_return(stripe_payment_intent)
25
+
26
+ result = gateway.authorize(123_45, payment.source, currency: 'USD', originator: order.payments.first)
27
+
28
+ expect(Stripe::PaymentIntent).to have_received(:update).with(
29
+ stripe_payment_intent.id,
30
+ { payment_method: stripe_payment_method.id }
31
+ )
32
+
33
+ expect(Stripe::PaymentIntent).to have_received(:create).with(
34
+ amount: 123_45,
35
+ currency: 'USD',
36
+ capture_method: 'manual',
37
+ confirm: false,
38
+ metadata: { solidus_order_number: order.number },
39
+ customer: "cus_123",
40
+ setup_future_usage: nil
41
+ )
42
+ expect(Stripe::PaymentIntent).to have_received(:confirm).with("pi_123")
43
+ expect(result.params).to eq("data" => '{"id":"pi_123"}')
44
+ expect(payment.reload.response_code).to eq("pi_123")
45
+ end
46
+
47
+ it 'generates error response on failure' do
48
+ stripe_payment_method = Stripe::PaymentMethod.construct_from(id: "pm_123")
49
+ stripe_payment_intent = Stripe::PaymentIntent.construct_from(id: "pi_123")
50
+
51
+ payment_method = build(:stripe_payment_method)
52
+ gateway = payment_method.gateway
53
+ order = create(:order_with_stripe_payment,
54
+ amount: 123.45,
55
+ payment_method: payment_method,
56
+ stripe_payment_method_id: stripe_payment_method.id)
57
+ payment = order.payments.last
58
+
59
+ [:create, :update, :retrieve].each do |method|
60
+ allow(Stripe::PaymentIntent).to receive(method).and_return(stripe_payment_intent)
61
+ end
62
+ allow(Stripe::PaymentIntent).to receive(:confirm).with(
63
+ stripe_payment_intent.id
64
+ ).and_raise(Stripe::StripeError.new("auth error"))
65
+
66
+ result = gateway.authorize(123_45, payment.source, currency: 'USD', originator: order.payments.first)
67
+
68
+ expect(Stripe::PaymentIntent).to have_received(:confirm).with("pi_123")
69
+ expect(result.success?).to be(false)
70
+ expect(result.message).to eq("auth error")
71
+ end
72
+
73
+ it "raises if the given amount doesn't match the order total" do
74
+ payment_method = build(:stripe_payment_method)
75
+ gateway = payment_method.gateway
76
+ order = create(:order_with_stripe_payment, amount: 123.45, payment_method: payment_method)
77
+
78
+ expect { gateway.authorize(10, :source, originator: order.payments.first ) }.to raise_error(
79
+ /custom amount is not supported/
80
+ )
81
+ end
82
+ end
83
+
84
+ describe '#capture' do
85
+ it 'captures a pre-authorized Stripe payment' do
86
+ stripe_payment_intent = Stripe::PaymentIntent.construct_from(id: "pi_123")
87
+
88
+ gateway = build(:stripe_payment_method).gateway
89
+ payment = build(:payment, response_code: "pi_123", amount: 123.45)
90
+
91
+ allow(Stripe::PaymentIntent).to receive(:capture).and_return(stripe_payment_intent)
92
+
93
+ result = gateway.capture(123_45, "pi_123", originator: payment)
94
+
95
+ expect(Stripe::PaymentIntent).to have_received(:capture).with('pi_123')
96
+ expect(result.params).to eq("data" => '{"id":"pi_123"}')
97
+ end
98
+
99
+ it "raises if the given amount doesn't match the order total" do
100
+ payment_method = build(:stripe_payment_method)
101
+ gateway = payment_method.gateway
102
+ order = create(:order_with_stripe_payment, amount: 123.45, payment_method: payment_method)
103
+
104
+ expect { gateway.capture(10, :payment_intent_id, originator: order.payments.first ) }.to raise_error(
105
+ /custom amount is not supported/
106
+ )
107
+ end
108
+
109
+ it "raises if no payment_intent_id is given" do
110
+ payment_method = build(:stripe_payment_method)
111
+ gateway = payment_method.gateway
112
+ order = create(:order_with_stripe_payment, amount: 123.45, payment_method: payment_method)
113
+
114
+ expect { gateway.capture(123_45, nil, originator: order.payments.first ) }.to raise_error(
115
+ ArgumentError,
116
+ /missing payment_intent_id/
117
+ )
118
+ end
119
+
120
+ it "raises if payment_intent_id is not valid" do
121
+ payment_method = build(:stripe_payment_method)
122
+ gateway = payment_method.gateway
123
+ order = create(:order_with_stripe_payment, amount: 123.45, payment_method: payment_method)
124
+
125
+ expect { gateway.capture(123_45, "invalid", originator: order.payments.first ) }.to raise_error(
126
+ ArgumentError,
127
+ /payment intent id has the wrong format/
128
+ )
129
+ end
130
+ end
131
+
132
+ describe '#void' do
133
+ it 'voids a payment that hasn not been captured yet' do
134
+ gateway = build(:stripe_payment_method).gateway
135
+ stripe_payment_intent = Stripe::PaymentIntent.construct_from(id: "pi_123")
136
+ allow(Stripe::PaymentIntent).to receive(:cancel).and_return(stripe_payment_intent)
137
+
138
+ result = gateway.void('pi_123')
139
+
140
+ expect(Stripe::PaymentIntent).to have_received(:cancel).with('pi_123')
141
+ expect(result.params).to eq("data" => '{"id":"pi_123"}')
142
+ end
143
+
144
+ it "raises if no payment_intent_id is given" do
145
+ payment_method = build(:stripe_payment_method)
146
+ gateway = payment_method.gateway
147
+
148
+ expect { gateway.void(nil) }.to raise_error(
149
+ ArgumentError,
150
+ /missing payment_intent_id/
151
+ )
152
+ end
153
+
154
+ it "raises if payment_intent_id is not valid" do
155
+ payment_method = build(:stripe_payment_method)
156
+ gateway = payment_method.gateway
157
+
158
+ expect { gateway.void("invalid") }.to raise_error(
159
+ ArgumentError,
160
+ /payment intent id has the wrong format/
161
+ )
162
+ end
163
+ end
164
+
165
+ describe '#purchase' do
166
+ it 'authorizes and captures in a single operation' do
167
+ stripe_payment_method = Stripe::PaymentMethod.construct_from(id: "pm_123")
168
+ stripe_customer = Stripe::Customer.construct_from(id: 'cus_123')
169
+ stripe_payment_intent = Stripe::PaymentIntent.construct_from(id: "pi_123")
170
+
171
+ payment_method = build(:stripe_payment_method)
172
+ gateway = payment_method.gateway
173
+ order = create(:order_with_stripe_payment,
174
+ amount: 123.45,
175
+ payment_method: payment_method,
176
+ stripe_payment_method_id: stripe_payment_method.id)
177
+ payment = order.payments.last
178
+
179
+ allow(Stripe::Customer).to receive(:create).and_return(stripe_customer)
180
+ [:create, :update, :retrieve].each do |method|
181
+ allow(Stripe::PaymentIntent).to receive(method).and_return(stripe_payment_intent)
182
+ end
183
+ allow(Stripe::PaymentIntent).to receive(:confirm).with(stripe_payment_intent.id).and_return(stripe_payment_intent)
184
+
185
+ result = gateway.purchase(123_45, payment.source, currency: 'USD', originator: order.payments.first)
186
+
187
+ expect(Stripe::PaymentIntent).to have_received(:update).with(
188
+ stripe_payment_intent.id,
189
+ { payment_method: stripe_payment_method.id }
190
+ )
191
+
192
+ expect(Stripe::PaymentIntent).to have_received(:create).with(
193
+ amount: 123_45,
194
+ currency: 'USD',
195
+ capture_method: 'automatic',
196
+ confirm: false,
197
+ metadata: { solidus_order_number: order.number },
198
+ customer: "cus_123",
199
+ setup_future_usage: nil
200
+ )
201
+ expect(Stripe::PaymentIntent).to have_received(:confirm).with("pi_123")
202
+ expect(result.params).to eq("data" => '{"id":"pi_123"}')
203
+ expect(payment.reload.response_code).to eq("pi_123")
204
+ end
205
+
206
+ it 'generates error response on failure' do
207
+ stripe_payment_method = Stripe::PaymentMethod.construct_from(id: "pm_123")
208
+ stripe_payment_intent = Stripe::PaymentIntent.construct_from(id: "pi_123")
209
+
210
+ payment_method = build(:stripe_payment_method)
211
+ gateway = payment_method.gateway
212
+ order = create(:order_with_stripe_payment,
213
+ amount: 123.45,
214
+ payment_method: payment_method,
215
+ stripe_payment_method_id: stripe_payment_method.id)
216
+ payment = order.payments.last
217
+
218
+ [:create, :update, :retrieve].each do |method|
219
+ allow(Stripe::PaymentIntent).to receive(method).and_return(stripe_payment_intent)
220
+ end
221
+ allow(Stripe::PaymentIntent).to receive(:confirm).with(
222
+ stripe_payment_intent.id
223
+ ).and_raise(Stripe::StripeError.new("auth error"))
224
+
225
+ result = gateway.purchase(123_45, payment.source, currency: 'USD', originator: order.payments.first)
226
+
227
+ expect(Stripe::PaymentIntent).to have_received(:confirm).with("pi_123")
228
+ expect(result.success?).to be(false)
229
+ expect(result.message).to eq("auth error")
230
+ end
231
+
232
+ it "raises if the given amount doesn't match the order total" do
233
+ payment_method = build(:stripe_payment_method)
234
+ gateway = payment_method.gateway
235
+ order = create(:order_with_stripe_payment, amount: 123.45, payment_method: payment_method)
236
+
237
+ expect { gateway.purchase(10, :source, originator: order.payments.first ) }.to raise_error(
238
+ /custom amount is not supported/
239
+ )
240
+ end
241
+ end
242
+
243
+ describe '#credit' do
244
+ it 'refunds when provided an originator payment' do
245
+ gateway = build(:stripe_payment_method).gateway
246
+ payment = instance_double(Spree::Payment, response_code: 'pi_123', currency: "USD")
247
+ refund = Stripe::Refund.construct_from(id: "re_123")
248
+ allow(Stripe::Refund).to receive(:create).and_return(refund)
249
+
250
+ result = gateway.credit(123_45, 'pi_123', currency: 'USD', originator: instance_double(
251
+ Spree::Refund,
252
+ payment: payment
253
+ ))
254
+
255
+ expect(Stripe::Refund).to have_received(:create).with(
256
+ payment_intent: 'pi_123',
257
+ amount: 123_45,
258
+ metadata: { solidus_skip_sync: 'true' }
259
+ )
260
+ expect(result.params).to eq("data" => '{"id":"re_123"}')
261
+ end
262
+
263
+ it "raises if no payment_intent_id is given" do
264
+ payment_method = build(:stripe_payment_method)
265
+ gateway = payment_method.gateway
266
+
267
+ expect { gateway.credit(:amount, nil) }.to raise_error(
268
+ ArgumentError,
269
+ /missing payment_intent_id/
270
+ )
271
+ end
272
+
273
+ it "raises if payment_intent_id is not valid" do
274
+ payment_method = build(:stripe_payment_method)
275
+ gateway = payment_method.gateway
276
+
277
+ expect { gateway.credit(:amount, "invalid") }.to raise_error(
278
+ ArgumentError,
279
+ /payment intent id has the wrong format/
280
+ )
281
+ end
282
+ end
283
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'solidus_stripe_spec_helper'
4
+
5
+ RSpec.describe SolidusStripe::PaymentIntent do
6
+ describe "#reload" do
7
+ it "reloads the stripe intent" do
8
+ intent = create(:stripe_payment_intent)
9
+ allow(Stripe::PaymentIntent).to receive(:retrieve) do
10
+ Stripe::PaymentIntent.construct_from(id: intent.stripe_intent_id)
11
+ end
12
+
13
+ expect(intent.stripe_intent.object_id).to eq(intent.stripe_intent.object_id)
14
+ expect(intent.stripe_intent.object_id).not_to eq(intent.reload.stripe_intent.object_id)
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,137 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'solidus_stripe_spec_helper'
4
+
5
+ RSpec.describe SolidusStripe::PaymentMethod do
6
+ it 'has a working factory' do
7
+ expect(create(:stripe_payment_method)).to be_valid
8
+ end
9
+
10
+ describe 'Callbacks' do
11
+ describe 'after_create' do
12
+ it 'creates a webhook endpoint' do
13
+ payment_method = create(:stripe_payment_method)
14
+
15
+ expect(payment_method.slug_entry).to be_present
16
+ end
17
+ end
18
+ end
19
+
20
+ describe '#intent_id_for_payment' do
21
+ context 'when the payment has a transaction_id' do
22
+ it 'fetches the payment intent id from the response code' do
23
+ payment = build(:payment, response_code: 'pi_123')
24
+
25
+ expect(described_class.intent_id_for_payment(payment)).to eq("pi_123")
26
+ end
27
+ end
28
+
29
+ context 'when the order has a payment intent' do
30
+ it 'fetches the payment intent id' do
31
+ intent = create(:stripe_payment_intent, stripe_intent_id: 'pi_123')
32
+ payment = build(:payment, response_code: nil, payment_method: intent.payment_method, order: intent.order)
33
+
34
+ expect(described_class.intent_id_for_payment(payment)).to eq("pi_123")
35
+ end
36
+ end
37
+
38
+ it 'returns nil without a payment' do
39
+ expect(described_class.intent_id_for_payment(nil)).to eq(nil)
40
+ end
41
+ end
42
+
43
+ describe '#stripe_dashboard_url' do
44
+ context 'with a payment intent id' do
45
+ it 'generates a dashboard link' do
46
+ payment_method = build(:stripe_payment_method, preferred_test_mode: false)
47
+
48
+ expect(payment_method.stripe_dashboard_url('pi_123')).to eq("https://dashboard.stripe.com/payments/pi_123")
49
+ end
50
+
51
+ it 'supports test mode' do
52
+ payment_method = build(:stripe_payment_method, preferred_test_mode: true)
53
+
54
+ expect(payment_method.stripe_dashboard_url('pi_123')).to eq("https://dashboard.stripe.com/test/payments/pi_123")
55
+ end
56
+ end
57
+
58
+ it 'returns nil with anything else' do
59
+ payment_method = build(:stripe_payment_method)
60
+
61
+ expect(payment_method.stripe_dashboard_url(Object.new)).to eq(nil)
62
+ expect(payment_method.stripe_dashboard_url('')).to eq(nil)
63
+ expect(payment_method.stripe_dashboard_url(123)).to eq(nil)
64
+ end
65
+ end
66
+
67
+ describe '.with_slug' do
68
+ it 'selects by slug' do
69
+ payment_method = create(:stripe_payment_method)
70
+
71
+ expect(described_class.with_slug(payment_method.slug)).to eq([payment_method])
72
+ expect(described_class.with_slug('bad-slug')).to eq([])
73
+ end
74
+ end
75
+
76
+ describe '.previous_sources' do
77
+ it 'finds no sources associated with the order' do
78
+ payment_method = create(:stripe_payment_method)
79
+ order = create(:order, user: nil)
80
+
81
+ expect(payment_method.previous_sources(order)).to be_empty
82
+ end
83
+
84
+ it 'finds no sources associated with the user' do
85
+ payment_method = create(:stripe_payment_method)
86
+ user = create(:user)
87
+ order = create(:order, user: user)
88
+
89
+ expect(payment_method.previous_sources(order)).to be_empty
90
+ end
91
+
92
+ it 'finds sources associated with the user' do
93
+ payment_source = create(:stripe_payment_source)
94
+ payment_method = payment_source.payment_method
95
+ user = create(:user)
96
+ order = create(:order, user: user.reload)
97
+
98
+ user.wallet.add(payment_source)
99
+
100
+ expect(payment_method.previous_sources(order)).to eq [payment_source]
101
+ end
102
+ end
103
+
104
+ describe '.assign_slug' do
105
+ it 'generates a "test" slug for the first payment method in test mode' do
106
+ payment_method = build(:stripe_payment_method)
107
+
108
+ payment_method.save!
109
+
110
+ expect(payment_method.slug).to eq('test')
111
+ end
112
+
113
+ it 'generates a "live" slug for the first payment method in live mode' do
114
+ payment_method = build(:stripe_payment_method, preferred_test_mode: false)
115
+
116
+ payment_method.save!
117
+
118
+ expect(payment_method.slug).to eq('live')
119
+ end
120
+
121
+ it 'generates a random hex string' do
122
+ _existing_payment_method = create(:stripe_payment_method)
123
+ payment_method = create(:stripe_payment_method)
124
+
125
+ expect(payment_method.slug).to match(/^[0-9a-f]{32}$/)
126
+ end
127
+
128
+ it 'generates a unique slug' do
129
+ slug = SecureRandom.hex(16)
130
+
131
+ create(:stripe_slug_entry, slug: slug)
132
+ allow(SecureRandom).to receive(:hex).and_return(slug).and_call_original
133
+
134
+ expect(create(:stripe_payment_method).slug).not_to eq(slug)
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'solidus_stripe_spec_helper'
4
+
5
+ RSpec.describe SolidusStripe::PaymentSource, type: :model do
6
+ it 'has a working factory' do
7
+ expect(create(:stripe_payment_source)).to be_valid
8
+ end
9
+
10
+ describe '#stripe_payment_method' do
11
+ it 'retrieves the Stripe::PaymentMethod object if the stripe_payment_method id is present' do
12
+ stripe_payment_method = Stripe::PaymentMethod.construct_from(id: 'pm_123')
13
+ source = create(:stripe_payment_source, stripe_payment_method_id: 'pm_123')
14
+ allow(Stripe::PaymentMethod).to receive(:retrieve).with('pm_123').and_return(stripe_payment_method)
15
+
16
+ expect(source.stripe_payment_method).to eq(stripe_payment_method)
17
+ end
18
+
19
+ it 'returns nil if the stripe_payment_method id is missing' do
20
+ source = create(:stripe_payment_source, stripe_payment_method_id: nil)
21
+
22
+ expect(source.stripe_payment_method).to be_nil
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,29 @@
1
+ require "solidus_stripe_spec_helper"
2
+
3
+ RSpec.describe SolidusStripe::IntentsController, type: :request do
4
+ describe "GET /after_confirmation" do
5
+ context 'when not provided a payment intent' do
6
+ it 'responds with unprocessable entity' do
7
+ payment_method = create(:stripe_payment_method)
8
+ order = create(:order_ready_to_complete)
9
+ sign_in order.user
10
+
11
+ get "/solidus_stripe/#{payment_method.slug}/after_confirmation"
12
+
13
+ expect(response.status).to eq(422)
14
+ end
15
+ end
16
+
17
+ context 'when the order is not at "confirm"' do
18
+ it 'redirects to the current order step' do
19
+ payment_method = create(:stripe_payment_method)
20
+ order = create(:order)
21
+ sign_in order.user
22
+
23
+ get "/solidus_stripe/#{payment_method.slug}/after_confirmation?payment_intent=pi_123"
24
+
25
+ expect(response).to redirect_to('/checkout/cart')
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,31 @@
1
+ require "solidus_stripe_spec_helper"
2
+
3
+ RSpec.describe SolidusStripe::WebhooksController, type: %i[request webhook_request] do
4
+ describe "POST /create charge.refunded" do
5
+ it "synchronizes refunds" do
6
+ SolidusStripe::Seeds.refund_reasons
7
+ payment_method = create(:stripe_payment_method)
8
+ stripe_payment_intent = Stripe::PaymentIntent.construct_from(id: "pi_123")
9
+ payment = create(:payment,
10
+ amount: 10,
11
+ payment_method: payment_method,
12
+ response_code: stripe_payment_intent.id,
13
+ state: "completed")
14
+ stripe_charge = Stripe::Charge.construct_from(id: "ch_123", payment_intent: "pi_123")
15
+ allow(Stripe::Refund).to receive(:list).with(payment_intent: stripe_payment_intent.id).and_return(
16
+ Stripe::ListObject.construct_from(
17
+ data: [{ id: "re_123", amount: 1000, currency: "usd", metadata: {} }]
18
+ )
19
+ )
20
+ context = SolidusStripe::Webhook::EventWithContextFactory.from_object(
21
+ payment_method: payment_method,
22
+ object: stripe_charge,
23
+ type: "charge.refunded"
24
+ )
25
+
26
+ webhook_request(context)
27
+
28
+ expect(payment.reload.refunds.count).to be(1)
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,23 @@
1
+ require "solidus_stripe_spec_helper"
2
+
3
+ RSpec.describe SolidusStripe::WebhooksController, type: %i[request webhook_request] do
4
+ describe "POST /create payment_intent.canceled" do
5
+ it "transitions the associated payment to failed" do
6
+ payment_method = create(:stripe_payment_method)
7
+ stripe_payment_intent = Stripe::PaymentIntent.construct_from(id: "pi_123", cancellation_reason: "duplicate")
8
+ payment = create(:payment,
9
+ payment_method: payment_method,
10
+ response_code: stripe_payment_intent.id,
11
+ state: "pending")
12
+ context = SolidusStripe::Webhook::EventWithContextFactory.from_object(
13
+ payment_method: payment_method,
14
+ object: stripe_payment_intent,
15
+ type: "payment_intent.canceled"
16
+ )
17
+
18
+ expect do
19
+ webhook_request(context)
20
+ end.to change { payment.reload.state }.from("pending").to("void")
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,23 @@
1
+ require "solidus_stripe_spec_helper"
2
+
3
+ RSpec.describe SolidusStripe::WebhooksController, type: %i[request webhook_request] do
4
+ describe "POST /create payment_intent.payment_failed" do
5
+ it "transitions the associated payment to failed" do
6
+ payment_method = create(:stripe_payment_method)
7
+ stripe_payment_intent = Stripe::PaymentIntent.construct_from(id: "pi_123")
8
+ payment = create(:payment,
9
+ payment_method: payment_method,
10
+ response_code: stripe_payment_intent.id,
11
+ state: "pending")
12
+ context = SolidusStripe::Webhook::EventWithContextFactory.from_object(
13
+ payment_method: payment_method,
14
+ object: stripe_payment_intent,
15
+ type: "payment_intent.payment_failed"
16
+ )
17
+
18
+ expect do
19
+ webhook_request(context)
20
+ end.to change { payment.reload.state }.from("pending").to("failed")
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,29 @@
1
+ require "solidus_stripe_spec_helper"
2
+
3
+ RSpec.describe SolidusStripe::WebhooksController, type: %i[request webhook_request] do
4
+ describe "POST /create payment_intent.succeeded" do
5
+ it "captures the associated payment" do
6
+ payment_method = create(:stripe_payment_method)
7
+ stripe_payment_intent = Stripe::PaymentIntent.construct_from(
8
+ id: "pi_123",
9
+ amount: 1000,
10
+ amount_received: 1000,
11
+ currency: "usd"
12
+ )
13
+ payment = create(:payment,
14
+ amount: 10,
15
+ payment_method: payment_method,
16
+ response_code: stripe_payment_intent.id,
17
+ state: "pending")
18
+ context = SolidusStripe::Webhook::EventWithContextFactory.from_object(
19
+ payment_method: payment_method,
20
+ object: stripe_payment_intent,
21
+ type: "payment_intent.succeeded"
22
+ )
23
+
24
+ expect do
25
+ webhook_request(context)
26
+ end.to change { payment.reload.state }.from("pending").to("completed")
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,52 @@
1
+ require "solidus_stripe_spec_helper"
2
+
3
+ RSpec.describe SolidusStripe::WebhooksController, type: [:request, :webhook_request] do
4
+ describe "POST /create" do
5
+ let(:stripe) { create(:stripe_payment_method) }
6
+ let(:payment_intent) { stripe.gateway.request { Stripe::PaymentIntent.create(amount: 100, currency: 'usd') } }
7
+ let(:context) do
8
+ SolidusStripe::Webhook::EventWithContextFactory.from_object(
9
+ object: payment_intent,
10
+ type: "payment_intent.created",
11
+ payment_method: stripe
12
+ )
13
+ end
14
+
15
+ context "when the request is valid" do
16
+ around do |example|
17
+ if Spree::Bus.registry.registered?(:"stripe.payment_intent.created")
18
+ example.run
19
+ else
20
+ Spree::Bus.register(:"stripe.payment_intent.created")
21
+ example.run
22
+ Spree::Bus.registry.unregister(:"stripe.payment_intent.created")
23
+ end
24
+ end
25
+
26
+ it "triggers a matching event on Spree::Bus" do
27
+ event_type = nil
28
+ subscription = Spree::Bus.subscribe(:"stripe.payment_intent.created") { |event| event_type = event.type }
29
+
30
+ webhook_request(context)
31
+
32
+ expect(event_type).to eq("payment_intent.created")
33
+ ensure
34
+ Spree::Bus.unsubscribe(subscription)
35
+ end
36
+
37
+ it "returns a 200 status code" do
38
+ webhook_request(context)
39
+
40
+ expect(response).to have_http_status(:ok)
41
+ end
42
+ end
43
+
44
+ context "when the event can't be generated" do
45
+ it "returns a 400 status code" do
46
+ webhook_request(context, timestamp: Time.zone.yesterday.to_time)
47
+
48
+ expect(response).to have_http_status(:bad_request)
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'stripe'
4
+ require 'solidus_starter_frontend_spec_helper'
5
+
6
+ Dir["#{__dir__}/support/solidus_stripe/**/*.rb"].sort.each { |f| require f }
7
+
8
+ RSpec.configure do |config|
9
+ config.include SolidusStripe::Webhook::RequestHelper, type: :webhook_request
10
+ end