solidus_stripe 5.0.0.alpha.1 → 5.0.0.rc.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +15 -8
  3. data/README.md +94 -52
  4. data/app/models/solidus_stripe/gateway.rb +21 -23
  5. data/app/models/solidus_stripe/payment_intent.rb +27 -14
  6. data/app/subscribers/solidus_stripe/webhook/charge_subscriber.rb +3 -3
  7. data/app/subscribers/solidus_stripe/webhook/payment_intent_subscriber.rb +5 -5
  8. data/app/views/spree/admin/payments/source_forms/existing_payment/_stripe.html.erb +6 -1
  9. data/db/migrate/20230306105520_create_solidus_stripe_payment_intents.rb +6 -2
  10. data/db/migrate/20230308122414_create_solidus_stripe_slug_entries.rb +12 -0
  11. data/db/migrate/20230313150008_create_solidus_stripe_customers.rb +2 -3
  12. data/lib/generators/solidus_stripe/install/templates/app/javascript/controllers/solidus_stripe_payment_controller.js +3 -3
  13. data/lib/generators/solidus_stripe/install/templates/app/views/checkouts/existing_payment/_stripe.html.erb +6 -1
  14. data/lib/generators/solidus_stripe/install/templates/app/views/checkouts/payment/_stripe.html.erb +49 -14
  15. data/lib/generators/solidus_stripe/install/templates/app/views/orders/payment_info/_stripe.html.erb +15 -3
  16. data/lib/solidus_stripe/refunds_synchronizer.rb +26 -24
  17. data/lib/solidus_stripe/testing_support/factories.rb +18 -18
  18. data/lib/solidus_stripe/version.rb +1 -1
  19. data/lib/solidus_stripe/webhook/event.rb +6 -5
  20. data/solidus_stripe.gemspec +1 -1
  21. data/spec/lib/solidus_stripe/refunds_synchronizer_spec.rb +47 -47
  22. data/spec/lib/solidus_stripe/webhook/event_spec.rb +10 -10
  23. data/spec/models/solidus_stripe/customer_spec.rb +3 -3
  24. data/spec/models/solidus_stripe/gateway_spec.rb +52 -54
  25. data/spec/models/solidus_stripe/payment_intent_spec.rb +62 -1
  26. data/spec/models/solidus_stripe/payment_method_spec.rb +16 -16
  27. data/spec/models/solidus_stripe/payment_source_spec.rb +3 -3
  28. data/spec/requests/solidus_stripe/intents_controller_spec.rb +2 -2
  29. data/spec/requests/solidus_stripe/webhooks_controller/charge/refunded_spec.rb +1 -1
  30. data/spec/requests/solidus_stripe/webhooks_controller/payment_intent/canceled_spec.rb +1 -1
  31. data/spec/requests/solidus_stripe/webhooks_controller/payment_intent/payment_failed_spec.rb +1 -1
  32. data/spec/requests/solidus_stripe/webhooks_controller/payment_intent/succeeded_spec.rb +1 -1
  33. data/spec/requests/solidus_stripe/webhooks_controller_spec.rb +8 -4
  34. data/spec/subscribers/solidus_stripe/webhook/charge_subscriber_spec.rb +1 -1
  35. data/spec/subscribers/solidus_stripe/webhook/payment_intent_subscriber_spec.rb +12 -12
  36. data/spec/support/solidus_stripe/backend_test_helper.rb +17 -42
  37. data/spec/support/solidus_stripe/checkout_test_helper.rb +27 -1
  38. data/spec/support/solidus_stripe/webhook/event_with_context_factory.rb +1 -1
  39. data/spec/system/backend/solidus_stripe/orders/payments_spec.rb +47 -21
  40. data/spec/system/frontend/solidus_stripe/checkout_spec.rb +19 -0
  41. metadata +6 -11
  42. data/db/migrate/20230303154931_create_solidus_stripe_setup_intent.rb +0 -10
  43. data/db/migrate/20230308122414_create_solidus_stripe_webhook_endpoints.rb +0 -10
  44. data/db/migrate/20230310152615_add_payment_method_reference_to_stripe_intents.rb +0 -6
  45. data/db/migrate/20230310171444_normalize_stripe_intent_id_attributes.rb +0 -6
  46. data/db/migrate/20230323154931_drop_solidus_stripe_setup_intent.rb +0 -13
  47. data/db/migrate/20230403094916_rename_webhook_endpoint_to_payment_method_slug_entries.rb +0 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 58adb3a4786a07b4728a38452a8b45389e4947078a6696c421e66d9202bec935
