solidus_stripe 4.4.0 → 5.0.0.rc.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (148) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +102 -41
  3. data/.gitignore +3 -0
  4. data/.rubocop.yml +94 -4
  5. data/.yardopts +1 -0
  6. data/CHANGELOG.md +1 -265
  7. data/Gemfile +10 -30
  8. data/LICENSE +2 -2
  9. data/Procfile.dev +3 -0
  10. data/README.md +185 -213
  11. data/Rakefile +7 -6
  12. data/app/assets/javascripts/spree/backend/solidus_stripe.js +2 -0
  13. data/app/assets/stylesheets/spree/backend/solidus_stripe.css +4 -0
  14. data/app/controllers/solidus_stripe/intents_controller.rb +36 -52
  15. data/app/controllers/solidus_stripe/webhooks_controller.rb +28 -0
  16. data/app/models/concerns/solidus_stripe/log_entries.rb +31 -0
  17. data/app/models/solidus_stripe/customer.rb +21 -0
  18. data/app/models/solidus_stripe/gateway.rb +229 -0
  19. data/app/models/solidus_stripe/payment_intent.rb +124 -0
  20. data/app/models/solidus_stripe/payment_method.rb +106 -0
  21. data/app/models/solidus_stripe/payment_source.rb +31 -0
  22. data/app/models/solidus_stripe/slug_entry.rb +20 -0
  23. data/app/models/solidus_stripe.rb +7 -0
  24. data/app/subscribers/solidus_stripe/webhook/charge_subscriber.rb +28 -0
  25. data/app/subscribers/solidus_stripe/webhook/payment_intent_subscriber.rb +112 -0
  26. data/app/views/spree/admin/payments/source_forms/_stripe.html.erb +29 -0
  27. data/app/views/spree/admin/payments/source_forms/existing_payment/_stripe.html.erb +19 -0
  28. data/app/views/spree/admin/payments/source_forms/existing_payment/stripe/_card.html.erb +8 -0
  29. data/app/views/spree/admin/payments/source_forms/existing_payment/stripe/_default.html.erb +7 -0
  30. data/app/views/spree/admin/payments/source_views/_stripe.html.erb +15 -0
  31. data/app/views/spree/api/payments/source_views/_stripe.json.jbuilder +8 -0
  32. data/bin/dev +13 -0
  33. data/bin/dummy-app +29 -0
  34. data/bin/rails +38 -3
  35. data/bin/rails-dummy-app +3 -0
  36. data/bin/rails-engine +1 -11
  37. data/bin/rails-new +55 -0
  38. data/bin/rails-sandbox +1 -14
  39. data/bin/rspec +10 -0
  40. data/bin/sandbox +12 -74
  41. data/bin/setup +1 -0
  42. data/bin/update-migrations +56 -0
  43. data/codecov.yml +12 -0
  44. data/config/locales/en.yml +16 -1
  45. data/config/routes.rb +5 -11
  46. data/coverage.rb +42 -0
  47. data/db/migrate/20230109183332_create_solidus_stripe_payment_sources.rb +10 -0
  48. data/db/migrate/20230306105520_create_solidus_stripe_payment_intents.rb +14 -0
  49. data/db/migrate/20230308122414_create_solidus_stripe_slug_entries.rb +12 -0
  50. data/db/migrate/20230313150008_create_solidus_stripe_customers.rb +15 -0
  51. data/db/seeds.rb +6 -24
  52. data/lib/generators/solidus_stripe/install/install_generator.rb +121 -14
  53. data/lib/generators/solidus_stripe/install/templates/app/assets/stylesheets/spree/frontend/solidus_stripe.css +13 -0
  54. data/lib/generators/solidus_stripe/install/templates/app/javascript/controllers/solidus_stripe_confirm_controller.js +39 -0
  55. data/lib/generators/solidus_stripe/install/templates/app/javascript/controllers/solidus_stripe_payment_controller.js +89 -0
  56. data/lib/generators/solidus_stripe/install/templates/app/views/checkouts/existing_payment/_stripe.html.erb +21 -0
  57. data/lib/generators/solidus_stripe/install/templates/app/views/checkouts/existing_payment/stripe/_card.html.erb +8 -0
  58. data/lib/generators/solidus_stripe/install/templates/app/views/checkouts/existing_payment/stripe/_default.html.erb +7 -0
  59. data/lib/generators/solidus_stripe/install/templates/app/views/checkouts/payment/_stripe.html.erb +74 -0
  60. data/lib/generators/solidus_stripe/install/templates/app/views/orders/payment_info/_stripe.html.erb +32 -0
  61. data/lib/generators/solidus_stripe/install/templates/config/initializers/solidus_stripe.rb +31 -0
  62. data/lib/solidus_stripe/configuration.rb +24 -3
  63. data/lib/solidus_stripe/engine.rb +19 -6
  64. data/lib/solidus_stripe/money_to_stripe_amount_converter.rb +109 -0
  65. data/lib/solidus_stripe/refunds_synchronizer.rb +98 -0
  66. data/lib/solidus_stripe/seeds.rb +19 -0
  67. data/lib/solidus_stripe/testing_support/factories.rb +153 -0
  68. data/lib/solidus_stripe/version.rb +1 -1
  69. data/lib/solidus_stripe/webhook/event.rb +91 -0
  70. data/lib/solidus_stripe.rb +0 -2
  71. data/solidus_stripe.gemspec +29 -6
  72. data/spec/lib/solidus_stripe/configuration_spec.rb +21 -0
  73. data/spec/lib/solidus_stripe/money_to_stripe_amount_converter_spec.rb +133 -0
  74. data/spec/lib/solidus_stripe/refunds_synchronizer_spec.rb +238 -0
  75. data/spec/lib/solidus_stripe/seeds_spec.rb +43 -0
  76. data/spec/lib/solidus_stripe/webhook/event_spec.rb +134 -0
  77. data/spec/models/concerns/solidus_stripe/log_entries_spec.rb +54 -0
  78. data/spec/models/solidus_stripe/customer_spec.rb +47 -0
  79. data/spec/models/solidus_stripe/gateway_spec.rb +281 -0
  80. data/spec/models/solidus_stripe/payment_intent_spec.rb +78 -0
  81. data/spec/models/solidus_stripe/payment_method_spec.rb +137 -0
  82. data/spec/models/solidus_stripe/payment_source_spec.rb +25 -0
  83. data/spec/requests/solidus_stripe/intents_controller_spec.rb +29 -0
  84. data/spec/requests/solidus_stripe/webhooks_controller/charge/refunded_spec.rb +31 -0
  85. data/spec/requests/solidus_stripe/webhooks_controller/payment_intent/canceled_spec.rb +23 -0
  86. data/spec/requests/solidus_stripe/webhooks_controller/payment_intent/payment_failed_spec.rb +23 -0
  87. data/spec/requests/solidus_stripe/webhooks_controller/payment_intent/succeeded_spec.rb +29 -0
  88. data/spec/requests/solidus_stripe/webhooks_controller_spec.rb +56 -0
  89. data/spec/solidus_stripe_spec_helper.rb +10 -0
  90. data/spec/subscribers/solidus_stripe/webhook/charge_subscriber_spec.rb +33 -0
  91. data/spec/subscribers/solidus_stripe/webhook/payment_intent_subscriber_spec.rb +297 -0
  92. data/spec/support/solidus_stripe/backend_test_helper.rb +185 -0
  93. data/spec/support/solidus_stripe/checkout_test_helper.rb +365 -0
  94. data/spec/support/solidus_stripe/factories.rb +5 -0
  95. data/spec/support/solidus_stripe/webhook/data_fixtures.rb +106 -0
  96. data/spec/support/solidus_stripe/webhook/event_with_context_factory.rb +82 -0
  97. data/spec/support/solidus_stripe/webhook/request_helper.rb +32 -0
  98. data/spec/system/backend/solidus_stripe/orders/payments_spec.rb +145 -0
  99. data/spec/system/frontend/.keep +0 -0
  100. data/spec/system/frontend/solidus_stripe/checkout_spec.rb +206 -0
  101. data/tmp/.keep +0 -0
  102. metadata +196 -75
  103. data/.rubocop_todo.yml +0 -298
  104. data/.travis.yml +0 -28
  105. data/app/assets/javascripts/spree/frontend/solidus_stripe/stripe-cart-page-checkout.js +0 -122
  106. data/app/assets/javascripts/spree/frontend/solidus_stripe/stripe-elements.js +0 -148
  107. data/app/assets/javascripts/spree/frontend/solidus_stripe/stripe-init.js +0 -20
  108. data/app/assets/javascripts/spree/frontend/solidus_stripe/stripe-payment-intents.js +0 -84
  109. data/app/assets/javascripts/spree/frontend/solidus_stripe/stripe-payment-request-button-shared.js +0 -160
  110. data/app/assets/javascripts/spree/frontend/solidus_stripe/stripe-payment.js +0 -16
  111. data/app/assets/javascripts/spree/frontend/solidus_stripe.js +0 -6
  112. data/app/controllers/solidus_stripe/payment_request_controller.rb +0 -52
  113. data/app/controllers/spree/stripe_controller.rb +0 -13
  114. data/app/decorators/models/spree/order_update_attributes_decorator.rb +0 -39
  115. data/app/decorators/models/spree/payment_decorator.rb +0 -11
  116. data/app/decorators/models/spree/refund_decorator.rb +0 -9
  117. data/app/models/solidus_stripe/address_from_params_service.rb +0 -72
  118. data/app/models/solidus_stripe/create_intents_payment_service.rb +0 -114
  119. data/app/models/solidus_stripe/prepare_order_for_payment_service.rb +0 -46
  120. data/app/models/solidus_stripe/shipping_rates_service.rb +0 -46
  121. data/app/models/spree/payment_method/stripe_credit_card.rb +0 -230
  122. data/bin/r +0 -13
  123. data/bin/sandbox_rails +0 -18
  124. data/db/migrate/20181010123508_update_stripe_payment_method_type_to_credit_card.rb +0 -21
  125. data/lib/assets/stylesheets/spree/frontend/solidus_stripe.scss +0 -11
  126. data/lib/solidus_stripe/testing_support/card_input_helper.rb +0 -34
  127. data/lib/tasks/solidus_stripe/db/seed.rake +0 -14
  128. data/lib/views/api/spree/api/payments/source_views/_stripe.json.jbuilder +0 -3
  129. data/lib/views/backend/spree/admin/log_entries/_stripe.html.erb +0 -28
  130. data/lib/views/backend/spree/admin/payments/source_forms/_stripe.html.erb +0 -1
  131. data/lib/views/backend/spree/admin/payments/source_views/_stripe.html.erb +0 -1
  132. data/lib/views/frontend/spree/checkout/existing_payment/_stripe.html.erb +0 -1
  133. data/lib/views/frontend/spree/checkout/payment/_stripe.html.erb +0 -8
  134. data/lib/views/frontend/spree/checkout/payment/v2/_javascript.html.erb +0 -78
  135. data/lib/views/frontend/spree/checkout/payment/v3/_elements.html.erb +0 -1
  136. data/lib/views/frontend/spree/checkout/payment/v3/_form_elements.html.erb +0 -40
  137. data/lib/views/frontend/spree/checkout/payment/v3/_intents.html.erb +0 -1
  138. data/lib/views/frontend/spree/checkout/payment/v3/_stripe.html.erb +0 -2
  139. data/lib/views/frontend/spree/orders/_stripe_payment_request_button.html.erb +0 -14
  140. data/spec/features/stripe_checkout_spec.rb +0 -486
  141. data/spec/models/solidus_stripe/address_from_params_service_spec.rb +0 -87
  142. data/spec/models/solidus_stripe/create_intents_payment_service_spec.rb +0 -127
  143. data/spec/models/solidus_stripe/prepare_order_for_payment_service_spec.rb +0 -65
  144. data/spec/models/solidus_stripe/shipping_rates_service_spec.rb +0 -54
  145. data/spec/models/spree/payment_method/stripe_credit_card_spec.rb +0 -350
  146. data/spec/requests/payment_requests_spec.rb +0 -152
  147. data/spec/spec_helper.rb +0 -37
  148. data/spec/support/solidus_address_helper.rb +0 -15
