spree_stripe 1.5.1 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +2 -0
  3. data/app/jobs/spree_stripe/complete_order_from_session_job.rb +12 -0
  4. data/app/models/spree/payment_sessions/stripe.rb +54 -0
  5. data/app/models/spree_stripe/custom_domain_decorator.rb +3 -1
  6. data/app/models/spree_stripe/gateway/payment_intents.rb +138 -0
  7. data/app/models/spree_stripe/gateway/payment_sessions.rb +177 -0
  8. data/app/models/spree_stripe/gateway.rb +53 -126
  9. data/app/presenters/spree_stripe/statement_descriptor_suffix_presenter.rb +1 -1
  10. data/app/services/spree_stripe/create_payment_intent.rb +1 -2
  11. data/app/services/spree_stripe/create_source.rb +1 -1
  12. data/app/services/spree_stripe/register_domain.rb +1 -1
  13. data/app/services/spree_stripe/webhook_handlers/payment_intent_payment_failed.rb +13 -1
  14. data/app/services/spree_stripe/webhook_handlers/payment_intent_succeeded.rb +11 -2
  15. data/config/routes.rb +11 -9
  16. data/lib/spree_stripe/configuration.rb +2 -0
  17. data/lib/spree_stripe/engine.rb +13 -2
  18. data/lib/spree_stripe/testing_support/factories/stripe_payment_session_factory.rb +33 -0
  19. data/lib/spree_stripe/version.rb +1 -1
  20. metadata +34 -29
  21. /data/{app/serializers → lib/spree_api_v2}/spree/api/v2/platform/affirm_serializer.rb +0 -0
  22. /data/{app/serializers → lib/spree_api_v2}/spree/api/v2/platform/after_pay_serializer.rb +0 -0
  23. /data/{app/serializers → lib/spree_api_v2}/spree/api/v2/platform/alipay_serializer.rb +0 -0
  24. /data/{app/serializers → lib/spree_api_v2}/spree/api/v2/platform/ideal_serializer.rb +0 -0
  25. /data/{app/serializers → lib/spree_api_v2}/spree/api/v2/platform/klarna_serializer.rb +0 -0
  26. /data/{app/serializers → lib/spree_api_v2}/spree/api/v2/platform/link_serializer.rb +0 -0
  27. /data/{app/serializers → lib/spree_api_v2}/spree/api/v2/platform/przelewy24_serializer.rb +0 -0
  28. /data/{app/serializers → lib/spree_api_v2}/spree/api/v2/platform/sepa_debit_serializer.rb +0 -0
  29. /data/{app/controllers → lib/spree_api_v2}/spree/api/v2/storefront/stripe/base_controller.rb +0 -0
  30. /data/{app/controllers → lib/spree_api_v2}/spree/api/v2/storefront/stripe/payment_intents_controller.rb +0 -0
  31. /data/{app/controllers → lib/spree_api_v2}/spree/api/v2/storefront/stripe/setup_intents_controller.rb +0 -0
  32. /data/{app/serializers → lib/spree_api_v2}/spree/v2/storefront/affirm_serializer.rb +0 -0
  33. /data/{app/serializers → lib/spree_api_v2}/spree/v2/storefront/after_pay_serializer.rb +0 -0
  34. /data/{app/serializers → lib/spree_api_v2}/spree/v2/storefront/alipay_serializer.rb +0 -0
  35. /data/{app/serializers → lib/spree_api_v2}/spree/v2/storefront/ideal_serializer.rb +0 -0
  36. /data/{app/serializers → lib/spree_api_v2}/spree/v2/storefront/klarna_serializer.rb +0 -0
  37. /data/{app/serializers → lib/spree_api_v2}/spree/v2/storefront/link_serializer.rb +0 -0
  38. /data/{app/serializers → lib/spree_api_v2}/spree/v2/storefront/payment_intent_serializer.rb +0 -0
  39. /data/{app/serializers → lib/spree_api_v2}/spree/v2/storefront/przelewy24_serializer.rb +0 -0
  40. /data/{app/serializers → lib/spree_api_v2}/spree/v2/storefront/sepa_debit_serializer.rb +0 -0
  41. /data/{app/serializers → lib/spree_api_v2}/spree_stripe/v2/storefront/payment_method_serializer_decorator.rb +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 296413327864ba7d62dbcbe59c93f4b20bbe8ca0eaed1f0379ab30548da13412
4
- data.tar.gz: 58bb81757cc9097d009fc3d0cb3733455ab0e22c5012a0310ca9e583da7bb744
3
+ metadata.gz: 1b3cc636d33d9aa8152aba43d60a15a1d3c44dca2feeafcd30c215cec823c028
4
+ data.tar.gz: 82cf337690f6c7f03d4770e2001e4d4dc3a273d4b84887f079e664a02788edd6
5
5
  SHA512:
6
- metadata.gz: 4aca196555667bc988d69382638bd235450deb8a66da6680915476e771c3c745d32f0ce2300ba737863998ec012d8929144cd8792d8c1c121d92fdbf065fef80
7
- data.tar.gz: 5ad609bed31583b62956e3be2517fc7b42a4a99beec2067cc4993ee4f771f38d2cb5040d477154cc0cc6132ea1a85ddddb200279ce404f6ec83de2ca56974b24
6
+ metadata.gz: 0ddd1b76f5c7991a636f78751fc43c8bc5eac6b952a91621ad928eaa66823f0b2bfa42a21ba7ba2942934c9146eb362841ad8ef7cc74f63b1422f8a2fbdf23ec
7
+ data.tar.gz: 9b9d768391f1e41ff1444dcf9f6faad7bab6c73c25e9712f0628171d770c3d00ef9b71d38f8be1ab471a21e979e9d5d8bd03e2b3cb8d0c68623e4d717d0f87b1
data/Rakefile CHANGED
@@ -21,5 +21,7 @@ task :test_app do
21
21
  install_storefront: true,
22
22
  install_admin: true
23
23
  )
24
+ system({ 'BUNDLE_GEMFILE' => File.expand_path('Gemfile', __dir__) }, 'rails g spree_posts:install')
24
25
  system({ 'BUNDLE_GEMFILE' => File.expand_path('Gemfile', __dir__) }, 'rails g spree_legacy_api_v2:install')
26
+ system({ 'BUNDLE_GEMFILE' => File.expand_path('Gemfile', __dir__) }, 'rails g spree_legacy_product_properties:install')
25
27
  end