4
- data.tar.gz: ceb39ec60811d59e8544cef740d470b41fdfa2b7dad668c557e5f7a55622536c
3
+ metadata.gz: 80590bf550dd5c471e94cfd6edd9d0ca246c650db87741939a288db52b509b23
4
+ data.tar.gz: 30ff110815833b15746669b8b3db0c0ff995baeaac6f74cea09fa20db13ef46d
5
5
  SHA512:
6
- metadata.gz: c2964495f7edeab038c993972069bd72788b7c978a738889277eecfe2c688c62a124e25e9b511d483ee6df67accb33d0bdb311f718cb97d883a6f9bd306de09e
7
- data.tar.gz: 24547d5de71774cfdb1c82b955118067f9abb27a472a2d0d767ed5d21a12f997d5e60cdef3ebbe0298bb2916ec789d850a80bb3d9fd3d1310941d4c7b26be7b5
6
+ metadata.gz: 289adffe4dd54fcf0d862d5c618db0730339125bc9b7f8c137c9050d2e93f653e57e2613943d0f94a3a188e5798c0e1783d7990794437fc89bcb386a7a1112a5
7
+ data.tar.gz: bd46f58b38a8b1a6ad858d0403927497c19ae993371702850b01c8ab8a71378d710eee0dae8d03b155c4c6b4b8dd8a81924cdad8385a3d0a1d70526709df5e11
data/.circleci/config.yml CHANGED
@@ -49,8 +49,8 @@ jobs:
49
49
  cat /tmp/.tool-versions
50
50
  - restore_cache:
51
51
  keys:
52
- - solidus-stripe-gems-v1-{{ checksum "/tmp/.tool-versions" }}
53
- - solidus-stripe-gems-v1-
52
+ - solidus-stripe-gems-v1-<<parameters.ruby>>-{{ checksum "/tmp/.tool-versions" }}
53
+ - solidus-stripe-gems-v1-<<parameters.ruby>>-
54
54
  - run:
55
55
  name: "Install gems"
56
56
  command: |
@@ -64,7 +64,7 @@ jobs:
64
64
  FRONTEND: starter
65
65
  SOLIDUS_BRANCH: master
66
66
  - save_cache:
67
- key: solidus-stripe-gems-v1-{{ checksum "/tmp/.tool-versions" }}
67
+ key: solidus-stripe-gems-v1-<<parameters.ruby>>-{{ checksum "/tmp/.tool-versions" }}
68
68
  paths:
69
69
  - /home/circleci/.rubygems
70
70
  - run:
@@ -104,12 +104,19 @@ workflows:
104
104
  build:
105
105
  jobs:
106
106
  - run-specs:
107
- name: "run-specs-ruby-<<matrix.ruby>>-db-<<matrix.database>>"
107
+ name: &name "run-specs-ruby-<<matrix.ruby>>-db-<<matrix.database>>"
108
108
  context: stripe-test-credentials
109
109
  matrix:
110
- parameters:
111
- ruby: ["3.2"] # TODO: ['3.2', '3.1', '3.0']
112
- database: ["sqlite"] # TODO: ['mysql', 'sqlite', 'postgres']
113
- coverage: [true]
110
+ parameters: { ruby: ["3.2"], database: ["sqlite"], coverage: [true] }
111
+ - run-specs:
112
+ name: *name
113
+ context: stripe-test-credentials
114
+ matrix:
115
+ parameters: { ruby: ["3.1"], database: ["mysql"], coverage: [false] }
116
+ - run-specs:
117
+ name: *name
118
+ context: stripe-test-credentials
119
+ matrix:
120
+ parameters: { ruby: ["3.0"], database: ["postgres"], coverage: [false] }
114
121
  - lint-code:
115
122
  ruby: "3.0"
