spree_stripe 1.0.2

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 (100) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.md +14 -0
  3. data/README.md +132 -0
  4. data/Rakefile +21 -0
  5. data/app/assets/config/spree_stripe_manifest.js +3 -0
  6. data/app/controllers/spree/api/v2/storefront/stripe/base_controller.rb +21 -0
  7. data/app/controllers/spree/api/v2/storefront/stripe/payment_intents_controller.rb +61 -0
  8. data/app/controllers/spree/api/v2/storefront/stripe/setup_intents_controller.rb +19 -0
  9. data/app/controllers/spree_stripe/apple_pay_domain_verification_controller.rb +11 -0
  10. data/app/controllers/spree_stripe/payment_intents_controller.rb +61 -0
  11. data/app/controllers/spree_stripe/store_controller_decorator.rb +9 -0
  12. data/app/controllers/stripe_event/webhook_controller_decorator.rb +17 -0
  13. data/app/helpers/spree_stripe/base_helper.rb +13 -0
  14. data/app/javascript/spree_stripe/application.js +18 -0
  15. data/app/javascript/spree_stripe/controllers/stripe_button_controller.js +452 -0
  16. data/app/javascript/spree_stripe/controllers/stripe_controller.js +311 -0
  17. data/app/jobs/spree_stripe/base_job.rb +5 -0
  18. data/app/jobs/spree_stripe/complete_order_job.rb +8 -0
  19. data/app/jobs/spree_stripe/create_webhook_endpoint_job.rb +7 -0
  20. data/app/jobs/spree_stripe/register_domain_job.rb +20 -0
  21. data/app/models/spree_stripe/base.rb +6 -0
  22. data/app/models/spree_stripe/credit_card_decorator.rb +13 -0
  23. data/app/models/spree_stripe/custom_domain_decorator.rb +18 -0
  24. data/app/models/spree_stripe/gateway.rb +366 -0
  25. data/app/models/spree_stripe/order_decorator.rb +19 -0
  26. data/app/models/spree_stripe/payment_decorator.rb +46 -0
  27. data/app/models/spree_stripe/payment_intent.rb +66 -0
  28. data/app/models/spree_stripe/payment_method_decorator.rb +18 -0
  29. data/app/models/spree_stripe/payment_methods_webhook_key.rb +8 -0
  30. data/app/models/spree_stripe/payment_source_decorator.rb +9 -0
  31. data/app/models/spree_stripe/payment_sources/affirm.rb +9 -0
  32. data/app/models/spree_stripe/payment_sources/after_pay.rb +13 -0
  33. data/app/models/spree_stripe/payment_sources/alipay.rb +9 -0
  34. data/app/models/spree_stripe/payment_sources/ideal.rb +15 -0
  35. data/app/models/spree_stripe/payment_sources/klarna.rb +9 -0
  36. data/app/models/spree_stripe/payment_sources/link.rb +13 -0
  37. data/app/models/spree_stripe/payment_sources/przelewy24.rb +11 -0
  38. data/app/models/spree_stripe/payment_sources/sepa_debit.rb +13 -0
  39. data/app/models/spree_stripe/store_decorator.rb +24 -0
  40. data/app/models/spree_stripe/webhook_key.rb +14 -0
  41. data/app/presenters/spree_stripe/customer_presenter.rb +35 -0
  42. data/app/presenters/spree_stripe/payment_intent_presenter.rb +103 -0
  43. data/app/serializers/spree/api/v2/platform/affirm_serializer.rb +9 -0
  44. data/app/serializers/spree/api/v2/platform/after_pay_serializer.rb +9 -0
  45. data/app/serializers/spree/api/v2/platform/alipay_serializer.rb +9 -0
  46. data/app/serializers/spree/api/v2/platform/ideal_serializer.rb +9 -0
  47. data/app/serializers/spree/api/v2/platform/klarna_serializer.rb +9 -0
  48. data/app/serializers/spree/api/v2/platform/link_serializer.rb +9 -0
  49. data/app/serializers/spree/api/v2/platform/przelewy24_serializer.rb +9 -0
  50. data/app/serializers/spree/api/v2/platform/sepa_debit_serializer.rb +9 -0
  51. data/app/serializers/spree/v2/storefront/affirm_serializer.rb +7 -0
  52. data/app/serializers/spree/v2/storefront/after_pay_serializer.rb +7 -0
  53. data/app/serializers/spree/v2/storefront/alipay_serializer.rb +7 -0
  54. data/app/serializers/spree/v2/storefront/ideal_serializer.rb +7 -0
  55. data/app/serializers/spree/v2/storefront/klarna_serializer.rb +7 -0
  56. data/app/serializers/spree/v2/storefront/link_serializer.rb +7 -0
  57. data/app/serializers/spree/v2/storefront/payment_intent_serializer.rb +11 -0
  58. data/app/serializers/spree/v2/storefront/przelewy24_serializer.rb +7 -0
  59. data/app/serializers/spree/v2/storefront/sepa_debit_serializer.rb +7 -0
  60. data/app/services/spree_stripe/complete_order.rb +99 -0
  61. data/app/services/spree_stripe/create_gateway_webhooks.rb +55 -0
  62. data/app/services/spree_stripe/create_payment.rb +43 -0
  63. data/app/services/spree_stripe/create_payment_intent.rb +40 -0
  64. data/app/services/spree_stripe/create_setup_intent.rb +20 -0
  65. data/app/services/spree_stripe/create_source.rb +87 -0
  66. data/app/services/spree_stripe/register_domain.rb +22 -0
  67. data/app/services/spree_stripe/webhook_handlers/payment_intent_payment_failed.rb +18 -0
  68. data/app/services/spree_stripe/webhook_handlers/payment_intent_succeeded.rb +13 -0
  69. data/app/services/spree_stripe/webhook_handlers/setup_intent_succeeded.rb +34 -0
  70. data/app/views/spree/admin/payment_methods/configuration_guides/_spree_stripe.html.erb +0 -0
  71. data/app/views/spree/admin/payment_methods/custom_form_fields/_spree_stripe.html.erb +47 -0
  72. data/app/views/spree/admin/payment_methods/descriptions/_spree_stripe.html.erb +15 -0
  73. data/app/views/spree/checkout/payment/_spree_stripe.html.erb +63 -0
  74. data/app/views/spree_stripe/_head.html.erb +2 -0
  75. data/app/views/spree_stripe/_quick_checkout.html.erb +34 -0
  76. data/config/importmap.rb +8 -0
  77. data/config/initializers/spree.rb +8 -0
  78. data/config/initializers/stripe.rb +14 -0
  79. data/config/locales/en.yml +5 -0
  80. data/config/routes.rb +24 -0
  81. data/db/migrate/20250310152812_setup_spree_stripe_models.rb +41 -0
  82. data/lib/generators/spree_stripe/install/install_generator.rb +20 -0
  83. data/lib/spree_stripe/configuration.rb +5 -0
  84. data/lib/spree_stripe/engine.rb +36 -0
  85. data/lib/spree_stripe/factories.rb +3 -0
  86. data/lib/spree_stripe/testing_support/factories/after_pay_payment_source_factory.rb +6 -0
  87. data/lib/spree_stripe/testing_support/factories/alipay_payment_source_factory.rb +6 -0
  88. data/lib/spree_stripe/testing_support/factories/gateway_factory.rb +23 -0
  89. data/lib/spree_stripe/testing_support/factories/ideal_payment_source_factory.rb +6 -0
  90. data/lib/spree_stripe/testing_support/factories/klarna_payment_source_factory.rb +6 -0
  91. data/lib/spree_stripe/testing_support/factories/link_payment_source_factory.rb +6 -0
  92. data/lib/spree_stripe/testing_support/factories/payment_intent_factory.rb +24 -0
  93. data/lib/spree_stripe/testing_support/factories/payment_source_factory.rb +8 -0
  94. data/lib/spree_stripe/testing_support/factories/przelewy24_payment_source_factory.rb +6 -0
  95. data/lib/spree_stripe/testing_support/factories/sepa_debit_payment_source_factory.rb +6 -0
  96. data/lib/spree_stripe/testing_support/factories/webhook_key_factory.rb +23 -0
  97. data/lib/spree_stripe/version.rb +7 -0
  98. data/lib/spree_stripe.rb +14 -0
  99. data/vendor/javascript/@stripe--stripe-js--dist--pure.esm.js.js +4 -0
  100. metadata +295 -0
