solidus_stripe 4.4.1 → 5.0.0.alpha.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|