solidus_stripe 4.4.1 → 5.0.0.alpha.1

Sign up to get free protection for your applications and to get access to all the features.
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
data/README.md CHANGED
@@ -1,260 +1,200 @@
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
+
1
17
  # Solidus Stripe
2
18
 
3
19
  [![CircleCI](https://circleci.com/gh/solidusio/solidus_stripe.svg?style=shield)](https://circleci.com/gh/solidusio/solidus_stripe)
4
20
  [![codecov](https://codecov.io/gh/solidusio/solidus_stripe/branch/master/graph/badge.svg)](https://codecov.io/gh/solidusio/solidus_stripe)
5
21
 
6
- Stripe Payment Method for Solidus. It works as a wrapper for the ActiveMerchant Stripe gateway.
22
+ <!-- Explain what your extension does. -->
7
23
 
8
24
  ## Installation
9
25
 
26
+ Add solidus_stripe to your Gemfile:
10
27
 
11
- Run from the command line:
28
+ ```ruby
29
+ gem 'solidus_stripe'
30
+ ```
31
+
32
+ Bundle your dependencies and run the installation generator:
12
33
 
13
34
  ```shell
14
- bundle add solidus_stripe
15
- bundle exec rails g solidus_stripe:install
35
+ bin/rails generate solidus_stripe:install
16
36
  ```
17
37
 
18
- Usage
19
- -----
38
+ ### Webhooks
20
39
 
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.
40
+ This library makes use of some [Stripe webhooks](https://stripe.com/docs/webhooks).
25
41
 
26
- **Configure via database configuration**
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:
27
45
 
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.
31
-
32
- **Configure via static configuration**
46
+ ```ruby
47
+ SolidusStripe::PaymentMethod.last.slug
48
+ # "365a8435cd11300e87de864c149516e0"
49
+ ```
33
50
 
34
- If you want to store your credentials into your codebase or use ENV
35
- variables you can create the following static configuration:
51
+ For the above example, and if you mounted the `SolidusStripe::Engine` routes on
52
+ the default scope, the webhook endpoint would look like:
36
53
 
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
54
+ ```
55
+ /solidus_stripe/webhooks/365a8435cd11300e87de864c149516e0
50
56
  ```
51
57
 
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.
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.
56
61
 
62
+ Before going to production, you'll need to [register the webhook endpoint with
63
+ Stripe](https://stripe.com/docs/webhooks/go-live), and make sure to subscribe
64
+ 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).
57
66
 
58
- Using Stripe Payment Intents API
59
- --------------------------------
67
+ On development, you can
68
+ [test webhooks by using Stripe CLI](https://stripe.com/docs/webhooks/test).
60
69
 
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.
70
+ ## Usage
63
71
 
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.
72
+ ### Showing reusable sources in the checkout
69
73
 
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.
74
+ When saving stripe payment methods for future usage the checkout requires
75
+ a partial for each supported payment method type.
73
76
 
74
- The following configuration will use both Payment Intents and the
75
- payment request button API on the store payment page:
77
+ For the full list of types see: https://stripe.com/docs/api/payment_methods/object#payment_method_object-type.
76
78
 
79
+ The extension will only install a partial for the `card` type, located in `app/views/checkouts/existing_payment/stripe/_card.html.erb`,
80
+ and fall back to a `default` partial otherwise (see `app/views/checkouts/existing_payment/stripe/_default.html.erb`).
77
81
 
78
- ```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
- )
91
- end
92
- ```
82
+ As an example, in order to show a wallet source connected to a
83
+ [SEPA Debit payment method](https://stripe.com/docs/api/payment_methods/object#payment_method_object-sepa_debit)
84
+ the following partial should be added:
93
85
 
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`:
86
+ `app/views/checkouts/existing_payment/stripe/_sepa_debit.html.erb`
128
87
 
129
- ```ruby
130
- <%= render 'stripe_payment_request_button', cart_checkout_payment_method: Spree::PaymentMethod::StripeCreditCard.first %>
88
+ ```erb
89
+ <% sepa_debit = stripe_payment_method.sepa_debit %>
90
+ 🏦 <%= sepa_debit.bank_code %> / <%= sepa_debit.branch_code %><br>
91
+ IBAN: **** **** **** **** **** <%= sepa_debit.last4 %>
131
92
  ```
132
93
 
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
- ```
94
+ ### Showing reusable sources in the admin interface
157
95
 
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
- ```
96
+ Refer to the previous section for information on how to set up a new payment method type.
97
+ However, it's important to note that if you have to display a wallet source connected to a
98
+ Stripe Payment Method other than "card" on the admin interface, you must include the partial in:
190
99
 
191
- You can also style your element containers directly by using CSS rules like this:
100
+ `app/views/spree/admin/payments/source_forms/existing_payment/stripe/`
192
101
 
193
- ```css
194
- .StripeElement {
195
- border: 1px solid transparent;
196
- }
102
+ ### Custom webhooks
197
103
 
198
- .StripeElement--focus {
199
- box-shadow: 0 1px 3px 0 #cfd7df;
200
- }
104
+ You can also use [Stripe webhooks](https://stripe.com/docs/webhooks) to trigger
105
+ custom actions in your application.
201
106
 
202
- .StripeElement--invalid {
203
- border-color: #fa755a;
204
- }
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:
205
109
 
206
- .StripeElement--webkit-autofill {
207
- background-color: #fefde5 !important;
208
- }
110
+ ```ruby
111
+ # config/initializers/solidus_stripe.rb
112
+ SolidusStripe.configure do |config|
113
+ config.webhook_events = %i[charge.succeeded]
114
+ end
209
115
  ```
210
116
 
211
- ### Customizing individual input fields
117
+ That will register a new `:"stripe.charge.succeeded"` event in the [Solidus
118
+ bus](https://guides.solidus.io/customization/subscribing-to-events). The
119
+ Solidus event will be published whenever a matching incoming webhook event is
120
+ received. You can subscribe to it as regular:
212
121
 
213
- If you want to customize individual input fields, you can override these methods
122
+ ```ruby
123
+ # app/subscribers/update_account_balance_subscriber.rb
124
+ class UpdateAccountBalanceSubscriber
125
+ include Omnes::Subscriber
214
126
 
215
- * `SolidusStripe.Elements.prototype.cardNumberElementOptions`
216
- * `SolidusStripe.Elements.prototype.cardExpiryElementOptions`
217
- * `SolidusStripe.Elements.prototype.cardCvcElementOptions`
127
+ handle :"stripe.charge.succeeded", with: :call
218
128
 
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
129
+ def call(event)
130
+ # ...
131
+ end
132
+ end
220
133
 
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
- }
134
+ # config/initializers/solidus_stripe.rb
135
+ # ...
136
+ Rails.application.config.to_prepare do
137
+ UpdateAccountBalanceSubscriber.new.subscribe_to(Spree::Bus)
138
+ end
229
139
  ```
230
140
 
231
- ### Passing options to the Stripe Elements instance
232
-
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.
141
+ The passed event object is a thin wrapper around the [Stripe
142
+ event](https://www.rubydoc.info/gems/stripe/Stripe/Event) and the associated
143
+ Solidus Stripe payment method. It will delegate all unknown methods to the
144
+ underlying stripe event object. It can also be used in async [
145
+ adapters](https://github.com/nebulab/omnes#adapters), which is recommended as
146
+ otherwise the response to Stripe will be delayed until subscribers are done.
234
147
 
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:
148
+ You can also configure the signature verification tolerance in seconds (it
149
+ defaults to the [same value as Stripe
150
+ default](https://stripe.com/docs/webhooks/signatures#replay-attacks)):
236
151
 
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
- };
152
+ ```ruby
153
+ # config/initializers/solidus_stripe.rb
154
+ SolidusStripe.configure do |config|
155
+ config.webhook_signature_tolerance = 150
156
+ end
248
157
  ```
249
158
 
250
- ## Migrating from solidus_gateway
159
+ ## Implementation
160
+
161
+ ### Payment state-machine vs. PaymentIntent statuses
162
+
163
+ When compared to the Payment state machine, Stripe payment intents have different set of states and transitions.
164
+ The most important difference is that on Stripe a failure is not a final state, rather just a way to start over.
165
+
166
+ In order to map these concepts SolidusStripe will match states in a slightly unexpected way, as shown below.
167
+
168
+ | Stripe PaymentIntent Status | Solidus Payment State |
169
+ | --------------------------- | --------------------- |
170
+ | requires_payment_method | checkout |
171
+ | requires_action | checkout |
172
+ | processing | checkout |
173
+ | requires_confirmation | checkout |
174
+ | requires_capture | pending |
175
+ | succeeded | completed |
176
+
177
+ Reference:
251
178
 
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.
179
+ - https://stripe.com/docs/payments/intents?intent=payment
180
+ - https://github.com/solidusio/solidus/blob/master/core/lib/spree/core/state_machines/payment.rb
255
181
 
256
182
  ## Development
257
183
 
184
+ Retrieve your API Key and Publishable Key from your [Stripe testing dashboard](https://stripe.com/docs/testing). You can
185
+ get your webhook signing secret executing the `stripe listen` command.
186
+
187
+ Set `SOLIDUS_STRIPE_API_KEY`, `SOLIDUS_STRIPE_PUBLISHABLE_KEY` and `SOLIDUS_STRIPE_WEBHOOK_SIGNING_SECRET` environment
188
+ variables (e.g. via `direnv`), this will trigger the default initializer to create a static preference for SolidusStripe.
189
+
190
+ Run `bin/dev` to start both the sandbox rail server and the file watcher through Foreman. That will update the sandbox whenever
191
+ a file is changed. When using `bin/dev` you can safely add `debugger` statements, even if Foreman won't provide a TTY, by connecting
192
+ to the debugger session through `rdbg --attach` from another terminal.
193
+
194
+ Visit `/admin/payments` and create a new Stripe payment using the static preferences.
195
+
196
+ See the [Webhooks section](#webhooks) to learn how to configure Stripe webhooks.
197
+
258
198
  ### Testing the extension
259
199
 
260
200
  First bundle your dependencies, then run `bin/rake`. `bin/rake` will default to building the dummy
@@ -301,21 +241,11 @@ $ bin/rails server
301
241
  Use Ctrl-C to stop
302
242
  ```
303
243
 
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
244
  ### Releasing new versions
316
245
 
317
246
  Please refer to the dedicated [page](https://github.com/solidusio/solidus/wiki/How-to-release-extensions) on Solidus wiki.
318
247
 
319
248
  ## License
249
+
320
250
  Copyright (c) 2014 Spree Commerce Inc., released under the New BSD License
321
- Copyright (c) 2021 Solidus Team, released under the New BSD License
251
+ Copyright (c) 2021 Solidus Team, released under the New BSD License.
data/Rakefile CHANGED
@@ -1,49 +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'
5
-
6
- require 'solidus_dev_support/rake_tasks'
7
- SolidusDevSupport::RakeTasks.install
8
-
9
3
  require 'bundler/gem_tasks'
10
4
 
11
- # Override the default dummy app generation task to
12
- # make it compatible with all the supported Solidus versions.
13
- Rake::Task['extension:test_app'].clear
14
- task 'extension:test_app' do # rubocop:disable Rails/RakeEnvironment
15
- Spree::DummyGeneratorHelper.inject_extension_requirements = true
16
-
17
- require 'solidus_stripe'
18
-
19
- Rails.env = ENV["RAILS_ENV"] = 'test'
20
-
21
- Spree::DummyGenerator.start ["--lib-name=solidus_stripe"]
22
-
23
- # While the dummy app is generated the current directory
24
- # within ruby is changed to that of the dummy app.
25
- sh({
26
- 'FRONTEND' => ENV['FRONTEND'] || "#{__dir__}/spec/solidus_frontend_app_template.rb",
27
- }, [
28
- 'bin/rails',
29
- 'generate',
30
- 'solidus:install',
31
- Dir.pwd, # use the current dir as Rails.root
32
- "--auto-accept",
33
- "--authentication=none",
34
- "--payment-method=none",
35
- "--migrate=false",
36
- "--seed=false",
37
- "--sample=false",
38
- "--user-class=Spree::LegacyUser",
39
- ].shelljoin)
40
-
41
- puts "Setting up dummy database..."
42
- sh "bin/rails db:environment:set RAILS_ENV=test"
43
- sh "bin/rails db:drop db:create db:migrate VERBOSE=false RAILS_ENV=test"
44
-
45
- puts 'Running extension installation generator...'
46
- sh "bin/rails generate solidus_stripe:install --auto-run-migrations"
5
+ task :default do
6
+ require 'bundler'
7
+ Bundler.with_unbundled_env do
8
+ sh 'bin/rspec'
9
+ end
47
10
  end
48
-
49
- task default: ['extension:specs']
@@ -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
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusStripe::LogEntries
4
+ extend ActiveSupport::Concern
5
+ extend self
6
+
7
+ # Builds an ActiveMerchant::Billing::Response
8
+ #
9
+ # @option [true,false] :success
10
+ # @option [String] :message
11
+ # @option [String] :response_code
12
+ # @option [#to_json] :data
13
+ #
14
+ # @return [return type] return description
15
+ def build_payment_log(success:, message:, response_code: nil, data: nil)
16
+ ActiveMerchant::Billing::Response.new(
17
+ success,
18
+ message,
19
+ { 'data' => data.to_json },
20
+ { authorization: response_code },
21
+ )
22
+ end
23
+
24
+ def payment_log(payment, **options)
25
+ payment.log_entries.create!(details: YAML.safe_dump(
26
+ build_payment_log(**options),
27
+ permitted_classes: Spree::LogEntry.permitted_classes,
28
+ aliases: Spree::Config.log_entry_allow_aliases,
29
+ ))
30
+ end
31
+ end