solidus_stripe 4.3.0 → 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 +98 -23
  3. data/.github/stale.yml +1 -17
  4. data/.gitignore +3 -0
  5. data/.rubocop.yml +94 -2
  6. data/.yardopts +1 -0
  7. data/CHANGELOG.md +1 -231
  8. data/Gemfile +10 -25
  9. data/LICENSE +2 -2
  10. data/Procfile.dev +3 -0
  11. data/README.md +155 -223
  12. data/Rakefile +7 -3
  13. data/app/assets/javascripts/spree/backend/solidus_stripe.js +2 -0
  14. data/app/assets/stylesheets/spree/backend/solidus_stripe.css +4 -0
  15. data/app/controllers/solidus_stripe/intents_controller.rb +36 -52
  16. data/app/controllers/solidus_stripe/webhooks_controller.rb +28 -0
  17. data/app/models/concerns/solidus_stripe/log_entries.rb +31 -0
  18. data/app/models/solidus_stripe/customer.rb +21 -0
  19. data/app/models/solidus_stripe/gateway.rb +231 -0
  20. data/app/models/solidus_stripe/payment_intent.rb +111 -0
  21. data/app/models/solidus_stripe/payment_method.rb +106 -0
  22. data/app/models/solidus_stripe/payment_source.rb +31 -0
  23. data/app/models/solidus_stripe/slug_entry.rb +20 -0
  24. data/app/models/solidus_stripe.rb +7 -0
  25. data/app/subscribers/solidus_stripe/webhook/charge_subscriber.rb +28 -0
  26. data/app/subscribers/solidus_stripe/webhook/payment_intent_subscriber.rb +112 -0
  27. data/app/views/spree/admin/payments/source_forms/_stripe.html.erb +29 -0
  28. data/app/views/spree/admin/payments/source_forms/existing_payment/_stripe.html.erb +14 -0
  29. data/app/views/spree/admin/payments/source_forms/existing_payment/stripe/_card.html.erb +8 -0
  30. data/app/views/spree/admin/payments/source_forms/existing_payment/stripe/_default.html.erb +7 -0
  31. data/app/views/spree/admin/payments/source_views/_stripe.html.erb +15 -0
  32. data/app/views/spree/api/payments/source_views/_stripe.json.jbuilder +8 -0
  33. data/bin/dev +13 -0
  34. data/bin/dummy-app +29 -0
  35. data/bin/rails +38 -3
  36. data/bin/rails-dummy-app +3 -0
  37. data/bin/rails-engine +1 -11
  38. data/bin/rails-new +55 -0
  39. data/bin/rails-sandbox +1 -14
  40. data/bin/rspec +10 -0
  41. data/bin/sandbox +12 -74
  42. data/bin/setup +1 -0
  43. data/bin/update-migrations +56 -0
  44. data/codecov.yml +12 -0
  45. data/config/locales/en.yml +16 -1
  46. data/config/routes.rb +5 -11
  47. data/coverage.rb +42 -0
  48. data/db/migrate/20230109183332_create_solidus_stripe_payment_sources.rb +10 -0
  49. data/db/migrate/20230303154931_create_solidus_stripe_setup_intent.rb +10 -0
  50. data/db/migrate/20230306105520_create_solidus_stripe_payment_intents.rb +10 -0
  51. data/db/migrate/20230308122414_create_solidus_stripe_webhook_endpoints.rb +10 -0
  52. data/db/migrate/20230310152615_add_payment_method_reference_to_stripe_intents.rb +6 -0
  53. data/db/migrate/20230310171444_normalize_stripe_intent_id_attributes.rb +6 -0
  54. data/db/migrate/20230313150008_create_solidus_stripe_customers.rb +16 -0
  55. data/db/migrate/20230323154931_drop_solidus_stripe_setup_intent.rb +13 -0
  56. data/db/migrate/20230403094916_rename_webhook_endpoint_to_payment_method_slug_entries.rb +5 -0
  57. data/db/seeds.rb +6 -24
  58. data/lib/generators/solidus_stripe/install/install_generator.rb +121 -14
  59. data/lib/generators/solidus_stripe/install/templates/app/assets/stylesheets/spree/frontend/solidus_stripe.css +13 -0
  60. data/lib/generators/solidus_stripe/install/templates/app/javascript/controllers/solidus_stripe_confirm_controller.js +39 -0
  61. data/lib/generators/solidus_stripe/install/templates/app/javascript/controllers/solidus_stripe_payment_controller.js +89 -0
  62. data/lib/generators/solidus_stripe/install/templates/app/views/checkouts/existing_payment/_stripe.html.erb +16 -0
  63. data/lib/generators/solidus_stripe/install/templates/app/views/checkouts/existing_payment/stripe/_card.html.erb +8 -0
  64. data/lib/generators/solidus_stripe/install/templates/app/views/checkouts/existing_payment/stripe/_default.html.erb +7 -0
  65. data/lib/generators/solidus_stripe/install/templates/app/views/checkouts/payment/_stripe.html.erb +39 -0
  66. data/lib/generators/solidus_stripe/install/templates/app/views/orders/payment_info/_stripe.html.erb +20 -0
  67. data/lib/generators/solidus_stripe/install/templates/config/initializers/solidus_stripe.rb +31 -0
  68. data/lib/solidus_stripe/configuration.rb +24 -3
  69. data/lib/solidus_stripe/engine.rb +19 -6
  70. data/lib/solidus_stripe/money_to_stripe_amount_converter.rb +109 -0
  71. data/lib/solidus_stripe/refunds_synchronizer.rb +96 -0
  72. data/lib/solidus_stripe/seeds.rb +19 -0
  73. data/lib/solidus_stripe/testing_support/factories.rb +153 -0
  74. data/lib/solidus_stripe/version.rb +1 -1
  75. data/lib/solidus_stripe/webhook/event.rb +90 -0
  76. data/lib/solidus_stripe.rb +0 -2
  77. data/solidus_stripe.gemspec +29 -5
  78. data/spec/lib/solidus_stripe/configuration_spec.rb +21 -0
  79. data/spec/lib/solidus_stripe/money_to_stripe_amount_converter_spec.rb +133 -0
  80. data/spec/lib/solidus_stripe/refunds_synchronizer_spec.rb +238 -0
  81. data/spec/lib/solidus_stripe/seeds_spec.rb +43 -0
  82. data/spec/lib/solidus_stripe/webhook/event_spec.rb +134 -0
  83. data/spec/models/concerns/solidus_stripe/log_entries_spec.rb +54 -0
  84. data/spec/models/solidus_stripe/customer_spec.rb +47 -0
  85. data/spec/models/solidus_stripe/gateway_spec.rb +283 -0
  86. data/spec/models/solidus_stripe/payment_intent_spec.rb +17 -0
  87. data/spec/models/solidus_stripe/payment_method_spec.rb +137 -0
  88. data/spec/models/solidus_stripe/payment_source_spec.rb +25 -0
  89. data/spec/requests/solidus_stripe/intents_controller_spec.rb +29 -0
  90. data/spec/requests/solidus_stripe/webhooks_controller/charge/refunded_spec.rb +31 -0
  91. data/spec/requests/solidus_stripe/webhooks_controller/payment_intent/canceled_spec.rb +23 -0
  92. data/spec/requests/solidus_stripe/webhooks_controller/payment_intent/payment_failed_spec.rb +23 -0
  93. data/spec/requests/solidus_stripe/webhooks_controller/payment_intent/succeeded_spec.rb +29 -0
  94. data/spec/requests/solidus_stripe/webhooks_controller_spec.rb +52 -0
  95. data/spec/solidus_stripe_spec_helper.rb +10 -0
  96. data/spec/subscribers/solidus_stripe/webhook/charge_subscriber_spec.rb +33 -0
  97. data/spec/subscribers/solidus_stripe/webhook/payment_intent_subscriber_spec.rb +297 -0
  98. data/spec/support/solidus_stripe/backend_test_helper.rb +210 -0
  99. data/spec/support/solidus_stripe/checkout_test_helper.rb +339 -0
  100. data/spec/support/solidus_stripe/factories.rb +5 -0
  101. data/spec/support/solidus_stripe/webhook/data_fixtures.rb +106 -0
  102. data/spec/support/solidus_stripe/webhook/event_with_context_factory.rb +82 -0
  103. data/spec/support/solidus_stripe/webhook/request_helper.rb +32 -0
  104. data/spec/system/backend/solidus_stripe/orders/payments_spec.rb +119 -0
  105. data/spec/system/frontend/.keep +0 -0
  106. data/spec/system/frontend/solidus_stripe/checkout_spec.rb +187 -0
  107. data/tmp/.keep +0 -0
  108. metadata +210 -69
  109. data/.travis.yml +0 -28
  110. data/app/assets/javascripts/spree/frontend/solidus_stripe/stripe-cart-page-checkout.js +0 -122
  111. data/app/assets/javascripts/spree/frontend/solidus_stripe/stripe-elements.js +0 -148
  112. data/app/assets/javascripts/spree/frontend/solidus_stripe/stripe-init.js +0 -20
  113. data/app/assets/javascripts/spree/frontend/solidus_stripe/stripe-payment-intents.js +0 -84
  114. data/app/assets/javascripts/spree/frontend/solidus_stripe/stripe-payment-request-button-shared.js +0 -160
  115. data/app/assets/javascripts/spree/frontend/solidus_stripe/stripe-payment.js +0 -16
  116. data/app/assets/javascripts/spree/frontend/solidus_stripe.js +0 -6
  117. data/app/controllers/solidus_stripe/payment_request_controller.rb +0 -52
  118. data/app/controllers/spree/stripe_controller.rb +0 -13
  119. data/app/decorators/models/spree/order_update_attributes_decorator.rb +0 -39
  120. data/app/decorators/models/spree/payment_decorator.rb +0 -11
  121. data/app/decorators/models/spree/refund_decorator.rb +0 -9
  122. data/app/models/solidus_stripe/address_from_params_service.rb +0 -72
  123. data/app/models/solidus_stripe/create_intents_payment_service.rb +0 -114
  124. data/app/models/solidus_stripe/prepare_order_for_payment_service.rb +0 -46
  125. data/app/models/solidus_stripe/shipping_rates_service.rb +0 -46
  126. data/app/models/spree/payment_method/stripe_credit_card.rb +0 -217
  127. data/bin/r +0 -13
  128. data/bin/sandbox_rails +0 -18
  129. data/db/migrate/20181010123508_update_stripe_payment_method_type_to_credit_card.rb +0 -21
  130. data/lib/assets/stylesheets/spree/frontend/solidus_stripe.scss +0 -11
  131. data/lib/solidus_stripe/testing_support/card_input_helper.rb +0 -34
  132. data/lib/tasks/solidus_stripe/db/seed.rake +0 -14
  133. data/lib/views/api/spree/api/payments/source_views/_stripe.json.jbuilder +0 -3
  134. data/lib/views/backend/spree/admin/log_entries/_stripe.html.erb +0 -28
  135. data/lib/views/backend/spree/admin/payments/source_forms/_stripe.html.erb +0 -1
  136. data/lib/views/backend/spree/admin/payments/source_views/_stripe.html.erb +0 -1
  137. data/lib/views/frontend/spree/checkout/existing_payment/_stripe.html.erb +0 -1
  138. data/lib/views/frontend/spree/checkout/payment/_stripe.html.erb +0 -8
  139. data/lib/views/frontend/spree/checkout/payment/v2/_javascript.html.erb +0 -78
  140. data/lib/views/frontend/spree/checkout/payment/v3/_elements.html.erb +0 -1
  141. data/lib/views/frontend/spree/checkout/payment/v3/_form_elements.html.erb +0 -40
  142. data/lib/views/frontend/spree/checkout/payment/v3/_intents.html.erb +0 -1
  143. data/lib/views/frontend/spree/checkout/payment/v3/_stripe.html.erb +0 -2
  144. data/lib/views/frontend/spree/orders/_stripe_payment_request_button.html.erb +0 -14
  145. data/spec/features/stripe_checkout_spec.rb +0 -486
  146. data/spec/models/solidus_stripe/address_from_params_service_spec.rb +0 -87
  147. data/spec/models/solidus_stripe/create_intents_payment_service_spec.rb +0 -127
  148. data/spec/models/solidus_stripe/prepare_order_for_payment_service_spec.rb +0 -65
  149. data/spec/models/solidus_stripe/shipping_rates_service_spec.rb +0 -54
  150. data/spec/models/spree/payment_method/stripe_credit_card_spec.rb +0 -316
  151. data/spec/requests/payment_requests_spec.rb +0 -152
  152. data/spec/spec_helper.rb +0 -37
  153. data/spec/support/solidus_address_helper.rb +0 -15
