solidus_stripe 3.0.0 → 4.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 +4 -4
- data/.gitignore +1 -0
- data/.rubocop.yml +5 -0
- data/CHANGELOG.md +55 -0
- data/Gemfile +7 -0
- data/LICENSE +2 -2
- data/README.md +69 -11
- data/Rakefile +1 -1
- data/app/assets/javascripts/spree/frontend/solidus_stripe/stripe-cart-page-checkout.js +42 -9
- data/app/assets/javascripts/spree/frontend/solidus_stripe/stripe-elements.js +61 -25
- data/app/assets/javascripts/spree/frontend/solidus_stripe/stripe-payment-intents.js +4 -2
- data/app/assets/javascripts/spree/frontend/solidus_stripe/stripe-payment-request-button-shared.js +56 -19
- data/app/assets/javascripts/spree/frontend/solidus_stripe/stripe-payment.js +7 -1
- data/app/controllers/solidus_stripe/intents_controller.rb +42 -28
- data/app/decorators/models/spree/order_update_attributes_decorator.rb +39 -0
- data/app/decorators/models/spree/payment_decorator.rb +11 -0
- data/app/decorators/models/spree/refund_decorator.rb +9 -0
- data/app/models/solidus_stripe/address_from_params_service.rb +5 -2
- data/app/models/solidus_stripe/create_intents_payment_service.rb +113 -0
- data/app/models/spree/payment_method/stripe_credit_card.rb +19 -9
- data/bin/r +13 -0
- data/bin/rake +7 -0
- data/bin/sandbox +84 -0
- data/bin/sandbox_rails +18 -0
- data/bin/setup +1 -1
- data/config/routes.rb +4 -1
- data/lib/generators/solidus_stripe/install/install_generator.rb +7 -3
- data/lib/solidus_stripe/engine.rb +2 -2
- data/lib/solidus_stripe/factories.rb +4 -0
- data/lib/solidus_stripe/version.rb +1 -1
- data/lib/views/frontend/spree/checkout/payment/v3/_form_elements.html.erb +0 -1
- data/solidus_stripe.gemspec +34 -37
- data/spec/features/stripe_checkout_spec.rb +159 -64
- data/spec/models/solidus_stripe/address_from_params_service_spec.rb +19 -5
- data/spec/models/solidus_stripe/create_intents_payment_service_spec.rb +127 -0
- data/spec/models/spree/payment_method/stripe_credit_card_spec.rb +44 -1
- data/spec/spec_helper.rb +4 -1
- metadata +22 -12
- data/LICENSE.md +0 -26
data/app/assets/javascripts/spree/frontend/solidus_stripe/stripe-payment-request-button-shared.js
CHANGED
@@ -83,36 +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
|
-
spree_payment_method_id: this.config.id,
|
104
|
-
stripe_payment_intent_id: result.paymentIntent.id,
|
105
|
-
authenticity_token: this.authToken
|
106
|
-
})
|
107
|
-
}).then(function(confirmResult) {
|
108
|
-
return confirmResult.json();
|
109
|
-
}).then(this.handleServerResponse.bind(this));
|
114
|
+
this.completePaymentRequest(payment, 'success');
|
115
|
+
this.stripe.confirmCardPayment(clientSecret).then(function(response) {
|
116
|
+
onStripeResponse(response, payment);
|
117
|
+
});
|
110
118
|
}
|
111
119
|
},
|
112
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
|
+
|
113
146
|
completePaymentRequest: function(payment, state) {
|
114
147
|
if (payment && typeof payment.complete === 'function') {
|
115
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
|
+
}
|
116
153
|
}
|
117
154
|
}
|
118
155
|
};
|
@@ -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(
|
9
|
+
this.elements = this.stripe.elements(this.elementsBaseOptions());
|
10
|
+
};
|
11
|
+
|
12
|
+
SolidusStripe.Payment.prototype.elementsBaseOptions = function () {
|
13
|
+
return {
|
14
|
+
locale: 'en'
|
15
|
+
};
|
10
16
|
};
|
@@ -4,27 +4,23 @@ module SolidusStripe
|
|
4
4
|
class IntentsController < Spree::BaseController
|
5
5
|
include Spree::Core::ControllerHelpers::Order
|
6
6
|
|
7
|
-
def
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
)
|
19
|
-
elsif params[:stripe_payment_intent_id].present?
|
20
|
-
intent = stripe.confirm_intent(params[:stripe_payment_intent_id], nil)
|
21
|
-
end
|
22
|
-
rescue Stripe::CardError => e
|
23
|
-
render json: { error: e.message }, status: 500
|
24
|
-
return
|
25
|
-
end
|
7
|
+
def create_intent
|
8
|
+
@intent = create_payment_intent
|
9
|
+
generate_payment_response
|
10
|
+
end
|
11
|
+
|
12
|
+
def create_payment
|
13
|
+
create_payment_service = SolidusStripe::CreateIntentsPaymentService.new(
|
14
|
+
params[:stripe_payment_intent_id],
|
15
|
+
stripe,
|
16
|
+
self
|
17
|
+
)
|
26
18
|
|
27
|
-
|
19
|
+
if create_payment_service.call
|
20
|
+
render json: { success: true }
|
21
|
+
else
|
22
|
+
render json: { error: "Could not create payment" }, status: 500
|
23
|
+
end
|
28
24
|
end
|
29
25
|
|
30
26
|
private
|
@@ -33,20 +29,38 @@ module SolidusStripe
|
|
33
29
|
@stripe ||= Spree::PaymentMethod::StripeCreditCard.find(params[:spree_payment_method_id])
|
34
30
|
end
|
35
31
|
|
36
|
-
def generate_payment_response
|
37
|
-
response = intent.params
|
32
|
+
def generate_payment_response
|
33
|
+
response = @intent.params
|
38
34
|
# Note that if your API version is before 2019-02-11, 'requires_action'
|
39
35
|
# appears as 'requires_source_action'.
|
40
36
|
if %w[requires_source_action requires_action].include?(response['status']) && response['next_action']['type'] == 'use_stripe_sdk'
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
elsif response['status'] == '
|
46
|
-
render json: {
|
37
|
+
render json: {
|
38
|
+
requires_action: true,
|
39
|
+
stripe_payment_intent_client_secret: response['client_secret']
|
40
|
+
}
|
41
|
+
elsif response['status'] == 'requires_capture'
|
42
|
+
render json: {
|
43
|
+
success: true,
|
44
|
+
requires_capture: true,
|
45
|
+
stripe_payment_intent_id: response['id']
|
46
|
+
}
|
47
47
|
else
|
48
48
|
render json: { error: response['error']['message'] }, status: 500
|
49
49
|
end
|
50
50
|
end
|
51
|
+
|
52
|
+
def create_payment_intent
|
53
|
+
stripe.create_intent(
|
54
|
+
(current_order.total * 100).to_i,
|
55
|
+
params[:stripe_payment_method_id],
|
56
|
+
description: "Solidus Order ID: #{current_order.number} (pending)",
|
57
|
+
currency: current_order.currency,
|
58
|
+
confirmation_method: 'automatic',
|
59
|
+
capture_method: 'manual',
|
60
|
+
confirm: true,
|
61
|
+
setup_future_usage: 'off_session',
|
62
|
+
metadata: { order_id: current_order.id }
|
63
|
+
)
|
64
|
+
end
|
51
65
|
end
|
52
66
|
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
|
@@ -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
|
|
@@ -43,7 +43,10 @@ module SolidusStripe
|
|
43
43
|
end
|
44
44
|
|
45
45
|
def state
|
46
|
-
@state ||=
|
46
|
+
@state ||= begin
|
47
|
+
region = address_params[:region]
|
48
|
+
country.states.find_by(abbr: region) || country.states.find_by(name: region)
|
49
|
+
end
|
47
50
|
end
|
48
51
|
|
49
52
|
def default_attributes
|
@@ -0,0 +1,113 @@
|
|
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: card_holder_name || address_full_name,
|
65
|
+
address_attributes: address_attributes
|
66
|
+
}
|
67
|
+
}]
|
68
|
+
}
|
69
|
+
end
|
70
|
+
|
71
|
+
def intent_card
|
72
|
+
intent_data['payment_method_details']['card']
|
73
|
+
end
|
74
|
+
|
75
|
+
def intent_customer_profile
|
76
|
+
intent.params['payment_method']
|
77
|
+
end
|
78
|
+
|
79
|
+
def card_holder_name
|
80
|
+
(html_payment_source_data['name'] || intent_data['billing_details']['name']).presence
|
81
|
+
end
|
82
|
+
|
83
|
+
def intent_data
|
84
|
+
intent.params['charges']['data'][0]
|
85
|
+
end
|
86
|
+
|
87
|
+
def form_data
|
88
|
+
params[:form_data]
|
89
|
+
end
|
90
|
+
|
91
|
+
def html_payment_source_data
|
92
|
+
if form_data.is_a?(String)
|
93
|
+
data = Rack::Utils.parse_nested_query(form_data)
|
94
|
+
data['payment_source'][stripe.id.to_s]
|
95
|
+
else
|
96
|
+
{}
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def address_attributes
|
101
|
+
html_payment_source_data['address_attributes'] || SolidusStripe::AddressFromParamsService.new(form_data).call.attributes
|
102
|
+
end
|
103
|
+
|
104
|
+
def address_full_name
|
105
|
+
current_order.bill_address&.full_name || form_data[:recipient]
|
106
|
+
end
|
107
|
+
|
108
|
+
def update_stripe_payment_description
|
109
|
+
description = "Solidus Order ID: #{payment.gateway_order_identifier}"
|
110
|
+
stripe.update_intent(nil, intent_id, nil, description: description)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -15,6 +15,8 @@ module Spree
|
|
15
15
|
'Visa' => 'visa'
|
16
16
|
}
|
17
17
|
|
18
|
+
delegate :create_intent, :update_intent, :confirm_intent, :show_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
|
@@ -87,6 +81,21 @@ module Spree
|
|
87
81
|
gateway.void(response_code, {})
|
88
82
|
end
|
89
83
|
|
84
|
+
def payment_intents_refund_reason
|
85
|
+
Spree::RefundReason.where(name: Spree::Payment::Cancellation::DEFAULT_REASON).first_or_create
|
86
|
+
end
|
87
|
+
|
88
|
+
def try_void(payment)
|
89
|
+
if v3_intents? && payment.completed?
|
90
|
+
payment.refunds.create!(
|
91
|
+
amount: payment.credit_allowed,
|
92
|
+
reason: payment_intents_refund_reason
|
93
|
+
).response
|
94
|
+
else
|
95
|
+
payment.void_transaction!
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
90
99
|
def cancel(response_code)
|
91
100
|
gateway.void(response_code, {})
|
92
101
|
end
|
@@ -140,8 +149,9 @@ module Spree
|
|
140
149
|
|
141
150
|
def options_for_purchase_or_auth(money, creditcard, transaction_options)
|
142
151
|
options = {}
|
143
|
-
options[:description] = "
|
152
|
+
options[:description] = "Solidus Order ID: #{transaction_options[:order_id]}"
|
144
153
|
options[:currency] = transaction_options[:currency]
|
154
|
+
options[:off_session] = true if v3_intents?
|
145
155
|
|
146
156
|
if customer = creditcard.gateway_customer_profile_id
|
147
157
|
options[:customer] = customer
|