solidus_stripe 3.0.0 → 3.1.0

Sign up to get free protection for your applications and to get access to all the features.
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