solidus_stripe 3.2.1 → 4.0.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 +4 -4
- data/CHANGELOG.md +2 -0
- data/README.md +22 -3
- data/app/assets/javascripts/spree/frontend/solidus_stripe/stripe-cart-page-checkout.js +42 -9
- data/app/assets/javascripts/spree/frontend/solidus_stripe/stripe-payment-intents.js +2 -2
- data/app/assets/javascripts/spree/frontend/solidus_stripe/stripe-payment-request-button-shared.js +56 -20
- data/app/controllers/solidus_stripe/intents_controller.rb +23 -12
- data/app/models/solidus_stripe/address_from_params_service.rb +1 -1
- data/app/models/solidus_stripe/create_intents_payment_service.rb +101 -0
- data/app/models/spree/payment_method/stripe_credit_card.rb +1 -1
- data/config/routes.rb +2 -1
- data/lib/solidus_stripe/engine.rb +1 -1
- data/lib/solidus_stripe/version.rb +1 -1
- data/solidus_stripe.gemspec +1 -1
- data/spec/features/stripe_checkout_spec.rb +2 -4
- data/spec/models/solidus_stripe/create_intents_payment_service_spec.rb +111 -0
- metadata +9 -6
- data/app/models/solidus_stripe/create_intents_order_service.rb +0 -70
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 38e979d7eecd9e20f5d763ad5e2bec33312f0877b2cec287604801b483dcb006
|
4
|
+
data.tar.gz: 19a4708081571dbba8c34c7d0173c1a1704ff12df54902e488add9572c4a9080
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6cf3fd35258a8e0c300d0b2ca18d131d9ee3e2b74cc8522e911c8fe7a94fa7e987b66c445d52dace4df580ecf3c54eeec14be421f4f06d1e823d7705eb6c5ae3
|
7
|
+
data.tar.gz: d868652e26e2c4c3813e1f2438c4e2cbedc3f2e4de51a21e7eaafdb1e8fe37d35c4152e1e1ce215ba550be5a1b3d4caa15f4d4dc23f51446708f49c0d21c3f4d
|
data/CHANGELOG.md
CHANGED
@@ -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
|
-
|
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(
|
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(
|
43
|
-
var
|
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:
|
50
|
-
shipping_option:
|
51
|
-
email:
|
52
|
-
name:
|
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
|
-
|
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/
|
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/
|
67
|
+
fetch('/stripe/create_intent', {
|
68
68
|
method: 'POST',
|
69
69
|
headers: {
|
70
70
|
'Content-Type': 'application/json'
|
data/app/assets/javascripts/spree/frontend/solidus_stripe/stripe-payment-request-button-shared.js
CHANGED
@@ -83,37 +83,73 @@
|
|
83
83
|
this.showError(response.error);
|
84
84
|
this.completePaymentRequest(payment, 'fail');
|
85
85
|
} else if (response.requires_action) {
|
86
|
-
|
87
|
-
|
88
|
-
|
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.
|
91
|
-
this.submitPayment(payment);
|
97
|
+
this.completePayment(payment, response.stripe_payment_intent_id)
|
92
98
|
}
|
93
99
|
},
|
94
100
|
|
95
|
-
|
96
|
-
|
97
|
-
|
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
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
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
|
7
|
+
def create_intent
|
8
8
|
begin
|
9
|
-
@intent =
|
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
|
-
|
41
|
-
|
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
|
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: '
|
64
|
+
confirmation_method: 'automatic',
|
54
65
|
capture_method: 'manual',
|
55
66
|
confirm: true,
|
56
67
|
setup_future_usage: 'off_session',
|
@@ -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
|
data/config/routes.rb
CHANGED
@@ -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/
|
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'
|
data/solidus_stripe.gemspec
CHANGED
@@ -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.
|
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 "
|
490
|
+
within_frame "__privateStripeFrame11" do
|
491
491
|
within_frame "__stripeJSChallengeFrame" do
|
492
|
-
|
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:
|
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-
|
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.
|
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.
|
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/
|
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
|
-
|
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
|