@@ -0,0 +1,12 @@
1
+ module SpreeStripe
2
+ class CompleteOrderFromSessionJob < BaseJob
3
+ def perform(payment_session_id)
4
+ payment_session = Spree::PaymentSessions::Stripe.find(payment_session_id)
5
+
6
+ # PaymentSessions::Stripe duck-types as PaymentIntent
7
+ SpreeStripe::CompleteOrder.new(payment_intent: payment_session).call
8
+
9
+ payment_session.complete unless payment_session.completed?
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,54 @@
1
+ module Spree
2
+ class PaymentSessions::Stripe < PaymentSession
3
+ delegate :api_options, to: :payment_method
4
+
5
+ # Duck-type interface matching SpreeStripe::PaymentIntent
6
+ # This allows reuse of CompleteOrder and CreatePayment services
7
+ def stripe_id
8
+ external_id
9
+ end
10
+
11
+ def client_secret
12
+ external_data&.dig('client_secret')
13
+ end
14
+
15
+ def ephemeral_key_secret
16
+ external_data&.dig('ephemeral_key_secret')
17
+ end
18
+
19
+ def stripe_payment_intent
20
+ @stripe_payment_intent ||= payment_method.retrieve_payment_intent(external_id)
21
+ end
22
+
23
+ def stripe_charge
24
+ @stripe_charge ||= begin
25
+ latest_charge = stripe_payment_intent.latest_charge
26
+ latest_charge.present? ? payment_method.retrieve_charge(latest_charge) : nil
27
+ end
28
+ end
29
+
30
+ def accepted?
31
+ payment_method.payment_intent_accepted?(stripe_payment_intent)
32
+ end
33
+
34
+ def successful?
35
+ stripe_payment_intent.status == 'succeeded'
36
+ end
37
+
38
+ def charge_not_required?
39
+ payment_method.payment_intent_charge_not_required?(stripe_payment_intent)
40
+ end
41
+
42
+ def find_or_create_payment!(metadata = {})
43
+ return unless persisted?
44
+ return payment if payment.present?
45
+
46
+ SpreeStripe::CreatePayment.new(
47
+ order: order,
48
+ payment_intent: self,
49
+ gateway: payment_method,
50
+ amount: amount
51
+ ).call
52
+ end
53
+ end
54
+ end
@@ -15,4 +15,6 @@ module SpreeStripe
15
15
  end
16
16
  end
17
17
 
