mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-05-23 17:19:37 +02:00
[web] set default payment method based on recurly data (#31025)
GitOrigin-RevId: ad7ffe400748ace66aa7e4775eedb738028c8a0c
This commit is contained in:
@@ -367,47 +367,33 @@ export function getTaxIdType(country, taxIdValue, postalCode) {
|
||||
*
|
||||
* @param {Stripe.PaymentMethod[]} paymentMethods
|
||||
* @param {string} stripeCustomerId
|
||||
* @param {object|null} billingInfo - Recurly billing info object
|
||||
* @returns {Stripe.PaymentMethod} valid payment method
|
||||
* @throws {Error} if no valid payment method found
|
||||
*/
|
||||
export function coalesceOrThrowPaymentMethod(paymentMethods, stripeCustomerId) {
|
||||
export function coalesceOrThrowPaymentMethod(
|
||||
paymentMethods,
|
||||
stripeCustomerId,
|
||||
billingInfo
|
||||
) {
|
||||
if (paymentMethods.length === 0) {
|
||||
throw new Error(
|
||||
`Stripe customer ${stripeCustomerId} has no usable payment method`
|
||||
)
|
||||
}
|
||||
|
||||
const now = new Date()
|
||||
const matchingPaymentMethods = paymentMethods.filter(
|
||||
method =>
|
||||
method.card?.last4 === billingInfo?.paymentMethod?.lastFour &&
|
||||
method.card?.exp_month === billingInfo?.paymentMethod?.expMonth &&
|
||||
method.card?.exp_year === billingInfo?.paymentMethod?.expYear
|
||||
)
|
||||
|
||||
const nonExpiredPaymentMethods = paymentMethods.filter(method => {
|
||||
if (!method.card?.exp_month || !method.card?.exp_year) {
|
||||
return false
|
||||
}
|
||||
|
||||
const expirationDate = new Date(
|
||||
method.card.exp_year,
|
||||
method.card.exp_month,
|
||||
0,
|
||||
23,
|
||||
59,
|
||||
59,
|
||||
999
|
||||
)
|
||||
|
||||
return expirationDate >= now
|
||||
})
|
||||
|
||||
if (nonExpiredPaymentMethods.length === 0) {
|
||||
if (matchingPaymentMethods.length === 0) {
|
||||
throw new Error(
|
||||
`Stripe customer ${stripeCustomerId} has no usable payment method`
|
||||
)
|
||||
}
|
||||
|
||||
if (nonExpiredPaymentMethods.length > 1) {
|
||||
throw new Error(
|
||||
`Stripe customer ${stripeCustomerId} has multiple usable payment methods`
|
||||
)
|
||||
}
|
||||
|
||||
return nonExpiredPaymentMethods[0]
|
||||
return matchingPaymentMethods[0]
|
||||
}
|
||||
|
||||
@@ -234,115 +234,128 @@ test('getCanadaTaxIdType returns null when format is unknown/ambiguous', () => {
|
||||
|
||||
test('coalesceOrThrowPaymentMethod throws when payment methods array is empty', () => {
|
||||
assert.throws(
|
||||
() => coalesceOrThrowPaymentMethod([], 'cus_123'),
|
||||
() => coalesceOrThrowPaymentMethod([], 'cus_123', {}),
|
||||
/Stripe customer cus_123 has no usable payment method/
|
||||
)
|
||||
})
|
||||
|
||||
test('coalesceOrThrowPaymentMethod throws when no payment methods have card info', () => {
|
||||
const paymentMethods = [{ id: 'pm_1', card: null }, { id: 'pm_2' }]
|
||||
assert.throws(
|
||||
() => coalesceOrThrowPaymentMethod(paymentMethods, 'cus_123'),
|
||||
/Stripe customer cus_123 has no usable payment method/
|
||||
)
|
||||
})
|
||||
|
||||
test('coalesceOrThrowPaymentMethod throws when all payment methods are expired', () => {
|
||||
const paymentMethods = [
|
||||
{
|
||||
id: 'pm_expired',
|
||||
card: { exp_month: 1, exp_year: 2020 },
|
||||
},
|
||||
]
|
||||
assert.throws(
|
||||
() => coalesceOrThrowPaymentMethod(paymentMethods, 'cus_123'),
|
||||
/Stripe customer cus_123 has no usable payment method/
|
||||
)
|
||||
})
|
||||
|
||||
test('coalesceOrThrowPaymentMethod throws when multiple non-expired payment methods exist', () => {
|
||||
test('coalesceOrThrowPaymentMethod throws when no payment methods match billing info', () => {
|
||||
const paymentMethods = [
|
||||
{
|
||||
id: 'pm_1',
|
||||
card: { exp_month: 12, exp_year: 2030 },
|
||||
card: { last4: '1234', exp_month: 12, exp_year: 2030 },
|
||||
},
|
||||
]
|
||||
const billingInfo = {
|
||||
paymentMethod: { lastFour: '5678', expMonth: 12, expYear: 2030 },
|
||||
}
|
||||
assert.throws(
|
||||
() => coalesceOrThrowPaymentMethod(paymentMethods, 'cus_123', billingInfo),
|
||||
/Stripe customer cus_123 has no usable payment method/
|
||||
)
|
||||
})
|
||||
|
||||
test('coalesceOrThrowPaymentMethod returns matching payment method', () => {
|
||||
const paymentMethod = {
|
||||
id: 'pm_match',
|
||||
card: { last4: '1234', exp_month: 12, exp_year: 2030 },
|
||||
}
|
||||
const paymentMethods = [paymentMethod]
|
||||
const billingInfo = {
|
||||
paymentMethod: { lastFour: '1234', expMonth: 12, expYear: 2030 },
|
||||
}
|
||||
const result = coalesceOrThrowPaymentMethod(
|
||||
paymentMethods,
|
||||
'cus_123',
|
||||
billingInfo
|
||||
)
|
||||
assert.equal(result, paymentMethod)
|
||||
})
|
||||
|
||||
test('coalesceOrThrowPaymentMethod matches first of multiple matching methods', () => {
|
||||
const paymentMethod1 = {
|
||||
id: 'pm_1',
|
||||
card: { last4: '1234', exp_month: 12, exp_year: 2030 },
|
||||
}
|
||||
const paymentMethod2 = {
|
||||
id: 'pm_2',
|
||||
card: { last4: '1234', exp_month: 12, exp_year: 2030 },
|
||||
}
|
||||
const paymentMethods = [paymentMethod1, paymentMethod2]
|
||||
const billingInfo = {
|
||||
paymentMethod: { lastFour: '1234', expMonth: 12, expYear: 2030 },
|
||||
}
|
||||
const result = coalesceOrThrowPaymentMethod(
|
||||
paymentMethods,
|
||||
'cus_123',
|
||||
billingInfo
|
||||
)
|
||||
assert.equal(result, paymentMethod1)
|
||||
})
|
||||
|
||||
test('coalesceOrThrowPaymentMethod filters out non-matching methods', () => {
|
||||
const matchingMethod = {
|
||||
id: 'pm_match',
|
||||
card: { last4: '1234', exp_month: 12, exp_year: 2030 },
|
||||
}
|
||||
const nonMatchingMethod = {
|
||||
id: 'pm_no_match',
|
||||
card: { last4: '5678', exp_month: 12, exp_year: 2030 },
|
||||
}
|
||||
const paymentMethods = [nonMatchingMethod, matchingMethod]
|
||||
const billingInfo = {
|
||||
paymentMethod: { lastFour: '1234', expMonth: 12, expYear: 2030 },
|
||||
}
|
||||
const result = coalesceOrThrowPaymentMethod(
|
||||
paymentMethods,
|
||||
'cus_123',
|
||||
billingInfo
|
||||
)
|
||||
assert.equal(result, matchingMethod)
|
||||
})
|
||||
|
||||
test('coalesceOrThrowPaymentMethod handles null billingInfo', () => {
|
||||
const paymentMethods = [
|
||||
{
|
||||
id: 'pm_2',
|
||||
card: { exp_month: 12, exp_year: 2031 },
|
||||
id: 'pm_1',
|
||||
card: { last4: '1234', exp_month: 12, exp_year: 2030 },
|
||||
},
|
||||
]
|
||||
assert.throws(
|
||||
() => coalesceOrThrowPaymentMethod(paymentMethods, 'cus_123'),
|
||||
/Stripe customer cus_123 has multiple usable payment methods/
|
||||
() => coalesceOrThrowPaymentMethod(paymentMethods, 'cus_123', null),
|
||||
/Stripe customer cus_123 has no usable payment method/
|
||||
)
|
||||
})
|
||||
|
||||
test('coalesceOrThrowPaymentMethod returns single non-expired payment method', () => {
|
||||
const paymentMethod = {
|
||||
id: 'pm_valid',
|
||||
card: { exp_month: 12, exp_year: 2030 },
|
||||
}
|
||||
const result = coalesceOrThrowPaymentMethod([paymentMethod], 'cus_123')
|
||||
assert.equal(result, paymentMethod)
|
||||
test('coalesceOrThrowPaymentMethod handles missing paymentMethod in billingInfo', () => {
|
||||
const paymentMethods = [
|
||||
{
|
||||
id: 'pm_1',
|
||||
card: { last4: '1234', exp_month: 12, exp_year: 2030 },
|
||||
},
|
||||
]
|
||||
const billingInfo = {}
|
||||
assert.throws(
|
||||
() => coalesceOrThrowPaymentMethod(paymentMethods, 'cus_123', billingInfo),
|
||||
/Stripe customer cus_123 has no usable payment method/
|
||||
)
|
||||
})
|
||||
|
||||
test('coalesceOrThrowPaymentMethod filters out expired and returns valid method', () => {
|
||||
const expiredMethod = {
|
||||
id: 'pm_expired',
|
||||
card: { exp_month: 1, exp_year: 2020 },
|
||||
}
|
||||
const validMethod = {
|
||||
id: 'pm_valid',
|
||||
card: { exp_month: 12, exp_year: 2030 },
|
||||
test('coalesceOrThrowPaymentMethod handles missing card in payment method', () => {
|
||||
const paymentMethods = [
|
||||
{ id: 'pm_1' }, // no card property
|
||||
{
|
||||
id: 'pm_2',
|
||||
card: { last4: '1234', exp_month: 12, exp_year: 2030 },
|
||||
},
|
||||
]
|
||||
const billingInfo = {
|
||||
paymentMethod: { lastFour: '1234', expMonth: 12, expYear: 2030 },
|
||||
}
|
||||
const result = coalesceOrThrowPaymentMethod(
|
||||
[expiredMethod, validMethod],
|
||||
'cus_123'
|
||||
paymentMethods,
|
||||
'cus_123',
|
||||
billingInfo
|
||||
)
|
||||
assert.equal(result, validMethod)
|
||||
})
|
||||
|
||||
test('coalesceOrThrowPaymentMethod keeps payment method expiring this month', () => {
|
||||
const now = new Date()
|
||||
const currentMonth = now.getMonth() + 1 // JavaScript months are 0-indexed, Stripe uses 1-indexed
|
||||
const currentYear = now.getFullYear()
|
||||
|
||||
const paymentMethod = {
|
||||
id: 'pm_expiring_this_month',
|
||||
card: { exp_month: currentMonth, exp_year: currentYear },
|
||||
}
|
||||
const result = coalesceOrThrowPaymentMethod([paymentMethod], 'cus_123')
|
||||
assert.equal(result, paymentMethod)
|
||||
})
|
||||
|
||||
test('coalesceOrThrowPaymentMethod filters out method without exp_month', () => {
|
||||
const invalidMethod = {
|
||||
id: 'pm_invalid',
|
||||
card: { exp_year: 2030 },
|
||||
}
|
||||
const validMethod = {
|
||||
id: 'pm_valid',
|
||||
card: { exp_month: 12, exp_year: 2030 },
|
||||
}
|
||||
const result = coalesceOrThrowPaymentMethod(
|
||||
[invalidMethod, validMethod],
|
||||
'cus_123'
|
||||
)
|
||||
assert.equal(result, validMethod)
|
||||
})
|
||||
|
||||
test('coalesceOrThrowPaymentMethod filters out method without exp_year', () => {
|
||||
const invalidMethod = {
|
||||
id: 'pm_invalid',
|
||||
card: { exp_month: 12 },
|
||||
}
|
||||
const validMethod = {
|
||||
id: 'pm_valid',
|
||||
card: { exp_month: 12, exp_year: 2030 },
|
||||
}
|
||||
const result = coalesceOrThrowPaymentMethod(
|
||||
[invalidMethod, validMethod],
|
||||
'cus_123'
|
||||
)
|
||||
assert.equal(result, validMethod)
|
||||
assert.equal(result.id, 'pm_2')
|
||||
})
|
||||
|
||||
@@ -973,7 +973,8 @@ async function processCustomer(
|
||||
)
|
||||
const paymentMethod = coalesceOrThrowPaymentMethod(
|
||||
paymentMethods,
|
||||
stripeCustomerId
|
||||
stripeCustomerId,
|
||||
billingInfo
|
||||
)
|
||||
|
||||
/** @type {Record<string, string>} */
|
||||
|
||||
Reference in New Issue
Block a user