Просмотр исходного кода

[web] set default payment method based on recurly data (#31025)

GitOrigin-RevId: ad7ffe400748ace66aa7e4775eedb738028c8a0c
Kristina 8 месяцев назад
Родитель
Сommit
c876bf2c5f

+ 14 - 28
services/web/scripts/helpers/migrate_recurly_customers_to_stripe.helpers.mjs

@@ -367,47 +367,33 @@ export function getTaxIdType(country, taxIdValue, postalCode) {
  *
  *
  * @param {Stripe.PaymentMethod[]} paymentMethods
  * @param {Stripe.PaymentMethod[]} paymentMethods
  * @param {string} stripeCustomerId
  * @param {string} stripeCustomerId
+ * @param {object|null} billingInfo - Recurly billing info object
  * @returns {Stripe.PaymentMethod} valid payment method
  * @returns {Stripe.PaymentMethod} valid payment method
  * @throws {Error} if no valid payment method found
  * @throws {Error} if no valid payment method found
  */
  */
-export function coalesceOrThrowPaymentMethod(paymentMethods, stripeCustomerId) {
+export function coalesceOrThrowPaymentMethod(
+  paymentMethods,
+  stripeCustomerId,
+  billingInfo
+) {
   if (paymentMethods.length === 0) {
   if (paymentMethods.length === 0) {
     throw new Error(
     throw new Error(
       `Stripe customer ${stripeCustomerId} has no usable payment method`
       `Stripe customer ${stripeCustomerId} has no usable payment method`
     )
     )
   }
   }
 
 
-  const now = new Date()
-
-  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
-  })
+  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
+  )
 
 
-  if (nonExpiredPaymentMethods.length === 0) {
+  if (matchingPaymentMethods.length === 0) {
     throw new Error(
     throw new Error(
       `Stripe customer ${stripeCustomerId} has no usable payment method`
       `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]
 }
 }

+ 90 - 77
services/web/scripts/helpers/migrate_recurly_customers_to_stripe.helpers.node.test.mjs

@@ -234,115 +234,128 @@ test('getCanadaTaxIdType returns null when format is unknown/ambiguous', () => {
 
 
 test('coalesceOrThrowPaymentMethod throws when payment methods array is empty', () => {
 test('coalesceOrThrowPaymentMethod throws when payment methods array is empty', () => {
   assert.throws(
   assert.throws(
-    () => coalesceOrThrowPaymentMethod([], 'cus_123'),
+    () => coalesceOrThrowPaymentMethod([], 'cus_123', {}),
     /Stripe customer cus_123 has no usable payment method/
     /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 = [
   const paymentMethods = [
     {
     {
       id: 'pm_1',
       id: 'pm_1',
-      card: { exp_month: 12, exp_year: 2030 },
-    },
-    {
-      id: 'pm_2',
-      card: { exp_month: 12, exp_year: 2031 },
+      card: { last4: '1234', exp_month: 12, exp_year: 2030 },
     },
     },
   ]
   ]
+  const billingInfo = {
+    paymentMethod: { lastFour: '5678', expMonth: 12, expYear: 2030 },
+  }
   assert.throws(
   assert.throws(
-    () => coalesceOrThrowPaymentMethod(paymentMethods, 'cus_123'),
-    /Stripe customer cus_123 has multiple usable payment methods/
+    () => coalesceOrThrowPaymentMethod(paymentMethods, 'cus_123', billingInfo),
+    /Stripe customer cus_123 has no usable payment method/
   )
   )
 })
 })
 
 
-test('coalesceOrThrowPaymentMethod returns single non-expired payment method', () => {
+test('coalesceOrThrowPaymentMethod returns matching payment method', () => {
   const paymentMethod = {
   const paymentMethod = {
-    id: 'pm_valid',
-    card: { exp_month: 12, exp_year: 2030 },
+    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([paymentMethod], 'cus_123')
+  const result = coalesceOrThrowPaymentMethod(
+    paymentMethods,
+    'cus_123',
+    billingInfo
+  )
   assert.equal(result, paymentMethod)
   assert.equal(result, paymentMethod)
 })
 })
 
 
-test('coalesceOrThrowPaymentMethod filters out expired and returns valid method', () => {
-  const expiredMethod = {
-    id: 'pm_expired',
-    card: { exp_month: 1, exp_year: 2020 },
+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 validMethod = {
-    id: 'pm_valid',
-    card: { exp_month: 12, exp_year: 2030 },
+  const paymentMethods = [paymentMethod1, paymentMethod2]
+  const billingInfo = {
+    paymentMethod: { lastFour: '1234', expMonth: 12, expYear: 2030 },
   }
   }
   const result = coalesceOrThrowPaymentMethod(
   const result = coalesceOrThrowPaymentMethod(
-    [expiredMethod, validMethod],
-    'cus_123'
+    paymentMethods,
+    'cus_123',
+    billingInfo
   )
   )
-  assert.equal(result, validMethod)
+  assert.equal(result, paymentMethod1)
 })
 })
 
 
-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 },
+test('coalesceOrThrowPaymentMethod filters out non-matching methods', () => {
+  const matchingMethod = {
+    id: 'pm_match',
+    card: { last4: '1234', exp_month: 12, exp_year: 2030 },
   }
   }
-  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 nonMatchingMethod = {
+    id: 'pm_no_match',
+    card: { last4: '5678', exp_month: 12, exp_year: 2030 },
   }
   }
-  const validMethod = {
-    id: 'pm_valid',
-    card: { exp_month: 12, exp_year: 2030 },
+  const paymentMethods = [nonMatchingMethod, matchingMethod]
+  const billingInfo = {
+    paymentMethod: { lastFour: '1234', expMonth: 12, expYear: 2030 },
   }
   }
   const result = coalesceOrThrowPaymentMethod(
   const result = coalesceOrThrowPaymentMethod(
-    [invalidMethod, validMethod],
-    'cus_123'
+    paymentMethods,
+    'cus_123',
+    billingInfo
   )
   )
-  assert.equal(result, validMethod)
+  assert.equal(result, matchingMethod)
 })
 })
 
 
-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 },
+test('coalesceOrThrowPaymentMethod handles null billingInfo', () => {
+  const paymentMethods = [
+    {
+      id: 'pm_1',
+      card: { last4: '1234', exp_month: 12, exp_year: 2030 },
+    },
+  ]
+  assert.throws(
+    () => coalesceOrThrowPaymentMethod(paymentMethods, 'cus_123', null),
+    /Stripe customer cus_123 has no usable payment method/
+  )
+})
+
+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 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(
   const result = coalesceOrThrowPaymentMethod(
-    [invalidMethod, validMethod],
-    'cus_123'
+    paymentMethods,
+    'cus_123',
+    billingInfo
   )
   )
-  assert.equal(result, validMethod)
+  assert.equal(result.id, 'pm_2')
 })
 })

+ 2 - 1
services/web/scripts/recurly/migrate_recurly_customers_to_stripe.mjs

@@ -973,7 +973,8 @@ async function processCustomer(
     )
     )
     const paymentMethod = coalesceOrThrowPaymentMethod(
     const paymentMethod = coalesceOrThrowPaymentMethod(
       paymentMethods,
       paymentMethods,
-      stripeCustomerId
+      stripeCustomerId,
+      billingInfo
     )
     )
 
 
     /** @type {Record<string, string>} */
     /** @type {Record<string, string>} */