18
- Spree::CustomDomain.prepend(SpreeStripe::CustomDomainDecorator)
18
+ if defined?(Spree::CustomDomain)
19
+ Spree::CustomDomain.prepend(SpreeStripe::CustomDomainDecorator)
20
+ end
@@ -0,0 +1,138 @@
1
+ module SpreeStripe
2
+ class Gateway < ::Spree::Gateway
3
+ module PaymentIntents
4
+ extend ActiveSupport::Concern
5
+
6
+ DELAYED_NOTIFICATION_PAYMENT_METHOD_TYPES = %w[sepa_debit us_bank_account].freeze
7
+ BANK_PAYMENT_METHOD_TYPES = %w[customer_balance us_bank_account].freeze
8
+
9
+ included do
10
+ has_many :payment_intents, class_name: 'SpreeStripe::PaymentIntent', foreign_key: 'payment_method_id', dependent: :delete_all
11
+ end
12
+
13
+ def payment_intent_accepted?(payment_intent)
14
+ payment_intent.status.in?(payment_intent_accepted_statuses(payment_intent))
15
+ end
16
+
17
+ def payment_intent_delayed_notification?(payment_intent)
18
+ payment_method = payment_intent.payment_method
19
+ return false unless payment_method.respond_to?(:type)
20
+
21
+ payment_intent.payment_method.type.in?(DELAYED_NOTIFICATION_PAYMENT_METHOD_TYPES)
22
+ end
23
+
24
+ def payment_intent_charge_not_required?(payment_intent)
25
+ payment_intent_bank_payment_method?(payment_intent)
26
+ end
27
+
28
+ def payment_intent_bank_payment_method?(payment_intent)
29
+ payment_method = payment_intent.payment_method
30
+ return false unless payment_method.respond_to?(:type)
31
+
32
+ payment_intent.payment_method.type.in?(BANK_PAYMENT_METHOD_TYPES)
33
+ end
34
+
35
+ # Creates a Stripe payment intent for the order
36
+ #
37
+ # @param amount_in_cents [Integer] the amount in cents
38
+ # @param order [Spree::Order] the order to create a payment intent for
39
+ # @param payment_method_id [String] Stripe payment method id to use, eg. a card token
40
+ # @param off_session [Boolean] whether the payment intent is off session
41
+ # @param customer_profile_id [String] Stripe customer profile id to use, eg. cus_123
42
+ # @return [Spree::PaymentResponse] the response from the payment intent creation
43
+ def create_payment_intent(amount_in_cents, order, payment_method_id: nil, off_session: false, customer_profile_id: nil)
44
+ payload = SpreeStripe::PaymentIntentPresenter.new(
45
+ amount: amount_in_cents,
46
+ order: order,
47
+ customer: customer_profile_id || fetch_or_create_customer(order: order)&.profile_id,
48
+ payment_method_id: payment_method_id,
49
+ off_session: off_session
50
+ ).call
51
+
52
+ protect_from_error do
53
+ response = send_request { |opts| Stripe::PaymentIntent.create(payload, opts) }
54
+
55
+ success(response.id, response)
56
+ end
57
+ end
58
+
59
+ # Updates a Stripe payment intent for the order
60
+ #
61
+ # @param payment_intent_id [String] Stripe payment intent id
62
+ # @param amount_in_cents [Integer] the amount in cents
63
+ # @param order [Spree::Order] the order to update the payment intent for
64
+ # @param payment_method_id [String] Stripe payment method id to use, eg. a card token
65
+ # @return [Spree::PaymentResponse] the response from the payment intent update
66
+ def update_payment_intent(payment_intent_id, amount_in_cents, order, payment_method_id = nil)
67
+ protect_from_error do
68
+ payload = SpreeStripe::PaymentIntentPresenter.new(
69
+ amount: amount_in_cents,
70
+ order: order,
71
+ customer: fetch_or_create_customer(order: order)&.profile_id,
72
+ payment_method_id: payment_method_id
73
+ ).call.slice(:amount, :currency, :payment_method, :shipping, :customer)
74
+
75
+ response = send_request { |opts| Stripe::PaymentIntent.update(payment_intent_id, payload, opts) }
76
+
77
+ success(response.id, response)
78
+ end
79
+ end
80
+
81
+ def retrieve_payment_intent(payment_intent_id)
82
+ send_request { |opts| Stripe::PaymentIntent.retrieve({ id: payment_intent_id, expand: ['payment_method'] }, opts) }
83
+ end
84
+
85
+ def confirm_payment_intent(payment_intent_id)
86
+ send_request { |opts| Stripe::PaymentIntent.confirm(payment_intent_id, {}, opts) }
87
+ end
88
+
89
+ def capture_payment_intent(payment_intent_id, amount_in_cents)
90
+ send_request { |opts| Stripe::PaymentIntent.capture(payment_intent_id, { amount_to_capture: amount_in_cents }, opts) }
91
+ end
92
+
93
+ # Cancels a Stripe payment intent
94
+ #
95
+ # @param payment_intent_id [String] Stripe payment intent ID, eg. pi_123
96
+ def cancel_payment_intent(payment_intent_id)
97
+ send_request { |opts| Stripe::PaymentIntent.cancel(payment_intent_id, {}, opts) }
98
+ end
99
+
100
+ # Ensures a Stripe payment intent exists for Spree payment
101
+ #
102
+ # @param payment [Spree::Payment] the payment to ensure a payment intent exists for
103
+ # @param amount_in_cents [Integer] the amount in cents
104
+ # @param payment_source [Spree::CreditCard | Spree::PaymentSource] the payment source to use
105
+ # @return [Spree::Payment] the payment with the payment intent
106
+ def ensure_payment_intent_exists_for_payment(payment, amount_in_cents = nil, payment_source = nil)
107
+ return payment if payment.response_code.present?
108
+
109
+ amount_in_cents ||= payment.display_amount.cents
110
+ payment_source ||= payment.source
111
+
112
+ response = create_payment_intent(
113
+ amount_in_cents,
114
+ payment.order,
115
+ payment_method_id: payment_source.gateway_payment_profile_id,
116
+ off_session: true,
117
+ customer_profile_id: payment_source.gateway_customer_profile_id
118
+ )
119
+
120
+ payment.update_columns(
121
+ response_code: response.authorization,
122
+ updated_at: Time.current
123
+ )
124
+
125
+ payment
126
+ end
127
+
128
+ private
129
+
130
+ def payment_intent_accepted_statuses(payment_intent)
131
+ statuses = %w[succeeded]
132
+ statuses << 'processing' if payment_intent_delayed_notification?(payment_intent)
133
+ statuses << 'requires_action' if payment_intent_charge_not_required?(payment_intent)
134
+ statuses
135
+ end
136
+ end
137
+ end
138
+ end
@@ -0,0 +1,177 @@
1
+ module SpreeStripe
2
+ class Gateway < ::Spree::Gateway
3
+ module PaymentSessions
4
+ extend ActiveSupport::Concern
5
+
6
+ def session_required?
7
+ !SpreeStripe::Config[:use_legacy_payment_intents]
8
+ end
9
+
10
+ def payment_session_class
11
+ Spree::PaymentSessions::Stripe
12
+ end
13
+
14
+ def create_payment_session(order:, amount: nil, external_data: {})
15
+ total = amount.presence || order.total_minus_store_credits
16
+ amount_in_cents = Spree::Money.new(total, currency: order.currency).cents
17
+
18
+ return nil if amount_in_cents.zero?
19
+
20
+ customer = fetch_or_create_customer(order: order)
21
+ stripe_pm_id = external_data[:stripe_payment_method_id] || external_data['stripe_payment_method_id']
22
+
23
+ response = create_payment_intent(
24
+ amount_in_cents, order,
25
+ payment_method_id: stripe_pm_id,
26
+ customer_profile_id: customer&.profile_id
27
+ )
28
+
29
+ ephemeral_key_response = create_ephemeral_key(customer.profile_id) if customer.present?
30
+ ephemeral_key_secret = ephemeral_key_response&.params&.dig('secret')
31
+
32
+ payment_session_class.create!(
33
+ order: order,
34
+ payment_method: self,
35
+ amount: total,
36
+ currency: order.currency,
37
+ status: 'pending',
38
+ external_id: response.authorization,
39
+ customer: order.user,
40
+ customer_external_id: customer&.profile_id,
41
+ external_data: {
42
+ 'client_secret' => response.params['client_secret'],
43
+ 'ephemeral_key_secret' => ephemeral_key_secret,
44
+ 'stripe_payment_method_id' => stripe_pm_id
45
+ }.compact
46
+ )
47
+ end
48
+
49
+ def update_payment_session(payment_session:, amount: nil, external_data: {})
50
+ attrs = {}
51
+ amount_in_cents = nil
52
+
53
+ if amount.present?
54
+ attrs[:amount] = amount
55
+ amount_in_cents = Spree::Money.new(amount, currency: payment_session.currency).cents
56
+ end
57
+
58
+ stripe_pm_id = external_data[:stripe_payment_method_id] || external_data['stripe_payment_method_id']
59
+
60
+ update_payment_intent(
61
+ payment_session.external_id,
62
+ amount_in_cents || payment_session.amount_in_cents,
63
+ payment_session.order,
64
+ stripe_pm_id
65
+ )
66
+
67
+ if external_data.present?
68
+ attrs[:external_data] = (payment_session.external_data || {}).merge(external_data.stringify_keys)
69
+ end
70
+
71
+ payment_session.update!(attrs) if attrs.any?
72
+ end
73
+
74
+ # Completes a payment session by verifying with Stripe, creating the
75
+ # Payment record, and patching wallet address data.
76
+ #
77
+ # Does NOT complete the order — that is handled by Carts::Complete
78
+ # (called by the storefront or by the webhook handler).
79
+ def complete_payment_session(payment_session:, params: {})
80
+ stripe_pi = retrieve_payment_intent(payment_session.external_id)
81
+
82
+ if payment_intent_accepted?(stripe_pi)
83
+ payment_session.process if payment_session.can_process?
84
+
85
+ charge = payment_session.stripe_charge
86
+
87
+ # Patch wallet billing address (Apple Pay, Google Pay)
88
+ patch_wallet_address(payment_session.order, charge) if charge.present?
89
+
90
+ # Create the Payment record
91
+ payment_session.find_or_create_payment!
92
+
93
+ # Process the payment state
94
+ payment = payment_session.payment
95
+ if payment.present? && !payment.completed?
96
+ if payment_intent_successful?(stripe_pi)
97
+ payment.process!
98
+ else
99
+ payment.authorize!
100
+ end
101
+ end
102
+
103
+ payment_session.complete unless payment_session.completed?
104
+ else
105
+ payment_session.fail if payment_session.can_fail?
106
+ end
107
+ end
108
+
109
+ private
110
+
111
+ def payment_intent_successful?(stripe_pi)
112
+ stripe_pi.status == 'succeeded'
113
+ end
114
+
115
+ # Patches the order's billing address with data from the Stripe charge.
116
+ # Needed for quick checkout (Apple Pay/Google Pay) where the storefront
117
+ # doesn't have the billing address before payment confirmation.
118
+ def patch_wallet_address(order, charge)
119
+ return if charge.blank?
120
+
121
+ billing_details = charge.billing_details
122
+ address = billing_details.address
123
+
124
+ order.email ||= billing_details.email
125
+ order.save! if order.email_changed?
126
+
127
+ # Skip if billing address is already valid
128
+ return if order.bill_address.present? && order.bill_address.valid?
129
+
130
+ country_iso = address.country
131
+ country = Spree::Country.find_by(iso: country_iso) || Spree::Country.default
132
+
133
+ order.bill_address ||= Spree::Address.new(country: country, user: order.user)
134
+ order.bill_address.quick_checkout = true
135
+
136
+ first_name = billing_details.name&.split(' ')&.first || order.ship_address&.first_name || order.user&.first_name
137
+ last_name = billing_details.name&.split(' ')&.last || order.ship_address&.last_name || order.user&.last_name
138
+
139
+ order.bill_address.first_name ||= first_name
140
+ order.bill_address.last_name ||= last_name
141
+ order.bill_address.phone ||= billing_details.phone
142
+ order.bill_address.address1 ||= address.line1
143
+ order.bill_address.address2 ||= address.line2
144
+ order.bill_address.city ||= address.city
145
+ order.bill_address.zipcode ||= address.postal_code
146
+
147
+ state_name = address.state
148
+ if country.states_required?
149
+ order.bill_address.state = country.states.find_all_by_name_or_abbr(state_name)&.first
150
+ else
151
+ order.bill_address.state_name = state_name
152
+ end
153
+ order.bill_address.state_name ||= state_name
154
+
155
+ if order.bill_address.invalid?
156
+ return if order.ship_address.blank?
157
+
158
+ order.bill_address = order.ship_address
159
+ end
160
+
161
+ order.bill_address.save! if order.bill_address&.changed?
162
+ order.save!
163
+
164
+ copy_bill_info_to_user(order) if order.user.present?
165
+ end
166
+
167
+ def copy_bill_info_to_user(order)
168
+ user = order.user
169
+ user.first_name ||= order.bill_address.first_name
170
+ user.last_name ||= order.bill_address.last_name
171
+ user.phone ||= order.bill_address.phone
172
+ user.bill_address_id ||= order.bill_address.id
173
+ user.save! if user.changed?
174
+ end
175
+ end
176
+ end
177
+ end
@@ -1,10 +1,9 @@
1
1
  module SpreeStripe