data/README.md CHANGED
@@ -1,265 +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
25
+
26
+ Add solidus_stripe to your Gemfile:
9
27
 
10
- Installation
11
- ------------
28
+ ```ruby
29
+ gem 'solidus_stripe'
30
+ ```
12
31
 
13
- Run from the command line:
32
+ Bundle your dependencies and run the installation generator:
14
33
 
15
34
  ```shell
16
- bundle add solidus_stripe
17
- bundle exec rails g solidus_stripe:install
35
+ bin/rails generate solidus_stripe:install
18
36
  ```
19
37
 
20
- Usage
21
- -----
22
-
23
- Navigate to *Settings > Payments > Payment Methods* in the admin panel.
24
- You can now create a new payment method that uses Stripe by selecting
25
- `Stripe credit card` under Type in the New Payment Method form and saving.
26
- The Stripe payment method's extra fields will be now shown in the form.
38
+ ### Webhooks
27
39
 
28
- **Configure via database configuration**
40
+ This library makes use of some [Stripe webhooks](https://stripe.com/docs/webhooks).
29
41
 
30
- If you want to store your Stripe credentials in the database just
31
- fill the new fields in the form, selecting `custom` (default) in the
32
- Preference Source field.
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:
33
45
 
34
- **Configure via static configuration**
46
+ ```ruby
47
+ SolidusStripe::PaymentMethod.last.slug
48
+ # "365a8435cd11300e87de864c149516e0"
49
+ ```
35
50
 
36
- If you want to store your credentials into your codebase or use ENV
37
- 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:
38
53
 
39
- ```ruby
40
- # config/initializers/spree.rb
41
-
42
- Spree.config do |config|
43
- # ...
44
-
45
- config.static_model_preferences.add(
46
- Spree::PaymentMethod::StripeCreditCard,
47
- 'stripe_env_credentials',
48
- secret_key: ENV['STRIPE_SECRET_KEY'],
49
- publishable_key: ENV['STRIPE_PUBLISHABLE_KEY'],
50
- stripe_country: 'US',
51
- v3_elements: false,
52
- v3_intents: false
53
- )
54
- end
54
+ ```
55
+ /solidus_stripe/webhooks/365a8435cd11300e87de864c149516e0
55
56
  ```
56
57
 
57
- Once your server has been restarted, you can select in the Preference
58
- Source field a new entry called `stripe_env_credentials`. After saving,
59
- your application will start using the static configuration to process
60
- 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.
61
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).
62
66
 
63
- Using Stripe Payment Intents API
64
- --------------------------------
67
+ On development, you can
68
+ [test webhooks by using Stripe CLI](https://stripe.com/docs/webhooks/test).
65
69
 
66
- If you want to use the new SCA-ready Stripe Payment Intents API you need
67
- to change the `v3_intents` preference from the code above to true.
70
+ ## Usage
68
71
 
69
- Also, if you want to allow Apple Pay and Google Pay payments using the
70
- Stripe payment request button API, you only need to set the `stripe_country`
71
- preference, which represents the two-letter country code of your Stripe
72
- account. Conversely, if you need to disable the button you can simply remove
73
- the `stripe_country` preference.
72
+ ### Showing reusable sources in the checkout
74
73
 
75
- Please refer to Stripe official
76
- [documentation](https://stripe.com/docs/payments/payment-intents)
77
- 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.
78
76
 
79
- The following configuration will use both Payment Intents and the
80
- 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.
81
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`).
82
81
 
