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,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "solidus_stripe_spec_helper"
4
+
5
+ RSpec.describe SolidusStripe::Webhook::ChargeSubscriber do
6
+ describe "#sync_refunds" do
7
+ it "synchronizes refunds" do
8
+ SolidusStripe::Seeds.refund_reasons
9
+ payment_method = create(:stripe_payment_method)
10
+ stripe_payment_intent = Stripe::PaymentIntent.construct_from(id: "pi_123")
11
+ payment = create(:payment,
12
+ amount: 10,
13
+ payment_method: payment_method,
14
+ response_code: stripe_payment_intent.id,
15
+ state: "completed")
16
+ stripe_charge = Stripe::Charge.construct_from(id: "ch_123", payment_intent: "pi_123")
17
+ allow(Stripe::Refund).to receive(:list).with(payment_intent: stripe_payment_intent.id).and_return(
18
+ Stripe::ListObject.construct_from(
19
+ data: [{ id: "re_123", amount: 1000, currency: "usd", metadata: {} }]
20
+ )
21
+ )
22
+ event = SolidusStripe::Webhook::EventWithContextFactory.from_object(
23
+ payment_method: payment_method,
24
+ object: stripe_charge,
25
+ type: "charge.refunded"
26
+ ).solidus_stripe_object
27
+
28
+ described_class.new.sync_refunds(event)
29
+
30
+ expect(payment.refunds.count).to be(1)
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,297 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "solidus_stripe_spec_helper"
4
+
5
+ RSpec.describe SolidusStripe::Webhook::PaymentIntentSubscriber do
6
+ describe "#capture_payment" do
7
+ context "when a full capture is performed" do
8
+ it "completes a pending payment" do
9
+ payment_method = create(:stripe_payment_method)
10
+ stripe_payment_intent = Stripe::PaymentIntent.construct_from(
11
+ id: "pi_123",
12
+ amount: 1000,
13
+ amount_received: 1000,
14
+ currency: "usd"
15
+ )
16
+ payment = create(:payment,
17
+ amount: 10,
18
+ payment_method: payment_method,
19
+ response_code: stripe_payment_intent.id,
20
+ state: "pending")
21
+ event = SolidusStripe::Webhook::EventWithContextFactory.from_object(
22
+ payment_method: payment_method,
23
+ object: stripe_payment_intent,
24
+ type: "payment_intent.succeeded"
25
+ ).solidus_stripe_object
26
+
27
+ described_class.new.capture_payment(event)
28
+
29
+ expect(payment.reload.state).to eq "completed"
30
+ end
31
+
32
+ it "adds a log entry to the payment" do
33
+ payment_method = create(:stripe_payment_method)
34
+ stripe_payment_intent = Stripe::PaymentIntent.construct_from(
35
+ id: "pi_123",
36
+ amount: 1000,
37
+ amount_received: 1000,
38
+ currency: "usd"
39
+ )
40
+ payment = create(:payment,
41
+ amount: 10,
42
+ payment_method: payment_method,
43
+ response_code: stripe_payment_intent.id,
44
+ state: "pending")
45
+ event = SolidusStripe::Webhook::EventWithContextFactory.from_object(
46
+ payment_method: payment_method,
47
+ object: stripe_payment_intent,
48
+ type: "payment_intent.succeeded"
49
+ ).solidus_stripe_object
50
+
51
+ described_class.new.capture_payment(event)
52
+
53
+ details = payment.log_entries.last.parsed_details
54
+ expect(details.success?).to be(true)
55
+ expect(
56
+ details.message
57
+ ).to eq "Capture was successful after payment_intent.succeeded webhook"
58
+ end
59
+ end
60
+
61
+ context "when a partial capture is performed" do
62
+ it "completes a pending payment" do
63
+ SolidusStripe::Seeds.refund_reasons
64
+ payment_method = create(:stripe_payment_method)
65
+ stripe_payment_intent = Stripe::PaymentIntent.construct_from(
66
+ id: "pi_123",
67
+ amount: 1000,
68
+ amount_received: 700,
69
+ currency: "usd"
70
+ )
71
+ payment = create(:payment,
72
+ amount: 10,
73
+ payment_method: payment_method,
74
+ response_code: stripe_payment_intent.id,
75
+ state: "pending")
76
+ allow(Stripe::Refund).to receive(:list).with(payment_intent: stripe_payment_intent.id).and_return(
77
+ Stripe::ListObject.construct_from(
78
+ data: [{ id: "re_123", amount: 700, currency: "usd", metadata: {} }]
79
+ )
80
+ )
81
+ event = SolidusStripe::Webhook::EventWithContextFactory.from_object(
82
+ payment_method: payment_method,
83
+ object: stripe_payment_intent,
84
+ type: "payment_intent.succeeded"
85
+ ).solidus_stripe_object
86
+
87
+ described_class.new.capture_payment(event)
88
+
89
+ expect(payment.reload.state).to eq "completed"
90
+ end
91
+
92
+ it "synchronizes refunds" do
93
+ SolidusStripe::Seeds.refund_reasons
94
+ payment_method = create(:stripe_payment_method)
95
+ stripe_payment_intent = Stripe::PaymentIntent.construct_from(
96
+ id: "pi_123",
97
+ amount: 1000,
98
+ amount_received: 700,
99
+ currency: "usd"
100
+ )
101
+ payment = create(:payment,
102
+ amount: 7,
103
+ payment_method: payment_method,
104
+ response_code: stripe_payment_intent.id,
105
+ state: "pending")
106
+ allow(Stripe::Refund).to receive(:list).with(payment_intent: stripe_payment_intent.id).and_return(
107
+ Stripe::ListObject.construct_from(
108
+ data: [{ id: "re_123", amount: 200, currency: "usd", metadata: {} }]
109
+ )
110
+ )
111
+ event = SolidusStripe::Webhook::EventWithContextFactory.from_object(
112
+ payment_method: payment_method,
113
+ object: stripe_payment_intent,
114
+ type: "payment_intent.succeeded"
115
+ ).solidus_stripe_object
116
+
117
+ described_class.new.capture_payment(event)
118
+
119
+ expect(payment.refunds.count).to be(1)
120
+ end
121
+
122
+ it "adds a log entry for the captured payment" do
123
+ SolidusStripe::Seeds.refund_reasons
124
+ payment_method = create(:stripe_payment_method)
125
+ stripe_payment_intent = Stripe::PaymentIntent.construct_from(
126
+ id: "pi_123",
127
+ amount: 1000,
128
+ amount_received: 700,
129
+ currency: "usd"
130
+ )
131
+ payment = create(:payment,
132
+ amount: 10,
133
+ payment_method: payment_method,
134
+ response_code: stripe_payment_intent.id,
135
+ state: "pending")
136
+ allow(Stripe::Refund).to receive(:list).with(payment_intent: stripe_payment_intent.id).and_return(
137
+ Stripe::ListObject.construct_from(
138
+ data: [{ id: "re_123", amount: 700, currency: "usd", metadata: {} }]
139
+ )
140
+ )
141
+ event = SolidusStripe::Webhook::EventWithContextFactory.from_object(
142
+ payment_method: payment_method,
143
+ object: stripe_payment_intent,
144
+ type: "payment_intent.succeeded"
145
+ ).solidus_stripe_object
146
+
147
+ described_class.new.capture_payment(event)
148
+
149
+ log_entries = payment.log_entries.map { [_1.parsed_details.success?, _1.parsed_details.message] }
150
+ expect(
151
+ log_entries
152
+ ).to include([true, "Capture was successful after payment_intent.succeeded webhook"])
153
+ end
154
+ end
155
+
156
+ it "does nothing if the payment is already completed" do
157
+ payment_method = create(:stripe_payment_method)
158
+ stripe_payment_intent = Stripe::PaymentIntent.construct_from(id: "pi_123")
159
+ payment = create(:payment,
160
+ payment_method: payment_method,
161
+ response_code: stripe_payment_intent.id,
162
+ state: "completed")
163
+ event = SolidusStripe::Webhook::EventWithContextFactory.from_object(
164
+ payment_method: payment_method,
165
+ object: stripe_payment_intent,
166
+ type: "payment_intent.succeeded",
167
+ ).solidus_stripe_object
168
+
169
+ described_class.new.capture_payment(event)
170
+
171
+ expect(payment.reload.state).to eq "completed"
172
+ expect(payment.log_entries.count).to be(0)
173
+ end
174
+ end
175
+
176
+ describe "#fail_payment" do
177
+ it "fails a pending payment" do
178
+ payment_method = create(:stripe_payment_method)
179
+ stripe_payment_intent = Stripe::PaymentIntent.construct_from(id: "pi_123")
180
+ payment = create(:payment,
181
+ payment_method: payment_method,
182
+ response_code: stripe_payment_intent.id,
183
+ state: "pending")
184
+ event = SolidusStripe::Webhook::EventWithContextFactory.from_object(
185
+ payment_method: payment_method,
186
+ object: stripe_payment_intent,
187
+ type: "payment_intent.payment_failed"
188
+ ).solidus_stripe_object
189
+
190
+ described_class.new.fail_payment(event)
191
+
192
+ expect(payment.reload.state).to eq "failed"
193
+ end
194
+
195
+ it "adds a log entry to the payment" do
196
+ payment_method = create(:stripe_payment_method)
197
+ stripe_payment_intent = Stripe::PaymentIntent.construct_from(id: "pi_123")
198
+ payment = create(:payment,
199
+ payment_method: payment_method,
200
+ response_code: stripe_payment_intent.id,
201
+ state: "pending")
202
+ event = SolidusStripe::Webhook::EventWithContextFactory.from_object(
203
+ payment_method: payment_method,
204
+ object: stripe_payment_intent,
205
+ type: "payment_intent.payment_failed"
206
+ ).solidus_stripe_object
207
+
208
+ described_class.new.fail_payment(event)
209
+
210
+ details = payment.log_entries.last.parsed_details
211
+ expect(details.success?).to be(false)
212
+ expect(
213
+ details.message
214
+ ).to eq "Payment was marked as failed after payment_intent.failed webhook"
215
+ end
216
+
217
+ it "does nothing if the payment is already failed" do
218
+ payment_method = create(:stripe_payment_method)
219
+ stripe_payment_intent = Stripe::PaymentIntent.construct_from(id: "pi_123")
220
+ payment = create(:payment,
221
+ payment_method: payment_method,
222
+ response_code: stripe_payment_intent.id,
223
+ state: "failed")
224
+ event = SolidusStripe::Webhook::EventWithContextFactory.from_object(
225
+ payment_method: payment_method,
226
+ object: stripe_payment_intent,
227
+ type: "payment_intent.payment_failed",
228
+ ).solidus_stripe_object
229
+
230
+ described_class.new.fail_payment(event)
231
+
232
+ expect(payment.reload.state).to eq "failed"
233
+ expect(payment.log_entries.count).to be(0)
234
+ end
235
+ end
236
+
237
+ describe "#void_payment" do
238
+ it "voids a pending payment" do
239
+ payment_method = create(:stripe_payment_method)
240
+ stripe_payment_intent = Stripe::PaymentIntent.construct_from(id: "pi_123", cancellation_reason: "duplicate")
241
+ payment = create(:payment,
242
+ payment_method: payment_method,
243
+ response_code: stripe_payment_intent.id,
244
+ state: "pending")
245
+ event = SolidusStripe::Webhook::EventWithContextFactory.from_object(
246
+ payment_method: payment_method,
247
+ object: stripe_payment_intent,
248
+ type: "payment_intent.canceled"
249
+ ).solidus_stripe_object
250
+
251
+ described_class.new.void_payment(event)
252
+
253
+ expect(payment.reload.state).to eq "void"
254
+ end
255
+
256
+ it "adds a log entry to the payment" do
257
+ payment_method = create(:stripe_payment_method)
258
+ stripe_payment_intent = Stripe::PaymentIntent.construct_from(id: "pi_123", cancellation_reason: "duplicate")
259
+ payment = create(:payment,
260
+ payment_method: payment_method,
261
+ response_code: stripe_payment_intent.id,
262
+ state: "pending")
263
+ event = SolidusStripe::Webhook::EventWithContextFactory.from_object(
264
+ payment_method: payment_method,
265
+ object: stripe_payment_intent,
266
+ type: "payment_intent.canceled"
267
+ ).solidus_stripe_object
268
+
269
+ described_class.new.void_payment(event)
270
+
271
+ details = payment.log_entries.last.parsed_details
272
+ expect(details.success?).to be(true)
273
+ expect(
274
+ details.message
275
+ ).to eq "Payment was voided after payment_intent.voided webhook (duplicate)"
276
+ end
277
+
278
+ it "does nothing if the payment is already voided" do
279
+ payment_method = create(:stripe_payment_method)
280
+ stripe_payment_intent = Stripe::PaymentIntent.construct_from(id: "pi_123", cancellation_reason: "duplicate")
281
+ payment = create(:payment,
282
+ payment_method: payment_method,
283
+ response_code: stripe_payment_intent.id,
284
+ state: "void")
285
+ event = SolidusStripe::Webhook::EventWithContextFactory.from_object(
286
+ payment_method: payment_method,
287
+ object: stripe_payment_intent,
288
+ type: "payment_intent.payment_void",
289
+ ).solidus_stripe_object
290
+
291
+ described_class.new.void_payment(event)
292
+
293
+ expect(payment.reload.state).to eq "void"
294
+ expect(payment.log_entries.count).to be(0)
295
+ end
296
+ end
297
+ end
@@ -0,0 +1,210 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusStripe::BackendTestHelper
4
+ def create_payment_method(setup_future_usage: 'off_session', auto_capture: false)
5
+ @payment_method = create(
6
+ :stripe_payment_method,
7
+ preferred_setup_future_usage: setup_future_usage,
8
+ auto_capture: auto_capture
9
+ )
10
+ end
11
+
12
+ def payment_method
13
+ # Memoize the payment method id to avoid fetching it multiple times
14
+ @payment_method ||= SolidusStripe::PaymentMethod.first || create_payment_method
15
+ end
16
+
17
+ def order
18
+ @order ||= create(:completed_order_with_totals, line_items_price: 50)
19
+ end
20
+
21
+ def last_valid_payment
22
+ order.payments.valid.last
23
+ end
24
+
25
+ # Stripe-related helper methods for creating and fetching Stripe objects
26
+
27
+ def create_stripe_payment_method(card_number)
28
+ payment_method.gateway.request do
29
+ Stripe::PaymentMethod.create({
30
+ type: 'card',
31
+ card: {
32
+ number: card_number,
33
+ exp_month: 12,
34
+ exp_year: (Time.zone.now.year + 1),
35
+ cvc: '123',
36
+ },
37
+ })
38
+ end
39
+ end
40
+
41
+ def create_stripe_payment_intent(stripe_payment_method_id)
42
+ payment_method.gateway.request do
43
+ Stripe::PaymentIntent.create(
44
+ amount: (order.outstanding_balance * 100).to_i,
45
+ capture_method: 'manual',
46
+ confirm: true,
47
+ currency: 'usd',
48
+ payment_method: stripe_payment_method_id
49
+ )
50
+ end
51
+ end
52
+
53
+ def fetch_stripe_refund(payment)
54
+ payment_method.gateway.request do
55
+ Stripe::Refund.list(payment_intent: payment.response_code).data.first
56
+ end
57
+ end
58
+
59
+ def fetch_stripe_payment_intent(payment)
60
+ payment_method.gateway.request do
61
+ Stripe::PaymentIntent.retrieve(payment.response_code)
62
+ end
63
+ end
64
+
65
+ # Navigation helper methods for interacting with the admin panel
66
+
67
+ def visit_payments_page
68
+ visit "/admin/orders/#{order.number}/payments"
69
+ end
70
+
71
+ def visit_payment_page(payment)
72
+ visit_payments_page
73
+ click_on payment.number
74
+ end
75
+
76
+ # Action helper methods for performing operations on payments
77
+
78
+ def create_authorized_payment(opts = {})
79
+ stripe_payment_method = create_stripe_payment_method(opts[:card_number] || '4242424242424242')
80
+ payment_intent = create_stripe_payment_intent(stripe_payment_method.id)
81
+
82
+ create(:stripe_payment,
83
+ :authorized,
84
+ order: order,
85
+ response_code: payment_intent.id,
86
+ stripe_payment_method_id: stripe_payment_method.id,
87
+ payment_method: payment_method)
88
+ end
89
+
90
+ def create_captured_payment(card_number: '4242424242424242')
91
+ payment = create_authorized_payment(card_number: card_number)
92
+ payment.capture!
93
+ payment
94
+ end
95
+
96
+ def create_valid_payment_source
97
+ stripe_payment_method = create_stripe_payment_method('4242424242424242')
98
+ SolidusStripe::PaymentSource.create!(
99
+ stripe_payment_method_id: stripe_payment_method.id,
100
+ payment_method_id: payment_method.id
101
+ )
102
+ end
103
+
104
+ def create_order_with_existing_payment_source
105
+ payment_source = create_valid_payment_source
106
+ @order = create(:order_with_line_items, line_items_price: 50)
107
+ order.user.wallet.add(payment_source)
108
+ end
109
+
110
+ def complete_order_with_existing_payment_source
111
+ visit_payments_page
112
+ click_on "Continue"
113
+ click_on "Confirm"
114
+ click_on "Complete Order"
115
+ end
116
+
117
+ def refund_payment
118
+ refund_reason = create :refund_reason
119
+ click_icon(:"mail-reply") # Refund icon style reference in solidus_backend
120
+ within '.new_refund' do
121
+ select refund_reason.name, from: 'Reason'
122
+ click_button 'Refund'
123
+ end
124
+ end
125
+
126
+ def partially_refund_payment(refund_reason, amount)
127
+ click_icon(:"mail-reply") # Refund icon style reference in solidus_backend
128
+ within '.new_refund' do
129
+ select refund_reason.name, from: 'Reason'
130
+ fill_in 'Amount', with: amount
131
+ click_button 'Refund'
132
+ end
133
+ end
134
+
135
+ def void_payment
136
+ click_icon(:void)
137
+ end
138
+
139
+ def capture_payment
140
+ click_icon(:capture)
141
+ end
142
+
143
+ def cancel_order
144
+ visit "/admin/orders/#{order.number}/edit"
145
+ accept_alert do
146
+ click_on 'Cancel'
147
+ end
148
+ end
149
+
150
+ # Helper methods for checking expected outcomes and states
151
+
152
+ def expects_page_to_display_successfully_captured_payment(payment)
153
+ expect(page).to have_content('Payment Updated')
154
+ expect(payment.reload.state).to eq('completed')
155
+ expect(payment.capture_events.first.amount).to eq(payment.amount)
156
+ end
157
+
158
+ def expects_page_to_display_successfully_refunded_payment(payment)
159
+ expect(page).to have_content('Refund has been successfully created!')
160
+ expect(payment).to be_fully_refunded
161
+ end
162
+
163
+ def expects_page_to_display_successfully_partially_refunded_payment(payment, amount)
164
+ expect(page).to have_content('Refund has been successfully created!')
165
+ expect(payment.reload.state).to eq('completed')
166
+ expect(payment.refunds.first.amount).to eq(amount)
167
+ end
168
+
169
+ def expects_page_to_display_successfully_voided_payment(payment)
170
+ expect(page).to have_content('Payment Updated')
171
+ expect(payment.reload.state).to eq('void')
172
+ end
173
+
174
+ def expects_page_to_display_successfully_canceled_order_payment(payment)
175
+ expect(page).to have_content('Order canceled')
176
+ expect(payment.reload.state).to eq('void')
177
+ end
178
+
179
+ def expects_page_to_display_capture_fail_message(payment, message)
180
+ expect(page).to have_content(message)
181
+ expect(payment.reload.state).to eq('failed')
182
+ end
183
+
184
+ def expects_page_to_display_payment(payment)
185
+ click_on 'Payments'
186
+ expect(page).to have_content(payment.number)
187
+ end
188
+
189
+ def expects_page_to_display_log_messages(log_messages = [])
190
+ expect(page).to have_content('Log Entries')
191
+ log_messages.each do |log_message|
192
+ expect(page).to have_content(log_message)
193
+ end
194
+ end
195
+
196
+ def expects_payment_to_be_refunded_on_stripe(payment, amount)
197
+ stripe_refund = fetch_stripe_refund(payment)
198
+ expect(stripe_refund.amount).to eq(amount * 100)
199
+ end
200
+
201
+ def expects_payment_to_be_voided_on_stripe(payment)
202
+ stripe_payment_intent = fetch_stripe_payment_intent(payment)
203
+ expect(stripe_payment_intent.status).to eq('canceled')
204
+ end
205
+
206
+ def expects_payment_to_have_correct_capture_amount_on_stripe(payment, amount)
207
+ stripe_payment_intent = fetch_stripe_payment_intent(payment)
208
+ expect(stripe_payment_intent.amount_received).to eq(amount * 100)
209
+ end
210
+ end