2
2
  class Gateway < ::Spree::Gateway
3
+ include SpreeStripe::Gateway::PaymentIntents
4
+ include SpreeStripe::Gateway::PaymentSessions
3
5
  include SpreeStripe::Gateway::Tax if defined?(SpreeStripe::Gateway::Tax)
4
6
 
5
- DELAYED_NOTIFICATION_PAYMENT_METHOD_TYPES = %w[sepa_debit us_bank_account].freeze
6
- BANK_PAYMENT_METHOD_TYPES = %w[customer_balance us_bank_account].freeze
7
-
8
7
  preference :publishable_key, :password
9
8
  preference :secret_key, :password
10
9
 
@@ -16,36 +15,38 @@ module SpreeStripe
16
15
  after_commit :create_webhook_endpoint_async, on: %i[create update]
17
16
  after_commit :register_domain, on: :create
18
17
 
19
- has_many :payment_intents, class_name: 'SpreeStripe::PaymentIntent', foreign_key: 'payment_method_id', dependent: :delete_all
20
-
21
18
  def webhook_url
22
- StripeEvent::Engine.routes.url_helpers.root_url(host: stores.first.url, protocol: 'https')
23
- end
24
-
25
- def provider_class
26
- self.class
27
- end
19
+ store = stores.first
20
+ return nil unless store
28
21
 
29
- def payment_intent_accepted?(payment_intent)
30
- payment_intent.status.in?(payment_intent_accepted_statuses(payment_intent))
22
+ if SpreeStripe::Config[:use_legacy_webhook_handlers]
23
+ StripeEvent::Engine.routes.url_helpers.root_url(host: store.url, protocol: 'https')
24
+ else
25
+ "#{store.formatted_url}/api/v3/webhooks/payments/#{prefixed_id}"
26
+ end
31
27
  end
32
28
 
33
- def payment_intent_delayed_notification?(payment_intent)
34
- payment_method = payment_intent.payment_method
35
- return false unless payment_method.respond_to?(:type)
36
-
37
- payment_intent.payment_method.type.in?(DELAYED_NOTIFICATION_PAYMENT_METHOD_TYPES)
38
- end
29
+ def parse_webhook_event(raw_body, headers)
30
+ event = verify_webhook_signature(raw_body, headers)
39
31
 
40
- def payment_intent_charge_not_required?(payment_intent)
41
- payment_intent_bank_payment_method?(payment_intent)
32
+ payment_session = Spree::PaymentSessions::Stripe.find_by(
33
+ payment_method: self,
34
+ external_id: event.data.object[:id]
35
+ )
36
+ return nil unless payment_session
37
+
38
+ case event.type
39
+ when 'payment_intent.succeeded'
40
+ { action: :captured, payment_session: payment_session, metadata: { stripe_event: event } }
41
+ when 'payment_intent.payment_failed'
42
+ { action: :failed, payment_session: payment_session, metadata: { stripe_event: event } }
43
+ else
44
+ nil
45
+ end
42
46
  end
43
47
 
44
- def payment_intent_bank_payment_method?(payment_intent)
45
- payment_method = payment_intent.payment_method
46
- return false unless payment_method.respond_to?(:type)
47
-
48
- payment_intent.payment_method.type.in?(BANK_PAYMENT_METHOD_TYPES)
48
+ def provider_class
49
+ self.class
49
50
  end
