solidus_stripe 3.2.1 → 4.0.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: 7a9c4fef33f8a78362d726cdd997b494a5127c0dd5ae3a7298fda3e5e7a8efb1
4
- data.tar.gz: a7ef47465e84012d7c9e1f873381ce3f761bb65810d997ac2eae4ef04f5d1428
3
+ metadata.gz: 38e979d7eecd9e20f5d763ad5e2bec33312f0877b2cec287604801b483dcb006
4
+ data.tar.gz: 19a4708081571dbba8c34c7d0173c1a1704ff12df54902e488add9572c4a9080
5
5
  SHA512:
6
- metadata.gz: 72f01baad84dfb2e9ebe654e0fbb4f5fa8d8f00b8debf04caab1deb94b48a1dc6c2dcde4508b5c195c4f6a30b24abe6292f5f500c922d562a2a19eeb83aa1fef
7
- data.tar.gz: 2bc6f12bd539f13b17a7c754b9259aa0fd3110b2db2abd938a1558b733c15bd133efd80be78964964b83f600d6eeb434214c4387a8862d70689b887d99d1e442
6
+ metadata.gz: 6cf3fd35258a8e0c300d0b2ca18d131d9ee3e2b74cc8522e911c8fe7a94fa7e987b66c445d52dace4df580ecf3c54eeec14be421f4f06d1e823d7705eb6c5ae3
7
+ data.tar.gz: d868652e26e2c4c3813e1f2438c4e2cbedc3f2e4de51a21e7eaafdb1e8fe37d35c4152e1e1ce215ba550be5a1b3d4caa15f4d4dc23f51446708f49c0d21c3f4d
@@ -7,6 +7,7 @@
7
7
  **Fixed bugs:**
8
8
 