@@ -0,0 +1,35 @@
1
+ module SpreeStripe
2
+ class CustomerPresenter
3
+ def initialize(address: nil, email: nil, name: nil)
4
+ @email = email
5
+ @name = name
6
+ @address = address
7
+ end
8
+
9
+ def call
10
+ hash = {}
11
+ hash[:address] = address_payload if address.present?
12
+ hash[:email] = email if email.present?
13
+ hash[:name] = name if name.present?
14
+
15
+ hash
16
+ end
17
+
18
+ private
19
+
20
+ attr_reader :email, :name, :address
21
+
22
+ def address_payload
23
+ return {} unless address
24
+
25
+ {
26
+ city: address.city,
27
+ line1: address.address1,
28
+ line2: address.address2,
29
+ postal_code: address.zipcode,
30
+ country: address.country_name,
31
+ state: address.state_name
32
+ }
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,103 @@
1
+ module SpreeStripe
2
+ class PaymentIntentPresenter
3
+ SETUP_FUTURE_USAGE = 'off_session'
4
+ STATEMENT_DESCRIPTOR_MAX_CHARACTERS = 22
5
+
6
+ def initialize(amount:, order:, customer: nil, payment_method_id: nil, off_session: false)
7
+ @amount = amount
8
+ @order = order
9
+ @customer = customer
10
+ @ship_address = order.ship_address
11
+ @payment_method_id = payment_method_id
12
+ @off_session = off_session
13
+ end
14
+
15
+ def call
16
+ payload = if payment_method_id.present?
17
+ saved_payment_method_payload
18
+ else
19
+ new_payment_method_payload
20
+ end
21
+
22
+ payload = payload.deep_merge(basic_payload)
23
+
24
+ return payload unless ship_address
25
+
26
+ # we don't validate address1, but it's required by Stripe
27
+ if ship_address.invalid? || ship_address.address1.blank?
28
+ ship_address.errors.clear
29
+ return payload
30
+ end
31
+
32
+ payload.merge(ship_address_payload)
33
+ end
34
+
35
+ def ship_address_payload
36
+ {
37
+ shipping: {
38
+ address: {
39
+ city: ship_address.city,
40
+ country: ship_address.country_iso,
41
+ line1: ship_address.address1,
42
+ line2: ship_address.address2,
43
+ postal_code: ship_address.zipcode,
44
+ state: ship_address.state_abbr
45
+ },
46
+ name: ship_address.full_name
47
+ }
48
+ }
49
+ end
50
+
51
+ private
52
+
53
+ def basic_payload
54
+ {
55
+ amount: amount,
56
+ customer: customer,
57
+ currency: order.currency,
58
+ statement_descriptor_suffix: statement_descriptor,
59
+ automatic_payment_methods: {
60
+ enabled: true
61
+ },
62
+ transfer_group: order.number,
63
+ metadata: {
64
+ spree_order_id: order.id
65
+ }
66
+ }
67
+ end
68
+
69
+ def statement_descriptor
70
+ billing_name = order.store.billing_name
71
+ descriptor = "#{order.number} #{billing_name}".strip
72
+
73
+ if descriptor.length > STATEMENT_DESCRIPTOR_MAX_CHARACTERS
74
+ additional_char_count = descriptor.length - STATEMENT_DESCRIPTOR_MAX_CHARACTERS + 1
75
+ short_billing_name = billing_name[0...-additional_char_count]
76
+
77
+ descriptor = "#{order.number} #{short_billing_name}".strip
78
+ end
79
+
80
+ descriptor
81
+ end
82
+
83
+ def new_payment_method_payload
84
+ {
85
+ payment_method_options: {
86
+ card: {
87
+ setup_future_usage: SETUP_FUTURE_USAGE
88
+ }
89
+ }
90
+ }
91
+ end
92
+
93
+ def saved_payment_method_payload
94
+ {
95
+ payment_method: payment_method_id,
96
+ off_session: @off_session,
97
+ confirm: @off_session # confirm is required for off_session payments
98
+ }
99
+ end
100
+
101
+ attr_reader :order, :amount, :customer, :ship_address, :payment_method_id
102
+ end
103
+ end
@@ -0,0 +1,9 @@
1
+ module Spree
2
+ module Api
3
+ module V2
4
+ module Platform
5
+ class AffirmSerializer < PaymentSourceSerializer; end
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module Spree
2
+ module Api
3
+ module V2
4
+ module Platform
5
+ class AfterPaySerializer < PaymentSourceSerializer; end
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module Spree
2
+ module Api
3
+ module V2
4
+ module Platform
5
+ class AlipaySerializer < PaymentSourceSerializer; end
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module Spree
2
+ module Api
3
+ module V2
4
+ module Platform
5
+ class IdealSerializer < PaymentSourceSerializer; end
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module Spree
2
+ module Api
3
+ module V2
4
+ module Platform
5
+ class KlarnaSerializer < PaymentSourceSerializer; end
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module Spree
2
+ module Api
3
+ module V2
4
+ module Platform
5
+ class LinkSerializer < PaymentSourceSerializer; end
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module Spree
2
+ module Api
3
+ module V2
4
+ module Platform
5
+ class Przelewy24Serializer < PaymentSourceSerializer; end
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module Spree
2
+ module Api
3
+ module V2
4
+ module Platform
5
+ class SepaDebitSerializer < PaymentSourceSerializer; end
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,7 @@
1
+ module Spree
2
+ module V2
3
+ module Storefront
4
+ class AffirmSerializer < PaymentSourceSerializer; end
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ module Spree
2
+ module V2
3
+ module Storefront
4
+ class AfterPaySerializer < PaymentSourceSerializer; end
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ module Spree
2
+ module V2
3
+ module Storefront
4
+ class AlipaySerializer < PaymentSourceSerializer; end
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ module Spree
2
+ module V2
3
+ module Storefront
4
+ class IdealSerializer < PaymentSourceSerializer; end
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ module Spree
2
+ module V2
3
+ module Storefront
4
+ class KlarnaSerializer < PaymentSourceSerializer; end
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ module Spree
2
+ module V2
3
+ module Storefront
4
+ class LinkSerializer < PaymentSourceSerializer; end
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,11 @@
1
+ module Spree
2
+ module V2
3
+ module Storefront
4
+ class PaymentIntentSerializer < BaseSerializer
5
+ set_type :payment_intent
6
+
7
+ attributes :stripe_id, :client_secret, :ephemeral_key_secret, :customer_id, :amount, :stripe_payment_method_id
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,7 @@
1
+ module Spree
2
+ module V2
3
+ module Storefront
4
+ class Przelewy24Serializer < PaymentSourceSerializer; end
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ module Spree
2
+ module V2
3
+ module Storefront
4
+ class SepaDebitSerializer < PaymentSourceSerializer; end
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,99 @@
1
+ module SpreeStripe
2
+ class CompleteOrder
3
+ def initialize(payment_intent:)
4
+ @payment_intent = payment_intent
5
+ end
6
+
7
+ attr_reader :payment_intent
8
+
9
+ def call
10
+ order = payment_intent.order
11
+
12
+ return order if order.completed? || order.canceled?
13
+
14
+ charge = payment_intent.stripe_charge
15
+
16
+ order.with_lock do
17
+ # needed for quick checkout orders
18
+ order = add_customer_information(order, charge)
19
+
20
+ # find or create the payment
21
+ payment = payment_intent.find_or_create_payment!
22
+
23
+ # process the payment
24
+ payment.process!
25
+
26
+ # complete the order
27
+ Spree::Dependencies.checkout_complete_service.constantize.call(order: order)
28
+ end
29
+
30
+ order.reload
31
+ end
32
+
33
+ private
34
+
35
+ # we need to perform this for quick checkout orders which do not have these fields filled
36
+ def add_customer_information(order, charge)
37
+ billing_details = charge.billing_details
38
+ address = billing_details.address
39
+
40
+ order.email ||= billing_details.email
41
+ order.save! if order.email_changed?
42
+
43
+ # we don't need to perform this if we already have the billing address filled
44
+ return order if order.bill_address.present? && order.bill_address.valid?
45
+
46
+ # determine country...
47
+ country_iso = address.country
48
+ country = Spree::Country.find_by(iso: country_iso) || Spree::Country.default
49
+
50
+ # assign new address if we don't have one
51
+ order.bill_address ||= Spree::Address.new(country: country, user: order.user)
52
+
53
+ # assign attributes
54
+ order.bill_address.quick_checkout = true # skipping some validations
55
+
56
+ # sometimes google pay doesn't provide name (geez)
57
+ first_name = billing_details.name&.split(' ')&.first || order.ship_address&.first_name || order.user&.first_name
58
+ last_name = billing_details.name&.split(' ')&.last || order.ship_address&.last_name || order.user&.last_name
59
+
60
+ order.bill_address.first_name ||= first_name
61
+ order.bill_address.last_name ||= last_name
62
+ order.bill_address.phone ||= billing_details.phone
63
+ order.bill_address.address1 ||= address.line1
64
+ order.bill_address.address2 ||= address.line2
65
+ order.bill_address.city ||= address.city
66
+ order.bill_address.zipcode ||= address.postal_code
67
+
68
+ state_name = address.state
69
+ if country.states_required?
70
+ order.bill_address.state = country.states.find_all_by_name_or_abbr(state_name)&.first if country.states_required?
71
+ else
72
+ order.bill_address.state_name = state_name
73
+ end
74
+
75
+ order.bill_address.state_name ||= state_name
76
+
77
+ if order.bill_address.invalid?
78
+ order.bill_address = order.ship_address
79
+ else
80
+ order.bill_address.save!
81
+ end
82
+
83
+ order.save!
84
+
85
+ copy_bill_info_to_user(order) if order.user.present?
86
+
87
+ order
88
+ end
89
+
90
+ def copy_bill_info_to_user(order)
91
+ user = order.user
92
+ user.first_name ||= order.bill_address.first_name
93
+ user.last_name ||= order.bill_address.last_name
94
+ user.phone ||= order.bill_address.phone
95
+ user.bill_address_id ||= order.bill_address.id
96
+ user.save! if user.changed?
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,55 @@
1
+ module SpreeStripe
2
+ class CreateGatewayWebhooks
3
+ def call(payment_method:, events: SpreeStripe::Config[:supported_webhook_events], connect: false)
4
+ webhook_list = Stripe::WebhookEndpoint.list({}, { api_key: payment_method.preferred_secret_key })
5
+ webhook = find_webhook(webhook_list[:data], events)
6
+
7
+ ensure_webhook(payment_method, webhook, connect, events)
8
+ end
9
+
10
+ private
11
+
12
+ def find_webhook(webhooks_data, enabled_events)
13
+ webhook_url = SpreeStripe::Gateway.webhook_url
14
+
15
+ webhooks_data.find do |webhook|
16
+ webhook[:url] == webhook_url && webhook[:enabled_events].sort == enabled_events.sort
17
+ end
18
+ end
19
+
20
+ def ensure_webhook(payment_method, stripe_webhook, connect, events)
21
+ if stripe_webhook.nil?
22
+ create_webhook_endpoint(payment_method, connect, events)
23
+ else
24
+ webhook_key = WebhookKey.find_by(stripe_id: stripe_webhook[:id])
25
+
26
+ if webhook_key.present?
27
+ webhook_key.payment_methods << payment_method if webhook_key.payment_methods.exclude?(payment_method)
28
+ else
29
+ create_webhook_endpoint(payment_method, connect, events)
30
+ end
31
+ end
32
+ end
33
+
34
+ def create_webhook_endpoint(payment_method, connect, events)
35
+ params = build_webhook_params(connect, events)
36
+ stripe_webhook = Stripe::WebhookEndpoint.create(params, { api_key: payment_method.preferred_secret_key })
37
+
38
+ SpreeStripe::WebhookKey.create!(
39
+ stripe_id: stripe_webhook[:id],
40
+ signing_secret: stripe_webhook[:secret],
41
+ payment_methods: [payment_method]
42
+ )
43
+
44
+ Rails.cache.delete('stripe_webhook_signing_keys')
45
+ end
46
+
47
+ def build_webhook_params(connect, events)
48
+ {
49
+ url: SpreeStripe::Gateway.webhook_url,
50
+ enabled_events: events,
51
+ connect: connect
52
+ }
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,43 @@
1
+ module SpreeStripe
2
+ class CreatePayment
3
+ def initialize(order:, payment_intent:, gateway: nil, tax_transaction: nil, amount: nil)
4
+ @order = order
5
+ @gateway = gateway || order.store.stripe_gateway
6
+ @payment_intent = payment_intent
7
+ @tax_transaction = tax_transaction
8
+ @amount = amount || order.total_minus_store_credits
9
+ end
10
+
11
+ def call
12
+ stripe_charge = payment_intent.stripe_charge
13
+
14
+ if stripe_charge.present?
15
+ source = SpreeStripe::CreateSource.new(
16
+ order: order,
17
+ stripe_payment_method_details: stripe_charge.payment_method_details,
18
+ stripe_payment_method_id: stripe_charge.payment_method,
19
+ stripe_billing_details: stripe_charge.billing_details,
20
+ gateway: gateway
21
+ ).call
22
+ end
23
+
24
+ # sometimes a job is re-tried and creates a double payment record so we need to avoid it!
25
+ payment = order.payments.find_or_initialize_by(
26
+ payment_method_id: gateway.id,
27
+ response_code: payment_intent.stripe_id,
28
+ amount: amount
29
+ )
30
+
31
+ payment.source = source if source.present?
32
+ payment.stripe_tax_transaction_id = tax_transaction
33
+ payment.stripe_charge_id = stripe_charge&.id
34
+
35
+ payment.save!
36
+ payment
37
+ end
38
+
39
+ private
40
+
41
+ attr_reader :order, :gateway, :payment_intent, :tax_transaction, :source, :amount
42
+ end
43
+ end
@@ -0,0 +1,40 @@
1
+ module SpreeStripe
2
+ class CreatePaymentIntent
3
+ def call(order, gateway, stripe_payment_method_id: nil, off_session: false)
4
+ total = order.total_minus_store_credits
5
+ amount = Spree::Money.new(total).cents
6
+
7
+ return nil if amount.zero?
8
+
9
+ # lookup in database for existing payment intent
10
+ payment_intent = SpreeStripe::PaymentIntent.find_by(order: order, payment_method: gateway)
11
+
12
+ return payment_intent if payment_intent.present?
13
+
14
+ # response is an ActiveMerchant::Billing::Response object
15
+ # https://github.com/activemerchant/active_merchant/blob/master/lib/active_merchant/billing/response.rb
16
+ response = gateway.create_payment_intent(
17
+ amount,
18
+ order,
19
+ payment_method_id: stripe_payment_method_id,
20
+ off_session: off_session
21
+ )
22
+
23
+ # we should only create a customer if the order was placed by a user
24
+ customer = gateway.fetch_or_create_customer(user: order.user) if order.user.present?
25
+ ephemeral_key_response = gateway.create_ephemeral_key(customer.profile_id) if customer.present?
26
+ ephemeral_key_secret = ephemeral_key_response&.params['secret'] if ephemeral_key_response.present?
27
+
28
+ # persist the payment intent
29
+ SpreeStripe::PaymentIntent.create!(
30
+ order: order,
31
+ payment_method: gateway,
32
+ amount: total,
33
+ stripe_id: response.authorization,
34
+ client_secret: response.params['client_secret'],
35
+ customer_id: customer&.profile_id,
36
+ ephemeral_key_secret: ephemeral_key_secret
37
+ )
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,20 @@
1
+ module SpreeStripe
2
+ class CreateSetupIntent
3
+ prepend Spree::ServiceModule::Base
4
+
5
+ def call(gateway:, user:)
6
+ customer = gateway.fetch_or_create_customer(user: user)
7
+
8
+ ephemeral_key_response = gateway.create_ephemeral_key(customer.profile_id)
9
+ setup_intent = gateway.create_setup_intent(customer.profile_id)
10
+
11
+ success(
12
+ {
13
+ customer_id: customer.profile_id,
14
+ ephemeral_key_secret: ephemeral_key_response.params['secret'],
15
+ setup_intent_client_secret: setup_intent.params['client_secret']
16
+ }
17
+ )
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,87 @@
1
+ module SpreeStripe
2
+ class CreateSource
3
+ def initialize(stripe_payment_method_details:, stripe_payment_method_id:, gateway:, stripe_billing_details:, order: nil, user: nil)
4
+ @stripe_payment_method_details = stripe_payment_method_details
5
+ @stripe_payment_method_id = stripe_payment_method_id
6
+ @gateway = gateway
7
+ @user = user || order&.user
8
+ @stripe_billing_details = stripe_billing_details
9
+ @order = order
10
+ end
11
+
12
+ def call
13
+ case stripe_payment_method_details.type
14
+ when 'card'
15
+ find_or_create_credit_card
16
+ when 'klarna'
17
+ SpreeStripe::PaymentSources::Klarna.create!(source_params)
18
+ when 'afterpay_clearpay'
19
+ SpreeStripe::PaymentSources::AfterPay.create!(source_params)
20
+ when 'sepa_debit'
21
+ SpreeStripe::PaymentSources::SepaDebit.create!(source_params)
22
+ when 'p24'
23
+ SpreeStripe::PaymentSources::Przelewy24.create!(source_params.merge(bank: stripe_payment_method_details.p24.bank))
24
+ when 'ideal'
25
+ SpreeStripe::PaymentSources::Ideal.create!(
26
+ source_params.merge(
27
+ bank: stripe_payment_method_details.ideal.bank,
28
+ last4: stripe_payment_method_details.ideal.iban_last4
29
+ )
30
+ )
31
+ when 'alipay'
32
+ SpreeStripe::PaymentSources::Alipay.create!(source_params)
33
+ when 'link'
34
+ SpreeStripe::PaymentSources::Link.create!(source_params)
35
+ when 'affirm'
36
+ SpreeStripe::PaymentSources::Affirm.create!(source_params)
37
+ else
38
+ raise "[STRIPE] Unknown payment method #{stripe_payment_method_details.type}"
39
+ end
40
+ end
41
+
42
+ private
43
+
44
+ attr_reader :gateway, :user, :stripe_payment_method_details, :stripe_payment_method_id, :stripe_billing_details, :order
45
+
46
+ def find_or_create_credit_card
47
+ if user
48
+ source = user.credit_cards.find_by(gateway_payment_profile_id: stripe_payment_method_id)
49
+
50
+ return source if source
51
+ end
52
+
53
+ Spree::CreditCard.create!(credit_card_params)
54
+ end
55
+
56
+ def credit_card_params
57
+ card_details = stripe_payment_method_details.card
58
+ customer = gateway.fetch_or_create_customer(user: user, order: order)
59
+
60
+ {
61
+ user: user,
62
+ gateway_customer: customer,
63
+ payment_method: gateway,
64
+ gateway_customer_profile_id: customer&.profile_id,
65
+ gateway_payment_profile_id: stripe_payment_method_id,
66
+ name: stripe_billing_details.name,
67
+ month: card_details.exp_month,
68
+ year: card_details.exp_year,
69
+ last_digits: card_details.last4,
70
+ brand: card_details.brand,
71
+ private_metadata: {
72
+ checks: stripe_payment_method_details.card&.checks,
73
+ wallet: {
74
+ type: stripe_payment_method_details.card&.wallet&.type
75
+ }
76
+ }
77
+ }
78
+ end
79
+
80
+ def source_params
81
+ {
82
+ gateway_payment_profile_id: stripe_payment_method_id,
83
+ payment_method: gateway
84
+ }
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,22 @@
1
+ module SpreeStripe
2
+ class RegisterDomain
3
+ def call(model:)
4
+ gateway = model.is_a?(Spree::Store) ? model.stripe_gateway : model.store.stripe_gateway
5
+
6
+ payment_method_domain = gateway.send_request { Stripe::PaymentMethodDomain.create({ domain_name: model.url }) }
7
+
8
+ attributes_to_update = { stripe_apple_pay_domain_id: payment_method_domain.id }
9
+
10
+ tld_length = model.url.split('.').length
11
+ if tld_length > 2 && model.is_a?(Spree::CustomDomain)
12
+ top_level_domain_name = model.url.split('.').last(tld_length - 1).join('.')
13
+ top_level_domain = gateway.send_request { Stripe::PaymentMethodDomain.create({ domain_name: top_level_domain_name }) }
14
+ attributes_to_update[:stripe_top_level_domain_id] = top_level_domain.id
15
+ end
16
+
17
+ model.update!(**attributes_to_update)
18
+
19
+ payment_method_domain
20
+ end
21
+ end
22
+ end