data/README.md CHANGED
@@ -1,260 +1,242 @@
1
1
  # Solidus Stripe
2
2
 
3
3
  [![CircleCI](https://circleci.com/gh/solidusio/solidus_stripe.svg?style=shield)](https://circleci.com/gh/solidusio/solidus_stripe)
4
- [![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)
5
6
 
6
- Stripe Payment Method for Solidus. It works as a wrapper for the ActiveMerchant Stripe gateway.
7
+ <!-- Explain what your extension does. -->
7
8
 
8
9
  ## Installation
9
10
 
10
-
11
- Run from the command line:
11
+ Add solidus_stripe to your bundle and run the installation generator:
12
12
 
13
13
  ```shell
14
14
  bundle add solidus_stripe
15
- bundle exec rails g solidus_stripe:install
15
+ bin/rails generate solidus_stripe:install
16
16
  ```
17
17
 
18
- Usage
19
- -----
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:
20
20
 
21
- Navigate to *Settings > Payments > Payment Methods* in the admin panel.
22
- You can now create a new payment method that uses Stripe by selecting
23
- `Stripe credit card` under Type in the New Payment Method form and saving.
24
- The Stripe payment method's extra fields will be now shown in the form.
21
+ ```shell
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
25
+ ```
25
26
 
26
- **Configure via database configuration**
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.
27
29
 
28
- If you want to store your Stripe credentials in the database just
29
- fill the new fields in the form, selecting `custom` (default) in the
30
- Preference Source field.
30
+ ⚠️ Be sure to set the enviroment variables to the values for test mode in your development environment.
31
31
 
32
- **Configure via static configuration**
32
+ ### Webhooks setup
33
33
 
34
- If you want to store your credentials into your codebase or use ENV
35
- variables you can create the following static configuration:
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.
36
36
 
37
- ```ruby
38
- # config/initializers/spree.rb
39
- Rails.application.config.to_prepare do
40
- Spree::Config.static_model_preferences.add(
41
- Spree::PaymentMethod::StripeCreditCard,
42
- 'stripe_env_credentials',
43
- secret_key: ENV['STRIPE_SECRET_KEY'],
44
- publishable_key: ENV['STRIPE_PUBLISHABLE_KEY'],
45
- stripe_country: 'US',
46
- v3_elements: false,
47
- v3_intents: false
48
- )
49
- end
37
+ #### Production enviroment
38
+
39
+ Before going to production, you'll need to [register the webhook endpoint with
40
+ Stripe](https://stripe.com/docs/webhooks/go-live), and make sure to subscribe
41
+ to the events listed in [the `SolidusStripe::Webhook::Event::CORE`
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
50
55
  ```
51
56
 
52
- Once your server has been restarted, you can select in the Preference
53
- Source field a new entry called `stripe_env_credentials`. After saving,
54
- your application will start using the static configuration to process
55
- Stripe payments.
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.
56
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.
79
+
80
+ ## Usage
81
+
82
+ ### Showing reusable sources in the checkout
83
+
84
+ When saving stripe payment methods for future usage the checkout requires
85
+ a partial for each supported payment method type.
86
+
87
+ For the full list of types see: https://stripe.com/docs/api/payment_methods/object#payment_method_object-type.
88
+
89
+ The extension will only install a partial for the `card` type, located in `app/views/checkouts/existing_payment/stripe/_card.html.erb`,
90
+ and fall back to a `default` partial otherwise (see `app/views/checkouts/existing_payment/stripe/_default.html.erb`).
91
+
92
+ As an example, in order to show a wallet source connected to a
93
+ [SEPA Debit payment method](https://stripe.com/docs/api/payment_methods/object#payment_method_object-sepa_debit)
94
+ the following partial should be added:
95
+
96
+ `app/views/checkouts/existing_payment/stripe/_sepa_debit.html.erb`
97
+
98
+ ```erb
99
+ <% sepa_debit = stripe_payment_method.sepa_debit %>
100
+ 🏦 <%= sepa_debit.bank_code %> / <%= sepa_debit.branch_code %><br>
101
+ IBAN: **** **** **** **** **** <%= sepa_debit.last4 %>
102
+ ```
57
103
 
58
- Using Stripe Payment Intents API
59
- --------------------------------
104
+ ### Showing reusable sources in the admin interface
60
105
 
61
- If you want to use the new SCA-ready Stripe Payment Intents API you need
62
- to change the `v3_intents` preference from the code above to true.
106
+ Refer to the previous section for information on how to set up a new payment method type.
107
+ However, it's important to note that if you have to display a wallet source connected to a
108
+ Stripe Payment Method other than "card" on the admin interface, you must include the partial in:
63
109
 
64
- Also, if you want to allow Apple Pay and Google Pay payments using the
65
- Stripe payment request button API, you only need to set the `stripe_country`
66
- preference, which represents the two-letter country code of your Stripe
67
- account. Conversely, if you need to disable the button you can simply remove
68
- the `stripe_country` preference.
110
+ `app/views/spree/admin/payments/source_forms/existing_payment/stripe/`
69
111
 
70
- Please refer to Stripe official
71
- [documentation](https://stripe.com/docs/payments/payment-intents)
72
- for further instructions on how to make this work properly.
112
+ ### Customizing Webhooks
73
113
 
74
- The following configuration will use both Payment Intents and the
75
- payment request button API on the store payment page:
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.
76
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`.
117
+
118
+ Here's the list of events that are supported by default:
119
+
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:
77
129
 
78
130
  ```ruby
79
- Spree.config do |config|
80
- # ...
81
-
82
- config.static_model_preferences.add(
83
- Spree::PaymentMethod::StripeCreditCard,
84
- 'stripe_env_credentials',
85
- secret_key: ENV['STRIPE_SECRET_KEY'],
86
- publishable_key: ENV['STRIPE_PUBLISHABLE_KEY'],
87
- stripe_country: 'US',
88
- v3_elements: false,
89
- v3_intents: true
90
- )
131
+ # config/initializers/solidus_stripe.rb
132
+ SolidusStripe.configure do |config|
133
+ config.webhook_events = %i[charge.succeeded]
91
134
  end
92
135
  ```
93
136
 
94
- When using the Payment Intents API, be aware that the charge flow will be a bit
95
- different than when using the old V2 API or Elements. It's advisable that all
96
- Payment Intents charges are captured only by using the Solidus backend, as it is
97
- the final source of truth in regards of Solidus orders payments.
98
-
99
- A Payment Intent is created as soon as the customer enters their credit card
100
- data. A tentative charge will be created on Stripe, easily recognizable by its
101
- description: `Solidus Order ID: R987654321 (pending)`. As soon as the credit
102
- card is confirmed (ie. when the customer passes the 3DSecure authorization, when
103
- required) then the charge description gets updated to include the Solidus payment
104
- number: `Solidus Order ID: R987654321-Z4VYUDB3`.
105
-
106
- These charges are created `uncaptured` and will need to be captured in Solidus
107
- backend later, after the customer confirms the order. If the customer never
108
- completes the checkout, that charge must remain uncaptured. If the customer
109
- decides to change their payment method after creating a Payment Request, then
110
- that Payment Request charge will be canceled.
111
-
112
-
113
- Apple Pay and Google Pay
114
- -----------------------
115
-
116
- The Payment Intents API now supports also Apple Pay and Google Pay via
117
- the [payment request button API](https://stripe.com/docs/stripe-js/elements/payment-request-button).
118
- Check the Payment Intents section for setup details. Also, please
119
- refer to the official Stripe documentation for configuring your
120
- Stripe account to receive payments via Apple Pay.
121
-
122
- It's possible to pay with Apple Pay and Google Pay directly from the cart
123
- page. The functionality is self-contained in the view partial
124
- `_stripe_payment_request_button.html.erb`. In order to use it, you need
125
- to render that partial in the `orders#edit` frontend page, and pass it the
126
- payment method configured for Stripe via the local variable
127
- `cart_checkout_payment_method`:
137
+ That will register a new `:"stripe.charge.succeeded"` event in the [Solidus
138
+ bus](https://guides.solidus.io/customization/subscribing-to-events). The
139
+ Solidus event will be published whenever a matching incoming webhook event is
140
+ received. You can subscribe to it [as usual](https://guides.solidus.io/customization/subscribing-to-events):
128
141
 
129
142
  ```ruby
130
- <%= render 'stripe_payment_request_button', cart_checkout_payment_method: Spree::PaymentMethod::StripeCreditCard.first %>
131
- ```
143
+ # app/subscribers/update_account_balance_subscriber.rb
144
+ class UpdateAccountBalanceSubscriber
145
+ include Omnes::Subscriber
132
146
 
133
- Of course, the rules listed in the Payment Intents section (adding the stripe
134
- country config value, for example) apply also for this feature.
135
-
136
- Customizing the V3 API javascript
137
- ---------------------------------
138
-
139
- Stripe V3 JS code is now managed via Sprockets. If you need to customize the JS,
140
- you can simply override or/and add new methods to the relevant object prototype.
141
- Make sure you load your customizations after Stripe initalization code from
142
- `spree/frontend/solidus_stripe`.
143
-
144
- For example, the following code adds a callback method in order to print a debug
145
- message on the console:
146
-
147
- ```js
148
- SolidusStripe.CartPageCheckout.prototype.onPrButtonMounted = function(id, result) {
149
- if (result) {
150
- $('#' + id).parent().show();
151
- console.log('Payment request button is now mounted on element with id #' + id);
152
- } else {
153
- console.log('Payment request button failed initalization.');
154
- }
155
- }
156
- ```
147
+ handle :"stripe.charge.succeeded", with: :call
157
148
 
158
- Customizing Stripe Elements
159
- -----------------------
160
-
161
- ### Styling input fields
162
-
163
- The default style this gem provides for Stripe Elements input fields is defined in `SolidusStripe.Elements.prototype.baseStyle`. You can override this method to return your own custom style (make sure it returns a valid [Stripe Style](https://stripe.com/docs/js/appendix/style)
164
- object):
165
-
166
- ```js
167
- SolidusStripe.Elements.prototype.baseStyle = function () {
168
- return {
169
- base: {
170
- iconColor: '#c4f0ff',
171
- color: '#fff',
172
- fontWeight: 500,
173
- fontFamily: 'Roboto, Open Sans, Segoe UI, sans-serif',
174
- fontSize: '16px',
175
- fontSmoothing: 'antialiased',
176
- ':-webkit-autofill': {
177
- color: '#fce883',
178
- },
179
- '::placeholder': {
180
- color: '#87BBFD',
181
- },
182
- },
183
- invalid: {
184
- iconColor: '#FFC7EE',
185
- color: '#FFC7EE',
186
- }
187
- }
188
- };
189
- ```
149
+ def call(event)
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
190
155
 
191
- You can also style your element containers directly by using CSS rules like this:
156
+ Rails.logger.info "Charge succeeded: #{event.data.to_json}"
157
+ end
158
+ end
192
159
 
193
- ```css
194
- .StripeElement {
195
- border: 1px solid transparent;
196
- }
160
+ # config/initializers/solidus_stripe.rb
161
+ # ...
162
+ Rails.application.config.to_prepare do
163
+ UpdateAccountBalanceSubscriber.new.subscribe_to(Spree::Bus)
164
+ end
165
+ ```
166
+
167
+ The passed event object is a thin wrapper around the [Stripe
168
+ event](https://www.rubydoc.info/gems/stripe/Stripe/Event) and the associated
169
+ Solidus Stripe payment method. It will delegate all unknown methods to the
170
+ underlying stripe event object. It can also be used in async [
171
+ adapters](https://github.com/nebulab/omnes#adapters), which is recommended as
172
+ otherwise the response to Stripe will be delayed until subscribers are done.
197
173
 
198
- .StripeElement--focus {
199
- box-shadow: 0 1px 3px 0 #cfd7df;
200
- }
174
+ #### Configuring the webhook signature tolerance
201
175
 
202
- .StripeElement--invalid {
203
- border-color: #fa755a;
204
- }
176
+ You can also configure the signature verification tolerance in seconds (it
177
+ defaults to the [same value as Stripe
178
+ default](https://stripe.com/docs/webhooks/signatures#replay-attacks)):
205
179
 
206
- .StripeElement--webkit-autofill {
207
- background-color: #fefde5 !important;
208
- }
180
+ ```ruby
181
+ # config/initializers/solidus_stripe.rb
182
+ SolidusStripe.configure do |config|
183
+ config.webhook_signature_tolerance = 150
184
+ end
209
185
  ```
210
186
 
211
- ### Customizing individual input fields
187
+ ### Customizing the list of available Stripe payment methods
212
188
 
213
- If you want to customize individual input fields, you can override these methods
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.
214
190
 
215
- * `SolidusStripe.Elements.prototype.cardNumberElementOptions`
216
- * `SolidusStripe.Elements.prototype.cardExpiryElementOptions`
217
- * `SolidusStripe.Elements.prototype.cardCvcElementOptions`
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.
218
192
 
219
- and return a valid [options object](https://stripe.com/docs/js/elements_object/create_element?type=cardNumber) for the corresponding field type. For example, this code sets a custom placeholder and enables the credit card icon for the card number field
193
+ ### Non-card payment methods and "auto_capture"
220
194
 
221
- ```js
222
- SolidusStripe.Elements.prototype.cardNumberElementOptions = function () {
223
- return {
224
- style: this.baseStyle(),
225
- showIcon: true,
226
- placeholder: "I'm a custom placeholder!"
227
- }
228
- }
229
- ```
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.
230
196
 
231
- ### Passing options to the Stripe Elements instance
197
+ ## Implementation
232
198
 
233
- By overriding the `SolidusStripe.Payment.prototype.elementsBaseOptions` method and returning a [valid options object](https://stripe.com/docs/js/elements_object/create), you can pass custom options to the Stripe Elements instance.
199
+ ### Payment state-machine vs. PaymentIntent statuses
234
200
 
235
- Note that in order to use web fonts with Stripe Elements, you must specify the fonts when creating the Stripe Elements instance. Here's an example specifying a custom web font and locale:
201
+ When compared to the Payment state machine, Stripe payment intents have different set of states and transitions.
202
+ The most important difference is that on Stripe a failure is not a final state, rather just a way to start over.
236
203
 
237
- ```js
238
- SolidusStripe.Payment.prototype.elementsBaseOptions = function () {
239
- return {
240
- locale: 'de',
241
- fonts: [
242
- {
243
- cssSrc: 'https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,600'
244
- }
245
- ]
246
- };
247
- };
248
- ```
204
+ In order to map these concepts SolidusStripe will match states in a slightly unexpected way, as shown below.
205
+
206
+ | Stripe PaymentIntent Status | Solidus Payment State |
207
+ | --------------------------- | --------------------- |
208
+ | requires_payment_method | checkout |
209
+ | requires_action | checkout |
210
+ | processing | processing |
211
+ | requires_confirmation | checkout |
212
+ | requires_capture | pending |
213
+ | succeeded | completed |
214
+
215
+ Reference:
216
+
217
+ - https://stripe.com/docs/payments/intents?intent=payment
218
+ - https://github.com/solidusio/solidus/blob/master/core/lib/spree/core/state_machines/payment.rb
249
219
 
250
- ## Migrating from solidus_gateway
220
+ ### Deferred payment confirmation
251
221
 
252
- If you were previously using `solidus_gateway` gem you might want to
253
- check out our [Wiki page](https://github.com/solidusio/solidus_stripe/wiki/Migrating-from-solidus_gateway)
254
- that describes how to handle this migration.
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.
255
223
 
256
224
  ## Development
257
225
 
226
+ Retrieve your API Key and Publishable Key from your [Stripe testing dashboard](https://stripe.com/docs/testing). You can
227
+ get your webhook signing secret executing the `stripe listen` command.
228
+
229
+ Set `SOLIDUS_STRIPE_API_KEY`, `SOLIDUS_STRIPE_PUBLISHABLE_KEY` and `SOLIDUS_STRIPE_WEBHOOK_SIGNING_SECRET` environment
230
+ variables (e.g. via `direnv`), this will trigger the default initializer to create a static preference for SolidusStripe.
231
+
232
+ Run `bin/dev` to start both the sandbox rail server and the file watcher through Foreman. That will update the sandbox whenever
233
+ a file is changed. When using `bin/dev` you can safely add `debugger` statements, even if Foreman won't provide a TTY, by connecting
234
+ to the debugger session through `rdbg --attach` from another terminal.
235
+
236
+ Visit `/admin/payments` and create a new Stripe payment using the static preferences.
237
+
238
+ See the [Webhooks section](#webhooks) to learn how to configure Stripe webhooks.
239
+
258
240
  ### Testing the extension
259
241
 
260
242
  First bundle your dependencies, then run `bin/rake`. `bin/rake` will default to building the dummy
@@ -301,21 +283,11 @@ $ bin/rails server
301
283
  Use Ctrl-C to stop
302
284
  ```
303
285
 
304
- ### Updating the changelog
305
-
306
- Before and after releases the changelog should be updated to reflect the up-to-date status of
307
- the project:
308
-
309
- ```shell
310
- bin/rake changelog
311
- git add CHANGELOG.md
312
- git commit -m "Update the changelog"
313
- ```
314
-
315
286
  ### Releasing new versions
316
287
 
317
288
  Please refer to the dedicated [page](https://github.com/solidusio/solidus/wiki/How-to-release-extensions) on Solidus wiki.
318
289
 
319
290
  ## License
291
+
320
292
  Copyright (c) 2014 Spree Commerce Inc., released under the New BSD License
321
- Copyright (c) 2021 Solidus Team, released under the New BSD License
293
+ Copyright (c) 2021 Solidus Team, released under the New BSD License.
data/Rakefile CHANGED
@@ -1,9 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Don't build a dummy app with solidus_bolt enabled
4
- ENV['SKIP_SOLIDUS_BOLT'] = 'true'
3
+ require 'bundler/gem_tasks'
5
4
 
6
- require 'solidus_dev_support/rake_tasks'
7
- SolidusDevSupport::RakeTasks.install
8
-
9
- task default: 'extension:specs'
5
+ task :default do
6
+ require 'bundler'
7
+ Bundler.with_unbundled_env do
8
+ sh 'bin/rspec'
9
+ end
10
+ end
@@ -0,0 +1,2 @@
1
+ // Placeholder manifest file.
2
+ // the installer will append this file to the app vendored assets here: vendor/assets/javascripts/spree/backend/all.js'
@@ -0,0 +1,4 @@
1
+ /*
2
+ Placeholder manifest file.
3
+ the installer will append this file to the app vendored assets here: 'vendor/assets/stylesheets/spree/backend/all.css'
4
+ */
@@ -1,66 +1,50 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module SolidusStripe
4
- class IntentsController < Spree::BaseController
5
- include Spree::Core::ControllerHelpers::Order
3
+ require 'stripe'
6
4
 
7
- def create_intent
8
- @intent = create_payment_intent
9
- generate_payment_response
10
- end
5
+ class SolidusStripe::IntentsController < Spree::BaseController
6
+ include Spree::Core::ControllerHelpers::Order
11
7
 
12
- def create_payment
13
- create_payment_service = SolidusStripe::CreateIntentsPaymentService.new(
14
- params[:stripe_payment_intent_id],
15
- stripe,
16
- self
17
- )
8
+ before_action :load_payment_method
18
9
 
19
- if create_payment_service.call
20
- render json: { success: true }
21
- else
22
- render json: { error: "Could not create payment" }, status: :internal_server_error
23
- end
10
+ def after_confirmation
11
+ unless params[:payment_intent]
12
+ return head :unprocessable_entity
24
13
  end
25
14
 
26
- private
27
-
28
- def stripe
29
- @stripe ||= Spree::PaymentMethod::StripeCreditCard.find(params[:spree_payment_method_id])
15
+ unless current_order.confirm?
16
+ redirect_to main_app.checkout_state_path(current_order.state)
17
+ return
30
18
  end
31
19
 
32
- def generate_payment_response
33
- response = @intent.params
34
- # Note that if your API version is before 2019-02-11, 'requires_action'
35
- # appears as 'requires_source_action'.
36
- if %w[requires_source_action requires_action].include?(response['status']) && response['next_action']['type'] == 'use_stripe_sdk'
37
- render json: {
38
- requires_action: true,
39
- stripe_payment_intent_client_secret: response['client_secret']
40
- }
41
- elsif response['status'] == 'requires_capture'
42
- render json: {
43
- success: true,
44
- requires_capture: true,
45
- stripe_payment_intent_id: response['id']
46
- }
47
- else
48
- render json: { error: response['error']['message'] }, status: :internal_server_error
49
- end
50
- end
20
+ intent = SolidusStripe::PaymentIntent.find_by!(
21
+ payment_method: @payment_method,
22
+ order: current_order,
23
+ stripe_intent_id: params[:payment_intent],
24
+ )
25
+
26
+ if intent.process_payment
27
+ flash.notice = t('spree.order_processed_successfully')
51
28
 
52
- def create_payment_intent
53
- stripe.create_intent(
54
- (current_order.total * 100).to_i,
55
- params[:stripe_payment_method_id],
56
- description: "Solidus Order ID: #{current_order.number} (pending)",
57
- currency: current_order.currency,
58
- confirmation_method: 'automatic',
59
- capture_method: 'manual',
60
- confirm: true,
61
- setup_future_usage: 'off_session',
62
- metadata: { order_id: current_order.id }
29
+ flash['order_completed'] = true
30
+
31
+ redirect_to(
32
+ spree_current_user ?
33
+ main_app.order_path(current_order) :
34
+ main_app.token_order_path(current_order, current_order.guest_token)
63
35
  )
36
+ else
37
+ flash[:error] = params[:error_message] || t('spree.payment_processing_failed')
38
+ redirect_to(main_app.checkout_state_path(:payment))
64
39
  end
65
40
  end
41
+
42
+ private
43
+
44
+ def load_payment_method
45
+ @payment_method = current_order(create_order_if_necessary: true)
46
+ .available_payment_methods
47
+ .merge(SolidusStripe::PaymentMethod.with_slug(params[:slug]))
48
+ .first!
49
+ end
66
50
  end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "solidus_stripe/webhook/event"
4
+ require "stripe"
5
+
6
+ module SolidusStripe
7
+ class WebhooksController < Spree::BaseController
8
+ SIGNATURE_HEADER = "HTTP_STRIPE_SIGNATURE"
9
+
10
+ skip_before_action :verify_authenticity_token, only: :create
11
+
12
+ respond_to :json
13
+
14
+ def create
15
+ event = Webhook::Event.from_request(payload: request.body.read, signature_header: signature_header,
16
+ slug: params[:slug])
17
+ return head(:bad_request) unless event
18
+
19
+ Spree::Bus.publish(event) && head(:ok)
20
+ end
21
+
22
+ private
23
+
24
+ def signature_header
25
+ request.headers[SIGNATURE_HEADER]
26
+ end
27
+ end
28
+ end