solidus_stripe 3.0.0 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d9e6ec7ddfb05d802b145edb2e2e50d58510148be740c085dd0a7eb99a8e367b
4
- data.tar.gz: c6598070cbe6c6e18504f27ab999ef0ca6d1b331ecdee9e1838434c261fe699d
3
+ metadata.gz: 929c7fff2fc949fe3979ae7a9403903b563a3ef67c5c8588f171c9bf42181ce6
4
+ data.tar.gz: a00f65c3222947871e6a97e5fd3e7d72eb7e5edc134128e300578ff68b13f876
5
5
  SHA512:
6
- metadata.gz: 8faba0fdc2be35b90a936273a8d3aedf2411a10e8c01489c5a66b5859fbbcccb38ffcfec3e9d2a34dcdbf065228396b3047210cdbcbb0818c495ff2abf76f098
7
- data.tar.gz: 82f01e68ff9a5fa8bcc74c759c3974d78cf05411b83cb732ca0ef32553d62d920697093db371a174c826d3391ca19908f0e4a35a4ae152863c61a94ae595f2c4
6
+ metadata.gz: 8090f338dfefc83e121000e88d467eabc6631e6501eea750b2103c31d285a1a28364e01968c0586f564099780d69bde29f736cc2bf4b34f9456b7bf02ea84501
7
+ data.tar.gz: d7e5b50dd60a80c74754acd3bbcc0552af926c04d05f380995319a0336daf2aa37f8c127966d68dcecf8bb308d7ef6607ff5d948bc2e23a1544179a41c93fac5
@@ -1,5 +1,39 @@
1
1
  # Changelog
2
2
 
