solidus_stripe 4.4.1 → 5.0.0.alpha.1
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/.circleci/config.yml +92 -52
- data/.gitignore +3 -0
- data/.rubocop.yml +94 -4
- data/.yardopts +1 -0
- data/Gemfile +10 -30
- data/LICENSE +2 -2
- data/Procfile.dev +3 -0
- data/README.md +145 -215
- data/Rakefile +5 -44
- data/app/assets/javascripts/spree/backend/solidus_stripe.js +2 -0
- data/app/assets/stylesheets/spree/backend/solidus_stripe.css +4 -0
- data/app/controllers/solidus_stripe/intents_controller.rb +36 -52
- data/app/controllers/solidus_stripe/webhooks_controller.rb +28 -0
- data/app/models/concerns/solidus_stripe/log_entries.rb +31 -0
- data/app/models/solidus_stripe/customer.rb +21 -0
- data/app/models/solidus_stripe/gateway.rb +231 -0
- data/app/models/solidus_stripe/payment_intent.rb +111 -0
- data/app/models/solidus_stripe/payment_method.rb +106 -0
- data/app/models/solidus_stripe/payment_source.rb +31 -0
- data/app/models/solidus_stripe/slug_entry.rb +20 -0
- data/app/models/solidus_stripe.rb +7 -0
- data/app/subscribers/solidus_stripe/webhook/charge_subscriber.rb +28 -0
- data/app/subscribers/solidus_stripe/webhook/payment_intent_subscriber.rb +112 -0
- data/app/views/spree/admin/payments/source_forms/_stripe.html.erb +29 -0
- data/app/views/spree/admin/payments/source_forms/existing_payment/_stripe.html.erb +14 -0
- data/app/views/spree/admin/payments/source_forms/existing_payment/stripe/_card.html.erb +8 -0
- data/app/views/spree/admin/payments/source_forms/existing_payment/stripe/_default.html.erb +7 -0
- data/app/views/spree/admin/payments/source_views/_stripe.html.erb +15 -0
- data/app/views/spree/api/payments/source_views/_stripe.json.jbuilder +8 -0
- data/bin/dev +13 -0
- data/bin/dummy-app +29 -0
- data/bin/rails +38 -3
- data/bin/rails-dummy-app +3 -0
- data/bin/rails-engine +1 -11
- data/bin/rails-new +55 -0
- data/bin/rails-sandbox +1 -14
- data/bin/rspec +10 -0
- data/bin/sandbox +12 -74
- data/bin/setup +1 -0
- data/bin/update-migrations +56 -0
- data/codecov.yml +12 -0
- data/config/locales/en.yml +16 -1
- data/config/routes.rb +5 -11
- data/coverage.rb +42 -0
- data/db/migrate/20230109183332_create_solidus_stripe_payment_sources.rb +10 -0
- data/db/migrate/20230303154931_create_solidus_stripe_setup_intent.rb +10 -0
- data/db/migrate/20230306105520_create_solidus_stripe_payment_intents.rb +10 -0
- data/db/migrate/20230308122414_create_solidus_stripe_webhook_endpoints.rb +10 -0
- data/db/migrate/20230310152615_add_payment_method_reference_to_stripe_intents.rb +6 -0
- data/db/migrate/20230310171444_normalize_stripe_intent_id_attributes.rb +6 -0
- data/db/migrate/20230313150008_create_solidus_stripe_customers.rb +16 -0
- data/db/migrate/20230323154931_drop_solidus_stripe_setup_intent.rb +13 -0
- data/db/migrate/20230403094916_rename_webhook_endpoint_to_payment_method_slug_entries.rb +5 -0
- data/db/seeds.rb +6 -24
- data/lib/generators/solidus_stripe/install/install_generator.rb +121 -14
- data/lib/generators/solidus_stripe/install/templates/app/assets/stylesheets/spree/frontend/solidus_stripe.css +13 -0
- data/lib/generators/solidus_stripe/install/templates/app/javascript/controllers/solidus_stripe_confirm_controller.js +39 -0
- data/lib/generators/solidus_stripe/install/templates/app/javascript/controllers/solidus_stripe_payment_controller.js +89 -0
- data/lib/generators/solidus_stripe/install/templates/app/views/checkouts/existing_payment/_stripe.html.erb +16 -0
- data/lib/generators/solidus_stripe/install/templates/app/views/checkouts/existing_payment/stripe/_card.html.erb +8 -0
- data/lib/generators/solidus_stripe/install/templates/app/views/checkouts/existing_payment/stripe/_default.html.erb +7 -0
- data/lib/generators/solidus_stripe/install/templates/app/views/checkouts/payment/_stripe.html.erb +39 -0
- data/lib/generators/solidus_stripe/install/templates/app/views/orders/payment_info/_stripe.html.erb +20 -0
- data/lib/generators/solidus_stripe/install/templates/config/initializers/solidus_stripe.rb +31 -0
- data/lib/solidus_stripe/configuration.rb +24 -3
- data/lib/solidus_stripe/engine.rb +19 -6
- data/lib/solidus_stripe/money_to_stripe_amount_converter.rb +109 -0
- data/lib/solidus_stripe/refunds_synchronizer.rb +96 -0
- data/lib/solidus_stripe/seeds.rb +19 -0
- data/lib/solidus_stripe/testing_support/factories.rb +153 -0
- data/lib/solidus_stripe/version.rb +1 -1
- data/lib/solidus_stripe/webhook/event.rb +90 -0
- data/lib/solidus_stripe.rb +0 -2
- data/solidus_stripe.gemspec +29 -6
- data/spec/lib/solidus_stripe/configuration_spec.rb +21 -0
- data/spec/lib/solidus_stripe/money_to_stripe_amount_converter_spec.rb +133 -0
- data/spec/lib/solidus_stripe/refunds_synchronizer_spec.rb +238 -0
- data/spec/lib/solidus_stripe/seeds_spec.rb +43 -0
- data/spec/lib/solidus_stripe/webhook/event_spec.rb +134 -0
- data/spec/models/concerns/solidus_stripe/log_entries_spec.rb +54 -0
- data/spec/models/solidus_stripe/customer_spec.rb +47 -0
- data/spec/models/solidus_stripe/gateway_spec.rb +283 -0
- data/spec/models/solidus_stripe/payment_intent_spec.rb +17 -0
- data/spec/models/solidus_stripe/payment_method_spec.rb +137 -0
- data/spec/models/solidus_stripe/payment_source_spec.rb +25 -0
- data/spec/requests/solidus_stripe/intents_controller_spec.rb +29 -0
- data/spec/requests/solidus_stripe/webhooks_controller/charge/refunded_spec.rb +31 -0
- data/spec/requests/solidus_stripe/webhooks_controller/payment_intent/canceled_spec.rb +23 -0
- data/spec/requests/solidus_stripe/webhooks_controller/payment_intent/payment_failed_spec.rb +23 -0
- data/spec/requests/solidus_stripe/webhooks_controller/payment_intent/succeeded_spec.rb +29 -0
- data/spec/requests/solidus_stripe/webhooks_controller_spec.rb +52 -0
- data/spec/solidus_stripe_spec_helper.rb +10 -0
- data/spec/subscribers/solidus_stripe/webhook/charge_subscriber_spec.rb +33 -0
- data/spec/subscribers/solidus_stripe/webhook/payment_intent_subscriber_spec.rb +297 -0
- data/spec/support/solidus_stripe/backend_test_helper.rb +210 -0
- data/spec/support/solidus_stripe/checkout_test_helper.rb +339 -0
- data/spec/support/solidus_stripe/factories.rb +5 -0
- data/spec/support/solidus_stripe/webhook/data_fixtures.rb +106 -0
- data/spec/support/solidus_stripe/webhook/event_with_context_factory.rb +82 -0
- data/spec/support/solidus_stripe/webhook/request_helper.rb +32 -0
- data/spec/system/backend/solidus_stripe/orders/payments_spec.rb +119 -0
- data/spec/system/frontend/.keep +0 -0
- data/spec/system/frontend/solidus_stripe/checkout_spec.rb +187 -0
- data/tmp/.keep +0 -0
- metadata +202 -78
- data/.rubocop_todo.yml +0 -298
- data/.travis.yml +0 -28
- data/app/assets/javascripts/spree/frontend/solidus_stripe/stripe-cart-page-checkout.js +0 -122
- data/app/assets/javascripts/spree/frontend/solidus_stripe/stripe-elements.js +0 -148
- data/app/assets/javascripts/spree/frontend/solidus_stripe/stripe-init.js +0 -20
- data/app/assets/javascripts/spree/frontend/solidus_stripe/stripe-payment-intents.js +0 -84
- data/app/assets/javascripts/spree/frontend/solidus_stripe/stripe-payment-request-button-shared.js +0 -160
- data/app/assets/javascripts/spree/frontend/solidus_stripe/stripe-payment.js +0 -16
- data/app/assets/javascripts/spree/frontend/solidus_stripe.js +0 -6
- data/app/controllers/solidus_stripe/payment_request_controller.rb +0 -52
- data/app/controllers/spree/stripe_controller.rb +0 -13
- data/app/decorators/models/spree/order_update_attributes_decorator.rb +0 -39
- data/app/decorators/models/spree/payment_decorator.rb +0 -11
- data/app/decorators/models/spree/refund_decorator.rb +0 -9
- data/app/models/solidus_stripe/address_from_params_service.rb +0 -72
- data/app/models/solidus_stripe/create_intents_payment_service.rb +0 -114
- data/app/models/solidus_stripe/prepare_order_for_payment_service.rb +0 -46
- data/app/models/solidus_stripe/shipping_rates_service.rb +0 -46
- data/app/models/spree/payment_method/stripe_credit_card.rb +0 -230
- data/bin/r +0 -13
- data/bin/sandbox_rails +0 -18
- data/db/migrate/20181010123508_update_stripe_payment_method_type_to_credit_card.rb +0 -21
- data/lib/assets/stylesheets/spree/frontend/solidus_stripe.scss +0 -11
- data/lib/solidus_stripe/testing_support/card_input_helper.rb +0 -34
- data/lib/tasks/solidus_stripe/db/seed.rake +0 -14
- data/lib/views/api/spree/api/payments/source_views/_stripe.json.jbuilder +0 -3
- data/lib/views/backend/spree/admin/log_entries/_stripe.html.erb +0 -28
- data/lib/views/backend/spree/admin/payments/source_forms/_stripe.html.erb +0 -1
- data/lib/views/backend/spree/admin/payments/source_views/_stripe.html.erb +0 -1
- data/lib/views/frontend/spree/checkout/existing_payment/_stripe.html.erb +0 -1
- data/lib/views/frontend/spree/checkout/payment/_stripe.html.erb +0 -8
- data/lib/views/frontend/spree/checkout/payment/v2/_javascript.html.erb +0 -78
- data/lib/views/frontend/spree/checkout/payment/v3/_elements.html.erb +0 -1
- data/lib/views/frontend/spree/checkout/payment/v3/_form_elements.html.erb +0 -40
- data/lib/views/frontend/spree/checkout/payment/v3/_intents.html.erb +0 -1
- data/lib/views/frontend/spree/checkout/payment/v3/_stripe.html.erb +0 -2
- data/lib/views/frontend/spree/orders/_stripe_payment_request_button.html.erb +0 -14
- data/spec/features/stripe_checkout_spec.rb +0 -486
- data/spec/models/solidus_stripe/address_from_params_service_spec.rb +0 -87
- data/spec/models/solidus_stripe/create_intents_payment_service_spec.rb +0 -127
- data/spec/models/solidus_stripe/prepare_order_for_payment_service_spec.rb +0 -65
- data/spec/models/solidus_stripe/shipping_rates_service_spec.rb +0 -54
- data/spec/models/spree/payment_method/stripe_credit_card_spec.rb +0 -354
- data/spec/requests/payment_requests_spec.rb +0 -152
- data/spec/solidus_frontend_app_template.rb +0 -17
- data/spec/spec_helper.rb +0 -37
- data/spec/support/solidus_address_helper.rb +0 -15
|
@@ -3,30 +3,137 @@
|
|
|
3
3
|
module SolidusStripe
|
|
4
4
|
module Generators
|
|
5
5
|
class InstallGenerator < Rails::Generators::Base
|
|
6
|
-
class_option :
|
|
6
|
+
class_option :migrations, type: :boolean, default: true
|
|
7
|
+
class_option :core, type: :boolean, default: true
|
|
8
|
+
class_option :backend, type: :boolean, default: true
|
|
9
|
+
class_option :starter_frontend, type: :boolean, default: true
|
|
7
10
|
|
|
8
|
-
|
|
9
|
-
|
|
11
|
+
class_option :migrate, type: :boolean, default: true
|
|
12
|
+
class_option :load_seeds, type: :boolean, default: true
|
|
13
|
+
class_option :watch, type: :boolean, default: false, hide: true
|
|
14
|
+
class_option :sync, type: :boolean, default: false, hide: true
|
|
15
|
+
|
|
16
|
+
# This is only used to run all-specs during development and CI, regular installation limits
|
|
17
|
+
# installed specs to frontend, which are the ones related to code copied to the target application.
|
|
18
|
+
class_option :specs, type: :string, enum: %w[all frontend], default: 'frontend', hide: true
|
|
19
|
+
|
|
20
|
+
source_root File.expand_path('templates', __dir__)
|
|
21
|
+
|
|
22
|
+
def install_migrations
|
|
23
|
+
return if options[:sync]
|
|
24
|
+
|
|
25
|
+
say_status :install, "[#{engine.engine_name}] migrations", :blue
|
|
26
|
+
shell.indent do
|
|
27
|
+
rake 'railties:install:migrations FROM=solidus_stripe'
|
|
28
|
+
run 'bin/rails db:migrate' if options[:migrate]
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def install_solidus_core_support
|
|
33
|
+
support_code_for(:core) do
|
|
34
|
+
directory 'config/initializers', 'config/initializers'
|
|
35
|
+
route "mount SolidusStripe::Engine, at: '/solidus_stripe'"
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def install_solidus_backend_support
|
|
40
|
+
support_code_for(:backend) do
|
|
41
|
+
append_file(
|
|
42
|
+
'vendor/assets/javascripts/spree/backend/all.js',
|
|
43
|
+
"//= require spree/backend/solidus_stripe\n"
|
|
44
|
+
)
|
|
45
|
+
inject_into_file(
|
|
46
|
+
'vendor/assets/stylesheets/spree/backend/all.css',
|
|
47
|
+
" *= require spree/backend/solidus_stripe\n",
|
|
48
|
+
before: %r{\*/},
|
|
49
|
+
verbose: true,
|
|
50
|
+
)
|
|
51
|
+
end
|
|
10
52
|
end
|
|
11
53
|
|
|
12
|
-
def
|
|
13
|
-
|
|
54
|
+
def install_solidus_starter_frontend_support
|
|
55
|
+
support_code_for(:starter_frontend) do
|
|
56
|
+
directory 'app', 'app'
|
|
57
|
+
inject_into_file(
|
|
58
|
+
'app/assets/stylesheets/solidus_starter_frontend.css',
|
|
59
|
+
" *= require spree/frontend/solidus_stripe\n",
|
|
60
|
+
before: %r{\*/},
|
|
61
|
+
verbose: true,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
spec_paths =
|
|
65
|
+
case options[:specs]
|
|
66
|
+
when 'all' then %w[spec]
|
|
67
|
+
when 'frontend'
|
|
68
|
+
%w[
|
|
69
|
+
spec/solidus_stripe_spec_helper.rb
|
|
70
|
+
spec/system/frontend
|
|
71
|
+
spec/support
|
|
72
|
+
]
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
spec_paths.each do |path|
|
|
76
|
+
if engine.root.join(path).directory?
|
|
77
|
+
directory engine.root.join(path), path
|
|
78
|
+
else
|
|
79
|
+
template engine.root.join(path), path
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
run 'bin/importmap pin @stripe/stripe-js'
|
|
84
|
+
end
|
|
14
85
|
end
|
|
15
86
|
|
|
16
|
-
def
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
87
|
+
def load_seeds
|
|
88
|
+
if options[:migrate] && options[:load_seeds]
|
|
89
|
+
say_status :load, "seed data", :blue
|
|
90
|
+
append_file "db/seeds.rb", <<~RUBY
|
|
91
|
+
#{engine.name}.load_seed
|
|
92
|
+
RUBY
|
|
93
|
+
engine.load_seed
|
|
20
94
|
else
|
|
21
|
-
|
|
95
|
+
say_status :skip, "loading seed data", :blue
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def watch
|
|
100
|
+
return unless options[:watch]
|
|
101
|
+
|
|
102
|
+
glob = "#{::SolidusStripe::Engine.root}/{app,lib,config}"
|
|
103
|
+
say_status :watch, "starting watcher... #{glob}", :cyan
|
|
104
|
+
|
|
105
|
+
require 'listen'
|
|
106
|
+
listener = Listen.to(*Dir[glob], relative: true) do |*changes|
|
|
107
|
+
say_status :watch, "changed: #{changes.flatten.join(', ')}", :cyan
|
|
108
|
+
shell.indent do
|
|
109
|
+
install_solidus_core_support
|
|
110
|
+
install_solidus_backend_support
|
|
111
|
+
install_solidus_starter_frontend_support
|
|
112
|
+
end
|
|
113
|
+
say_status :watch, "update completed", :cyan
|
|
22
114
|
end
|
|
115
|
+
listener.start
|
|
116
|
+
sleep
|
|
117
|
+
rescue Interrupt
|
|
118
|
+
say_status :watch, "stopping watcher...", :cyan
|
|
119
|
+
listener.stop
|
|
120
|
+
rescue LoadError
|
|
121
|
+
say_status :error, 'in order for the --watch option to work you need the "listen" gem in your Gemfile', :red
|
|
23
122
|
end
|
|
24
123
|
|
|
25
|
-
|
|
26
|
-
|
|
124
|
+
private
|
|
125
|
+
|
|
126
|
+
def support_code_for(component_name, &block)
|
|
127
|
+
if options[component_name]
|
|
128
|
+
say_status :install, "[#{engine.engine_name}] solidus_#{component_name}", :blue
|
|
129
|
+
shell.indent(&block)
|
|
130
|
+
else
|
|
131
|
+
say_status :skip, "[#{engine.engine_name}] solidus_#{component_name}", :blue
|
|
132
|
+
end
|
|
133
|
+
end
|
|
27
134
|
|
|
28
|
-
|
|
29
|
-
|
|
135
|
+
def engine
|
|
136
|
+
::SolidusStripe::Engine
|
|
30
137
|
end
|
|
31
138
|
end
|
|
32
139
|
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Placeholder manifest file.
|
|
3
|
+
the installer will append this file to the app vendored assets here: 'vendor/assets/stylesheets/spree/frontend/all.css'
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
.solidus-stripe-payment {
|
|
7
|
+
padding: 1em 0;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
[type="submit"]:disabled {
|
|
11
|
+
opacity: 0.5;
|
|
12
|
+
cursor: not-allowed;
|
|
13
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
|
2
|
+
import { loadStripe } from "@stripe/stripe-js"
|
|
3
|
+
|
|
4
|
+
export default class extends Controller {
|
|
5
|
+
static values = {
|
|
6
|
+
clientSecret: String,
|
|
7
|
+
publishableKey: String,
|
|
8
|
+
returnUrl: String,
|
|
9
|
+
errorBaseUrl: String,
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
async connect() {
|
|
13
|
+
this.stripe = await loadStripe(this.publishableKeyValue)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// action
|
|
17
|
+
async confirm(e) {
|
|
18
|
+
// Bail out if not on the confirm method form.
|
|
19
|
+
if (e.target !== this.element.form) return
|
|
20
|
+
|
|
21
|
+
e.preventDefault()
|
|
22
|
+
|
|
23
|
+
const { error } = await this.stripe.confirmPayment({
|
|
24
|
+
clientSecret: this.clientSecretValue,
|
|
25
|
+
confirmParams: { return_url: this.returnUrlValue },
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
if (error) {
|
|
29
|
+
// This point will only be reached if there is an immediate error when
|
|
30
|
+
// confirming the payment. Show error to your customer.
|
|
31
|
+
const messageParam = `error_message=${encodeURIComponent(error.message)}`
|
|
32
|
+
location.href = `${this.errorBaseUrlValue}&${messageParam}`
|
|
33
|
+
} else {
|
|
34
|
+
// Your customer will be redirected to your `return_url`. For some payment
|
|
35
|
+
// methods like iDEAL, your customer will be redirected to an intermediate
|
|
36
|
+
// site first to authorize the payment, then redirected to the `return_url`.
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
|
2
|
+
import { loadStripe } from "@stripe/stripe-js"
|
|
3
|
+
|
|
4
|
+
export default class extends Controller {
|
|
5
|
+
static values = {
|
|
6
|
+
clientSecret: String,
|
|
7
|
+
publishableKey: String,
|
|
8
|
+
paymentElementOptions: Object,
|
|
9
|
+
|
|
10
|
+
// For now we don't have a controller to interact with
|
|
11
|
+
// and we can't use outlets, so we fallback on acquiring selectors.
|
|
12
|
+
submitSelector: String,
|
|
13
|
+
radioSelector: String,
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
static targets = ["paymentElement", "message", "paymentMethodInput"]
|
|
17
|
+
|
|
18
|
+
get submitOutletElement() {
|
|
19
|
+
return document.querySelector(this.submitSelectorValue)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
get radioOutletElement() {
|
|
23
|
+
return document.querySelector(this.radioSelectorValue)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async connect() {
|
|
27
|
+
this.stripe = await loadStripe(this.publishableKeyValue)
|
|
28
|
+
this.setupPaymentElement()
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// @action
|
|
32
|
+
async handleSubmit(e) {
|
|
33
|
+
// Bail out if not on the payment method form.
|
|
34
|
+
if (e.target !== this.radioOutletElement.form) return
|
|
35
|
+
|
|
36
|
+
// Bail out if the current payment method is not selected.
|
|
37
|
+
if (!this.radioOutletElement.checked) return
|
|
38
|
+
|
|
39
|
+
e.preventDefault()
|
|
40
|
+
|
|
41
|
+
this.setLoading(true)
|
|
42
|
+
|
|
43
|
+
// Trigger form validation and wallet collection
|
|
44
|
+
const { error: submitError } = await this.elements.submit()
|
|
45
|
+
|
|
46
|
+
if (submitError) {
|
|
47
|
+
this.messageTarget.textContent = submitError.message
|
|
48
|
+
this.setLoading(false)
|
|
49
|
+
return
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Create the PaymentMethod using the details collected by the Payment Element
|
|
53
|
+
const { error, paymentMethod } = await this.stripe.createPaymentMethod({
|
|
54
|
+
elements: this.elements,
|
|
55
|
+
params: { billing_details: {} },
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
if (error) {
|
|
59
|
+
if (error.type === "card_error" || error.type === "validation_error") {
|
|
60
|
+
this.messageTarget.textContent = error.message
|
|
61
|
+
} else {
|
|
62
|
+
this.messageTarget.textContent = "An unexpected error occurred."
|
|
63
|
+
}
|
|
64
|
+
this.setLoading(false)
|
|
65
|
+
return
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
this.setLoading(false)
|
|
69
|
+
this.paymentMethodInputTarget.value = paymentMethod.id
|
|
70
|
+
this.paymentMethodInputTarget.form.submit()
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
setupPaymentElement() {
|
|
74
|
+
this.elements = this.stripe.elements(this.paymentElementOptionsValue)
|
|
75
|
+
this.paymentElement = this.elements.create("payment", { layout: "tabs" })
|
|
76
|
+
this.paymentElementTarget.innerHTML = "" // Remove child nodes used for loading
|
|
77
|
+
this.paymentElement.mount(this.paymentElementTarget)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
setLoading(isLoading) {
|
|
81
|
+
const element = this.submitOutletElement
|
|
82
|
+
|
|
83
|
+
if (isLoading) {
|
|
84
|
+
element.setAttribute("disabled", "")
|
|
85
|
+
} else {
|
|
86
|
+
element.removeAttribute("disabled")
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<%
|
|
2
|
+
stripe_payment_method = wallet_payment_source.payment_source.stripe_payment_method
|
|
3
|
+
|
|
4
|
+
# https://stripe.com/docs/api/payment_methods/object#payment_method_object-type
|
|
5
|
+
partial_base = "checkouts/existing_payment/stripe"
|
|
6
|
+
payment_type = stripe_payment_method.type
|
|
7
|
+
|
|
8
|
+
# Fallback on the default partial if a specialized partial is not available.
|
|
9
|
+
payment_type = 'default' if lookup_context.find_all("#{partial_base}/_#{payment_type}").none?
|
|
10
|
+
%>
|
|
11
|
+
|
|
12
|
+
<div>
|
|
13
|
+
<label>
|
|
14
|
+
<%= render "#{partial_base}/#{payment_type}", stripe_payment_method: stripe_payment_method %>
|
|
15
|
+
</label>
|
|
16
|
+
</div>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
<% card = stripe_payment_method.card %>
|
|
2
|
+
<%#
|
|
3
|
+
Add credit card logos:
|
|
4
|
+
https://support.stripe.com/questions/where-to-find-logos-for-accepted-credit-card-types
|
|
5
|
+
%>
|
|
6
|
+
💳 <%= card.brand %>
|
|
7
|
+
<span>**** **** **** <%= card.last4 %></span>
|
|
8
|
+
(<%= t 'spree.expiration' %>: <%= card.exp_month %>/<%= card.exp_year %>)
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
<% logger.error(
|
|
2
|
+
%{Can't find a partial for payments with type #{payment_type} } +
|
|
3
|
+
%{please add a partial named "#{partial_base}/_#{payment_type}.html.erb".}
|
|
4
|
+
) %>
|
|
5
|
+
|
|
6
|
+
<%= stripe_payment_method.type.humanize %>
|
|
7
|
+
(<%= time_ago_in_words Time.at(stripe_payment_method.created) %>)
|
data/lib/generators/solidus_stripe/install/templates/app/views/checkouts/payment/_stripe.html.erb
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
<% payment_element_options = {
|
|
2
|
+
mode: 'payment',
|
|
3
|
+
amount: SolidusStripe::MoneyToStripeAmountConverter.to_stripe_amount(
|
|
4
|
+
current_order.display_order_total_after_store_credit.money.fractional,
|
|
5
|
+
current_order.currency,
|
|
6
|
+
),
|
|
7
|
+
currency: current_order.currency.downcase,
|
|
8
|
+
paymentMethodCreation: 'manual',
|
|
9
|
+
captureMethod: payment_method.auto_capture? ? 'automatic' : 'manual',
|
|
10
|
+
# Fully customizable with appearance API.
|
|
11
|
+
# https://stripe.com/docs/elements/appearance-api
|
|
12
|
+
# appearance: {},
|
|
13
|
+
} %>
|
|
14
|
+
|
|
15
|
+
<div
|
|
16
|
+
class="solidus-stripe-payment"
|
|
17
|
+
data-controller="solidus-stripe-payment"
|
|
18
|
+
data-solidus-stripe-payment-publishable-key-value="<%= payment_method.preferred_publishable_key %>"
|
|
19
|
+
data-solidus-stripe-payment-payment-element-options-value="<%= payment_element_options.to_json %>"
|
|
20
|
+
data-solidus-stripe-payment-radio-selector-value="#order_payments_attributes__payment_method_id_<%= payment_method.id %>"
|
|
21
|
+
data-solidus-stripe-payment-submit-selector-value="#checkout_form_payment [type='submit']"
|
|
22
|
+
data-action="submit@window->solidus-stripe-payment#handleSubmit"
|
|
23
|
+
>
|
|
24
|
+
<input
|
|
25
|
+
type="hidden"
|
|
26
|
+
name="payment_source[<%= payment_method.id %>][stripe_payment_method_id]"
|
|
27
|
+
value=""
|
|
28
|
+
data-solidus-stripe-payment-target="paymentMethodInput"
|
|
29
|
+
>
|
|
30
|
+
|
|
31
|
+
<div data-solidus-stripe-payment-target="paymentElement">
|
|
32
|
+
<!-- Elements will create form elements here -->
|
|
33
|
+
<em><%= t('solidus_stripe.loading') %></em>
|
|
34
|
+
</div>
|
|
35
|
+
|
|
36
|
+
<div data-solidus-stripe-payment-target="message">
|
|
37
|
+
<!-- Display error/notification messages to your customers here -->
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
data/lib/generators/solidus_stripe/install/templates/app/views/orders/payment_info/_stripe.html.erb
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<% stripe_payment_method = payment.source.stripe_payment_method %>
|
|
2
|
+
<%= render(
|
|
3
|
+
"checkouts/existing_payment/stripe/#{stripe_payment_method.type}",
|
|
4
|
+
stripe_payment_method: stripe_payment_method
|
|
5
|
+
) %>
|
|
6
|
+
|
|
7
|
+
<% if current_order&.confirm? %>
|
|
8
|
+
<% stripe_intent =
|
|
9
|
+
SolidusStripe::PaymentIntent.prepare_for_payment(payment).stripe_intent %>
|
|
10
|
+
|
|
11
|
+
<input
|
|
12
|
+
type="hidden"
|
|
13
|
+
data-controller="solidus-stripe-confirm"
|
|
14
|
+
data-solidus-stripe-confirm-publishable-key-value="<%= payment.payment_method.preferred_publishable_key %>"
|
|
15
|
+
data-solidus-stripe-confirm-client-secret-value="<%= stripe_intent.client_secret %>"
|
|
16
|
+
data-solidus-stripe-confirm-return-url-value="<%= solidus_stripe.after_confirmation_url(payment.payment_method.slug) %>"
|
|
17
|
+
data-solidus-stripe-confirm-error-base-url-value="<%= solidus_stripe.after_confirmation_url(payment.payment_method.slug, payment_intent: stripe_intent.id) %>"
|
|
18
|
+
data-action="submit@window->solidus-stripe-confirm#confirm"
|
|
19
|
+
>
|
|
20
|
+
<% end %>
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
SolidusStripe.configure do |config|
|
|
4
|
+
# List of webhook events you want to handle.
|
|
5
|
+
# For instance, if you want to handle the `payment_intent.succeeded` event,
|
|
6
|
+
# you should add it to the list below. A corresponding
|
|
7
|
+
# `:"stripe.payment_intent.succeeded"` event will be published in `Spree::Bus`
|
|
8
|
+
# whenever a `payment_intent.succeeded` event is received from Stripe.
|
|
9
|
+
# config.webhook_events = %i[payment_intent.succeeded]
|
|
10
|
+
#
|
|
11
|
+
# Number of seconds while a webhook event is valid after its creation.
|
|
12
|
+
# Defaults to the same value as Stripe's default.
|
|
13
|
+
# config.webhook_signature_tolerance = 150
|
|
14
|
+
#
|
|
15
|
+
# Name of the `Spree::RefundReason` used for Stripe-generated refunds.
|
|
16
|
+
# Defaults to {SolidusStripe::DEFAULT_STRIPE_REFUND_REASON_NAME}. If you
|
|
17
|
+
# change it, make sure that the corresponding `Spree::RefundReason` exists in
|
|
18
|
+
# the database with that name.
|
|
19
|
+
# config.refund_reason_name = "Stripe refund"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
if ENV['SOLIDUS_STRIPE_API_KEY']
|
|
23
|
+
Spree::Config.static_model_preferences.add(
|
|
24
|
+
'SolidusStripe::PaymentMethod',
|
|
25
|
+
'solidus_stripe_env_credentials',
|
|
26
|
+
api_key: ENV.fetch('SOLIDUS_STRIPE_API_KEY'),
|
|
27
|
+
publishable_key: ENV.fetch('SOLIDUS_STRIPE_PUBLISHABLE_KEY'),
|
|
28
|
+
test_mode: ENV.fetch('SOLIDUS_STRIPE_API_KEY').start_with?('sk_test_'),
|
|
29
|
+
webhook_endpoint_signing_secret: ENV.fetch('SOLIDUS_STRIPE_WEBHOOK_SIGNING_SECRET')
|
|
30
|
+
)
|
|
31
|
+
end
|
|
@@ -1,10 +1,31 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "stripe/webhook"
|
|
4
|
+
require "solidus_stripe/seeds"
|
|
5
|
+
|
|
3
6
|
module SolidusStripe
|
|
4
7
|
class Configuration
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
+
# @!attribute [rw] webhook_events
|
|
9
|
+
# @return [Array<Symbol>] stripe events to handle. You also need to
|
|
10
|
+
# register them in the Stripe dashboard. For an event `:foo`, a matching
|
|
11
|
+
# `:"stripe.foo"` event will be registered in `Spree::Bus`.
|
|
12
|
+
attr_accessor :webhook_events
|
|
13
|
+
|
|
14
|
+
# @!attribute [rw] webhook_signature_tolerance
|
|
15
|
+
# @return [Integer] number of seconds while a webhook event is valid after
|
|
16
|
+
# its creation. Defaults to `Stripe::Webhook::DEFAULT_TOLERANCE`.
|
|
17
|
+
attr_accessor :webhook_signature_tolerance
|
|
18
|
+
|
|
19
|
+
# @!attribute [rw] refund_reason_name
|
|
20
|
+
# @return [String] `Spree::RefundReason#name` used for Stripe-generated
|
|
21
|
+
# refunds. Defaults to {SolidusStripe::Seeds::DEFAULT_STRIPE_REFUND_REASON_NAME}.
|
|
22
|
+
attr_accessor :refund_reason_name
|
|
23
|
+
|
|
24
|
+
def initialize
|
|
25
|
+
@webhook_events = []
|
|
26
|
+
@webhook_signature_tolerance = Stripe::Webhook::DEFAULT_TOLERANCE
|
|
27
|
+
@refund_reason_name = Seeds::DEFAULT_STRIPE_REFUND_REASON_NAME
|
|
28
|
+
end
|
|
8
29
|
end
|
|
9
30
|
|
|
10
31
|
class << self
|
|
@@ -7,17 +7,30 @@ module SolidusStripe
|
|
|
7
7
|
class Engine < Rails::Engine
|
|
8
8
|
include SolidusSupport::EngineExtensions
|
|
9
9
|
|
|
10
|
-
isolate_namespace
|
|
11
|
-
|
|
10
|
+
isolate_namespace SolidusStripe
|
|
12
11
|
engine_name 'solidus_stripe'
|
|
13
12
|
|
|
13
|
+
initializer "solidus_stripe.add_payment_method", after: "spree.register.payment_methods" do |app|
|
|
14
|
+
app.config.spree.payment_methods << 'SolidusStripe::PaymentMethod'
|
|
15
|
+
|
|
16
|
+
::Spree::PermittedAttributes.source_attributes.prepend :stripe_payment_method_id
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
initializer "solidus_stripe.pub_sub", after: "spree.core.pub_sub" do |app|
|
|
20
|
+
require "solidus_stripe/webhook/event"
|
|
21
|
+
app.reloader.to_prepare do
|
|
22
|
+
SolidusStripe::Webhook::Event.register(
|
|
23
|
+
user_events: SolidusStripe.configuration.webhook_events,
|
|
24
|
+
bus: Spree::Bus
|
|
25
|
+
)
|
|
26
|
+
SolidusStripe::Webhook::PaymentIntentSubscriber.new.subscribe_to(Spree::Bus)
|
|
27
|
+
SolidusStripe::Webhook::ChargeSubscriber.new.subscribe_to(Spree::Bus)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
14
31
|
# use rspec for tests
|
|
15
32
|
config.generators do |g|
|
|
16
33
|
g.test_framework :rspec
|
|
17
34
|
end
|
|
18
|
-
|
|
19
|
-
initializer "spree.payment_method.add_stripe_credit_card", after: "spree.register.payment_methods" do |app|
|
|
20
|
-
app.config.spree.payment_methods << "Spree::PaymentMethod::StripeCreditCard"
|
|
21
|
-
end
|
|
22
35
|
end
|
|
23
36
|
end
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Helpers to interoperate amounts between Solidus and Stripe.
|
|
4
|
+
#
|
|
5
|
+
# Solidus will provide a "fractional" amount, that is specific for each currency
|
|
6
|
+
# following the configuration defined in the Money gem.
|
|
7
|
+
#
|
|
8
|
+
# Stripe uses the "smallest currency unit", (e.g., 100 cents to charge $1.00 or
|
|
9
|
+
# 100 to charge ¥100, a zero-decimal currency).
|
|
10
|
+
#
|
|
11
|
+
# @see https://stripe.com/docs/currencies#zero-decimal
|
|
12
|
+
#
|
|
13
|
+
# We need to ensure the fractional amount is considering the same number of decimals.
|
|
14
|
+
module SolidusStripe::MoneyToStripeAmountConverter
|
|
15
|
+
extend ActiveSupport::Concern
|
|
16
|
+
extend self
|
|
17
|
+
|
|
18
|
+
# @api private
|
|
19
|
+
ZERO_DECIMAL_CURRENCIES = %w[
|
|
20
|
+
BIF
|
|
21
|
+
CLP
|
|
22
|
+
DJF
|
|
23
|
+
GNF
|
|
24
|
+
JPY
|
|
25
|
+
KMF
|
|
26
|
+
KRW
|
|
27
|
+
MGA
|
|
28
|
+
PYG
|
|
29
|
+
RWF
|
|
30
|
+
UGX
|
|
31
|
+
VND
|
|
32
|
+
VUV
|
|
33
|
+
XAF
|
|
34
|
+
XOF
|
|
35
|
+
XPF
|
|
36
|
+
].freeze
|
|
37
|
+
|
|
38
|
+
# @api private
|
|
39
|
+
THREE_DECIMAL_CURRENCIES = %w[
|
|
40
|
+
BHD
|
|
41
|
+
JOD
|
|
42
|
+
KWD
|
|
43
|
+
OMR
|
|
44
|
+
TND
|
|
45
|
+
].freeze
|
|
46
|
+
|
|
47
|
+
# @api private
|
|
48
|
+
#
|
|
49
|
+
# Special currencies that are represented in cents but should be
|
|
50
|
+
# divisible by 100, thus making them integer only.
|
|
51
|
+
DIVISIBLE_BY_100 = %w[
|
|
52
|
+
HUF
|
|
53
|
+
TWD
|
|
54
|
+
UGX
|
|
55
|
+
].freeze
|
|
56
|
+
|
|
57
|
+
def to_stripe_amount(fractional, currency)
|
|
58
|
+
solidus_subunit_to_unit, stripe_subunit_to_unit = subunit_to_unit(currency)
|
|
59
|
+
|
|
60
|
+
if stripe_subunit_to_unit == solidus_subunit_to_unit
|
|
61
|
+
fractional
|
|
62
|
+
else
|
|
63
|
+
(fractional / solidus_subunit_to_unit.to_d) * stripe_subunit_to_unit.to_d
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def to_solidus_amount(fractional, currency)
|
|
68
|
+
solidus_subunit_to_unit, stripe_subunit_to_unit = subunit_to_unit(currency)
|
|
69
|
+
|
|
70
|
+
if stripe_subunit_to_unit == solidus_subunit_to_unit
|
|
71
|
+
fractional
|
|
72
|
+
else
|
|
73
|
+
(fractional / stripe_subunit_to_unit.to_d) * solidus_subunit_to_unit.to_d
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Transforms a decimal amount into a subunit amount
|
|
78
|
+
#
|
|
79
|
+
# @param amount [BigDecimal]
|
|
80
|
+
# @param currency [String]
|
|
81
|
+
# @return [Integer]
|
|
82
|
+
def solidus_decimal_to_subunit(amount, currency)
|
|
83
|
+
Money.from_amount(amount, currency).fractional
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Transforms a subunit amount into a decimal amount
|
|
87
|
+
#
|
|
88
|
+
# @param amount [Integer]
|
|
89
|
+
# @param currency [String]
|
|
90
|
+
# @return [BigDecimal]
|
|
91
|
+
def solidus_subunit_to_decimal(amount, currency)
|
|
92
|
+
Money.new(amount, currency).to_d
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
private
|
|
96
|
+
|
|
97
|
+
def subunit_to_unit(currency)
|
|
98
|
+
solidus_subunit_to_unit = ::Money::Currency.new(currency).subunit_to_unit
|
|
99
|
+
stripe_subunit_to_unit =
|
|
100
|
+
case currency.to_s.upcase
|
|
101
|
+
when *ZERO_DECIMAL_CURRENCIES then 1
|
|
102
|
+
when *THREE_DECIMAL_CURRENCIES then 1000
|
|
103
|
+
when *DIVISIBLE_BY_100 then 100
|
|
104
|
+
else 100
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
[solidus_subunit_to_unit, stripe_subunit_to_unit]
|
|
108
|
+
end
|
|
109
|
+
end
|