83
- ```ruby
84
- Spree.config do |config|
85
- # ...
86
-
87
- config.static_model_preferences.add(
88
- Spree::PaymentMethod::StripeCreditCard,
89
- 'stripe_env_credentials',
90
- secret_key: ENV['STRIPE_SECRET_KEY'],
91
- publishable_key: ENV['STRIPE_PUBLISHABLE_KEY'],
92
- stripe_country: 'US',
93
- v3_elements: false,
94
- v3_intents: true
95
- )
96
- end
97
- ```
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:
98
85
 
99
- When using the Payment Intents API, be aware that the charge flow will be a bit
100
- different than when using the old V2 API or Elements. It's advisable that all
101
- Payment Intents charges are captured only by using the Solidus backend, as it is
102
- the final source of truth in regards of Solidus orders payments.
103
-
104
- A Payment Intent is created as soon as the customer enters their credit card
105
- data. A tentative charge will be created on Stripe, easily recognizable by its
106
- description: `Solidus Order ID: R987654321 (pending)`. As soon as the credit
107
- card is confirmed (ie. when the customer passes the 3DSecure authorization, when
108
- required) then the charge description gets updated to include the Solidus payment
109
- number: `Solidus Order ID: R987654321-Z4VYUDB3`.
110
-
111
- These charges are created `uncaptured` and will need to be captured in Solidus
112
- backend later, after the customer confirms the order. If the customer never
113
- completes the checkout, that charge must remain uncaptured. If the customer
114
- decides to change their payment method after creating a Payment Request, then
115
- that Payment Request charge will be canceled.
116
-
117
-
118
- Apple Pay and Google Pay
119
- -----------------------
120
-
121
- The Payment Intents API now supports also Apple Pay and Google Pay via
122
- the [payment request button API](https://stripe.com/docs/stripe-js/elements/payment-request-button).
123
- Check the Payment Intents section for setup details. Also, please
124
- refer to the official Stripe documentation for configuring your
125
- Stripe account to receive payments via Apple Pay.
126
-
127
- It's possible to pay with Apple Pay and Google Pay directly from the cart
128
- page. The functionality is self-contained in the view partial
129
- `_stripe_payment_request_button.html.erb`. In order to use it, you need
130
- to render that partial in the `orders#edit` frontend page, and pass it the
131
- payment method configured for Stripe via the local variable
132
- `cart_checkout_payment_method`:
86
+ `app/views/checkouts/existing_payment/stripe/_sepa_debit.html.erb`
133
87
 
134
- ```ruby
135
- <%= 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 %>
136
92
  ```
137
93
 
138
- Of course, the rules listed in the Payment Intents section (adding the stripe
139
- country config value, for example) apply also for this feature.
140
-
141
- Customizing the V3 API javascript
142
- ---------------------------------
143
-
144
- Stripe V3 JS code is now managed via Sprockets. If you need to customize the JS,
145
- you can simply override or/and add new methods to the relevant object prototype.
146
- Make sure you load your customizations after Stripe initalization code from
147
- `spree/frontend/solidus_stripe`.
148
-
149
- For example, the following code adds a callback method in order to print a debug
150
- message on the console:
151
-
152
- ```js
153
- SolidusStripe.CartPageCheckout.prototype.onPrButtonMounted = function(id, result) {
154
- if (result) {
155
- $('#' + id).parent().show();
156
- console.log('Payment request button is now mounted on element with id #' + id);
157
- } else {
158
- console.log('Payment request button failed initalization.');
159
- }
160
- }
161
- ```
94
+ ### Showing reusable sources in the admin interface
162
95
 
163
- Customizing Stripe Elements
164
- -----------------------
165
-
166
- ### Styling input fields
167
-
168
- 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)
169
- object):
170
-
171
- ```js
172
- SolidusStripe.Elements.prototype.baseStyle = function () {
173
- return {
174
- base: {
175
- iconColor: '#c4f0ff',
176
- color: '#fff',
177
- fontWeight: 500,
178
- fontFamily: 'Roboto, Open Sans, Segoe UI, sans-serif',
179
- fontSize: '16px',
180
- fontSmoothing: 'antialiased',
181
- ':-webkit-autofill': {
182
- color: '#fce883',
183
- },
184
- '::placeholder': {
185
- color: '#87BBFD',
186
- },
187
- },
188
- invalid: {
189
- iconColor: '#FFC7EE',
190
- color: '#FFC7EE',
191
- }
192
- }
193
- };
194
- ```
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:
195
99
 