data/README.md CHANGED
@@ -1,71 +1,81 @@
1
- ## 🚧 **WARNING** 🚧 Work In Progress
2
-
3
- You're looking at the source for `solidus_stripe` v5, which will only support the **starter frontend**
4
- but at the moment **it is not ready to be used**.
5
-
6
- Please use [`solidus_stripe` v4 on the corresponding branch](https://github.com/solidusio/solidus_stripe/tree/v4).
7
-
8
- ## 🚧 **WARNING** 🚧 Supporting `solidus_frontend`
9
-
10
- If you need support for `solidus_frontend` please add `< 5` as a version requirement in your gemfile:
11
- `gem 'solidus_stripe', '< 5'`
12
- or if your tracking the github version please switch to the `v4` branch:
13
- `gem 'solidus_stripe', git: 'https://github.com/solidusio/solidus_stripe.git', branch: 'v4'`
14
-
15
- ---
16
-
17
1
  # Solidus Stripe
18
2
 
19
3
  [![CircleCI](https://circleci.com/gh/solidusio/solidus_stripe.svg?style=shield)](https://circleci.com/gh/solidusio/solidus_stripe)
20
- [![codecov](https://codecov.io/gh/solidusio/solidus_stripe/branch/master/graph/badge.svg)](https://codecov.io/gh/solidusio/solidus_stripe)
4
+ [![codecov](https://codecov.io/gh/solidusio/solidus_stripe/branch/main/graph/badge.svg)](https://codecov.io/gh/solidusio/solidus_stripe)
5
+ [![yardoc](https://img.shields.io/badge/docs-rubydoc.info-informational)](https://rubydoc.info/gems/solidus_stripe)
21
6
 
22
7
  <!-- Explain what your extension does. -->
23
8
 
24
9
  ## Installation
25
10
 
26
- Add solidus_stripe to your Gemfile:
11
+ Add solidus_stripe to your bundle and run the installation generator:
27
12
 
28
- ```ruby
29
- gem 'solidus_stripe'
13
+ ```shell
14
+ bundle add solidus_stripe
15
+ bin/rails generate solidus_stripe:install
30
16
  ```
31
17
 
32
- Bundle your dependencies and run the installation generator:
18
+ Then set the following environment variables both locally and in production in order
19
+ to setup the `solidus_stripe_env_credentials` static preference as defined in the initializer:
33
20
 
34
21
  ```shell
35
- bin/rails generate solidus_stripe:install
22
+ SOLIDUS_STRIPE_API_KEY # will prefill the `api_key` preference
23
+ SOLIDUS_STRIPE_PUBLISHABLE_KEY # will prefill the `publishable_key` preference
24
+ SOLIDUS_STRIPE_WEBHOOK_SIGNING_SECRET # will prefill the `webhook_signing_secret` preference
36
25
  ```
37
26
 
38
- ### Webhooks
27
+ Once those are available you can create a new Stripe payment method in the /admin interface
28
+ and select the `solidus_stripe_env_credentials` static preference.
39
29
 
40
- This library makes use of some [Stripe webhooks](https://stripe.com/docs/webhooks).
30
+ ⚠️ Be sure to set the enviroment variables to the values for test mode in your development environment.
41
31
 
42
- Every Solidus Stripe payment method you create will get a slug assigned. You
43
- need to append it to a generic webhook endpoint to get the URL for that payment
44
- method. For example:
32
+ ### Webhooks setup
45
33
 
46
- ```ruby
47
- SolidusStripe::PaymentMethod.last.slug
48
- # "365a8435cd11300e87de864c149516e0"
49
- ```
34
+ The webhooks URLs are automatically generated based on the enviroment,
35
+ by default it will be scoped to `live` in production and `test` everywhere else.
50
36
 
51
- For the above example, and if you mounted the `SolidusStripe::Engine` routes on
52
- the default scope, the webhook endpoint would look like:
53
-
54
- ```
55
- /solidus_stripe/webhooks/365a8435cd11300e87de864c149516e0
56
- ```
57
-
58
- Besides, you also need to configure the webhook signing secret for that payment
59
- method. You can do that through the `webhook_endpoint_signing_secret`
60
- preference on the payment method.
37
+ #### Production enviroment
61
38
 
62
39
  Before going to production, you'll need to [register the webhook endpoint with
63
40
  Stripe](https://stripe.com/docs/webhooks/go-live), and make sure to subscribe
64
41
  to the events listed in [the `SolidusStripe::Webhook::Event::CORE`
65
- constant](https://github.com/solidusio/solidus_stripe/blob/master/lib/solidus_stripe/webhook/event.rb).
42
+ constant](https://github.com/solidusio/solidus_stripe/blob/main/lib/solidus_stripe/webhook/event.rb).
43
+
44
+ So in your Stripe dashboard you'll need to set the webhook URL to:
45
+
46
+ https://store.example.com/solidus_stripe/live/webhooks
47
+
48
+ #### Non-production enviroments
49
+
50
+ While for development [you should use the stripe CLI to forward the webhooks to your local server](https://stripe.com/docs/webhooks/test#webhook-test-cli):
51
+
52
+ ```shell
53
+ # Please refer to `stripe listen --help` for more options
54
+ stripe listen --forward-to http://localhost:3000/solidus_stripe/test/webhooks
55
+ ```
66
56
 
67
- On development, you can
68
- [test webhooks by using Stripe CLI](https://stripe.com/docs/webhooks/test).
57
+ ### Supporting `solidus_frontend`
58
+
59
+ If you need support for `solidus_frontend` please refer to the [README of solidus_stripe v4](https://github.com/solidusio/solidus_stripe/tree/v4#readme).
60
+
61
+ ### Installing on a custom frontend
62
+
63
+ If you're using a custom frontend you'll need to adjust the code copied to your application by the installation generator. Given frontend choices can vary wildly, we can't provide a one-size-fits-all solution, but we are providing this simple integration with `solidus_starter_frontend` as a reference implementation. The amount of code is intentionally kept to a minimum, so you can easily adapt it to your needs.
64
+
65
+ ## Caveats
66
+
67
+ ### Authorization and capture and checkout finalization
68
+
69
+ Stripe supports two different flows for payments: [authorization and capture](https://stripe.com/docs/payments/capture-later) and immediate payment.
70
+
71
+ Both flows are supported by this extension, but you should be aware that they will happen before the order finalization, just before the final confirmation. At that moment if the payment method of choice will require additional authentication (e.g. 3D Secure) the extra authentication will be shown to the user.
72
+
73
+ ### Upgrading from v4
74
+
75
+ This extension is a complete rewrite of the previous version, and it's not generally compatible with v4.
76
+
77
+ That being said, if you're upgrading from v4 you can check out this guide to help you with the transition
78
+ from payment tokens to payment intents: https://stripe.com/docs/payments/payment-intents/migration.
69
79
 
70
80
  ## Usage
71
81
 
@@ -99,13 +109,23 @@ Stripe Payment Method other than "card" on the admin interface, you must include
99
109
 
100
110
  `app/views/spree/admin/payments/source_forms/existing_payment/stripe/`
101
111
 
102
- ### Custom webhooks
112
+ ### Customizing Webhooks
113
+
114
+ Solidus Stripe comes with support for a few [webhook events](https://stripe.com/docs/webhooks), to which there's a default handler. You can customize the behavior of those handlers by or add to their behavior by replacing or adding subscribers in the internal Solidus event bus.
115
+
116
+ Each event will have the original Stripe name, prefixed by `stripe.`. For example, the `payment_intent.succeeded` event will be published as `stripe.payment_intent.succeeded`.
103
117
 
104
- You can also use [Stripe webhooks](https://stripe.com/docs/webhooks) to trigger
105
- custom actions in your application.
118
+ Here's the list of events that are supported by default:
106
119
 
107
- First, you need to register the event you want to listen to, both [in
108
- Stripe](https://stripe.com/docs/webhooks/go-live) and in your application:
120
+ payment_intent.succeeded
121
+ payment_intent.payment_failed
122
+ payment_intent.canceled
123
+ charge.refunded
124
+
125
+ #### Adding a new event handler
126
+
127
+ In order to add a new handler you need to register the event you want to listen to,
128
+ both [in Stripe](https://stripe.com/docs/webhooks/go-live) and in your application:
109
129
 
110
130
  ```ruby
111
131
  # config/initializers/solidus_stripe.rb
@@ -117,7 +137,7 @@ end
117
137
  That will register a new `:"stripe.charge.succeeded"` event in the [Solidus
118
138
  bus](https://guides.solidus.io/customization/subscribing-to-events). The
119
139
  Solidus event will be published whenever a matching incoming webhook event is
120
- received. You can subscribe to it as regular:
140
+ received. You can subscribe to it [as usual](https://guides.solidus.io/customization/subscribing-to-events):
121
141
 
122
142
  ```ruby
123
143
  # app/subscribers/update_account_balance_subscriber.rb
@@ -127,7 +147,13 @@ class UpdateAccountBalanceSubscriber
127
147
  handle :"stripe.charge.succeeded", with: :call
128
148
 
129
149
  def call(event)
130
- # ...
150
+ # Please refere to the Stripe gem and API documentation for more details on the
151
+ # structure of the event object. All methods called on `event` will be forwarded
152
+ # to the Stripe event object:
153
+ # - https://www.rubydoc.info/gems/stripe/Stripe/Event
154
+ # - https://stripe.com/docs/webhooks/stripe-events
155
+
156
+ Rails.logger.info "Charge succeeded: #{event.data.to_json}"
131
157
  end
132
158
  end
133
159
 
@@ -145,6 +171,8 @@ underlying stripe event object. It can also be used in async [
145
171
  adapters](https://github.com/nebulab/omnes#adapters), which is recommended as
146
172
  otherwise the response to Stripe will be delayed until subscribers are done.
147
173
 
174
+ #### Configuring the webhook signature tolerance
175
+
148
176
  You can also configure the signature verification tolerance in seconds (it
149
177
  defaults to the [same value as Stripe
150
178
  default](https://stripe.com/docs/webhooks/signatures#replay-attacks)):
@@ -156,6 +184,16 @@ SolidusStripe.configure do |config|
156
184
  end
157
185
  ```
158
186
 
187
+ ### Customizing the list of available Stripe payment methods
188
+
189
+ By default, the extension will show all the payment methods that are supported by Stripe in the current currency and for the merchant country.
190
+
191
+ You can customize the list of available payment methods by overriding the `payment_method_types` option in the `app/views/checkouts/payment/_stripe.html.erb` partial. Please refer to the [Stripe documentation](https://stripe.com/docs/payments/payment-methods) for the full list of supported payment methods.
192
+
193
+ ### Non-card payment methods and "auto_capture"
194
+
195
+ Solidus payment methods are configured with a `auto_capture` option, which is used to determine if the payment should be captured immediately or not. If you intend to use a non-card payment method, it's likely that you'll need to set `auto_capture` to `true` in the payment method configuration. Please refer to the [Stripe documentation](https://stripe.com/docs/payments/payment-methods/integration-options#additional-api-supportability) for more details.
196
+
159
197
  ## Implementation
160
198
 
161
199
  ### Payment state-machine vs. PaymentIntent statuses
@@ -169,7 +207,7 @@ In order to map these concepts SolidusStripe will match states in a slightly une
169
207
  | --------------------------- | --------------------- |
170
208
  | requires_payment_method | checkout |
171
209
  | requires_action | checkout |
172
- | processing | checkout |
210
+ | processing | processing |
173
211
  | requires_confirmation | checkout |
174
212
  | requires_capture | pending |
175
213
  | succeeded | completed |
@@ -179,6 +217,10 @@ Reference:
179
217
  - https://stripe.com/docs/payments/intents?intent=payment
180
218
  - https://github.com/solidusio/solidus/blob/master/core/lib/spree/core/state_machines/payment.rb
181
219
 
220
+ ### Deferred payment confirmation
221
+
222
+ This extensions is using the [two-step payment confirmation](https://stripe.com/docs/payments/build-a-two-step-confirmation) flow. This means that at the payment step the payment form will just collect the basic payment information (e.g. credit card details) and any additional confirmation is deferred to the confirmation step.
223
+
182
224
  ## Development
183
225
 
184
226
  Retrieve your API Key and Publishable Key from your [Stripe testing dashboard](https://stripe.com/docs/testing). You can
@@ -75,16 +75,16 @@ module SolidusStripe
75
75
  # @see https://stripe.com/docs/payments/capture-later
76
76
  #
77
77
  # @todo add support for capturing custom amounts
78
- def capture(amount_in_cents, payment_intent_id, options = {})
78
+ def capture(amount_in_cents, stripe_payment_intent_id, options = {})
79
79
  check_given_amount_matches_payment_intent(amount_in_cents, options)
80
- check_payment_intent_id(payment_intent_id)
80
+ check_stripe_payment_intent_id(stripe_payment_intent_id)
81
81
 
82
- payment_intent = capture_stripe_payment_intent(payment_intent_id)
82
+ stripe_payment_intent = capture_stripe_payment_intent(stripe_payment_intent_id, amount_in_cents)
83
83
  build_payment_log(
84
84
  success: true,
85
85
  message: "PaymentIntent was confirmed successfully",
86
- response_code: payment_intent.id,
87
- data: payment_intent,
86
+ response_code: stripe_payment_intent.id,
87
+ data: stripe_payment_intent,
88
88
  )
89
89
  rescue Stripe::InvalidRequestError => e
90
90
  build_payment_log(
@@ -130,18 +130,18 @@ module SolidusStripe
130
130
  end
131
131
 
132
132
  # Voids a previously authorized transaction, releasing the funds that are on hold.
133
- def void(payment_intent_id, _options = {})
134
- check_payment_intent_id(payment_intent_id)
133
+ def void(stripe_payment_intent_id, _options = {})
134
+ check_stripe_payment_intent_id(stripe_payment_intent_id)
135
135
 
136
- payment_intent = request do
137
- Stripe::PaymentIntent.cancel(payment_intent_id)
136
+ stripe_payment_intent = request do
137
+ Stripe::PaymentIntent.cancel(stripe_payment_intent_id)
138
138
  end
139
139
 
140
140
  build_payment_log(
141
141
  success: true,
142
142
  message: "PaymentIntent was canceled successfully",
143
- response_code: payment_intent_id,
144
- data: payment_intent,
143
+ response_code: stripe_payment_intent_id,
144
+ data: stripe_payment_intent,
145
145
  )
146
146
  rescue Stripe::InvalidRequestError => e
147
147
  build_payment_log(
@@ -158,8 +158,8 @@ module SolidusStripe
158
158
  # {RefundsSynchronizer}.
159
159
  #
160
160
  # TODO: check this method params twice.
161
- def credit(amount_in_cents, payment_intent_id, options = {})
162
- check_payment_intent_id(payment_intent_id)
161
+ def credit(amount_in_cents, stripe_payment_intent_id, options = {})
162
+ check_stripe_payment_intent_id(stripe_payment_intent_id)
163
163
 
164
164
  payment = options[:originator].payment
165
165
  currency = payment.currency
@@ -167,10 +167,8 @@ module SolidusStripe
167
167
  stripe_refund = request do
168
168
  Stripe::Refund.create(
169
169
  amount: to_stripe_amount(amount_in_cents, currency),
170
- payment_intent: payment_intent_id,
171
- metadata: {
172
- RefundsSynchronizer::SKIP_SYNC_METADATA_KEY => RefundsSynchronizer::SKIP_SYNC_METADATA_VALUE
173
- }
170
+ payment_intent: stripe_payment_intent_id,
171
+ metadata: RefundsSynchronizer.skip_sync_metadata
174
172
  )
175
173
  end
176
174
 
@@ -203,8 +201,8 @@ module SolidusStripe
203
201
  request { Stripe::PaymentIntent.confirm(stripe_payment_intent_id) }
204
202
  end
205
203
 
206
- def capture_stripe_payment_intent(stripe_payment_intent_id)
207
- request { Stripe::PaymentIntent.capture(stripe_payment_intent_id) }
204
+ def capture_stripe_payment_intent(stripe_payment_intent_id, amount)
205
+ request { Stripe::PaymentIntent.capture(stripe_payment_intent_id, amount: amount) }
208
206
  end
209
207
 
210
208
  def check_given_amount_matches_payment_intent(amount_in_cents, options)
@@ -218,12 +216,12 @@ module SolidusStripe
218
216
  "tried #{amount_in_cents} but can only accept #{payment.display_amount.cents}."
219
217
  end
220
218
 
221
- def check_payment_intent_id(payment_intent_id)
222
- unless payment_intent_id
223
- raise ArgumentError, "missing payment_intent_id"
219
+ def check_stripe_payment_intent_id(stripe_payment_intent_id)
220
+ unless stripe_payment_intent_id
221
+ raise ArgumentError, "missing stripe_payment_intent_id"
224
222
  end
225
223
 
226
- return if payment_intent_id.start_with?('pi_')
224
+ return if stripe_payment_intent_id.start_with?('pi_')
227
225
 
228
226
  raise ArgumentError, "the payment intent id has the wrong format"
229
227
  end
@@ -5,17 +5,9 @@ module SolidusStripe
5
5
  belongs_to :order, class_name: 'Spree::Order'
6
6
  belongs_to :payment_method, class_name: 'SolidusStripe::PaymentMethod'
7
7
 
8
- def self.prepare_for_payment(payment, **stripe_intent_options)
8
+ def self.prepare_for_payment(payment, **stripe_creation_options)
9
9
  # Find or create the intent for the payment.
10
- intent =
11
- retrieve_last_usable_intent(payment) ||
12
- new(payment_method: payment.payment_method, order: payment.order)
13
- .tap { _1.update!(stripe_intent_id: _1.create_stripe_intent(**stripe_intent_options).id) }
14
-
15
- # Update the intent with the previously acquired payment method.
16
- intent.payment_method.gateway.request {
17
- Stripe::PaymentIntent.update(intent.stripe_intent_id, payment_method: payment.source.stripe_payment_method_id)
18
- }
10
+ intent = retrieve_for_payment(payment) || create_for_payment(payment, **stripe_creation_options)
19
11
 
20
12
  # Attach the payment intent to the payment.
21
13
  payment.update!(response_code: intent.stripe_intent.id)
@@ -23,14 +15,31 @@ module SolidusStripe
23
15
  intent
24
16
  end
25
17
 
26
- def self.retrieve_last_usable_intent(payment)
18
+ def self.create_for_payment(payment, **stripe_intent_options)
19
+ new(payment_method: payment.payment_method, order: payment.order)
20
+ .tap { _1.update!(stripe_intent_id: _1.create_stripe_intent(payment, **stripe_intent_options).id) }
21
+ end
22
+
23
+ def self.retrieve_for_payment(payment)
27
24
  intent = where(payment_method: payment.payment_method, order: payment.order).last
28
- intent if intent&.usable?
25
+
26
+ return unless intent&.usable?
27
+
28
+ # Update the intent with the previously acquired payment method.
29
+ intent.payment_method.gateway.request {
30
+ Stripe::PaymentIntent.update(
31
+ intent.stripe_intent_id,
32
+ payment_method: payment.source.stripe_payment_method_id,
33
+ payment_method_types: [payment.source.stripe_payment_method.type],
34
+ )
35
+ }
36
+
37
+ intent
29
38
  end
30
39
 
31
40
  def usable?
32
41
  stripe_intent_id &&
33
- stripe_intent.status == 'requires_payment_method'
42
+ stripe_intent.status == 'requires_payment_method' &&
34
43
  stripe_intent.amount == stripe_order_amount
35
44
  end
36
45
 
@@ -50,6 +59,8 @@ module SolidusStripe
50
59
  payment.started_processing!
51
60
 
52
61
  case stripe_intent.status
62
+ when 'processing'
63
+ successful = true
53
64
  when 'requires_capture'
54
65
  payment.pend! unless payment.pending?
55
66
  successful = true
@@ -89,7 +100,7 @@ module SolidusStripe
89
100
  super
90
101
  end
91
102
 
92
- def create_stripe_intent(**stripe_intent_options)
103
+ def create_stripe_intent(payment, **stripe_intent_options)
93
104
  stripe_customer_id = SolidusStripe::Customer.retrieve_or_create_stripe_customer_id(
94
105
  payment_method: payment_method,
95
106
  order: order
@@ -102,6 +113,8 @@ module SolidusStripe
102
113
  capture_method: payment_method.auto_capture? ? 'automatic' : 'manual',
103
114
  setup_future_usage: payment_method.preferred_setup_future_usage.presence,
104
115
  customer: stripe_customer_id,
116
+ payment_method: payment.source.stripe_payment_method_id,
117
+ payment_method_types: [payment.source.stripe_payment_method.type],
105
118
  metadata: { solidus_order_number: order.number },
106
119
  **stripe_intent_options,
107
120
  })
@@ -16,12 +16,12 @@ module SolidusStripe
16
16
  # @param event [SolidusStripe::Webhook::Event]
17
17
  # @see SolidusStripe::RefundsSynchronizer
18
18
  def sync_refunds(event)
19
- payment_method = event.spree_payment_method
20
- payment_intent_id = event.data.object.payment_intent
19
+ payment_method = event.payment_method
20
+ stripe_payment_intent_id = event.data.object.payment_intent
21
21
 
22
22
  RefundsSynchronizer
23
23
  .new(payment_method)
24
- .call(payment_intent_id)
24
+ .call(stripe_payment_intent_id)
25
25
  end
26
26
  end
27
27
  end
@@ -79,8 +79,8 @@ module SolidusStripe
79
79
  private
80
80
 
81
81
  def extract_payment_from_event(event)
82
- payment_intent_id = event.data.object.id
83
- Spree::Payment.find_by!(response_code: payment_intent_id)
82
+ stripe_payment_intent_id = event.data.object.id
83
+ Spree::Payment.find_by!(response_code: stripe_payment_intent_id)
84
84
  end
85
85
 
86
86
  def complete_payment(payment)
@@ -95,17 +95,17 @@ module SolidusStripe
95
95
 
96
96
  def sync_refunds(event)
97
97
  event.data.object.to_hash => {
98
- id: payment_intent_id,
98
+ id: stripe_payment_intent_id,
99
99
  amount: stripe_amount,
100
100
  amount_received: stripe_amount_received,
101
101
  currency:
102
102
  }
103
103
  return if stripe_amount == stripe_amount_received
104
104
 
105
- payment_method = event.spree_payment_method
105
+ payment_method = event.payment_method
106
106
  RefundsSynchronizer
107
107
  .new(payment_method)
108
- .call(payment_intent_id)
108
+ .call(stripe_payment_intent_id)
109
109
  end
110
110
  end
111
111
  end
@@ -9,6 +9,11 @@
9
9
 
10
10
  <div>
11
11
  <label>
12
- <%= render "#{partial_base}/#{payment_type}", stripe_payment_method: stripe_payment_method %>
12
+ <%= render(
13
+ "#{partial_base}/#{payment_type}",
14
+ stripe_payment_method: stripe_payment_method,
15
+ partial_base: partial_base,
16
+ payment_type: payment_type
17
+ ) %>
13
18
  </label>
14
19
  </div>
@@ -1,10 +1,14 @@
1
1
  class CreateSolidusStripePaymentIntents < ActiveRecord::Migration[7.0]
2
2
  def change
3
3
  create_table :solidus_stripe_payment_intents do |t|
4
- t.string :stripe_payment_intent_id
5
- t.references :order, null: false, foreign_key: { to_table: :spree_orders }
4
+ t.string :stripe_intent_id
5
+ t.integer :order_id, null: false, index: true
6
+ t.integer :payment_method_id, null: false, index: true
6
7
 
7
8
  t.timestamps
9
+
10
+ t.foreign_key :spree_orders, column: :order_id
11
+ t.foreign_key :spree_payment_methods, column: :payment_method_id
8
12
  end
9
13
  end
10
14
  end
@@ -0,0 +1,12 @@
1
+ class CreateSolidusStripeSlugEntries < ActiveRecord::Migration[5.2]
2
+ def change
3
+ create_table :solidus_stripe_slug_entries do |t|
4
+ t.integer :payment_method_id, null: false, index: true
5
+ t.string :slug, null: false, index: { unique: true }
6
+
7
+ t.timestamps
8
+
9
+ t.foreign_key :spree_payment_methods, column: :payment_method_id
10
+ end
11
+ end
12
+ end
@@ -1,15 +1,14 @@
1
1
  class CreateSolidusStripeCustomers < ActiveRecord::Migration[7.0]
2
2
  def change
3
3
  create_table :solidus_stripe_customers do |t|
4
- t.references :payment_method, null: false, foreign_key: { to_table: :spree_payment_methods }
5
-
4
+ t.integer :payment_method_id, null: false
6
5
  t.string :source_type
7
6
  t.integer :source_id
8
-
9
7
  t.string :stripe_id, index: true
10
8
 
11
9
  t.timestamps
12
10
 
11
+ t.foreign_key :spree_payment_methods, column: :payment_method_id
13
12
  t.index [:payment_method_id, :source_type, :source_id], unique: true, name: :payment_method_and_source
14
13
  end
15
14
  end
@@ -5,7 +5,7 @@ export default class extends Controller {
5
5
  static values = {
6
6
  clientSecret: String,
7
7
  publishableKey: String,
8
- paymentElementOptions: Object,
8
+ options: Object,
9
9
 
10
10
  // For now we don't have a controller to interact with
11
11
  // and we can't use outlets, so we fallback on acquiring selectors.
@@ -71,8 +71,8 @@ export default class extends Controller {
71
71
  }
72
72
 
73
73
  setupPaymentElement() {
74
- this.elements = this.stripe.elements(this.paymentElementOptionsValue)
75
- this.paymentElement = this.elements.create("payment", { layout: "tabs" })
74
+ this.elements = this.stripe.elements(this.optionsValue.elementsInitialization)
75
+ this.paymentElement = this.elements.create("payment", this.optionsValue.paymentElementCreation)
76
76
  this.paymentElementTarget.innerHTML = "" // Remove child nodes used for loading
77
77
  this.paymentElement.mount(this.paymentElementTarget)
78
78
  }
@@ -11,6 +11,11 @@
11
11
 
12
12
  <div>
13
13
  <label>
14
- <%= render "#{partial_base}/#{payment_type}", stripe_payment_method: stripe_payment_method %>
14
+ <%= render(
15
+ "#{partial_base}/#{payment_type}",
16
+ stripe_payment_method: stripe_payment_method,
17
+ partial_base: partial_base,
18
+ payment_type: payment_type,
19
+ ) %>
15
20
  </label>
16
21
  </div>