3
+ ## [Unreleased](https://github.com/solidusio/solidus_stripe/tree/HEAD)
4
+
5
+ [Full Changelog](https://github.com/solidusio/solidus_stripe/compare/v3.0.0...HEAD)
6
+
7
+ **Closed issues:**
8
+
9
+ - Pay with Apple Pay from cart page [\#23](https://github.com/solidusio/solidus_stripe/issues/23)
10
+
11
+ ## [v3.0.0](https://github.com/solidusio/solidus_stripe/tree/v3.0.0) (2020-03-11)
12
+
13
+ [Full Changelog](https://github.com/solidusio/solidus_stripe/compare/v2.1.0...v3.0.0)
14
+
15
+ **Implemented enhancements:**
16
+
17
+ - Rename v3/stripe partial as v3/elements [\#30](https://github.com/solidusio/solidus_stripe/pull/30) ([spaghetticode](https://github.com/spaghetticode))
18
+
19
+ **Merged pull requests:**
20
+
21
+ - Allow to customize Stripe Elements styles via JS [\#34](https://github.com/solidusio/solidus_stripe/pull/34) ([spaghetticode](https://github.com/spaghetticode))
22
+ - Stop injecting css in host app while installing [\#33](https://github.com/solidusio/solidus_stripe/pull/33) ([kennyadsl](https://github.com/kennyadsl))
23
+ - Manage Stripe V3 JS code via Sprokets [\#32](https://github.com/solidusio/solidus_stripe/pull/32) ([spaghetticode](https://github.com/spaghetticode))
24
+
25
+ ## [v2.1.0](https://github.com/solidusio/solidus_stripe/tree/v2.1.0) (2020-03-11)
26
+
27
+ [Full Changelog](https://github.com/solidusio/solidus_stripe/compare/v2.0.0...v2.1.0)
28
+
29
+ **Closed issues:**
30
+
31
+ - Preference :stripe\_country is not defined on Spree::PaymentMethod::StripeCreditCard \(RuntimeError\) [\#27](https://github.com/solidusio/solidus_stripe/issues/27)
32
+
33
+ **Merged pull requests:**
34
+
35
+ - Refactor Stripe V3 Intents, Elements and cart checkout JS code [\#31](https://github.com/solidusio/solidus_stripe/pull/31) ([spaghetticode](https://github.com/spaghetticode))
36
+
3
37
  ## [v2.0.0](https://github.com/solidusio/solidus_stripe/tree/v2.0.0) (2020-03-03)
4
38
 
5
39
  [Full Changelog](https://github.com/solidusio/solidus_stripe/compare/v1.2.0...v2.0.0)
data/README.md CHANGED
@@ -13,7 +13,7 @@ Installation
13
13
  In your Gemfile:
14
14
 
15
15
  ```ruby
16
- gem 'solidus_stripe', '~> 1.0.0'
16
+ gem 'solidus_stripe', '~> 3.0'
17
17
  ```
18
18
 
19
19
  Then run from the command line:
@@ -155,13 +155,13 @@ SolidusStripe.CartPageCheckout.prototype.onPrButtonMounted = function(id, result
155
155
  }
156
156
  ```
157
157
 
158
- Styling Stripe Elements
158
+ Customizing Stripe Elements
159
159
  -----------------------
160
160
 
161
- The Elements feature built in this gem come with some standard styles. If you want
162
- to customize it, you can override the `SolidusStripe.Elements.prototype.baseStyle`
163
- method and make it return a valid [Stripe Style](https://stripe.com/docs/js/appendix/style)
164
- object:
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
165
 
166
166
  ```js
167
167
  SolidusStripe.Elements.prototype.baseStyle = function () {
@@ -208,6 +208,46 @@ You can also style your element containers directly by using CSS rules like this
208
208
  }
209
209
  ```
210
210
 
211
+ ### Customizing individual input fields
212
+
213
+ If you want to customize individual input fields, you can override these methods
214
+
215
+ * `SolidusStripe.Elements.prototype.cardNumberElementOptions`
216
+ * `SolidusStripe.Elements.prototype.cardExpiryElementOptions`
217
+ * `SolidusStripe.Elements.prototype.cardCvcElementOptions`
218
+
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
220
+
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
+ ```
230
+
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.
234
+
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:
236
+
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
+ ```
249
+
250
+
211
251
  Migrating from solidus_gateway
212
252
  ------------------------------
213
253
 
@@ -18,29 +18,45 @@ SolidusStripe.Elements.prototype.init = function() {
18
18
  };
19
19
 
20
20
  SolidusStripe.Elements.prototype.initElements = function() {
21
- var buildElements = function(elements) {
22
- var style = this.baseStyle();
21
+ var cardExpiry = this.elements.create('cardExpiry', this.cardExpiryElementOptions());
22
+ cardExpiry.mount('#card_expiry');
23
23
 
24
- elements.create('cardExpiry', {style: style}).mount('#card_expiry');
25
- elements.create('cardCvc', {style: style}).mount('#card_cvc');
24
+ var cardCvc = this.elements.create('cardCvc', this.cardCvcElementOptions());
25
+ cardCvc.mount('#card_cvc');
26
26
 
27
- var cardNumber = elements.create('cardNumber', {style: style});
28
- cardNumber.mount('#card_number');
27
+ this.cardNumber = this.elements.create('cardNumber', this.cardNumberElementOptions());
28
+ this.cardNumber.mount('#card_number');
29
29
 
30
- return cardNumber;
31
- }.bind(this);
32
-
33
- this.cardNumber = buildElements(this.elements);
34
-
35
- var cardChange = function(event) {
36
- if (event.error) {
37
- this.showError(event.error.message);
38
- } else {
39
- this.errorElement.hide().text('');
40
- }
41
- };
42
- this.cardNumber.addEventListener('change', cardChange.bind(this));
43
30
  this.form.bind('submit', this.onFormSubmit.bind(this));
31
+
32
+ // Listen for errors from each input field.
33
+ // Adapted from https://github.com/stripe/elements-examples/blob/master/js/index.js
34
+ var savedErrors = {};
35
+ [cardExpiry, cardCvc, this.cardNumber].forEach(function(element, idx) {
36
+ element.on('change', function(event) {
37
+ if (event.error) {
38
+ savedErrors[idx] = event.error.message;
39
+ this.showError(event.error.message);
40
+ } else {
41
+ savedErrors[idx] = null;
42
+
43
+ // Loop over the saved errors and find the first one, if any.
44
+ var nextError = Object.keys(savedErrors)
45
+ .sort()
46
+ .reduce(function(maybeFoundError, key) {
47
+ return maybeFoundError || savedErrors[key];
48
+ }, null);
49
+
50
+ if (nextError) {
51
+ // Now that they've fixed the current error, show another one.
52
+ this.showError(nextError);
53
+ } else {
54
+ // The user fixed the last error; no more errors.
55
+ this.errorElement.hide().text('');
56
+ }
57
+ }
58
+ }.bind(this));
59
+ }.bind(this));
44
60
  };
45
61
 
46
62
  SolidusStripe.Elements.prototype.baseStyle = function () {
@@ -61,6 +77,24 @@ SolidusStripe.Elements.prototype.baseStyle = function () {
61
77
  };
62
78
  };
63
79
 
80
+ SolidusStripe.Elements.prototype.cardNumberElementOptions = function () {
81
+ return {
82
+ style: this.baseStyle()
83
+ }
84
+ }
85
+
86
+ SolidusStripe.Elements.prototype.cardExpiryElementOptions = function () {
87
+ return {
88
+ style: this.baseStyle()
89
+ }
90
+ }
91
+
92
+ SolidusStripe.Elements.prototype.cardCvcElementOptions = function () {
93
+ return {
94
+ style: this.baseStyle()
95
+ }
96
+ }
97
+
64
98
  SolidusStripe.Elements.prototype.showError = function(error) {
65
99
  var message = error.message || error;
66
100
 
@@ -87,18 +121,20 @@ SolidusStripe.Elements.prototype.onFormSubmit = function(event) {
87
121
 
88
122
  SolidusStripe.Elements.prototype.elementsTokenHandler = function(token) {
89
123
  var mapCC = function(ccType) {
90
- if (ccType === 'MasterCard') {
124
+ if (ccType === 'MasterCard' || ccType === 'mastercard') {
91
125
  return 'mastercard';
92
- } else if (ccType === 'Visa') {
126
+ } else if (ccType === 'Visa' || ccType === 'visa') {
93
127
  return 'visa';
94
- } else if (ccType === 'American Express') {
128
+ } else if (ccType === 'American Express' || ccType === 'amex') {
95
129
  return 'amex';
96
- } else if (ccType === 'Discover') {
130
+ } else if (ccType === 'Discover' || ccType === 'discover') {
97
131
  return 'discover';
98
- } else if (ccType === 'Diners Club') {
132
+ } else if (ccType === 'Diners Club' || ccType === 'diners') {
99
133
  return 'dinersclub';
100
- } else if (ccType === 'JCB') {
134
+ } else if (ccType === 'JCB' || ccType === 'jcb') {
101
135
  return 'jcb';
136
+ } else if (ccType === 'Unionpay' || ccType === 'unionpay') {
137
+ return 'unionpay';
102
138
  }
103
139
  };
104
140
 
@@ -69,6 +69,7 @@ SolidusStripe.PaymentIntents.prototype.onIntentsPayment = function(payment) {
69
69
  'Content-Type': 'application/json'
70
70
  },
71
71
  body: JSON.stringify({
72
+ form_data: this.form.serialize(),
72
73
  spree_payment_method_id: this.config.id,
73
74
  stripe_payment_method_id: payment.paymentMethod.id,
74
75
  authenticity_token: this.authToken
@@ -100,6 +100,7 @@
100
100
  method: 'POST',
101
101
  headers: { 'Content-Type': 'application/json' },
102
102
  body: JSON.stringify({
103
+ form_data: this.form.serialize(),
103
104
  spree_payment_method_id: this.config.id,
104
105
  stripe_payment_intent_id: result.paymentIntent.id,
105
106
  authenticity_token: this.authToken
@@ -6,5 +6,11 @@ SolidusStripe.Payment = function() {
6
6
  this.authToken = $('meta[name="csrf-token"]').attr('content');
7
7
 
8
8
  this.stripe = Stripe(this.config.publishable_key);
9
- this.elements = this.stripe.elements({locale: 'en'});
9
+ this.elements = this.stripe.elements(this.elementsBaseOptions());
10
+ };
11
+
12
+ SolidusStripe.Payment.prototype.elementsBaseOptions = function () {
13
+ return {
14
+ locale: 'en'
15
+ };
10
16
  };
@@ -6,25 +6,19 @@ module SolidusStripe
6
6
 
7
7
  def confirm
8
8
  begin
9
- if params[:stripe_payment_method_id].present?
10
- intent = stripe.create_intent(
11
- (current_order.total * 100).to_i,
12
- params[:stripe_payment_method_id],
13
- currency: current_order.currency,
14
- confirmation_method: 'manual',
15
- confirm: true,
16
- setup_future_usage: 'on_session',
17
- metadata: { order_id: current_order.id }
18
- )
19
- elsif params[:stripe_payment_intent_id].present?
20
- intent = stripe.confirm_intent(params[:stripe_payment_intent_id], nil)
9
+ @intent = begin
10
+ if params[:stripe_payment_method_id].present?
11
+ create_intent
12
+ elsif params[:stripe_payment_intent_id].present?
13
+ stripe.confirm_intent(params[:stripe_payment_intent_id], nil)
14
+ end
21
15
  end
22
16
  rescue Stripe::CardError => e
23
17
  render json: { error: e.message }, status: 500
24
18
  return
25
19
  end
26
20
 
27
- generate_payment_response(intent)
21
+ generate_payment_response
28
22
  end
29
23
 
30
24
  private
@@ -33,20 +27,35 @@ module SolidusStripe
33
27
  @stripe ||= Spree::PaymentMethod::StripeCreditCard.find(params[:spree_payment_method_id])
34
28
  end
35
29
 
36
- def generate_payment_response(intent)
37
- response = intent.params
30
+ def generate_payment_response
31
+ response = @intent.params
38
32
  # Note that if your API version is before 2019-02-11, 'requires_action'
39
33
  # appears as 'requires_source_action'.
40
34
  if %w[requires_source_action requires_action].include?(response['status']) && response['next_action']['type'] == 'use_stripe_sdk'
41
- render json: {
42
- requires_action: true,
43
- stripe_payment_intent_client_secret: response['client_secret']
44
- }
45
- elsif response['status'] == 'succeeded'
35
+ render json: {
36
+ requires_action: true,
37
+ stripe_payment_intent_client_secret: response['client_secret']
38
+ }
39
+ elsif response['status'] == 'requires_capture'
40
+ SolidusStripe::CreateIntentsOrderService.new(@intent, stripe, self).call
46
41
  render json: { success: true }
47
42
  else
48
43
  render json: { error: response['error']['message'] }, status: 500
49
44
  end
50
45
  end
46
+
47
+ def create_intent
48
+ stripe.create_intent(
49
+ (current_order.total * 100).to_i,
50
+ params[:stripe_payment_method_id],
51
+ description: "Solidus Order ID: #{current_order.number} (pending)",
52
+ currency: current_order.currency,
53
+ confirmation_method: 'manual',
54
+ capture_method: 'manual',
55
+ confirm: true,
56
+ setup_future_usage: 'off_session',
57
+ metadata: { order_id: current_order.id }
58
+ )
59
+ end
51
60
  end
52
61
  end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spree
4
+ module OrderUpdateAttributesDecorator
5
+ def assign_payments_attributes
6
+ return if payments_attributes.empty?
7
+ return if adding_new_stripe_payment_intents_card?
8
+
9
+ stripe_intents_pending_payments.each(&:void_transaction!)
10
+
11
+ super
12
+ end
13
+
14
+ private
15
+
16
+ def adding_new_stripe_payment_intents_card?
17
+ paying_with_stripe_intents? && stripe_intents_pending_payments.any?
18
+ end
19
+
20
+ def stripe_intents_pending_payments
21
+ @stripe_intents_pending_payments ||= order.payments.valid.select do |payment|
22
+ payment_method = payment.payment_method
23
+ payment.pending? && stripe_intents?(payment_method)
24
+ end
25
+ end
26
+
27
+ def paying_with_stripe_intents?
28
+ if id = payments_attributes.first&.dig(:payment_method_id)
29
+ stripe_intents?(Spree::PaymentMethod.find(id))
30
+ end
31
+ end
32
+
33
+ def stripe_intents?(payment_method)
34
+ payment_method.respond_to?(:v3_intents?) && payment_method.v3_intents?
35
+ end
36
+
37
+ ::Spree::OrderUpdateAttributes.prepend(self)
38
+ end
39
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spree
4
+ module PaymentDecorator
5
+ def gateway_order_identifier
6
+ gateway_order_id
7
+ end
8
+
9
+ ::Spree::Payment.prepend(self)
10
+ end
11
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusStripe
4
+ class CreateIntentsOrderService
5
+ attr_reader :intent, :stripe, :controller
6
+
7
+ delegate :request, :current_order, :params, to: :controller
8
+
9
+ def initialize(intent, stripe, controller)
10
+ @intent, @stripe, @controller = intent, stripe, controller
11
+ end
12
+
13
+ def call
14
+ invalidate_previous_payment_intents_payments
15
+ payment = create_payment
16
+ description = "Solidus Order ID: #{payment.gateway_order_identifier}"
17
+ stripe.update_intent(nil, response['id'], nil, description: description)
18
+ end
19
+
20
+ private
21
+
22
+ def invalidate_previous_payment_intents_payments
23
+ if stripe.v3_intents?
24
+ current_order.payments.pending.where(payment_method: stripe).each(&:void_transaction!)
25
+ end
26
+ end
27
+
28
+ def create_payment
29
+ Spree::OrderUpdateAttributes.new(
30
+ current_order,
31
+ payment_params,
32
+ request_env: request.headers.env
33
+ ).apply
34
+
35
+ Spree::Payment.find_by(response_code: response['id']).tap do |payment|
36
+ payment.update!(state: :pending)
37
+ end
38
+ end
39
+
40
+ def payment_params
41
+ card = response['charges']['data'][0]['payment_method_details']['card']
42
+ address_attributes = form_data['payment_source'][stripe.id.to_s]['address_attributes']
43
+
44
+ {
45
+ payments_attributes: [{
46
+ payment_method_id: stripe.id,
47
+ amount: current_order.total,
48
+ response_code: response['id'],
49
+ source_attributes: {
50
+ month: card['exp_month'],
51
+ year: card['exp_year'],
52
+ cc_type: card['brand'],
53
+ gateway_payment_profile_id: response['payment_method'],
54
+ last_digits: card['last4'],
55
+ name: current_order.bill_address.full_name,
56
+ address_attributes: address_attributes
57
+ }
58
+ }]
59
+ }
60
+ end
61
+
62
+ def response
63
+ intent.params
64
+ end
65
+
66
+ def form_data
67
+ Rack::Utils.parse_nested_query(params[:form_data])
68
+ end
69
+ end
70
+ end
@@ -15,6 +15,8 @@ module Spree
15
15
  'Visa' => 'visa'
16
16
  }
17
17
 
18
+ delegate :create_intent, :update_intent, :confirm_intent, to: :gateway
19
+
18
20
  def stripe_config(order)
19
21
  {
20
22
  id: id,
@@ -59,14 +61,6 @@ module Spree
59
61
  true
60
62
  end
61
63
 
62
- def create_intent(*args)
63
- gateway.create_intent(*args)
64
- end
65
-
66
- def confirm_intent(*args)
67
- gateway.confirm_intent(*args)
68
- end
69
-
70
64
  def purchase(money, creditcard, transaction_options)
71
65
  gateway.purchase(*options_for_purchase_or_auth(money, creditcard, transaction_options))
72
66
  end
@@ -140,8 +134,9 @@ module Spree
140
134
 
141
135
  def options_for_purchase_or_auth(money, creditcard, transaction_options)
142
136
  options = {}
143
- options[:description] = "Spree Order ID: #{transaction_options[:order_id]}"
137
+ options[:description] = "Solidus Order ID: #{transaction_options[:order_id]}"
144
138
  options[:currency] = transaction_options[:currency]
139
+ options[:off_session] = true if v3_intents?
145
140
 
146
141
  if customer = creditcard.gateway_customer_profile_id
147
142
  options[:customer] = customer
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SolidusStripe
4
- VERSION = "3.0.0"
4
+ VERSION = "3.1.0"
5
5
  end
@@ -6,6 +6,8 @@ RSpec.describe "Stripe checkout", type: :feature do
6
6
  let(:zone) { FactoryBot.create(:zone) }
7
7
  let(:country) { FactoryBot.create(:country) }
8
8
 
9
+ let(:card_3d_secure) { "4000 0025 0000 3155" }
10
+
9
11
  before do
10
12
  FactoryBot.create(:store)
11
13
  zone.members << Spree::ZoneMember.create!(zoneable: country)
@@ -301,24 +303,8 @@ RSpec.describe "Stripe checkout", type: :feature do
301
303
  end
302
304
 
303
305
  context "when using a valid 3D Secure card" do
304
- let(:card_number) { "4000 0027 6000 3184" }
305
-
306
306
  it "successfully completes the checkout" do
307
- within_frame find('#card_number iframe') do
308
- card_number.split('').each { |n| find_field('cardnumber').native.send_keys(n) }
309
- end
310
- within_frame(find '#card_cvc iframe') { fill_in 'cvc', with: '123' }
311
- within_frame(find '#card_expiry iframe') do
312
- '0132'.split('').each { |n| find_field('exp-date').native.send_keys(n) }
313
- end
314
-
315
- click_button "Save and Continue"
316
-
317
- within_3d_secure_modal do
318
- expect(page).to have_content '$19.99 using 3D Secure'
319
-
320
- click_button 'Complete authentication'
321
- end
307
+ authenticate_3d_secure_card(card_3d_secure)
322
308
 
323
309
  expect(page).to have_current_path("/checkout/confirm")
324
310
 
@@ -368,59 +354,133 @@ RSpec.describe "Stripe checkout", type: :feature do
368
354
  end
369
355
  end
370
356
 
371
- it "can re-use saved cards" do
372
- within_frame find('#card_number iframe') do
373
- "4000 0027 6000 3184".split('').each { |n| find_field('cardnumber').native.send_keys(n) }
374
- end
375
- within_frame(find '#card_cvc iframe') { fill_in 'cvc', with: '123' }
376
- within_frame(find '#card_expiry iframe') do
377
- '0132'.split('').each { |n| find_field('exp-date').native.send_keys(n) }
378
- end
379
- click_button "Save and Continue"
357
+ context "when reusing a card" do
358
+ stub_authorization!
380
359
 
381
- within_3d_secure_modal do
382
- click_button 'Complete authentication'
360
+ it "succesfully creates a second payment that can be captured in the backend" do
361
+ authenticate_3d_secure_card(card_3d_secure)
362
+
363
+ expect(page).to have_current_path("/checkout/confirm")
364
+ click_button "Place Order"
365
+ expect(page).to have_content("Your order has been processed successfully")
366
+
367
+ visit spree.root_path
368
+ click_link "DL-44"
369
+ click_button "Add To Cart"
370
+
371
+ expect(page).to have_current_path("/cart")
372
+ click_button "Checkout"
373
+
374
+ # Address
375
+ expect(page).to have_current_path("/checkout/address")
376
+
377
+ within("#billing") do
378
+ fill_in_name
379
+ fill_in "Street Address", with: "YT-1300"
380
+ fill_in "City", with: "Mos Eisley"
381
+ select "United States of America", from: "Country"
382
+ select country.states.first.name, from: "order_bill_address_attributes_state_id"
383
+ fill_in "Zip", with: "12010"
384
+ fill_in "Phone", with: "(555) 555-5555"
385
+ end
386
+ click_on "Save and Continue"
387
+
388
+ # Delivery
389
+ expect(page).to have_current_path("/checkout/delivery")
390
+ expect(page).to have_content("UPS Ground")
391
+ click_on "Save and Continue"
392
+
393
+ # Payment
394
+ expect(page).to have_current_path("/checkout/payment")
395
+ choose "Use an existing card on file"
396
+ click_button "Save and Continue"
397
+
398
+ # Confirm
399
+ expect(page).to have_current_path("/checkout/confirm")
400
+ click_button "Place Order"
401
+ expect(page).to have_content("Your order has been processed successfully")
402
+
403
+ # Capture in backend
404
+ Spree::Order.complete.each do |order|
405
+ visit spree.admin_path
406
+
407
+ expect(page).to have_selector("#listing_orders tbody tr", count: 2)
408
+
409
+ click_link order.number
410
+
411
+ click_link "Payments"
412
+ find(".fa-capture").click
413
+
414
+ expect(page).to have_content "Payment Updated"
415
+ expect(find("table#payments")).to have_content "Completed"
416
+ end
383
417
  end
418
+ end
384
419
 
385
- expect(page).to have_current_path("/checkout/confirm")
386
- click_button "Place Order"
387
- expect(page).to have_content("Your order has been processed successfully")
420
+ context "when paying with multiple payment methods" do
421
+ stub_authorization!
388
422
 
389
- visit spree.root_path
390
- click_link "DL-44"
391
- click_button "Add To Cart"
423
+ context "when paying first with regular card, then with 3D-Secure card" do
424
+ let(:regular_card) { "4242 4242 4242 4242"}
392
425
 
393
- expect(page).to have_current_path("/cart")
394
- click_button "Checkout"
426
+ it "voids the first stripe payment and successfully pays with 3DS card" do
427
+ within_frame find('#card_number iframe') do
428
+ regular_card.split('').each { |n| find_field('cardnumber').native.send_keys(n) }
429
+ end
430
+ within_frame(find '#card_cvc iframe') { fill_in 'cvc', with: '123' }
431
+ within_frame(find '#card_expiry iframe') do
432
+ '0132'.split('').each { |n| find_field('exp-date').native.send_keys(n) }
433
+ end
434
+ click_button "Save and Continue"
395
435
 
396
- # Address
397
- expect(page).to have_current_path("/checkout/address")
436
+ expect(page).to have_content "Ending in 4242"
398
437
 
399
- within("#billing") do
400
- fill_in_name
401
- fill_in "Street Address", with: "YT-1300"
402
- fill_in "City", with: "Mos Eisley"
403
- select "United States of America", from: "Country"
404
- select country.states.first.name, from: "order_bill_address_attributes_state_id"
405
- fill_in "Zip", with: "12010"
406
- fill_in "Phone", with: "(555) 555-5555"
438
+ click_link "Payment"
439
+
440
+ authenticate_3d_secure_card(card_3d_secure)
441
+ click_button "Place Order"
442
+ expect(page).to have_content "Your order has been processed successfully"
443
+
444
+ visit spree.admin_path
445
+ click_link Spree::Order.complete.first.number
446
+ click_link "Payments"
447
+
448
+ payments = all('table#payments tbody tr')
449
+
450
+ expect(payments.first).to have_content "Stripe"
451
+ expect(payments.first).to have_content "Void"
452
+
453
+ expect(payments.last).to have_content "Stripe"
454
+ expect(payments.last).to have_content "Pending"
455
+ end
407
456
  end
408
- click_on "Save and Continue"
409
457
 
410
- # Delivery
411
- expect(page).to have_current_path("/checkout/delivery")
412
- expect(page).to have_content("UPS Ground")
413
- click_on "Save and Continue"
458
+ context "when paying first with 3D-Secure card, then with check" do
459
+ before { create :check_payment_method }
414
460
 
415
- # Payment
416
- expect(page).to have_current_path("/checkout/payment")
417
- choose "Use an existing card on file"
418
- click_button "Save and Continue"
461
+ it "voids the stripe payment and successfully pays with check" do
462
+ authenticate_3d_secure_card(card_3d_secure)
463
+ expect(page).to have_current_path("/checkout/confirm")
419
464
 
420
- # Confirm
421
- expect(page).to have_current_path("/checkout/confirm")
422
- click_button "Place Order"
423
- expect(page).to have_content("Your order has been processed successfully")
465
+ click_link "Payment"
466
+ choose "Check"
467
+ click_button "Save and Continue"
468
+ expect(find(".payment-info")).to have_content "Check"
469
+ expect(page).to have_content "Your order has been processed successfully"
470
+
471
+ visit spree.admin_path
472
+ click_link Spree::Order.complete.first.number
473
+ click_link "Payments"
474
+ payments = all('table#payments tbody tr')
475
+
476
+ stripe_payment = payments.first
477
+ expect(stripe_payment).to have_content "Stripe"
478
+ expect(stripe_payment).to have_content "Void"
479
+
480
+ check_payment = payments.last
481
+ expect(check_payment).to have_content "Check"
482
+ end
483
+ end
424
484
  end
425
485
 
426
486
  it_behaves_like "Stripe Elements invalid payments"
@@ -433,4 +493,21 @@ RSpec.describe "Stripe checkout", type: :feature do
433
493
  end
434
494
  end
435
495
  end
496
+
497
+ def authenticate_3d_secure_card(card_number)
498
+ within_frame find('#card_number iframe') do
499
+ card_number.split('').each { |n| find_field('cardnumber').native.send_keys(n) }
500
+ end
501
+ within_frame(find '#card_cvc iframe') { fill_in 'cvc', with: '123' }
502
+ within_frame(find '#card_expiry iframe') do
503
+ '0132'.split('').each { |n| find_field('exp-date').native.send_keys(n) }
504
+ end
505
+ click_button "Save and Continue"
506
+
507
+ within_3d_secure_modal do
508
+ expect(page).to have_content '$19.99 using 3D Secure'
509
+
510
+ click_button 'Complete authentication'
511
+ end
512
+ end
436
513
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: solidus_stripe
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.0
4
+ version: 3.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Solidus Team
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-03-11 00:00:00.000000000 Z
11
+ date: 2020-04-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: solidus_core
@@ -101,7 +101,10 @@ files:
101
101
  - app/controllers/solidus_stripe/intents_controller.rb
102
102
  - app/controllers/solidus_stripe/payment_request_controller.rb
103
103
  - app/controllers/spree/stripe_controller.rb
104
+ - app/decorators/models/spree/order_update_attributes_decorator.rb
105
+ - app/decorators/models/spree/payment_decorator.rb
104
106
  - app/models/solidus_stripe/address_from_params_service.rb
107
+ - app/models/solidus_stripe/create_intents_order_service.rb
105
108
  - app/models/solidus_stripe/prepare_order_for_payment_service.rb
106
109
  - app/models/solidus_stripe/shipping_rates_service.rb
107
110
  - app/models/spree/payment_method/stripe_credit_card.rb