196
- 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/`
197
101
 
198
- ```css
199
- .StripeElement {
200
- border: 1px solid transparent;
201
- }
102
+ ### Custom webhooks
202
103
 
203
- .StripeElement--focus {
204
- box-shadow: 0 1px 3px 0 #cfd7df;
205
- }
104
+ You can also use [Stripe webhooks](https://stripe.com/docs/webhooks) to trigger
105
+ custom actions in your application.
206
106
 
207
- .StripeElement--invalid {
208
- border-color: #fa755a;
209
- }
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:
210
109
 
211
- .StripeElement--webkit-autofill {
212
- background-color: #fefde5 !important;
213
- }
110
+ ```ruby
111
+ # config/initializers/solidus_stripe.rb
112
+ SolidusStripe.configure do |config|
113
+ config.webhook_events = %i[charge.succeeded]
114
+ end
214
115
  ```
215
116
 
216
- ### 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:
217
121
 
218
- 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
219
126
 
220
- * `SolidusStripe.Elements.prototype.cardNumberElementOptions`
221
- * `SolidusStripe.Elements.prototype.cardExpiryElementOptions`
222
- * `SolidusStripe.Elements.prototype.cardCvcElementOptions`
127
+ handle :"stripe.charge.succeeded", with: :call
223
128
 
224
- 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
225
133
 
226
- ```js
227
- SolidusStripe.Elements.prototype.cardNumberElementOptions = function () {
228
- return {
229
- style: this.baseStyle(),
230
- showIcon: true,
231
- placeholder: "I'm a custom placeholder!"
232
- }
233
- }
134
+ # config/initializers/solidus_stripe.rb
135
+ # ...
136
+ Rails.application.config.to_prepare do
137
+ UpdateAccountBalanceSubscriber.new.subscribe_to(Spree::Bus)
138
+ end
234
139
  ```
235
140
 
236
- ### Passing 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.
237
147
 
238
- 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.
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)):
239
151
 