9
9
  - Duplicates charges with Payment Intents [\#44](https://github.com/solidusio/solidus_stripe/issues/44)
10
+ - Send form data also when paying with payment request button [\#47](https://github.com/solidusio/solidus_stripe/pull/47) ([spaghetticode](https://github.com/spaghetticode))
10
11
  - Create a single charge when using Stripe Payment Intents [\#45](https://github.com/solidusio/solidus_stripe/pull/45) ([spaghetticode](https://github.com/spaghetticode))
11
12
 
12
13
  **Closed issues:**
@@ -17,6 +18,7 @@
17
18
 
18
19
  **Merged pull requests:**
19
20
 
21
+ - Replace deprecated route `/stripe/confirm\_payment` [\#46](https://github.com/solidusio/solidus_stripe/pull/46) ([spaghetticode](https://github.com/spaghetticode))
20
22
  - Custom Stripe Elements field options [\#42](https://github.com/solidusio/solidus_stripe/pull/42) ([stuffmatic](https://github.com/stuffmatic))
21
23
  - Improve the way Stripe Elements validation errors are displayed [\#40](https://github.com/solidusio/solidus_stripe/pull/40) ([stuffmatic](https://github.com/stuffmatic))
22
24
  - Fix stripe-to-solidus card type mapping [\#38](https://github.com/solidusio/solidus_stripe/pull/38) ([stuffmatic](https://github.com/stuffmatic))
data/README.md CHANGED
@@ -107,6 +107,25 @@ Spree.config do |config|
107
107
  end
108
108
  ```
109
109
 
110
+ When using the Payment Intents API, be aware that the charge flow will be a bit
111
+ different than when using the old V2 API or Elements. It's advisable that all
112
+ Payment Intents charges are captured only by using the Solidus backend, as it is
113
+ the final source of truth in regards of Solidus orders payments.
114
+
115
+ A Payment Intent is created as soon as the customer enters their credit card
116
+ data. A tentative charge will be created on Stripe, easily recognizable by its
117
+ description: `Solidus Order ID: R987654321 (pending)`. As soon as the credit
118
+ card is confirmed (ie. when the customer passes the 3DSecure authorization, when
119
+ required) then the charge description gets updated to include the Solidus payment
120
+ number: `Solidus Order ID: R987654321-Z4VYUDB3`.
121
+
122
+ These charges are created `uncaptured` and will need to be captured in Solidus
123
+ backend later, after the customer confirms the order. If the customer never
124
+ completes the checkout, that charge must remain uncaptured. If the customer
125
+ decides to change their payment method after creating a Payment Request, then
126
+ that Payment Request charge will be canceled.
127
+
128
+
110
129
  Apple Pay and Google Pay
111
130
  -----------------------
112
131
 
@@ -210,13 +229,13 @@ You can also style your element containers directly by using CSS rules like this
210
229
 
211
230
  ### Customizing individual input fields
212
231
 
213
- If you want to customize individual input fields, you can override these methods
232
+ If you want to customize individual input fields, you can override these methods
214
233
 
215
234
  * `SolidusStripe.Elements.prototype.cardNumberElementOptions`
216
235
  * `SolidusStripe.Elements.prototype.cardExpiryElementOptions`
217
236
  * `SolidusStripe.Elements.prototype.cardCvcElementOptions`
218
237
 
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
238
+ 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
239
 
221
240
  ```js
222
241
  SolidusStripe.Elements.prototype.cardNumberElementOptions = function () {
@@ -230,7 +249,7 @@ SolidusStripe.Elements.prototype.cardNumberElementOptions = function () {
230
249
 
231
250
  ### Passing options to the Stripe Elements instance
232
251
 
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.
252
+ 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
253
 
235
254
  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
255
 
@@ -16,11 +16,14 @@ SolidusStripe.CartPageCheckout.prototype.init = function() {
16
16
  };
17
17
 
18
18
  SolidusStripe.CartPageCheckout.prototype.showError = function(error) {
19
- this.errorElement.text(error).show();
19
+ var message = error.message || error;
20
+
21
+ this.errorElement.text(message).show();
20
22
  };
21
23
 
22
24
  SolidusStripe.CartPageCheckout.prototype.submitPayment = function(payment) {
23
25
  var showError = this.showError.bind(this);
26
+ var prTokenHandler = this.prTokenHandler.bind(this);
24
27
 
25
28
  $.ajax({
26
29
  url: $('[data-submit-url]').data('submit-url'),
@@ -29,7 +32,7 @@ SolidusStripe.CartPageCheckout.prototype.submitPayment = function(payment) {
29
32
  },
30
33
  type: 'PATCH',
31
34
  contentType: 'application/json',
32
- data: JSON.stringify(this.prTokenHandler(payment.paymentMethod)),
35
+ data: JSON.stringify(prTokenHandler(payment.paymentMethod)),
33
36
  success: function() {
34
37
  window.location = $('[data-complete-url]').data('complete-url');
35
38
  },
@@ -39,26 +42,56 @@ SolidusStripe.CartPageCheckout.prototype.submitPayment = function(payment) {
39
42
  });
40
43
  };
41
44
 
42
- SolidusStripe.CartPageCheckout.prototype.onPrPayment = function(result) {
43
- var handleServerResponse = this.handleServerResponse.bind(this);
45
+ SolidusStripe.CartPageCheckout.prototype.onPrPayment = function(payment) {
46
+ var createIntent = this.createIntent.bind(this);
44
47
 
45
48
  fetch('/stripe/update_order', {
46
49
  method: 'POST',
47
50
  headers: { 'Content-Type': 'application/json' },
48
51
  body: JSON.stringify({
49
- shipping_address: result.shippingAddress,
50
- shipping_option: result.shippingOption,
51
- email: result.payerEmail,
52
- name: result.payerName,
52
+ shipping_address: payment.shippingAddress,
53
+ shipping_option: payment.shippingOption,
54
+ email: payment.payerEmail,
55
+ name: payment.payerName,
53
56
  authenticity_token: this.authToken
54
57
  })
55
58
  }).then(function(response) {
56
59
  response.json().then(function(json) {
57
- handleServerResponse(json, result);
60
+ createIntent(json, payment);
58
61
  })
59
62
  });
60
63
  };
61
64
 
65
+ SolidusStripe.CartPageCheckout.prototype.createIntent = function(result, payment) {
66
+ var handleServerResponse = this.handleServerResponse.bind(this);
67
+
68
+ if (result.error) {
69
+ this.completePaymentRequest(payment, 'fail');
70
+ this.showError(result.error);
71
+ } else {
72
+ if (payment.error) {
73
+ this.showError(payment.error.message);
74
+ } else {
75
+ fetch('/stripe/create_intent', {
76
+ method: 'POST',
77
+ headers: {
78
+ 'Content-Type': 'application/json'
79
+ },
80
+ body: JSON.stringify({
81
+ form_data: payment.shippingAddress,
82
+ spree_payment_method_id: this.config.id,
83
+ stripe_payment_method_id: payment.paymentMethod.id,
84
+ authenticity_token: this.authToken
85
+ })
86
+ }).then(function(response) {
87
+ response.json().then(function(result) {
88
+ handleServerResponse(result, payment)
89
+ })
90
+ });
91
+ }
92
+ }
93
+ };
94
+
62
95
  SolidusStripe.CartPageCheckout.prototype.onPrButtonMounted = function(buttonId, success) {
63
96
  var container = document.getElementById(buttonId).parentElement;
64
97
 
@@ -21,7 +21,7 @@ SolidusStripe.PaymentIntents.prototype.onPrPayment = function(payment) {
21
21
  var that = this;
22
22
 
23
23
  this.elementsTokenHandler(payment.paymentMethod);
24
- fetch('/stripe/confirm_intents', {
24
+ fetch('/stripe/create_intent', {
25
25
  method: 'POST',
26
26
  headers: {
27
27
  'Content-Type': 'application/json'
@@ -64,7 +64,7 @@ SolidusStripe.PaymentIntents.prototype.onIntentsPayment = function(payment) {
64
64
  var that = this;
65
65
 
66
66
  this.elementsTokenHandler(payment.paymentMethod);
67
- fetch('/stripe/confirm_intents', {
67
+ fetch('/stripe/create_intent', {
68
68
  method: 'POST',
69
69
  headers: {
70
70
  'Content-Type': 'application/json'
@@ -83,37 +83,73 @@
83
83
  this.showError(response.error);
84
84
  this.completePaymentRequest(payment, 'fail');
85
85
  } else if (response.requires_action) {
86
- this.stripe.handleCardAction(
87
- response.stripe_payment_intent_client_secret
88
- ).then(this.onIntentsClientSecret.bind(this));
86
+ var clientSecret = response.stripe_payment_intent_client_secret;
87
+ var onConfirmCardPayment = this.onConfirmCardPayment.bind(this);
88
+
89
+ this.stripe.confirmCardPayment(
90
+ clientSecret,
91
+ {payment_method: payment.paymentMethod.id},
92
+ {handleActions: false}
93
+ ).then(function(confirmResult) {
94
+ onConfirmCardPayment(confirmResult, payment, clientSecret)
95
+ });
89
96
  } else {
90
- this.completePaymentRequest(payment, 'success');
91
- this.submitPayment(payment);
97
+ this.completePayment(payment, response.stripe_payment_intent_id)
92
98
  }
93
99
  },
94
100
 
95
- onIntentsClientSecret: function(result) {
96
- if (result.error) {
97
- this.showError(result.error);
101
+ onConfirmCardPayment: function(confirmResult, payment, clientSecret) {
102
+ onStripeResponse = function(response, payment) {
103
+ if (response.error) {
104
+ this.showError(response.error);
105
+ } else {
106
+ this.completePayment(payment, response.paymentIntent.id)
107
+ }
108
+ }.bind(this);
109
+
110
+ if (confirmResult.error) {
111
+ this.completePaymentRequest(payment, 'fail');
112
+ this.showError(confirmResult.error);
98
113
  } else {
99
- fetch('/stripe/confirm_intents', {
100
- method: 'POST',
101
- headers: { 'Content-Type': 'application/json' },
102
- body: JSON.stringify({
103
- form_data: this.form.serialize(),
104
- spree_payment_method_id: this.config.id,
105
- stripe_payment_intent_id: result.paymentIntent.id,
106
- authenticity_token: this.authToken
107
- })
108
- }).then(function(confirmResult) {
109
- return confirmResult.json();
110
- }).then(this.handleServerResponse.bind(this));
114
+ this.completePaymentRequest(payment, 'success');
115
+ this.stripe.confirmCardPayment(clientSecret).then(function(response) {
116
+ onStripeResponse(response, payment);
117
+ });
111
118
  }
112
119
  },
113
120
 
121
+ completePayment: function(payment, stripePaymentIntentId) {
122
+ var onCreateBackendPayment = function (response) {
123
+ if (response.error) {
124
+ this.completePaymentRequest(payment, 'fail');
125
+ this.showError(response.error);
126
+ } else {
127
+ this.completePaymentRequest(payment, 'success');
128
+ this.submitPayment(payment);
129
+ }
130
+ }.bind(this);
131
+
132
+ fetch('/stripe/create_payment', {
133
+ method: 'POST',
134
+ headers: { 'Content-Type': 'application/json' },
135
+ body: JSON.stringify({
136
+ form_data: this.form ? this.form.serialize() : payment.shippingAddress,
137
+ spree_payment_method_id: this.config.id,
138
+ stripe_payment_intent_id: stripePaymentIntentId,
139
+ authenticity_token: this.authToken
140
+ })
141
+ }).then(function(solidusPaymentResponse) {
142
+ return solidusPaymentResponse.json();
143
+ }).then(onCreateBackendPayment)
144
+ },
145
+
114
146
  completePaymentRequest: function(payment, state) {
115
147
  if (payment && typeof payment.complete === 'function') {
116
148
  payment.complete(state);
149
+ if (state === 'fail') {
150
+ // restart the button (required in order to force address choice)
151
+ new SolidusStripe.CartPageCheckout().init();
152
+ }
117
153
  }
118
154
  }
119
155
  };
@@ -4,15 +4,9 @@ module SolidusStripe
4
4
  class IntentsController < Spree::BaseController
5
5
  include Spree::Core::ControllerHelpers::Order
6
6
 
7
- def confirm
7
+ def create_intent
8
8
  begin
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
15
- end
9
+ @intent = create_payment_intent
16
10
  rescue Stripe::CardError => e
17
11
  render json: { error: e.message }, status: 500
18
12
  return
@@ -21,6 +15,20 @@ module SolidusStripe
21
15
  generate_payment_response
22
16
  end
23
17
 
18
+ def create_payment
19
+ create_payment_service = SolidusStripe::CreateIntentsPaymentService.new(
20
+ params[:stripe_payment_intent_id],
21
+ stripe,
22
+ self
23
+ )
24
+
25
+ if create_payment_service.call
26
+ render json: { success: true }
27
+ else
28
+ render json: { error: "Could not create payment" }, status: 500
29
+ end
30
+ end
31
+
24
32
  private
25
33
 
26
34
  def stripe
@@ -37,20 +45,23 @@ module SolidusStripe
37
45
  stripe_payment_intent_client_secret: response['client_secret']
38
46
  }
39
47
  elsif response['status'] == 'requires_capture'
40
- SolidusStripe::CreateIntentsOrderService.new(@intent, stripe, self).call
41
- render json: { success: true }
48
+ render json: {
49
+ success: true,
50
+ requires_capture: true,
51
+ stripe_payment_intent_id: response['id']
52
+ }
42
53
  else
43
54
  render json: { error: response['error']['message'] }, status: 500
44
55
  end
45
56
  end
46
57
 
47
- def create_intent
58
+ def create_payment_intent
48
59
  stripe.create_intent(
49
60
  (current_order.total * 100).to_i,
50
61
  params[:stripe_payment_method_id],
51
62
  description: "Solidus Order ID: #{current_order.number} (pending)",
52
63
  currency: current_order.currency,
53
- confirmation_method: 'manual',
64
+ confirmation_method: 'automatic',
54
65
  capture_method: 'manual',
55
66
  confirm: true,
56
67
  setup_future_usage: 'off_session',
@@ -4,7 +4,7 @@ module SolidusStripe
4
4
  class AddressFromParamsService
5
5
  attr_reader :address_params, :user
6
6
 
7
- def initialize(address_params, user)
7
+ def initialize(address_params, user = nil)
8
8
  @address_params, @user = address_params, user
9
9
  end
10
10
 
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusStripe
4
+ class CreateIntentsPaymentService
5
+ attr_reader :intent_id, :stripe, :controller
6
+
7
+ delegate :request, :current_order, :params, to: :controller
8
+
9
+ def initialize(intent_id, stripe, controller)
10
+ @intent_id, @stripe, @controller = intent_id, stripe, controller
11
+ end
12
+
13
+ def call
14
+ invalidate_previous_payment_intents_payments
15
+ if (payment = create_payment)
16
+ description = "Solidus Order ID: #{payment.gateway_order_identifier}"
17
+ stripe.update_intent(nil, intent_id, nil, description: description)
18
+ true
19
+ else
20
+ invalidate_current_payment_intent
21
+ false
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def intent
28
+ @intent ||= stripe.show_intent(intent_id, {})
29
+ end
30
+
31
+ def invalidate_current_payment_intent
32
+ stripe.cancel(intent_id)
33
+ end
34
+
35
+ def invalidate_previous_payment_intents_payments
36
+ if stripe.v3_intents?
37
+ current_order.payments.pending.where(payment_method: stripe).each(&:void_transaction!)
38
+ end
39
+ end
40
+
41
+ def create_payment
42
+ Spree::OrderUpdateAttributes.new(
43
+ current_order,
44
+ payment_params,
45
+ request_env: request.headers.env
46
+ ).apply
47
+
48
+ created_payment = Spree::Payment.find_by(response_code: intent_id)
49
+ created_payment&.tap { |payment| payment.update!(state: :pending) }
50
+ end
51
+
52
+ def payment_params
53
+ {
54
+ payments_attributes: [{
55
+ payment_method_id: stripe.id,
56
+ amount: current_order.total,
57
+ response_code: intent_id,
58
+ source_attributes: {
59
+ month: intent_card['exp_month'],
60
+ year: intent_card['exp_year'],
61
+ cc_type: intent_card['brand'],
62
+ last_digits: intent_card['last4'],
63
+ gateway_payment_profile_id: intent_customer_profile,
64
+ name: address_full_name,
65
+ address_attributes: address_attributes
66
+ }
67
+ }]
68
+ }
69
+ end
70
+
71
+ def intent_card
72
+ intent.params['charges']['data'][0]['payment_method_details']['card']
73
+ end
74
+
75
+ def intent_customer_profile
76
+ intent.params['payment_method']
77
+ end
78
+
79
+ def form_data
80
+ params[:form_data]
81
+ end
82
+
83
+ def address_attributes
84
+ if form_data.is_a?(String)
85
+ data = Rack::Utils.parse_nested_query(form_data)
86
+ data['payment_source'][stripe.id.to_s]['address_attributes']
87
+ else
88
+ SolidusStripe::AddressFromParamsService.new(form_data).call.attributes
89
+ end
90
+ end
91
+
92
+ def address_full_name
93
+ current_order.bill_address&.full_name || form_data[:recipient]
94
+ end
95
+
96
+ def update_stripe_payment_description
97
+ description = "Solidus Order ID: #{payment.gateway_order_identifier}"
98
+ stripe.update_intent(nil, intent_id, nil, description: description)
99
+ end
100
+ end
101
+ end
@@ -15,7 +15,7 @@ module Spree
15
15
  'Visa' => 'visa'
16
16
  }
17
17
 
18
- delegate :create_intent, :update_intent, :confirm_intent, to: :gateway
18
+ delegate :create_intent, :update_intent, :confirm_intent, :show_intent, to: :gateway
19
19
 
20
20
  def stripe_config(order)
21
21
  {
@@ -3,7 +3,8 @@ Spree::Core::Engine.routes.draw do
3
3
  post '/stripe/confirm_payment', to: 'stripe#confirm_payment'
4
4
 
5
5
  # payment intents routes:
6
- post '/stripe/confirm_intents', to: '/solidus_stripe/intents#confirm'
6
+ post '/stripe/create_intent', to: '/solidus_stripe/intents#create_intent'
7
+ post '/stripe/create_payment', to: '/solidus_stripe/intents#create_payment'
7
8
 
8
9
  # payment request routes:
9
10
  post '/stripe/shipping_rates', to: '/solidus_stripe/payment_request#shipping_rates'
@@ -4,7 +4,7 @@ require 'spree/core'
4
4
 
5
5
  module SolidusStripe
6
6
  class Engine < Rails::Engine
7
- include SolidusSupport::EngineExtensions::Decorators
7
+ include SolidusSupport::EngineExtensions
8
8
 
9
9
  isolate_namespace Spree
10
10
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SolidusStripe
4
- VERSION = "3.2.1"
4
+ VERSION = "4.0.0"
5
5
  end
@@ -29,7 +29,7 @@ Gem::Specification.new do |s|
29
29
  s.executables = s.files.grep(%r{^exe/}) { |f| File.basename(f) }
30
30
 
31
31
  s.add_dependency 'solidus_core', ['>= 2.3', '< 3']
32
- s.add_dependency 'solidus_support', '~> 0.4'
32
+ s.add_dependency 'solidus_support', '~> 0.5'
33
33
  # ActiveMerchant v1.58 through v1.59 introduced a breaking change
34
34
  # to the stripe gateway.
35
35
  #
@@ -487,11 +487,9 @@ RSpec.describe "Stripe checkout", type: :feature do
487
487
  end
488
488
 
489
489
  def within_3d_secure_modal
490
- within_frame "__privateStripeFrame10" do
490
+ within_frame "__privateStripeFrame11" do
491
491
  within_frame "__stripeJSChallengeFrame" do
492
- within_frame "acsFrame" do
493
- yield
494
- end
492
+ yield
495
493
  end
496
494
  end
497
495
  end
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe SolidusStripe::CreateIntentsPaymentService do
6
+ let(:service) { described_class.new(intent_id, stripe, controller) }
7
+
8
+ let(:stripe) {
9
+ Spree::PaymentMethod::StripeCreditCard.create!(
10
+ name: "Stripe",
11
+ preferred_secret_key: "sk_test_VCZnDv3GLU15TRvn8i2EsaAN",
12
+ preferred_publishable_key: "pk_test_Cuf0PNtiAkkMpTVC2gwYDMIg",
13
+ preferred_v3_elements: false,
14
+ preferred_v3_intents: true
15
+ )
16
+ }
17
+
18
+ let(:order) { create :order, state: :payment, total: 19.99 }
19
+
20
+ let(:intent_id) { "pi_123123ABC" }
21
+ let(:controller) { double(current_order: order.reload, params: params, request: spy) }
22
+
23
+ let(:params) do
24
+ {
25
+ spree_payment_method_id: stripe.id,
26
+ stripe_payment_intent_id: intent_id,
27
+ form_data: {
28
+ addressLine: ["31 Cotton Rd"],
29
+ city: "San Diego",
30
+ country: "US",
31
+ phone: "+188836412312",
32
+ postalCode: "12345",
33
+ recipient: "James Edwards",
34
+ region: "CA"
35
+ }
36
+ }
37
+ end
38
+
39
+ let(:intent) do
40
+ double(params: {
41
+ "id" => intent_id,
42
+ "charges" => {
43
+ "data" => [{
44
+ "payment_method_details" => {
45
+ "card" => {
46
+ "brand" => "visa",
47
+ "exp_month" => 1,
48
+ "exp_year" => 2022,
49
+ "last4" => "4242"
50
+ },
51
+ }
52
+ }]
53
+ }
54
+ })
55
+ end
56
+
57
+ describe '#call' do
58
+ subject { service.call }
59
+
60
+ before do
61
+ allow(stripe).to receive(:show_intent) { intent }
62
+ allow_any_instance_of(Spree::CreditCard).to receive(:require_card_numbers?) { false }
63
+ allow_any_instance_of(Spree::PaymentMethod::StripeCreditCard).to receive(:create_profile) { true }
64
+ end
65
+
66
+ it { expect(subject).to be true }
67
+
68
+ it "creates a new pending payment" do
69
+ expect { subject }.to change { order.payments.count }
70
+ expect(order.payments.last.reload).to be_pending
71
+ end
72
+
73
+ context "when for any reason the payment could not be created" do
74
+ before { params[:form_data].delete(:city) }
75
+
76
+ it "returns false" do
77
+ expect(subject).to be false
78
+ end
79
+ end
80
+
81
+ context "when there are previous pending payments" do
82
+ let!(:payment) do
83
+ create(:payment, order: order).tap do |payment|
84
+ payment.update!(state: :pending)
85
+ end
86
+ end
87
+
88
+ before do
89
+ response = double(success?: true, authorization: payment.response_code)
90
+ allow_any_instance_of(Spree::PaymentMethod::StripeCreditCard).to receive(:void) { response }
91
+ end
92
+
93
+ context "when one of them is a Payment Intent" do
94
+ before do
95
+ payment.update!(payment_method: stripe)
96
+ payment.source.update!(payment_method: stripe)
97
+ end
98
+
99
+ it "invalidates it" do
100
+ expect { subject }.to change { payment.reload.state }.to 'void'
101
+ end
102
+ end
103
+
104
+ context "when none is a Payment Intent" do
105
+ it "does not invalidate them" do
106
+ expect { subject }.not_to change { payment.reload.state }
107
+ end
108
+ end
109
+ end
110
+ end
111
+ 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.2.1
4
+ version: 4.0.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-06-29 00:00:00.000000000 Z
11
+ date: 2020-04-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: solidus_core
@@ -36,14 +36,14 @@ dependencies:
36
36
  requirements:
37
37
  - - "~>"
38
38
  - !ruby/object:Gem::Version
39
- version: '0.4'
39
+ version: '0.5'
40
40
  type: :runtime
41
41
  prerelease: false
42
42
  version_requirements: !ruby/object:Gem::Requirement
43
43
  requirements:
44
44
  - - "~>"
45
45
  - !ruby/object:Gem::Version
46
- version: '0.4'
46
+ version: '0.5'
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: activemerchant
49
49
  requirement: !ruby/object:Gem::Requirement
@@ -104,7 +104,7 @@ files:
104
104
  - app/decorators/models/spree/order_update_attributes_decorator.rb
105
105
  - app/decorators/models/spree/payment_decorator.rb
106
106
  - app/models/solidus_stripe/address_from_params_service.rb
107
- - app/models/solidus_stripe/create_intents_order_service.rb
107
+ - app/models/solidus_stripe/create_intents_payment_service.rb
108
108
  - app/models/solidus_stripe/prepare_order_for_payment_service.rb
109
109
  - app/models/solidus_stripe/shipping_rates_service.rb
110
110
  - app/models/spree/payment_method/stripe_credit_card.rb
@@ -135,6 +135,7 @@ files:
135
135
  - solidus_stripe.gemspec
136
136
  - spec/features/stripe_checkout_spec.rb
137
137
  - spec/models/solidus_stripe/address_from_params_service_spec.rb
138
+ - spec/models/solidus_stripe/create_intents_payment_service_spec.rb
138
139
  - spec/models/solidus_stripe/prepare_order_for_payment_service_spec.rb
139
140
  - spec/models/solidus_stripe/shipping_rates_service_spec.rb
140
141
  - spec/models/spree/payment_method/stripe_credit_card_spec.rb
@@ -162,13 +163,15 @@ required_rubygems_version: !ruby/object:Gem::Requirement
162
163
  version: '0'
163
164
  requirements:
164
165
  - none
165
- rubygems_version: 3.0.3
166
+ rubyforge_project:
167
+ rubygems_version: 2.7.10
166
168
  signing_key:
167
169
  specification_version: 4
168
170
  summary: Stripe Payment Method for Solidus
169
171
  test_files:
170
172
  - spec/features/stripe_checkout_spec.rb
171
173
  - spec/models/solidus_stripe/address_from_params_service_spec.rb
174
+ - spec/models/solidus_stripe/create_intents_payment_service_spec.rb
172
175
  - spec/models/solidus_stripe/prepare_order_for_payment_service_spec.rb
173
176
  - spec/models/solidus_stripe/shipping_rates_service_spec.rb
174
177
  - spec/models/spree/payment_method/stripe_credit_card_spec.rb
@@ -1,70 +0,0 @@
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