spree_stripe 1.8.0 → 1.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Rakefile +0 -3
- data/app/helpers/spree_stripe/base_helper.rb +7 -0
- data/app/javascript/spree_stripe/controllers/stripe_button_controller.js +48 -15
- data/app/javascript/spree_stripe/controllers/stripe_controller.js +2 -6
- data/app/models/spree_stripe/credit_card_decorator.rb +40 -4
- data/app/models/spree_stripe/store_decorator.rb +8 -6
- data/app/services/spree_stripe/create_source.rb +22 -2
- data/app/views/spree/checkout/payment/_spree_stripe.html.erb +1 -1
- data/app/views/spree_stripe/_quick_checkout.html.erb +1 -1
- data/db/migrate/20260610120000_add_fingerprint_to_spree_credit_cards.rb +52 -0
- data/lib/spree_stripe/testing_support/factories/stripe_payment_session_factory.rb +1 -1
- data/lib/spree_stripe/version.rb +1 -1
- metadata +5 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: cd7ae7fd51255dec03bb8bd709ceae4e9440dc8a61d64a62086fc91950937af0
|
|
4
|
+
data.tar.gz: 7f0c2798878679d21e6337269912e5a5be047c57348255b4536cf2f4de23ed87
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: edb953569b6fdab13d67ef13128f9edd29e2ce85a5a06f5ae7d1a4a2019dcf8a47aa313452474388ae983e2ccaacbbd8f9003dd8070ba34215fc5464a522fd1f
|
|
7
|
+
data.tar.gz: 77c47bb3f2d62f3ef0b6cbc48430dc834d6921c1a495aa293b7f3f8c82f25f17703c3a2cc0c3e7522d59e3008fedd5781bb3e9aeafb0d2183e0cf15d6e43cd8e
|
data/Rakefile
CHANGED
|
@@ -21,7 +21,4 @@ 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')
|
|
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')
|
|
27
24
|
end
|
|
@@ -21,6 +21,13 @@ module SpreeStripe
|
|
|
21
21
|
"cart_#{Spree::PrefixedId::SQIDS.encode([order.id])}"
|
|
22
22
|
end
|
|
23
23
|
|
|
24
|
+
# Server-generated v3 Store API path for the order's cart, passed to the
|
|
25
|
+
# storefront JS so proxied/prefixed deployments resolve the API correctly.
|
|
26
|
+
# @return [String]
|
|
27
|
+
def stripe_cart_api_path(order)
|
|
28
|
+
spree.api_v3_store_cart_path(stripe_cart_prefixed_id(order))
|
|
29
|
+
end
|
|
30
|
+
|
|
24
31
|
# The store's publishable Spree API key, used by the storefront JS as the
|
|
25
32
|
# `X-Spree-API-Key` header for v3 Store API calls. Creates one on first use
|
|
26
33
|
# when the store has no active publishable key yet.
|
|
@@ -10,15 +10,16 @@ const ZERO_DECIMAL_CURRENCIES = new Set([
|
|
|
10
10
|
])
|
|
11
11
|
|
|
12
12
|
// Apple Pay / Google Pay express checkout on the v3 Store API. The wallet sheet
|
|
13
|
-
// drives address + shipping selection against
|
|
14
|
-
// payment session is created and confirmed; completion is handled
|
|
15
|
-
// ConfirmPaymentsController via the `return_url` (same as the
|
|
13
|
+
// drives address + shipping selection against the server-provided cart API
|
|
14
|
+
// path, then a payment session is created and confirmed; completion is handled
|
|
15
|
+
// server-side by ConfirmPaymentsController via the `return_url` (same as the
|
|
16
|
+
// regular flow).
|
|
16
17
|
export default class extends Controller {
|
|
17
18
|
static values = {
|
|
18
19
|
apiKey: String,
|
|
19
20
|
spreeApiKey: String,
|
|
20
21
|
paymentMethodId: String,
|
|
21
|
-
|
|
22
|
+
cartApiPath: String,
|
|
22
23
|
cartToken: String,
|
|
23
24
|
confirmPaymentUrl: String,
|
|
24
25
|
currency: String,
|
|
@@ -38,6 +39,7 @@ export default class extends Controller {
|
|
|
38
39
|
connect() {
|
|
39
40
|
this.isGooglePay = false
|
|
40
41
|
this.shippingRateMap = new Map()
|
|
42
|
+
this.deliveryMethodMap = new Map()
|
|
41
43
|
this.initStripe()
|
|
42
44
|
}
|
|
43
45
|
|
|
@@ -139,8 +141,9 @@ export default class extends Controller {
|
|
|
139
141
|
})
|
|
140
142
|
if (!cart) return event.reject()
|
|
141
143
|
|
|
142
|
-
const { shippingRates, selectionMap } = this.buildShippingRates(cart.fulfillments || [])
|
|
144
|
+
const { shippingRates, selectionMap, methodMap } = this.buildShippingRates(cart.fulfillments || [])
|
|
143
145
|
this.shippingRateMap = selectionMap
|
|
146
|
+
this.deliveryMethodMap = methodMap
|
|
144
147
|
|
|
145
148
|
if (shippingRates.length === 0) return event.reject()
|
|
146
149
|
|
|
@@ -215,6 +218,17 @@ export default class extends Controller {
|
|
|
215
218
|
return
|
|
216
219
|
}
|
|
217
220
|
|
|
221
|
+
// The address patch above rebuilds fulfillments with their default rate,
|
|
222
|
+
// discarding the wallet's selection — re-select it before charging, or the
|
|
223
|
+
// order completes with the wrong delivery method and total.
|
|
224
|
+
if (this.shippingRequiredValue && event.shippingRate) {
|
|
225
|
+
const reselected = await this.reselectDeliveryRates(cart, event.shippingRate.id)
|
|
226
|
+
if (!reselected) {
|
|
227
|
+
event.paymentFailed({ reason: 'fail' })
|
|
228
|
+
return
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
218
232
|
const { error: submitError } = await this.elements.submit()
|
|
219
233
|
if (submitError) {
|
|
220
234
|
event.paymentFailed({ reason: 'fail' })
|
|
@@ -259,7 +273,7 @@ export default class extends Controller {
|
|
|
259
273
|
// --- v3 Store API calls -------------------------------------------------
|
|
260
274
|
|
|
261
275
|
async patchCart(body) {
|
|
262
|
-
const response = await fetch(this.
|
|
276
|
+
const response = await fetch(this.cartApiPathValue, {
|
|
263
277
|
method: 'PATCH',
|
|
264
278
|
headers: this.spreeApiHeaders,
|
|
265
279
|
body: JSON.stringify(body)
|
|
@@ -268,7 +282,7 @@ export default class extends Controller {
|
|
|
268
282
|
}
|
|
269
283
|
|
|
270
284
|
async patchFulfillment(fulfillmentId, deliveryRateId) {
|
|
271
|
-
const response = await fetch(`${this.
|
|
285
|
+
const response = await fetch(`${this.cartApiPathValue}/fulfillments/${fulfillmentId}`, {
|
|
272
286
|
method: 'PATCH',
|
|
273
287
|
headers: this.spreeApiHeaders,
|
|
274
288
|
body: JSON.stringify({ selected_delivery_rate_id: deliveryRateId })
|
|
@@ -277,7 +291,7 @@ export default class extends Controller {
|
|
|
277
291
|
}
|
|
278
292
|
|
|
279
293
|
async createPaymentSession(stripePaymentMethodId) {
|
|
280
|
-
const response = await fetch(`${this.
|
|
294
|
+
const response = await fetch(`${this.cartApiPathValue}/payment_sessions`, {
|
|
281
295
|
method: 'POST',
|
|
282
296
|
headers: this.spreeApiHeaders,
|
|
283
297
|
body: JSON.stringify({
|
|
@@ -304,11 +318,14 @@ export default class extends Controller {
|
|
|
304
318
|
return items
|
|
305
319
|
}
|
|
306
320
|
|
|
307
|
-
// Builds deduped Stripe shipping rates (by delivery_method_id)
|
|
308
|
-
// each Stripe rate id to the per-fulfillment delivery rate ids to select
|
|
321
|
+
// Builds deduped Stripe shipping rates (by delivery_method_id), a map from
|
|
322
|
+
// each Stripe rate id to the per-fulfillment delivery rate ids to select,
|
|
323
|
+
// and a map back to the delivery_method_id (the only id that survives a
|
|
324
|
+
// fulfillment rebuild — see reselectDeliveryRates).
|
|
309
325
|
buildShippingRates(fulfillments) {
|
|
310
326
|
const rateMap = new Map()
|
|
311
327
|
const selectionMap = new Map()
|
|
328
|
+
const methodMap = new Map()
|
|
312
329
|
|
|
313
330
|
for (const fulfillment of fulfillments) {
|
|
314
331
|
for (const rate of fulfillment.delivery_rates || []) {
|
|
@@ -320,6 +337,7 @@ export default class extends Controller {
|
|
|
320
337
|
const id = this.isGooglePay ? `${methodId}-${this.randomSuffix()}` : String(methodId)
|
|
321
338
|
rateMap.set(methodId, { id, displayName: rate.name, amount: this.toCents(rate.cost) })
|
|
322
339
|
selectionMap.set(id, [])
|
|
340
|
+
methodMap.set(id, methodId)
|
|
323
341
|
} else {
|
|
324
342
|
rateMap.get(methodId).amount += this.toCents(rate.cost)
|
|
325
343
|
}
|
|
@@ -329,7 +347,26 @@ export default class extends Controller {
|
|
|
329
347
|
}
|
|
330
348
|
}
|
|
331
349
|
|
|
332
|
-
return { shippingRates: Array.from(rateMap.values()), selectionMap }
|
|
350
|
+
return { shippingRates: Array.from(rateMap.values()), selectionMap, methodMap }
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Re-selects the delivery method chosen in the wallet sheet on freshly
|
|
354
|
+
// rebuilt fulfillments. Matches by delivery_method_id: fulfillment and rate
|
|
355
|
+
// ids do not survive the rebuild that a shipping-address update triggers
|
|
356
|
+
// server-side (checkout reverts to the address step and shipments are
|
|
357
|
+
// recreated with their default rate).
|
|
358
|
+
async reselectDeliveryRates(cart, stripeRateId) {
|
|
359
|
+
const methodId = this.deliveryMethodMap.get(stripeRateId)
|
|
360
|
+
if (!methodId) return false
|
|
361
|
+
|
|
362
|
+
for (const fulfillment of cart.fulfillments || []) {
|
|
363
|
+
const rate = (fulfillment.delivery_rates || []).find((r) => r.delivery_method_id === methodId)
|
|
364
|
+
if (!rate || rate.selected) continue
|
|
365
|
+
|
|
366
|
+
const updated = await this.patchFulfillment(fulfillment.id, rate.id)
|
|
367
|
+
if (!updated) return false
|
|
368
|
+
}
|
|
369
|
+
return true
|
|
333
370
|
}
|
|
334
371
|
|
|
335
372
|
// True when every shippable fulfillment has at least one delivery rate — i.e.
|
|
@@ -384,10 +421,6 @@ export default class extends Controller {
|
|
|
384
421
|
}
|
|
385
422
|
}
|
|
386
423
|
|
|
387
|
-
get cartApiBase() {
|
|
388
|
-
return `/api/v3/store/carts/${this.cartIdValue}`
|
|
389
|
-
}
|
|
390
|
-
|
|
391
424
|
get spreeApiHeaders() {
|
|
392
425
|
return {
|
|
393
426
|
'X-Spree-API-Key': this.spreeApiKeyValue,
|
|
@@ -10,7 +10,7 @@ export default class extends Controller {
|
|
|
10
10
|
static values = {
|
|
11
11
|
apiKey: String,
|
|
12
12
|
clientSecret: String,
|
|
13
|
-
|
|
13
|
+
cartApiPath: String,
|
|
14
14
|
cartToken: String,
|
|
15
15
|
spreeApiKey: String,
|
|
16
16
|
orderEmail: String,
|
|
@@ -134,7 +134,7 @@ export default class extends Controller {
|
|
|
134
134
|
return false
|
|
135
135
|
}
|
|
136
136
|
|
|
137
|
-
const response = await fetch(this.
|
|
137
|
+
const response = await fetch(this.cartApiPathValue, {
|
|
138
138
|
method: 'PATCH',
|
|
139
139
|
headers: this.spreeApiHeaders,
|
|
140
140
|
body: JSON.stringify(body)
|
|
@@ -224,10 +224,6 @@ export default class extends Controller {
|
|
|
224
224
|
return null
|
|
225
225
|
}
|
|
226
226
|
|
|
227
|
-
get cartApiBase() {
|
|
228
|
-
return `/api/v3/store/carts/${this.cartIdValue}`
|
|
229
|
-
}
|
|
230
|
-
|
|
231
227
|
get spreeApiHeaders() {
|
|
232
228
|
return {
|
|
233
229
|
'X-Spree-API-Key': this.spreeApiKeyValue,
|
|
@@ -1,13 +1,49 @@
|
|
|
1
1
|
module SpreeStripe
|
|
2
|
+
# Backports Stripe's credit-card fingerprint dedup onto Spree::CreditCard for
|
|
3
|
+
# spree < 5.6.0, which doesn't ship it. spree >= 5.6.0 provides the column,
|
|
4
|
+
# scope, and validation in core, so this is only prepended (below) when core
|
|
5
|
+
# lacks it. (Wallet metadata accessors already live in core since 5.4, so they
|
|
6
|
+
# don't need backporting here.)
|
|
2
7
|
module CreditCardDecorator
|
|
3
8
|
def self.prepended(base)
|
|
4
|
-
|
|
9
|
+
# Matches saved cards by the gateway's stable card fingerprint plus expiry,
|
|
10
|
+
# used to reuse an existing card instead of saving a duplicate when a gateway
|
|
11
|
+
# issues a fresh payment method id for the same physical card.
|
|
12
|
+
#
|
|
13
|
+
# @param fingerprint [String] gateway card fingerprint
|
|
14
|
+
# @param month [Integer, String] expiry month
|
|
15
|
+
# @param year [Integer, String] expiry year
|
|
16
|
+
base.scope :by_fingerprint, ->(fingerprint, month, year) {
|
|
17
|
+
cards_table = Spree::CreditCard.table_name
|
|
18
|
+
|
|
19
|
+
where(fingerprint: fingerprint).
|
|
20
|
+
where("CAST(#{cards_table}.month AS DECIMAL) = ?", month).
|
|
21
|
+
where("CAST(#{cards_table}.year AS DECIMAL) = ?", year)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
# Prevents saving the same physical card (same gateway fingerprint + expiry)
|
|
25
|
+
# twice for a user and payment method. Skipped when the gateway does not
|
|
26
|
+
# provide a fingerprint.
|
|
27
|
+
base.validate :fingerprint_not_duplicated, if: -> { fingerprint.present? }
|
|
5
28
|
end
|
|
6
29
|
|
|
7
|
-
|
|
8
|
-
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
# month/year are varchar columns exposed as integer attributes, so the
|
|
33
|
+
# match must go through #by_fingerprint's decimal cast rather than a plain
|
|
34
|
+
# equality (which Postgres rejects as `character varying = integer`).
|
|
35
|
+
def fingerprint_not_duplicated
|
|
36
|
+
duplicates = self.class.where(user_id: user_id, payment_method_id: payment_method_id).
|
|
37
|
+
by_fingerprint(fingerprint, month, year)
|
|
38
|
+
duplicates = duplicates.where.not(id: id) if persisted?
|
|
39
|
+
|
|
40
|
+
errors.add(:fingerprint, :taken) if duplicates.exists?
|
|
9
41
|
end
|
|
10
42
|
end
|
|
11
43
|
end
|
|
12
44
|
|
|
13
|
-
|
|
45
|
+
# spree >= 5.6.0 ships fingerprint dedup in core, so only prepend the backport
|
|
46
|
+
# when it's absent (spree < 5.6.0).
|
|
47
|
+
unless Spree::CreditCard.respond_to?(:by_fingerprint)
|
|
48
|
+
Spree::CreditCard.prepend(SpreeStripe::CreditCardDecorator)
|
|
49
|
+
end
|
|
@@ -3,21 +3,23 @@ module SpreeStripe
|
|
|
3
3
|
def self.prepended(base)
|
|
4
4
|
base.store_accessor :private_metadata, :stripe_apple_pay_domain_id
|
|
5
5
|
base.store_accessor :private_metadata, :stripe_top_level_domain_id
|
|
6
|
+
|
|
7
|
+
base.after_commit :register_stripe_domain, on: :update, if: -> { code_previously_changed? }
|
|
6
8
|
end
|
|
7
9
|
|
|
8
10
|
def stripe_gateway
|
|
9
11
|
@stripe_gateway ||= payment_methods.stripe.active.last
|
|
10
12
|
end
|
|
11
13
|
|
|
12
|
-
def handle_code_changes
|
|
13
|
-
super
|
|
14
|
-
|
|
15
|
-
SpreeStripe::RegisterDomainJob.perform_later(id)
|
|
16
|
-
end
|
|
17
|
-
|
|
18
14
|
def billing_name
|
|
19
15
|
name
|
|
20
16
|
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def register_stripe_domain
|
|
21
|
+
SpreeStripe::RegisterDomainJob.perform_later(id)
|
|
22
|
+
end
|
|
21
23
|
end
|
|
22
24
|
end
|
|
23
25
|
|
|
@@ -47,14 +47,33 @@ module SpreeStripe
|
|
|
47
47
|
|
|
48
48
|
def find_or_create_credit_card
|
|
49
49
|
if user
|
|
50
|
-
|
|
50
|
+
exact_source = user.credit_cards.find_by(gateway_payment_profile_id: stripe_payment_method_id)
|
|
51
|
+
return exact_source if exact_source
|
|
51
52
|
|
|
52
|
-
|
|
53
|
+
matching_source = match_credit_card_by_fingerprint
|
|
54
|
+
return matching_source if matching_source
|
|
53
55
|
end
|
|
54
56
|
|
|
55
57
|
Spree::CreditCard.create!(credit_card_params)
|
|
56
58
|
end
|
|
57
59
|
|
|
60
|
+
# Stripe issues a new PaymentMethod (pm_xxx) on every new credit card entry, even for the same physical card,
|
|
61
|
+
# so deduping on gateway_payment_profile_id alone misses repeat cards. Stripe's card.fingerprint
|
|
62
|
+
# is stable for the same card number, so we match on fingerprint + expiry (per Stripe guidance) to
|
|
63
|
+
# reuse the existing saved card instead of creating a duplicate.
|
|
64
|
+
#
|
|
65
|
+
# @return [Spree::CreditCard, nil] the existing saved card for this physical card, or nil
|
|
66
|
+
def match_credit_card_by_fingerprint
|
|
67
|
+
card = stripe_payment_method_details.card
|
|
68
|
+
return if card.fingerprint.blank?
|
|
69
|
+
|
|
70
|
+
user.credit_cards.
|
|
71
|
+
where(payment_method: gateway).
|
|
72
|
+
by_fingerprint(card.fingerprint, card.exp_month, card.exp_year).
|
|
73
|
+
order(created_at: :desc).
|
|
74
|
+
first
|
|
75
|
+
end
|
|
76
|
+
|
|
58
77
|
def credit_card_params
|
|
59
78
|
card_details = stripe_payment_method_details.card
|
|
60
79
|
customer = gateway.fetch_or_create_customer(user: user, order: order)
|
|
@@ -65,6 +84,7 @@ module SpreeStripe
|
|
|
65
84
|
payment_method: gateway,
|
|
66
85
|
gateway_customer_profile_id: customer&.profile_id,
|
|
67
86
|
gateway_payment_profile_id: stripe_payment_method_id,
|
|
87
|
+
fingerprint: card_details.fingerprint,
|
|
68
88
|
name: stripe_billing_details.name,
|
|
69
89
|
month: card_details.exp_month,
|
|
70
90
|
year: card_details.exp_year,
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
data-controller="checkout-stripe"
|
|
4
4
|
data-checkout-stripe-api-key-value="<%= current_stripe_gateway.try(:preferred_publishable_key) %>"
|
|
5
5
|
data-checkout-stripe-client-secret-value="<%= current_stripe_payment_session.client_secret %>"
|
|
6
|
-
data-checkout-stripe-cart-
|
|
6
|
+
data-checkout-stripe-cart-api-path-value="<%= stripe_cart_api_path(@order) %>"
|
|
7
7
|
data-checkout-stripe-cart-token-value="<%= @order.token %>"
|
|
8
8
|
data-checkout-stripe-spree-api-key-value="<%= current_store_publishable_api_key %>"
|
|
9
9
|
data-checkout-stripe-order-email-value="<%= @order.email %>"
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
checkout_stripe_button_api_key_value: current_stripe_gateway.try(:preferred_publishable_key),
|
|
7
7
|
checkout_stripe_button_spree_api_key_value: current_store_publishable_api_key,
|
|
8
8
|
checkout_stripe_button_payment_method_id_value: current_stripe_gateway.prefixed_id,
|
|
9
|
-
|
|
9
|
+
checkout_stripe_button_cart_api_path_value: stripe_cart_api_path(@order),
|
|
10
10
|
checkout_stripe_button_cart_token_value: @order.token,
|
|
11
11
|
checkout_stripe_button_confirm_payment_url_value: spree.stripe_confirm_payment_url(@order.prefixed_id),
|
|
12
12
|
checkout_stripe_button_currency_value: @order.currency.downcase,
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
class AddFingerprintToSpreeCreditCards < ActiveRecord::Migration[7.2]
|
|
2
|
+
INDEX_NAME = 'index_spree_credit_cards_unique_fingerprint'.freeze
|
|
3
|
+
|
|
4
|
+
# spree >= 5.6.0 ships this same migration in core (same name, so Rails' migration
|
|
5
|
+
# installer skips this copy). On spree < 5.6.0 it runs here, giving the gateway a
|
|
6
|
+
# place to store Stripe's card fingerprint for dedup. Guarded so it stays a no-op
|
|
7
|
+
# if the column/index already exist.
|
|
8
|
+
def up
|
|
9
|
+
add_column :spree_credit_cards, :fingerprint, :string, if_not_exists: true
|
|
10
|
+
|
|
11
|
+
return if index_name_exists?(:spree_credit_cards, INDEX_NAME)
|
|
12
|
+
|
|
13
|
+
# Prevent duplicate saved cards (same gateway fingerprint + expiry) per user and
|
|
14
|
+
# payment method at the database level. Only active, fingerprinted cards are
|
|
15
|
+
# constrained, so legacy/non-gateway cards (NULL fingerprint) and soft-deleted
|
|
16
|
+
# rows are left untouched.
|
|
17
|
+
if ActiveRecord::Base.connection.adapter_name == 'Mysql2'
|
|
18
|
+
# MySQL has no partial indexes, but treats NULL as distinct in unique indexes,
|
|
19
|
+
# so NULL fingerprints are naturally allowed. COALESCE on deleted_at keeps
|
|
20
|
+
# soft-deleted rows from colliding with active ones.
|
|
21
|
+
execute <<-SQL
|
|
22
|
+
CREATE UNIQUE INDEX #{INDEX_NAME}
|
|
23
|
+
ON spree_credit_cards(
|
|
24
|
+
user_id,
|
|
25
|
+
payment_method_id,
|
|
26
|
+
fingerprint,
|
|
27
|
+
month,
|
|
28
|
+
year,
|
|
29
|
+
(COALESCE(deleted_at, CAST('1970-01-01' AS DATETIME)))
|
|
30
|
+
)
|
|
31
|
+
SQL
|
|
32
|
+
else
|
|
33
|
+
add_index :spree_credit_cards, [:user_id, :payment_method_id, :fingerprint, :month, :year],
|
|
34
|
+
unique: true,
|
|
35
|
+
where: 'fingerprint IS NOT NULL AND deleted_at IS NULL',
|
|
36
|
+
name: INDEX_NAME
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def down
|
|
41
|
+
# From spree 5.6.0 the fingerprint column AND its unique index live in core (both
|
|
42
|
+
# share the same names), and Rails' migration installer dedupes this migration
|
|
43
|
+
# against core's — so on 5.6.0+ this migration never created them. Leave core's
|
|
44
|
+
# schema untouched there, which also covers an app that added the column here on
|
|
45
|
+
# older spree and later upgraded: the column is now core-owned and must survive
|
|
46
|
+
# a rollback.
|
|
47
|
+
return if Gem::Version.new(Spree.version) >= Gem::Version.new('5.6.0')
|
|
48
|
+
|
|
49
|
+
remove_index :spree_credit_cards, name: INDEX_NAME if index_name_exists?(:spree_credit_cards, INDEX_NAME)
|
|
50
|
+
remove_column :spree_credit_cards, :fingerprint if column_exists?(:spree_credit_cards, :fingerprint)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
FactoryBot.define do
|
|
2
2
|
factory :stripe_payment_session, class: 'Spree::PaymentSessions::Stripe' do
|
|
3
3
|
order { create(:order_with_line_items) }
|
|
4
|
-
payment_method { create(:stripe_gateway,
|
|
4
|
+
payment_method { create(:stripe_gateway, store: order.store) }
|
|
5
5
|
amount { order.total }
|
|
6
6
|
currency { order.currency }
|
|
7
7
|
status { 'pending' }
|
data/lib/spree_stripe/version.rb
CHANGED
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.8.
|
|
4
|
+
version: 1.8.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Vendo Connect Inc., Vendo Sp. z o.o.
|
|
@@ -227,6 +227,7 @@ files:
|
|
|
227
227
|
- config/locales/en.yml
|
|
228
228
|
- config/routes.rb
|
|
229
229
|
- db/migrate/20250310152812_setup_spree_stripe_models.rb
|
|
230
|
+
- db/migrate/20260610120000_add_fingerprint_to_spree_credit_cards.rb
|
|
230
231
|
- lib/generators/spree_stripe/install/install_generator.rb
|
|
231
232
|
- lib/spree_stripe.rb
|
|
232
233
|
- lib/spree_stripe/configuration.rb
|
|
@@ -249,9 +250,9 @@ licenses:
|
|
|
249
250
|
- MIT
|
|
250
251
|
metadata:
|
|
251
252
|
bug_tracker_uri: https://github.com/spree/spree_stripe/issues
|
|
252
|
-
changelog_uri: https://github.com/spree/spree_stripe/releases/tag/v1.8.
|
|
253
|
+
changelog_uri: https://github.com/spree/spree_stripe/releases/tag/v1.8.1
|
|
253
254
|
documentation_uri: https://docs.spreecommerce.org/
|
|
254
|
-
source_code_uri: https://github.com/spree/spree_stripe/tree/v1.8.
|
|
255
|
+
source_code_uri: https://github.com/spree/spree_stripe/tree/v1.8.1
|
|
255
256
|
rdoc_options: []
|
|
256
257
|
require_paths:
|
|
257
258
|
- lib
|
|
@@ -267,7 +268,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
267
268
|
version: '0'
|
|
268
269
|
requirements:
|
|
269
270
|
- none
|
|
270
|
-
rubygems_version: 4.0.
|
|
271
|
+
rubygems_version: 4.0.3
|
|
271
272
|
specification_version: 4
|
|
272
273
|
summary: Official Spree Commerce Stripe payment gateway extension
|
|
273
274
|
test_files: []
|