240
- 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:
241
-
242
- ```js
243
- SolidusStripe.Payment.prototype.elementsBaseOptions = function () {
244
- return {
245
- locale: 'de',
246
- fonts: [
247
- {
248
- cssSrc: 'https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,600'
249
- }
250
- ]
251
- };
252
- };
152
+ ```ruby
153
+ # config/initializers/solidus_stripe.rb
154
+ SolidusStripe.configure do |config|
155
+ config.webhook_signature_tolerance = 150
156
+ end
253
157
  ```
254
158
 
255
- ## 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.
256
165
 
257
- If you were previously using `solidus_gateway` gem you might want to
258
- check out our [Wiki page](https://github.com/solidusio/solidus_stripe/wiki/Migrating-from-solidus_gateway)
259
- that describes how to handle this migration.
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:
178
+
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
260
181
 
261
182
  ## Development
262
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
+
263
198
  ### Testing the extension
264
199
 
265
200
  First bundle your dependencies, then run `bin/rake`. `bin/rake` will default to building the dummy
@@ -277,10 +212,17 @@ bundle exec rubocop
277
212
  ```
278
213
 
279
214
  When testing your application's integration with this extension you may use its factories.
280
- Simply add this require statement to your spec_helper:
215
+ Simply add this require statement to your `spec/spec_helper.rb`:
216
+
217
+ ```ruby
218
+ require 'solidus_stripe/testing_support/factories'
219
+ ```
220
+
221
+ Or, if you are using `FactoryBot.definition_file_paths`, you can load Solidus core
222
+ factories along with this extension's factories using this statement:
281
223
 