50
51
 
51
52
  # @param amount_in_cents [Integer] the amount in cents to capture
@@ -188,99 +189,6 @@ module SpreeStripe
188
189
  send_request { |opts| Stripe::Customer.update(customer.profile_id, payload, opts) }
189
190
  end
190
191
 
191
- # Creates a Stripe payment intent for the order
192
- #
193
- # @param amount_in_cents [Integer] the amount in cents
194
- # @param order [Spree::Order] the order to create a payment intent for
195
- # @param payment_method_id [String] Stripe payment method id to use, eg. a card token
196
- # @param off_session [Boolean] whether the payment intent is off session
197
- # @param customer_profile_id [String] Stripe customer profile id to use, eg. cus_123
198
- # @return [ActiveMerchant::Billing::Response] the response from the payment intent creation
199
- def create_payment_intent(amount_in_cents, order, payment_method_id: nil, off_session: false, customer_profile_id: nil)
200
- payload = SpreeStripe::PaymentIntentPresenter.new(
201
- amount: amount_in_cents,
202
- order: order,
203
- customer: customer_profile_id || fetch_or_create_customer(order: order)&.profile_id,
204
- payment_method_id: payment_method_id,
205
- off_session: off_session
206
- ).call
207
-
208
- protect_from_error do
209
- response = send_request { |opts| Stripe::PaymentIntent.create(payload, opts) }
210
-
211
- success(response.id, response)
212
- end
213
- end
214
-
215
- # Updates a Stripe payment intent for the order
216
- #
217
- # @param payment_intent_id [String] Stripe payment intent id
218
- # @param amount_in_cents [Integer] the amount in cents
219
- # @param order [Spree::Order] the order to update the payment intent for
220
- # @param payment_method_id [String] Stripe payment method id to use, eg. a card token
221
- # @return [ActiveMerchant::Billing::Response] the response from the payment intent update
222
- def update_payment_intent(payment_intent_id, amount_in_cents, order, payment_method_id = nil)
223
- protect_from_error do
224
- payload = SpreeStripe::PaymentIntentPresenter.new(
225
- amount: amount_in_cents,
226
- order: order,
227
- customer: fetch_or_create_customer(order: order)&.profile_id,
228
- payment_method_id: payment_method_id
229
- ).call.slice(:amount, :currency, :payment_method, :shipping, :customer)
230
-
231
- response = send_request { |opts| Stripe::PaymentIntent.update(payment_intent_id, payload, opts) }
232
-
233
- success(response.id, response)
234
- end
235
- end
236
-
237
- def retrieve_payment_intent(payment_intent_id)
238
- send_request { |opts| Stripe::PaymentIntent.retrieve({ id: payment_intent_id, expand: ['payment_method'] }, opts) }
239
- end
240
-
241
- def confirm_payment_intent(payment_intent_id)
242
- send_request { |opts| Stripe::PaymentIntent.confirm(payment_intent_id, {}, opts) }
243
- end
244
-
245
- def capture_payment_intent(payment_intent_id, amount_in_cents)
246
- send_request { |opts| Stripe::PaymentIntent.capture(payment_intent_id, { amount_to_capture: amount_in_cents }, opts) }
247
- end
248
-
249
- # Cancels a Stripe payment intent
250
- #
251
- # @param payment_intent_id [String] Stripe payment intent ID, eg. pi_123
252
- def cancel_payment_intent(payment_intent_id)
253
- send_request { |opts| Stripe::PaymentIntent.cancel(payment_intent_id, {}, opts) }
254
- end
255
-
256
- # Ensures a Stripe payment intent exists for Spree payment
257
- #
258
- # @param payment [Spree::Payment] the payment to ensure a payment intent exists for
259
- # @param amount_in_cents [Integer] the amount in cents
260
- # @param payment_source [Spree::CreditCard | Spree::PaymentSource] the payment source to use
261
- # @return [Spree::Payment] the payment with the payment intent
262
- def ensure_payment_intent_exists_for_payment(payment, amount_in_cents = nil, payment_source = nil)
263
- return payment if payment.response_code.present?
264
-
265
- amount_in_cents ||= payment.display_amount.cents
266
- payment_source ||= payment.source
267
-
268
- response = create_payment_intent(
269
- amount_in_cents,
270
- payment.order,
271
- payment_method_id: payment_source.gateway_payment_profile_id,
272
- off_session: true,
273
- customer_profile_id: payment_source.gateway_customer_profile_id
274
- )
275
-
276
- payment.update_columns(
277
- response_code: response.authorization,
278
- updated_at: Time.current
279
- )
280
-
281
- payment
282
- end
283
-
284
192
  def retrieve_charge(charge_id)
285
193
  send_request { |opts| Stripe::Charge.retrieve(charge_id, opts) }
286
194
  end
@@ -407,11 +315,11 @@ module SpreeStripe
407
315
  end
408
316
 
409
317
  def success(authorization, full_response)
410
- ActiveMerchant::Billing::Response.new(true, nil, full_response.as_json, authorization: authorization)
318
+ Spree::PaymentResponse.new(true, nil, full_response.as_json, authorization: authorization)
411
319
  end
412
320
 
413
321
  def failure(error = nil)
414
- ActiveMerchant::Billing::Response.new(false, error)
322
+ Spree::PaymentResponse.new(false, error)
415
323
  end
416
324
 
417
325
  def protect_from_error
@@ -430,6 +338,8 @@ module SpreeStripe
430
338
  stores.each do |store|
431
339
  RegisterDomainJob.perform_later(store.id, 'store')
432
340
 
341
+ next unless defined?(Spree::CustomDomain)
342
+
433
343
  store.custom_domains.each do |custom_domain|
434
344
  RegisterDomainJob.perform_later(custom_domain.id, 'custom_domain')
435
345
  end
@@ -445,11 +355,28 @@ module SpreeStripe
445
355
  SpreeStripe::CustomerPresenter.new(name: name, email: email, address: address).call
446
356
  end
447
357
 
