solidus_stripe 1.0.0 → 1.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/.gem_release.yml +2 -5
- data/.github/stale.yml +17 -0
- data/.gitignore +11 -4
- data/.rspec +2 -1
- data/.rubocop.yml +2 -319
- data/Gemfile +16 -9
- data/LICENSE +26 -0
- data/README.md +58 -1
- data/Rakefile +3 -20
- data/app/assets/javascripts/solidus_stripe/stripe-init.js +1 -0
- data/app/assets/javascripts/solidus_stripe/stripe-init/base.js +180 -0
- data/app/controllers/solidus_stripe/intents_controller.rb +52 -0
- data/app/controllers/solidus_stripe/payment_request_controller.rb +42 -0
- data/app/controllers/spree/stripe_controller.rb +13 -0
- data/app/models/solidus_stripe/address_from_params_service.rb +57 -0
- data/app/models/solidus_stripe/prepare_order_for_payment_service.rb +46 -0
- data/app/models/solidus_stripe/shipping_rates_service.rb +46 -0
- data/app/models/spree/payment_method/stripe_credit_card.rb +57 -8
- data/bin/console +17 -0
- data/bin/rails +12 -4
- data/bin/setup +8 -0
- data/config/routes.rb +11 -0
- data/lib/assets/stylesheets/spree/frontend/solidus_stripe.scss +5 -0
- data/lib/generators/solidus_stripe/install/install_generator.rb +3 -13
- data/lib/solidus_stripe/engine.rb +14 -2
- data/lib/solidus_stripe/version.rb +1 -1
- data/lib/views/frontend/spree/checkout/payment/_stripe.html.erb +2 -0
- data/lib/views/frontend/spree/checkout/payment/v2/_javascript.html.erb +13 -12
- data/lib/views/frontend/spree/checkout/payment/v3/_elements_js.html.erb +28 -0
- data/lib/views/frontend/spree/checkout/payment/v3/_form_elements.html.erb +42 -0
- data/lib/views/frontend/spree/checkout/payment/v3/_intents.html.erb +5 -0
- data/lib/views/frontend/spree/checkout/payment/v3/_intents_js.html.erb +48 -0
- data/lib/views/frontend/spree/checkout/payment/v3/_stripe.html.erb +3 -131
- data/lib/views/frontend/spree/orders/_stripe_payment_request_button.html.erb +92 -0
- data/solidus_stripe.gemspec +15 -20
- data/spec/features/stripe_checkout_spec.rb +196 -35
- data/spec/models/solidus_stripe/address_from_params_service_spec.rb +62 -0
- data/spec/models/solidus_stripe/prepare_order_for_payment_service_spec.rb +65 -0
- data/spec/models/solidus_stripe/shipping_rates_service_spec.rb +54 -0
- data/spec/models/spree/payment_method/stripe_credit_card_spec.rb +44 -5
- data/spec/spec_helper.rb +9 -7
- metadata +38 -136
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SolidusStripe
|
4
|
+
class PrepareOrderForPaymentService
|
5
|
+
attr_reader :order, :address, :user, :email, :shipping_id
|
6
|
+
|
7
|
+
def initialize(address, controller)
|
8
|
+
@address = address
|
9
|
+
@order = controller.current_order
|
10
|
+
@user = controller.spree_current_user
|
11
|
+
@email = controller.params[:email]
|
12
|
+
@shipping_id = controller.params[:shipping_option][:id]
|
13
|
+
end
|
14
|
+
|
15
|
+
def call
|
16
|
+
set_order_addresses
|
17
|
+
manage_guest_checkout
|
18
|
+
advance_order_to_payment_state
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def set_shipping_rate
|
24
|
+
order.shipments.each do |shipment|
|
25
|
+
rate = shipment.shipping_rates.find_by(shipping_method: shipping_id)
|
26
|
+
shipment.selected_shipping_rate_id = rate.id
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def set_order_addresses
|
31
|
+
order.ship_address = address
|
32
|
+
order.bill_address ||= address
|
33
|
+
end
|
34
|
+
|
35
|
+
def manage_guest_checkout
|
36
|
+
order.email = email unless user
|
37
|
+
end
|
38
|
+
|
39
|
+
def advance_order_to_payment_state
|
40
|
+
while !order.payment?
|
41
|
+
set_shipping_rate if order.state == "delivery"
|
42
|
+
order.next || break
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SolidusStripe
|
4
|
+
class ShippingRatesService
|
5
|
+
attr_reader :order, :user, :shipping_address_params
|
6
|
+
|
7
|
+
def initialize(order, user, shipping_address_params)
|
8
|
+
@order = order
|
9
|
+
@user = user
|
10
|
+
@shipping_address_params = shipping_address_params
|
11
|
+
end
|
12
|
+
|
13
|
+
def call
|
14
|
+
# setting a temporary and probably incomplete address to the order
|
15
|
+
# only to calculate the available shipping options:
|
16
|
+
order.ship_address = address_from_params
|
17
|
+
|
18
|
+
available_shipping_methods.each_with_object([]) do |(id, rates), options|
|
19
|
+
options << shipping_method_data(id, rates)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def available_shipping_methods
|
26
|
+
shipments = Spree::Stock::SimpleCoordinator.new(order).shipments
|
27
|
+
all_rates = shipments.map(&:shipping_rates).flatten
|
28
|
+
|
29
|
+
all_rates.group_by(&:shipping_method_id).select do |_, rates|
|
30
|
+
rates.size == shipments.size
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def shipping_method_data(id, rates)
|
35
|
+
{
|
36
|
+
id: id.to_s,
|
37
|
+
label: Spree::ShippingMethod.find(id).name,
|
38
|
+
amount: (rates.sum(&:cost) * 100).to_i
|
39
|
+
}
|
40
|
+
end
|
41
|
+
|
42
|
+
def address_from_params
|
43
|
+
SolidusStripe::AddressFromParamsService.new(shipping_address_params, user).call
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -5,7 +5,9 @@ module Spree
|
|
5
5
|
class StripeCreditCard < Spree::PaymentMethod::CreditCard
|
6
6
|
preference :secret_key, :string
|
7
7
|
preference :publishable_key, :string
|
8
|
+
preference :stripe_country, :string
|
8
9
|
preference :v3_elements, :boolean
|
10
|
+
preference :v3_intents, :boolean
|
9
11
|
|
10
12
|
CARD_TYPE_MAPPING = {
|
11
13
|
'American Express' => 'american_express',
|
@@ -13,6 +15,22 @@ module Spree
|
|
13
15
|
'Visa' => 'visa'
|
14
16
|
}
|
15
17
|
|
18
|
+
def stripe_config(order)
|
19
|
+
{
|
20
|
+
id: id,
|
21
|
+
publishable_key: preferred_publishable_key
|
22
|
+
}.tap do |config|
|
23
|
+
config.merge!(
|
24
|
+
payment_request: {
|
25
|
+
country: preferred_stripe_country,
|
26
|
+
currency: order.currency.downcase,
|
27
|
+
label: "Payment for order #{order.number}",
|
28
|
+
amount: (order.total * 100).to_i
|
29
|
+
}
|
30
|
+
) if payment_request?
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
16
34
|
def partial_name
|
17
35
|
'stripe'
|
18
36
|
end
|
@@ -21,14 +39,34 @@ module Spree
|
|
21
39
|
!!preferred_v3_elements
|
22
40
|
end
|
23
41
|
|
42
|
+
def payment_request?
|
43
|
+
v3_intents? && preferred_stripe_country.present?
|
44
|
+
end
|
45
|
+
|
46
|
+
def v3_intents?
|
47
|
+
!!preferred_v3_intents
|
48
|
+
end
|
49
|
+
|
24
50
|
def gateway_class
|
25
|
-
|
51
|
+
if v3_intents?
|
52
|
+
ActiveMerchant::Billing::StripePaymentIntentsGateway
|
53
|
+
else
|
54
|
+
ActiveMerchant::Billing::StripeGateway
|
55
|
+
end
|
26
56
|
end
|
27
57
|
|
28
58
|
def payment_profiles_supported?
|
29
59
|
true
|
30
60
|
end
|
31
61
|
|
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
|
+
|
32
70
|
def purchase(money, creditcard, transaction_options)
|
33
71
|
gateway.purchase(*options_for_purchase_or_auth(money, creditcard, transaction_options))
|
34
72
|
end
|
@@ -63,19 +101,30 @@ module Spree
|
|
63
101
|
|
64
102
|
source = update_source!(payment.source)
|
65
103
|
if source.number.blank? && source.gateway_payment_profile_id.present?
|
66
|
-
|
104
|
+
if v3_intents?
|
105
|
+
creditcard = ActiveMerchant::Billing::StripeGateway::StripePaymentToken.new('id' => source.gateway_payment_profile_id)
|
106
|
+
else
|
107
|
+
creditcard = source.gateway_payment_profile_id
|
108
|
+
end
|
67
109
|
else
|
68
110
|
creditcard = source
|
69
111
|
end
|
70
112
|
|
71
113
|
response = gateway.store(creditcard, options)
|
72
114
|
if response.success?
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
115
|
+
if v3_intents?
|
116
|
+
payment.source.update!(
|
117
|
+
cc_type: payment.source.cc_type,
|
118
|
+
gateway_customer_profile_id: response.params['customer'],
|
119
|
+
gateway_payment_profile_id: response.params['id']
|
120
|
+
)
|
121
|
+
else
|
122
|
+
payment.source.update!(
|
123
|
+
cc_type: payment.source.cc_type,
|
124
|
+
gateway_customer_profile_id: response.params['id'],
|
125
|
+
gateway_payment_profile_id: response.params['default_source'] || response.params['default_card']
|
126
|
+
)
|
127
|
+
end
|
79
128
|
else
|
80
129
|
payment.send(:gateway_error, response.message)
|
81
130
|
end
|
data/bin/console
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# frozen_string_literal: true
|
4
|
+
|
5
|
+
require "bundler/setup"
|
6
|
+
require "solidus_stripe"
|
7
|
+
|
8
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
9
|
+
# with your gem easier. You can also use a different console, if you like.
|
10
|
+
$LOAD_PATH.unshift(*Dir["#{__dir__}/../app/*"])
|
11
|
+
|
12
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
13
|
+
# require "pry"
|
14
|
+
# Pry.start
|
15
|
+
|
16
|
+
require "irb"
|
17
|
+
IRB.start(__FILE__)
|
data/bin/rails
CHANGED
@@ -1,7 +1,15 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
|
4
|
-
ENGINE_PATH = File.expand_path('../../lib/solidus_stripe/engine', __FILE__)
|
3
|
+
# frozen_string_literal: true
|
5
4
|
|
6
|
-
|
7
|
-
|
5
|
+
app_root = 'spec/dummy'
|
6
|
+
|
7
|
+
unless File.exist? "#{app_root}/bin/rails"
|
8
|
+
system "bin/rake", app_root or begin # rubocop:disable Style/AndOr
|
9
|
+
warn "Automatic creation of the dummy app failed"
|
10
|
+
exit 1
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
Dir.chdir app_root
|
15
|
+
exec 'bin/rails', *ARGV
|
data/bin/setup
ADDED
data/config/routes.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
Spree::Core::Engine.routes.draw do
|
2
|
+
# route to a deprecated controller, will be removed in the future:
|
3
|
+
post '/stripe/confirm_payment', to: 'stripe#confirm_payment'
|
4
|
+
|
5
|
+
# payment intents routes:
|
6
|
+
post '/stripe/confirm_intents', to: '/solidus_stripe/intents#confirm'
|
7
|
+
|
8
|
+
# payment request routes:
|
9
|
+
post '/stripe/shipping_rates', to: '/solidus_stripe/payment_request#shipping_rates'
|
10
|
+
post '/stripe/update_order', to: '/solidus_stripe/payment_request#update_order'
|
11
|
+
end
|
@@ -3,9 +3,7 @@
|
|
3
3
|
module SolidusStripe
|
4
4
|
module Generators
|
5
5
|
class InstallGenerator < Rails::Generators::Base
|
6
|
-
class_option :migrate, type: :boolean, default: true
|
7
6
|
class_option :auto_run_migrations, type: :boolean, default: false
|
8
|
-
class_option :auto_run_seeds, type: :boolean, default: false
|
9
7
|
|
10
8
|
def add_stylesheets
|
11
9
|
filename = 'vendor/assets/stylesheets/spree/frontend/all.css'
|
@@ -19,10 +17,11 @@ module SolidusStripe
|
|
19
17
|
end
|
20
18
|
|
21
19
|
def run_migrations
|
22
|
-
|
20
|
+
run_migrations = options[:auto_run_migrations] || ['', 'y', 'Y'].include?(ask('Would you like to run the migrations now? [Y/n]'))
|
21
|
+
if run_migrations
|
23
22
|
run 'bundle exec rake db:migrate'
|
24
23
|
else
|
25
|
-
puts
|
24
|
+
puts 'Skipping rake db:migrate, don\'t forget to run it!' # rubocop:disable Rails/Output
|
26
25
|
end
|
27
26
|
end
|
28
27
|
|
@@ -32,15 +31,6 @@ module SolidusStripe
|
|
32
31
|
say_status :loading, 'stripe seed data'
|
33
32
|
rake('db:seed:solidus_stripe')
|
34
33
|
end
|
35
|
-
|
36
|
-
private
|
37
|
-
|
38
|
-
def running_migrations?
|
39
|
-
options.auto_run_migrations? || begin
|
40
|
-
response = ask 'Would you like to run the migrations now? [Y/n]'
|
41
|
-
['', 'y'].include? response.downcase
|
42
|
-
end
|
43
|
-
end
|
44
34
|
end
|
45
35
|
end
|
46
36
|
end
|
@@ -1,11 +1,18 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'spree/core'
|
4
|
+
|
3
5
|
module SolidusStripe
|
4
6
|
class Engine < Rails::Engine
|
7
|
+
include SolidusSupport::EngineExtensions::Decorators
|
8
|
+
|
9
|
+
isolate_namespace Spree
|
10
|
+
|
5
11
|
engine_name 'solidus_stripe'
|
6
12
|
|
7
|
-
|
8
|
-
|
13
|
+
# use rspec for tests
|
14
|
+
config.generators do |g|
|
15
|
+
g.test_framework :rspec
|
9
16
|
end
|
10
17
|
|
11
18
|
if SolidusSupport.backend_available?
|
@@ -14,10 +21,15 @@ module SolidusStripe
|
|
14
21
|
|
15
22
|
if SolidusSupport.frontend_available?
|
16
23
|
paths["app/views"] << "lib/views/frontend"
|
24
|
+
config.assets.precompile += ['solidus_stripe/stripe-init.js']
|
17
25
|
end
|
18
26
|
|
19
27
|
if SolidusSupport.api_available?
|
20
28
|
paths["app/views"] << "lib/views/api"
|
21
29
|
end
|
30
|
+
|
31
|
+
initializer "spree.payment_method.add_stripe_credit_card", after: "spree.register.payment_methods" do |app|
|
32
|
+
app.config.spree.payment_methods << Spree::PaymentMethod::StripeCreditCard
|
33
|
+
end
|
22
34
|
end
|
23
35
|
end
|
@@ -1,5 +1,7 @@
|
|
1
1
|
<% if payment_method.v3_elements? %>
|
2
2
|
<%= render 'spree/checkout/payment/v3/stripe', payment_method: payment_method %>
|
3
|
+
<% elsif payment_method.v3_intents? %>
|
4
|
+
<%= render 'spree/checkout/payment/v3/intents', payment_method: payment_method %>
|
3
5
|
<% else %>
|
4
6
|
<%= render "spree/checkout/payment/gateway", payment_method: payment_method %>
|
5
7
|
<%= render 'spree/checkout/payment/v2/javascript', payment_method: payment_method %>
|
@@ -4,7 +4,8 @@
|
|
4
4
|
</script>
|
5
5
|
|
6
6
|
<script>
|
7
|
-
|
7
|
+
window.SolidusStripe = window.SolidusStripe || {};
|
8
|
+
SolidusStripe.paymentMethod = $('#payment_method_' + <%= payment_method.id %>);
|
8
9
|
var mapCC, stripeResponseHandler;
|
9
10
|
|
10
11
|
mapCC = function(ccType) {
|
@@ -29,25 +30,25 @@
|
|
29
30
|
$('#stripeError').html(response.error.message);
|
30
31
|
return $('#stripeError').show();
|
31
32
|
} else {
|
32
|
-
|
33
|
-
|
34
|
-
|
33
|
+
SolidusStripe.paymentMethod.find('#card_number, #card_expiry, #card_code').prop("disabled", true);
|
34
|
+
SolidusStripe.paymentMethod.find(".ccType").prop("disabled", false);
|
35
|
+
SolidusStripe.paymentMethod.find(".ccType").val(mapCC(response.card.brand));
|
35
36
|
token = response['id'];
|
36
|
-
paymentMethodId =
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
return
|
37
|
+
paymentMethodId = SolidusStripe.paymentMethod.prop('id').split("_")[2];
|
38
|
+
SolidusStripe.paymentMethod.append("<input type='hidden' class='stripeToken' name='payment_source[" + paymentMethodId + "][gateway_payment_profile_id]' value='" + token + "'/>");
|
39
|
+
SolidusStripe.paymentMethod.append("<input type='hidden' class='stripeToken' name='payment_source[" + paymentMethodId + "][last_digits]' value='" + response.card.last4 + "'/>");
|
40
|
+
SolidusStripe.paymentMethod.append("<input type='hidden' class='stripeToken' name='payment_source[" + paymentMethodId + "][month]' value='" + response.card.exp_month + "'/>");
|
41
|
+
SolidusStripe.paymentMethod.append("<input type='hidden' class='stripeToken' name='payment_source[" + paymentMethodId + "][year]' value='" + response.card.exp_year + "'/>");
|
42
|
+
return SolidusStripe.paymentMethod.parents("form").get(0).submit();
|
42
43
|
}
|
43
44
|
};
|
44
45
|
|
45
46
|
$(document).ready(function() {
|
46
|
-
|
47
|
+
SolidusStripe.paymentMethod.prepend("<div id='stripeError' class='errorExplanation' style='display:none'></div>");
|
47
48
|
return $('#checkout_form_payment [data-hook=buttons]').click(function() {
|
48
49
|
var expiration, params;
|
49
50
|
$('#stripeError').hide();
|
50
|
-
if (
|
51
|
+
if (SolidusStripe.paymentMethod.is(':visible')) {
|
51
52
|
expiration = $('.cardExpiry:visible').payment('cardExpiryVal');
|
52
53
|
params = $.extend({
|
53
54
|
number: $('.cardNumber:visible').val(),
|
@@ -0,0 +1,28 @@
|
|
1
|
+
<script>
|
2
|
+
// Stripe V3 elements specific JS code
|
3
|
+
|
4
|
+
var cardNumber = initElements();
|
5
|
+
|
6
|
+
cardNumber.addEventListener('change', function(event) {
|
7
|
+
if (event.error) {
|
8
|
+
showError(event.error.message);
|
9
|
+
} else {
|
10
|
+
errorElement.hide().text('');
|
11
|
+
}
|
12
|
+
});
|
13
|
+
|
14
|
+
form.bind('submit', function(event) {
|
15
|
+
if (element.is(':visible')) {
|
16
|
+
event.preventDefault();
|
17
|
+
|
18
|
+
stripe.createToken(cardNumber).then(function(result) {
|
19
|
+
if (result.error) {
|
20
|
+
showError(result.error.message);
|
21
|
+
} else {
|
22
|
+
stripeTokenHandler(result.token);
|
23
|
+
form[0].submit();
|
24
|
+
}
|
25
|
+
});
|
26
|
+
}
|
27
|
+
});
|
28
|
+
</script>
|
@@ -0,0 +1,42 @@
|
|
1
|
+
<div id="payment-request-button" data-stripe-config="<%= payment_method.stripe_config(current_order).to_json %>"></div>
|
2
|
+
|
3
|
+
<%= image_tag 'credit_cards/credit_card.gif', id: 'credit-card-image' %>
|
4
|
+
<% param_prefix = "payment_source[#{payment_method.id}]" %>
|
5
|
+
|
6
|
+
<div class="field field-required">
|
7
|
+
<%= label_tag "name_on_card_#{payment_method.id}", t('spree.name_on_card') %>
|
8
|
+
<%= text_field_tag "#{param_prefix}[name]", "#{@order.billing_firstname} #{@order.billing_lastname}", { id: "name_on_card_#{payment_method.id}", autocomplete: "cc-name" } %>
|
9
|
+
</div>
|
10
|
+
|
11
|
+
<div class="field field-required" data-hook="card_number">
|
12
|
+
<%= label_tag "card_number", t('spree.card_number') %>
|
13
|
+
<div id="card_number"></div>
|
14
|
+
<span id="card_type" style="display:none;">
|
15
|
+
( <span id="looks_like" ><%= t('spree.card_type_is') %> <span id="type"></span></span>
|
16
|
+
<span id="unrecognized"><%= t('spree.unrecognized_card_type') %></span>
|
17
|
+
)
|
18
|
+
</span>
|
19
|
+
</div>
|
20
|
+
|
21
|
+
<div class="field field-required" data-hook="card_expiration">
|
22
|
+
<%= label_tag "card_expiry", t('spree.expiration') %>
|
23
|
+
<div id="card_expiry"></div>
|
24
|
+
</div>
|
25
|
+
|
26
|
+
<div class="field field-required" data-hook="card_code">
|
27
|
+
<%= label_tag "card_cvc", t('spree.card_code') %>
|
28
|
+
<div id="card_cvc"></div>
|
29
|
+
<%= link_to "(#{t('spree.what_is_this')})", spree.cvv_path, target: '_blank', "data-hook" => "cvv_link", id: "cvv_link" %>
|
30
|
+
</div>
|
31
|
+
|
32
|
+
<div id="card-errors" class='errorExplanation' role="alert" style="display: none"></div>
|
33
|
+
|
34
|
+
<% if @order.bill_address %>
|
35
|
+
<%= fields_for "#{param_prefix}[address_attributes]", @order.bill_address do |f| %>
|
36
|
+
<%= render partial: 'spree/address/form_hidden', locals: { form: f } %>
|
37
|
+
<% end %>
|
38
|
+
<% end %>
|
39
|
+
|
40
|
+
<%= hidden_field_tag "#{param_prefix}[cc_type]", '', id: "cc_type", class: 'ccType' %>
|
41
|
+
|
42
|
+
<script src="https://js.stripe.com/v3/"></script>
|