282
224
  ```ruby
283
- require '<%= file_name %>/factories'
225
+ SolidusDevSupport::TestingSupport::Factories.load_for(SolidusStripe::Engine)
284
226
  ```
285
227
 
286
228
  ### Running the sandbox
@@ -299,21 +241,11 @@ $ bin/rails server
299
241
  Use Ctrl-C to stop
300
242
  ```
301
243
 
302
- ### Updating the changelog
303
-
304
- Before and after releases the changelog should be updated to reflect the up-to-date status of
305
- the project:
306
-
307
- ```shell
308
- bin/rake changelog
309
- git add CHANGELOG.md
310
- git commit -m "Update the changelog"
311
- ```
312
-
313
244
  ### Releasing new versions
314
245
 
315
246
  Please refer to the dedicated [page](https://github.com/solidusio/solidus/wiki/How-to-release-extensions) on Solidus wiki.
316
247
 
317
248
  ## License
249
+
318
250
  Copyright (c) 2014 Spree Commerce Inc., released under the New BSD License
319
- 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,6 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'solidus_dev_support/rake_tasks'
4
- SolidusDevSupport::RakeTasks.install
3
+ require 'bundler/gem_tasks'
5
4
 
6
- 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: 500
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: 500
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
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusStripe
4
+ class Customer < ApplicationRecord
5
+ belongs_to :payment_method
6
+
7
+ # Source is supposed to be a user or an order and needs to respond to #email
8
+ belongs_to :source, polymorphic: true
9
+
10
+ def self.retrieve_or_create_stripe_customer_id(payment_method:, order:)
11
+ instance = find_or_initialize_by(payment_method: payment_method, source: order.user || order)
12
+
13
+ instance.stripe_id ||
14
+ instance.create_stripe_customer.tap { instance.update!(stripe_id: _1.id) }.id
15
+ end
16
+
17
+ def create_stripe_customer
18
+ payment_method.gateway.request { Stripe::Customer.create(email: source.email) }
19
+ end
20
+ end
21
+ end