448
- def payment_intent_accepted_statuses(payment_intent)
449
- statuses = %w[succeeded]
450
- statuses << 'processing' if payment_intent_delayed_notification?(payment_intent)
451
- statuses << 'requires_action' if payment_intent_charge_not_required?(payment_intent)
452
- statuses
358
+ def verify_webhook_signature(raw_body, headers)
359
+ signature = headers['HTTP_STRIPE_SIGNATURE']
360
+ signing_secrets = webhook_signing_secrets
361
+
362
+ signing_secrets.each do |secret|
363
+ return Stripe::Webhook.construct_event(raw_body, signature, secret)
364
+ rescue Stripe::SignatureVerificationError
365
+ next
366
+ end
367
+
368
+ raise Spree::PaymentMethod::WebhookSignatureError, 'Invalid webhook signature'
369
+ end
370
+
371
+ def webhook_signing_secrets
372
+ secrets = SpreeStripe::WebhookKey
373
+ .joins(:payment_methods_webhook_keys)
374
+ .where(payment_methods_webhook_keys: { payment_method_id: id })
375
+ .pluck(:signing_secret)
376
+ .compact
377
+
378
+ secrets << ENV['STRIPE_SIGNING_SECRET'] if ENV['STRIPE_SIGNING_SECRET'].present?
379
+ secrets
453
380
  end
454
381
  end
455
382
  end
@@ -24,7 +24,7 @@ module SpreeStripe
24
24
 
25
25
  def stripped_order_description
26
26
  @stripped_order_description ||= begin
27
- AnyAscii.transliterate(order_description)
27
+ I18n.transliterate(order_description)
28
28
  .gsub(STATEMENT_DESCRIPTOR_NOT_ALLOWED_CHARS, '')
29
29
  .strip
30
30
  .upcase[0...STATEMENT_DESCRIPTOR_MAX_CHARS]
@@ -11,8 +11,7 @@ module SpreeStripe
11
11
 
12
12
  return payment_intent if payment_intent.present?
13
13
 
