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.
Files changed (153) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +92 -52
  3. data/.gitignore +3 -0
  4. data/.rubocop.yml +94 -4
  5. data/.yardopts +1 -0
  6. data/Gemfile +10 -30
  7. data/LICENSE +2 -2
  8. data/Procfile.dev +3 -0
  9. data/README.md +145 -215
  10. data/Rakefile +5 -44
  11. data/app/assets/javascripts/spree/backend/solidus_stripe.js +2 -0
  12. data/app/assets/stylesheets/spree/backend/solidus_stripe.css +4 -0
  13. data/app/controllers/solidus_stripe/intents_controller.rb +36 -52
  14. data/app/controllers/solidus_stripe/webhooks_controller.rb +28 -0
  15. data/app/models/concerns/solidus_stripe/log_entries.rb +31 -0
  16. data/app/models/solidus_stripe/customer.rb +21 -0
  17. data/app/models/solidus_stripe/gateway.rb +231 -0
  18. data/app/models/solidus_stripe/payment_intent.rb +111 -0
  19. data/app/models/solidus_stripe/payment_method.rb +106 -0
  20. data/app/models/solidus_stripe/payment_source.rb +31 -0
  21. data/app/models/solidus_stripe/slug_entry.rb +20 -0
  22. data/app/models/solidus_stripe.rb +7 -0
  23. data/app/subscribers/solidus_stripe/webhook/charge_subscriber.rb +28 -0
  24. data/app/subscribers/solidus_stripe/webhook/payment_intent_subscriber.rb +112 -0
  25. data/app/views/spree/admin/payments/source_forms/_stripe.html.erb +29 -0
  26. data/app/views/spree/admin/payments/source_forms/existing_payment/_stripe.html.erb +14 -0
  27. data/app/views/spree/admin/payments/source_forms/existing_payment/stripe/_card.html.erb +8 -0
  28. data/app/views/spree/admin/payments/source_forms/existing_payment/stripe/_default.html.erb +7 -0
  29. data/app/views/spree/admin/payments/source_views/_stripe.html.erb +15 -0
  30. data/app/views/spree/api/payments/source_views/_stripe.json.jbuilder +8 -0
  31. data/bin/dev +13 -0
  32. data/bin/dummy-app +29 -0
  33. data/bin/rails +38 -3
  34. data/bin/rails-dummy-app +3 -0
  35. data/bin/rails-engine +1 -11
  36. data/bin/rails-new +55 -0
  37. data/bin/rails-sandbox +1 -14
  38. data/bin/rspec +10 -0
  39. data/bin/sandbox +12 -74
  40. data/bin/setup +1 -0
  41. data/bin/update-migrations +56 -0
  42. data/codecov.yml +12 -0
  43. data/config/locales/en.yml +16 -1
  44. data/config/routes.rb +5 -11
  45. data/coverage.rb +42 -0
  46. data/db/migrate/20230109183332_create_solidus_stripe_payment_sources.rb +10 -0
  47. data/db/migrate/20230303154931_create_solidus_stripe_setup_intent.rb +10 -0
  48. data/db/migrate/20230306105520_create_solidus_stripe_payment_intents.rb +10 -0
  49. data/db/migrate/20230308122414_create_solidus_stripe_webhook_endpoints.rb +10 -0
  50. data/db/migrate/20230310152615_add_payment_method_reference_to_stripe_intents.rb +6 -0
  51. data/db/migrate/20230310171444_normalize_stripe_intent_id_attributes.rb +6 -0
  52. data/db/migrate/20230313150008_create_solidus_stripe_customers.rb +16 -0
  53. data/db/migrate/20230323154931_drop_solidus_stripe_setup_intent.rb +13 -0
  54. data/db/migrate/20230403094916_rename_webhook_endpoint_to_payment_method_slug_entries.rb +5 -0
  55. data/db/seeds.rb +6 -24
  56. data/lib/generators/solidus_stripe/install/install_generator.rb +121 -14
  57. data/lib/generators/solidus_stripe/install/templates/app/assets/stylesheets/spree/frontend/solidus_stripe.css +13 -0
  58. data/lib/generators/solidus_stripe/install/templates/app/javascript/controllers/solidus_stripe_confirm_controller.js +39 -0
  59. data/lib/generators/solidus_stripe/install/templates/app/javascript/controllers/solidus_stripe_payment_controller.js +89 -0
  60. data/lib/generators/solidus_stripe/install/templates/app/views/checkouts/existing_payment/_stripe.html.erb +16 -0
  61. data/lib/generators/solidus_stripe/install/templates/app/views/checkouts/existing_payment/stripe/_card.html.erb +8 -0
  62. data/lib/generators/solidus_stripe/install/templates/app/views/checkouts/existing_payment/stripe/_default.html.erb +7 -0
  63. data/lib/generators/solidus_stripe/install/templates/app/views/checkouts/payment/_stripe.html.erb +39 -0
  64. data/lib/generators/solidus_stripe/install/templates/app/views/orders/payment_info/_stripe.html.erb +20 -0
  65. data/lib/generators/solidus_stripe/install/templates/config/initializers/solidus_stripe.rb +31 -0
  66. data/lib/solidus_stripe/configuration.rb +24 -3
  67. data/lib/solidus_stripe/engine.rb +19 -6
  68. data/lib/solidus_stripe/money_to_stripe_amount_converter.rb +109 -0
  69. data/lib/solidus_stripe/refunds_synchronizer.rb +96 -0
  70. data/lib/solidus_stripe/seeds.rb +19 -0
  71. data/lib/solidus_stripe/testing_support/factories.rb +153 -0
  72. data/lib/solidus_stripe/version.rb +1 -1
  73. data/lib/solidus_stripe/webhook/event.rb +90 -0
  74. data/lib/solidus_stripe.rb +0 -2
  75. data/solidus_stripe.gemspec +29 -6
  76. data/spec/lib/solidus_stripe/configuration_spec.rb +21 -0
  77. data/spec/lib/solidus_stripe/money_to_stripe_amount_converter_spec.rb +133 -0
  78. data/spec/lib/solidus_stripe/refunds_synchronizer_spec.rb +238 -0
  79. data/spec/lib/solidus_stripe/seeds_spec.rb +43 -0
  80. data/spec/lib/solidus_stripe/webhook/event_spec.rb +134 -0
  81. data/spec/models/concerns/solidus_stripe/log_entries_spec.rb +54 -0
  82. data/spec/models/solidus_stripe/customer_spec.rb +47 -0
  83. data/spec/models/solidus_stripe/gateway_spec.rb +283 -0
  84. data/spec/models/solidus_stripe/payment_intent_spec.rb +17 -0
  85. data/spec/models/solidus_stripe/payment_method_spec.rb +137 -0
  86. data/spec/models/solidus_stripe/payment_source_spec.rb +25 -0
  87. data/spec/requests/solidus_stripe/intents_controller_spec.rb +29 -0
  88. data/spec/requests/solidus_stripe/webhooks_controller/charge/refunded_spec.rb +31 -0
  89. data/spec/requests/solidus_stripe/webhooks_controller/payment_intent/canceled_spec.rb +23 -0
  90. data/spec/requests/solidus_stripe/webhooks_controller/payment_intent/payment_failed_spec.rb +23 -0
  91. data/spec/requests/solidus_stripe/webhooks_controller/payment_intent/succeeded_spec.rb +29 -0
  92. data/spec/requests/solidus_stripe/webhooks_controller_spec.rb +52 -0
  93. data/spec/solidus_stripe_spec_helper.rb +10 -0
  94. data/spec/subscribers/solidus_stripe/webhook/charge_subscriber_spec.rb +33 -0
  95. data/spec/subscribers/solidus_stripe/webhook/payment_intent_subscriber_spec.rb +297 -0
  96. data/spec/support/solidus_stripe/backend_test_helper.rb +210 -0
  97. data/spec/support/solidus_stripe/checkout_test_helper.rb +339 -0
  98. data/spec/support/solidus_stripe/factories.rb +5 -0
  99. data/spec/support/solidus_stripe/webhook/data_fixtures.rb +106 -0
  100. data/spec/support/solidus_stripe/webhook/event_with_context_factory.rb +82 -0
  101. data/spec/support/solidus_stripe/webhook/request_helper.rb +32 -0
  102. data/spec/system/backend/solidus_stripe/orders/payments_spec.rb +119 -0
  103. data/spec/system/frontend/.keep +0 -0
  104. data/spec/system/frontend/solidus_stripe/checkout_spec.rb +187 -0
  105. data/tmp/.keep +0 -0
  106. metadata +202 -78
  107. data/.rubocop_todo.yml +0 -298
  108. data/.travis.yml +0 -28
  109. data/app/assets/javascripts/spree/frontend/solidus_stripe/stripe-cart-page-checkout.js +0 -122
  110. data/app/assets/javascripts/spree/frontend/solidus_stripe/stripe-elements.js +0 -148
  111. data/app/assets/javascripts/spree/frontend/solidus_stripe/stripe-init.js +0 -20
  112. data/app/assets/javascripts/spree/frontend/solidus_stripe/stripe-payment-intents.js +0 -84
  113. data/app/assets/javascripts/spree/frontend/solidus_stripe/stripe-payment-request-button-shared.js +0 -160
  114. data/app/assets/javascripts/spree/frontend/solidus_stripe/stripe-payment.js +0 -16
  115. data/app/assets/javascripts/spree/frontend/solidus_stripe.js +0 -6
  116. data/app/controllers/solidus_stripe/payment_request_controller.rb +0 -52
  117. data/app/controllers/spree/stripe_controller.rb +0 -13
  118. data/app/decorators/models/spree/order_update_attributes_decorator.rb +0 -39
  119. data/app/decorators/models/spree/payment_decorator.rb +0 -11
  120. data/app/decorators/models/spree/refund_decorator.rb +0 -9
  121. data/app/models/solidus_stripe/address_from_params_service.rb +0 -72
  122. data/app/models/solidus_stripe/create_intents_payment_service.rb +0 -114
  123. data/app/models/solidus_stripe/prepare_order_for_payment_service.rb +0 -46
  124. data/app/models/solidus_stripe/shipping_rates_service.rb +0 -46
  125. data/app/models/spree/payment_method/stripe_credit_card.rb +0 -230
  126. data/bin/r +0 -13
  127. data/bin/sandbox_rails +0 -18
  128. data/db/migrate/20181010123508_update_stripe_payment_method_type_to_credit_card.rb +0 -21
  129. data/lib/assets/stylesheets/spree/frontend/solidus_stripe.scss +0 -11
  130. data/lib/solidus_stripe/testing_support/card_input_helper.rb +0 -34
  131. data/lib/tasks/solidus_stripe/db/seed.rake +0 -14
  132. data/lib/views/api/spree/api/payments/source_views/_stripe.json.jbuilder +0 -3
  133. data/lib/views/backend/spree/admin/log_entries/_stripe.html.erb +0 -28
  134. data/lib/views/backend/spree/admin/payments/source_forms/_stripe.html.erb +0 -1
  135. data/lib/views/backend/spree/admin/payments/source_views/_stripe.html.erb +0 -1
  136. data/lib/views/frontend/spree/checkout/existing_payment/_stripe.html.erb +0 -1
  137. data/lib/views/frontend/spree/checkout/payment/_stripe.html.erb +0 -8
  138. data/lib/views/frontend/spree/checkout/payment/v2/_javascript.html.erb +0 -78
  139. data/lib/views/frontend/spree/checkout/payment/v3/_elements.html.erb +0 -1
  140. data/lib/views/frontend/spree/checkout/payment/v3/_form_elements.html.erb +0 -40
  141. data/lib/views/frontend/spree/checkout/payment/v3/_intents.html.erb +0 -1
  142. data/lib/views/frontend/spree/checkout/payment/v3/_stripe.html.erb +0 -2
  143. data/lib/views/frontend/spree/orders/_stripe_payment_request_button.html.erb +0 -14
  144. data/spec/features/stripe_checkout_spec.rb +0 -486
  145. data/spec/models/solidus_stripe/address_from_params_service_spec.rb +0 -87
  146. data/spec/models/solidus_stripe/create_intents_payment_service_spec.rb +0 -127
  147. data/spec/models/solidus_stripe/prepare_order_for_payment_service_spec.rb +0 -65
  148. data/spec/models/solidus_stripe/shipping_rates_service_spec.rb +0 -54
  149. data/spec/models/spree/payment_method/stripe_credit_card_spec.rb +0 -354
  150. data/spec/requests/payment_requests_spec.rb +0 -152
  151. data/spec/solidus_frontend_app_template.rb +0 -17
  152. data/spec/spec_helper.rb +0 -37
  153. 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 :auto_run_migrations, type: :boolean, default: false
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
- def add_javascripts
9
- append_file 'vendor/assets/javascripts/spree/frontend/all.js', "//= require spree/frontend/solidus_stripe\n"
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 add_migrations
13
- run 'bin/rails railties:install:migrations FROM=solidus_stripe'
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 run_migrations
17
- run_migrations = options[:auto_run_migrations] || ENV['CI'] || ['', 'y', 'Y'].include?(ask('Would you like to run the migrations now? [Y/n]')) # rubocop:disable Layout/LineLength
18
- if run_migrations
19
- run 'bin/rails db:migrate'
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
- puts 'Skipping bin/rails db:migrate, don\'t forget to run it!' # rubocop:disable Rails/Output
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
- def populate_seed_data
26
- return unless options.auto_run_seeds?
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
- say_status :loading, 'stripe seed data'
29
- rake('db:seed:solidus_stripe')
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>****&nbsp;****&nbsp;****&nbsp;<%= 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) %>)
@@ -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>
@@ -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
- # Define here the settings for this extension, e.g.:
6
- #
7
- # attr_accessor :my_setting
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 ::Spree
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