14
- # response is an ActiveMerchant::Billing::Response object
15
- # https://github.com/activemerchant/active_merchant/blob/master/lib/active_merchant/billing/response.rb
14
+ # response is a Spree::PaymentResponse object
16
15
  response = gateway.create_payment_intent(
17
16
  amount,
18
17
  order,
@@ -37,7 +37,7 @@ module SpreeStripe
37
37
  when 'customer_balance', 'us_bank_account'
38
38
  SpreeStripe::PaymentSources::BankTransfer.create!(source_params)
39
39
  else
40
- raise "[STRIPE] Unknown payment method #{stripe_payment_method_details.type}"
40
+ Spree::PaymentSource.create!(source_params)
41
41
  end
42
42
  end
43
43
 
@@ -8,7 +8,7 @@ module SpreeStripe
8
8
  attributes_to_update = { stripe_apple_pay_domain_id: payment_method_domain.id }
9
9
 
10
10
  tld_length = model.url.split('.').length
11
- if tld_length > 2 && model.is_a?(Spree::CustomDomain)
11
+ if tld_length > 2 && defined?(Spree::CustomDomain) && model.is_a?(Spree::CustomDomain)
12
12
  top_level_domain_name = model.url.split('.').last(tld_length - 1).join('.')
13
13
  top_level_domain = gateway.send_request { |opts| Stripe::PaymentMethodDomain.create({ domain_name: top_level_domain_name }, opts) }
14
14
  attributes_to_update[:stripe_top_level_domain_id] = top_level_domain.id
@@ -2,7 +2,19 @@ module SpreeStripe
2
2
  module WebhookHandlers
3
3
  class PaymentIntentPaymentFailed
4
4
  def call(event)
5
- payment_intent = SpreeStripe::PaymentIntent.find_by(stripe_id: event.data.object.id)
5
+ stripe_id = event.data.object.id
6
+
7
+ # New system: PaymentSession
8
+ payment_session = Spree::PaymentSessions::Stripe.find_by(external_id: stripe_id)
9
+ if payment_session.present?
10
+ payment_session.fail if payment_session.can_fail?
11
+ order = payment_session.order
12
+ order.cancel! if !order.canceled? && order.can_cancel?
13
+ return
14
+ end
15
+
16
+ # Legacy system: PaymentIntent
17
+ payment_intent = SpreeStripe::PaymentIntent.find_by(stripe_id: stripe_id)
6
18
  return if payment_intent.nil?
7
19
 
8
20
  order = payment_intent.order
@@ -2,8 +2,17 @@ module SpreeStripe
2
2
  module WebhookHandlers
3
3
  class PaymentIntentSucceeded
4
4
  def call(event)
5
- payment_intent_data = event.data.object
6
- payment_intent = SpreeStripe::PaymentIntent.find_by(stripe_id: payment_intent_data[:id])
5
+ stripe_id = event.data.object[:id]
6
+
7
+ # New system: PaymentSession
8
+ payment_session = Spree::PaymentSessions::Stripe.find_by(external_id: stripe_id)
9
+ if payment_session.present?
10
+ SpreeStripe::CompleteOrderFromSessionJob.set(wait: 10.seconds).perform_later(payment_session.id)
11
+ return
12
+ end
13
+
14
+ # Legacy system: PaymentIntent
15
+ payment_intent = SpreeStripe::PaymentIntent.find_by(stripe_id: stripe_id)
7
16
  return if payment_intent.nil?
8
17
 
9
18
  SpreeStripe::CompleteOrderJob.set(wait: 10.seconds).perform_later(payment_intent.id)
data/config/routes.rb CHANGED
@@ -10,15 +10,17 @@ Spree::Core::Engine.add_routes do
10
10
  # Stripe webhooks
11
11
  mount StripeEvent::Engine, at: '/stripe'
12
12
 
13
- # Storefront API
14
- namespace :api, defaults: { format: 'json' } do
15
- namespace :v2 do
16
- namespace :storefront do
17
- namespace :stripe do
18
- resources :setup_intents, only: %i[create]
19
- resources :payment_intents, only: %i[show create update] do
20
- member do
21
- patch :confirm
13
+ # Storefront API (only when spree_legacy_api_v2 gem is available)
14
+ if defined?(SpreeLegacyApiV2::Engine)
15
+ namespace :api, defaults: { format: 'json' } do
16
+ namespace :v2 do
17
+ namespace :storefront do
18
+ namespace :stripe do
19
+ resources :setup_intents, only: %i[create]
20
+ resources :payment_intents, only: %i[show create update] do
21
+ member do
22
+ patch :confirm
23
+ end
22
24
  end
23
25
  end
24
26
  end
@@ -1,5 +1,7 @@
1
1
  module SpreeStripe
2
2
  class Configuration < Spree::Preferences::Configuration
3
3
  preference :supported_webhook_events, :array, default: %w[payment_intent.payment_failed payment_intent.succeeded setup_intent.succeeded]
4
+ preference :use_legacy_payment_intents, :boolean, default: false
5
+ preference :use_legacy_webhook_handlers, :boolean, default: false
4
6
  end
5
7
  end
@@ -7,6 +7,12 @@ module SpreeStripe
7
7
  # Add app/subscribers to autoload paths
8
8
  config.paths.add 'app/subscribers', eager_load: true
9
9
 
10
+ # Only load API controllers and serializers when spree_legacy_api_v2 gem is available
11
+ if defined?(SpreeLegacyApiV2::Engine)
12
+ config.autoload_paths << root.join('lib', 'spree_api_v2')
13
+ config.eager_load_paths << root.join('lib', 'spree_api_v2')
14
+ end
15
+
10
16
  # use rspec for tests
11
17
  config.generators do |g|
12
18
  g.test_framework :rspec
@@ -33,8 +39,13 @@ module SpreeStripe
33
39
  end
34
40
 
35
41
  def self.activate
36
- Dir.glob(File.join(File.dirname(__FILE__), '../../app/**/*_decorator*.rb')) do |c|
37
- Rails.configuration.cache_classes ? require(c) : load(c)
42
+ glob_paths = [File.join(File.dirname(__FILE__), '../../app/**/*_decorator*.rb')]
43
+ glob_paths << File.join(File.dirname(__FILE__), '../../lib/spree_api_v2/**/*_decorator*.rb') if defined?(SpreeLegacyApiV2::Engine)
44
+
45
+ glob_paths.each do |glob_path|
46
+ Dir.glob(glob_path) do |c|
47
+ Rails.configuration.cache_classes ? require(c) : load(c)
48
+ end
38
49
  end
39
50
  end
40
51
 
@@ -0,0 +1,33 @@
1
+ FactoryBot.define do
2
+ factory :stripe_payment_session, class: 'Spree::PaymentSessions::Stripe' do
3
+ order { create(:order_with_line_items) }
4
+ payment_method { create(:stripe_gateway, stores: [order.store]) }
5
+ amount { order.total }
6
+ currency { order.currency }
7
+ status { 'pending' }
8
+ type { 'Spree::PaymentSessions::Stripe' }
9
+ external_id { "pi_test_#{SecureRandom.hex(12)}" }
10
+ external_data { { 'client_secret' => "pi_secret_#{SecureRandom.hex(8)}" } }
11
+
12
+ trait :with_customer do
13
+ customer { order.user || create(:user) }
14
+ customer_external_id { "cus_#{SecureRandom.hex(8)}" }
15
+ end
16
+
17
+ trait :with_ephemeral_key do
18
+ external_data { { 'client_secret' => "pi_secret_#{SecureRandom.hex(8)}", 'ephemeral_key_secret' => "ek_test_#{SecureRandom.hex(8)}" } }
19
+ end
20
+
21
+ trait :processing do
22
+ status { 'processing' }
23
+ end
24
+
25
+ trait :completed do
26
+ status { 'completed' }
27
+ end
28
+
29
+ trait :failed do
30
+ status { 'failed' }
31
+ end
32
+ end
33
+ end
@@ -1,5 +1,5 @@
1
1
  module SpreeStripe
2
- VERSION = '1.5.1'.freeze
2
+ VERSION = '1.6.0'.freeze
3
3
 
4
4
  def gem_version
5
5
  Gem::Version.new(VERSION)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: spree_stripe
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.5.1
4
+ version: 1.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vendo Connect Inc., Vendo Sp. z o.o.
@@ -15,28 +15,28 @@ dependencies:
15
15
  requirements:
16
16
  - - ">="
17
17
  - !ruby/object:Gem::Version
18
- version: 5.3.0
18
+ version: 5.4.0.beta
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - ">="
24
24
  - !ruby/object:Gem::Version
25
- version: 5.3.0
25
+ version: 5.4.0.beta
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: spree_admin
28
28
  requirement: !ruby/object:Gem::Requirement
29
29
  requirements:
30
30
  - - ">="
31
31
  - !ruby/object:Gem::Version
32
- version: 5.3.0
32
+ version: 5.4.0.beta
33
33
  type: :runtime
34
34
  prerelease: false
35
35
  version_requirements: !ruby/object:Gem::Requirement
36
36
  requirements:
37
37
  - - ">="
38
38
  - !ruby/object:Gem::Version
39
- version: 5.3.0
39
+ version: 5.4.0.beta
40
40
  - !ruby/object:Gem::Dependency
41
41
  name: stripe
42
42
  requirement: !ruby/object:Gem::Requirement
@@ -166,9 +166,6 @@ files:
166
166
  - app/assets/images/payment_icons/banktransfer.svg
167
167
  - app/assets/images/payment_icons/link.svg
168
168
  - app/assets/images/payment_icons/sepadebit.svg
169
- - app/controllers/spree/api/v2/storefront/stripe/base_controller.rb
170
- - app/controllers/spree/api/v2/storefront/stripe/payment_intents_controller.rb
171
- - app/controllers/spree/api/v2/storefront/stripe/setup_intents_controller.rb
172
169
  - app/controllers/spree_stripe/apple_pay_domain_verification_controller.rb
173
170
  - app/controllers/spree_stripe/payment_intents_controller.rb
174
171
  - app/controllers/spree_stripe/store_controller_decorator.rb
@@ -180,16 +177,20 @@ files:
180
177
  - app/javascript/spree_stripe/controllers/stripe_controller.js
181
178
  - app/jobs/spree_stripe/attach_customer_to_credit_card_job.rb
182
179
  - app/jobs/spree_stripe/base_job.rb
180
+ - app/jobs/spree_stripe/complete_order_from_session_job.rb
183
181
  - app/jobs/spree_stripe/complete_order_job.rb
184
182
  - app/jobs/spree_stripe/create_tax_transaction_job.rb
185
183
  - app/jobs/spree_stripe/create_webhook_endpoint_job.rb
186
184
  - app/jobs/spree_stripe/register_domain_job.rb
187
185
  - app/jobs/spree_stripe/update_customer_job.rb
186
+ - app/models/spree/payment_sessions/stripe.rb
188
187
  - app/models/spree_stripe/base.rb
189
188
  - app/models/spree_stripe/calculators/stripe_tax.rb
190
189
  - app/models/spree_stripe/credit_card_decorator.rb
191
190
  - app/models/spree_stripe/custom_domain_decorator.rb
192
191
  - app/models/spree_stripe/gateway.rb
192
+ - app/models/spree_stripe/gateway/payment_intents.rb
193
+ - app/models/spree_stripe/gateway/payment_sessions.rb
193
194
  - app/models/spree_stripe/gateway_customer_decorator.rb
194
195
  - app/models/spree_stripe/order_decorator.rb
195
196
  - app/models/spree_stripe/payment_decorator.rb
@@ -214,24 +215,6 @@ files:
214
215
  - app/presenters/spree_stripe/payment_intent_presenter.rb
215
216
  - app/presenters/spree_stripe/statement_descriptor_suffix_presenter.rb
216
217
  - app/presenters/spree_stripe/tax_presenter.rb
217
- - app/serializers/spree/api/v2/platform/affirm_serializer.rb
218
- - app/serializers/spree/api/v2/platform/after_pay_serializer.rb
219
- - app/serializers/spree/api/v2/platform/alipay_serializer.rb
220
- - app/serializers/spree/api/v2/platform/ideal_serializer.rb
221
- - app/serializers/spree/api/v2/platform/klarna_serializer.rb
222
- - app/serializers/spree/api/v2/platform/link_serializer.rb
223
- - app/serializers/spree/api/v2/platform/przelewy24_serializer.rb
224
- - app/serializers/spree/api/v2/platform/sepa_debit_serializer.rb
225
- - app/serializers/spree/v2/storefront/affirm_serializer.rb
226
- - app/serializers/spree/v2/storefront/after_pay_serializer.rb
227
- - app/serializers/spree/v2/storefront/alipay_serializer.rb
228
- - app/serializers/spree/v2/storefront/ideal_serializer.rb
229
- - app/serializers/spree/v2/storefront/klarna_serializer.rb
230
- - app/serializers/spree/v2/storefront/link_serializer.rb
231
- - app/serializers/spree/v2/storefront/payment_intent_serializer.rb
232
- - app/serializers/spree/v2/storefront/przelewy24_serializer.rb
233
- - app/serializers/spree/v2/storefront/sepa_debit_serializer.rb
234
- - app/serializers/spree_stripe/v2/storefront/payment_method_serializer_decorator.rb
235
218
  - app/services/spree_stripe/complete_order.rb
236
219
  - app/services/spree_stripe/create_gateway_webhooks.rb
237
220
  - app/services/spree_stripe/create_payment.rb
@@ -258,6 +241,27 @@ files:
258
241
  - config/routes.rb
259
242
  - db/migrate/20250310152812_setup_spree_stripe_models.rb
260
243
  - lib/generators/spree_stripe/install/install_generator.rb
244
+ - lib/spree_api_v2/spree/api/v2/platform/affirm_serializer.rb
245
+ - lib/spree_api_v2/spree/api/v2/platform/after_pay_serializer.rb
246
+ - lib/spree_api_v2/spree/api/v2/platform/alipay_serializer.rb
247
+ - lib/spree_api_v2/spree/api/v2/platform/ideal_serializer.rb
248
+ - lib/spree_api_v2/spree/api/v2/platform/klarna_serializer.rb
249
+ - lib/spree_api_v2/spree/api/v2/platform/link_serializer.rb
250
+ - lib/spree_api_v2/spree/api/v2/platform/przelewy24_serializer.rb
251
+ - lib/spree_api_v2/spree/api/v2/platform/sepa_debit_serializer.rb
252
+ - lib/spree_api_v2/spree/api/v2/storefront/stripe/base_controller.rb
253
+ - lib/spree_api_v2/spree/api/v2/storefront/stripe/payment_intents_controller.rb
254
+ - lib/spree_api_v2/spree/api/v2/storefront/stripe/setup_intents_controller.rb
255
+ - lib/spree_api_v2/spree/v2/storefront/affirm_serializer.rb
256
+ - lib/spree_api_v2/spree/v2/storefront/after_pay_serializer.rb
257
+ - lib/spree_api_v2/spree/v2/storefront/alipay_serializer.rb
258
+ - lib/spree_api_v2/spree/v2/storefront/ideal_serializer.rb
259
+ - lib/spree_api_v2/spree/v2/storefront/klarna_serializer.rb
260
+ - lib/spree_api_v2/spree/v2/storefront/link_serializer.rb
261
+ - lib/spree_api_v2/spree/v2/storefront/payment_intent_serializer.rb
262
+ - lib/spree_api_v2/spree/v2/storefront/przelewy24_serializer.rb
263
+ - lib/spree_api_v2/spree/v2/storefront/sepa_debit_serializer.rb
264
+ - lib/spree_api_v2/spree_stripe/v2/storefront/payment_method_serializer_decorator.rb
261
265
  - lib/spree_stripe.rb
262
266
  - lib/spree_stripe/configuration.rb
263
267
  - lib/spree_stripe/engine.rb
@@ -271,6 +275,7 @@ files:
271
275
  - lib/spree_stripe/testing_support/factories/payment_intent_factory.rb
272
276
  - lib/spree_stripe/testing_support/factories/przelewy24_payment_source_factory.rb
273
277
  - lib/spree_stripe/testing_support/factories/sepa_debit_payment_source_factory.rb
278
+ - lib/spree_stripe/testing_support/factories/stripe_payment_session_factory.rb
274
279
  - lib/spree_stripe/testing_support/factories/webhook_key_factory.rb
275
280
  - lib/spree_stripe/version.rb
276
281
  - vendor/javascript/@stripe--stripe-js--dist--pure.esm.js.js
@@ -279,9 +284,9 @@ licenses:
279
284
  - MIT
280
285
  metadata:
281
286
  bug_tracker_uri: https://github.com/spree/spree_stripe/issues
282
- changelog_uri: https://github.com/spree/spree_stripe/releases/tag/v1.5.1
287
+ changelog_uri: https://github.com/spree/spree_stripe/releases/tag/v1.6.0
283
288
  documentation_uri: https://docs.spreecommerce.org/
284
- source_code_uri: https://github.com/spree/spree_stripe/tree/v1.5.1
289
+ source_code_uri: https://github.com/spree/spree_stripe/tree/v1.6.0
285
290
  rdoc_options: []
286
291
  require_paths:
287
292
  - lib
@@ -289,7 +294,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
289
294
  requirements:
290
295
  - - ">="
291
296
  - !ruby/object:Gem::Version
292
- version: '3.0'
297
+ version: '3.2'
293
298
  required_rubygems_version: !ruby/object:Gem::Requirement
294
299
  requirements:
295
300
  - - ">="