StripeのSubscriptionをまとめて作り直すコード

請求サイクルを変更する必要が出てきたのですが、Stripeではアクティブなsubscriptionの請求サイクルを更新することはできません。

まぁ、ユーザーからしたら当然のことですが。

ただ、開発環境だとたまにこのあたりを変更したくなる時があったりするので、plan_id指定で一括再生成するコードを書いてみました。

コード

長いです。

import * as  Stripe from 'stripe'
import * as moment from 'moment'
const stripe = new Stripe(process.env.STRIPE_API_KEY as string)
import subscriptions = Stripe.subscriptions
import ISubscription = subscriptions.ISubscription


/**
 * Subscriptionを再帰的に全取得する
 * @param {string} planId 
 * @param [stripe.subscriptions.ISubscription[] = []] subscriptions 
 * @param [string] finalId 
 */
const getAllSubscription = async (planId: string, subscriptions: ISubscription[] = [], finalId?: string): Promise<ISubscription[]> => {
  const props: subscriptions.ISubscriptionListOptions = {
    plan: planId,
    limit:  100
  }
  if (finalId) props.starting_after = finalId
  const sub = await stripe.subscriptions.list(props)
  const merged = sub.data.concat(subscriptions)
  if (sub.has_more) {
    const finalId = sub.data[sub.data.length - 1].id
    return getAllSubscription(planId, merged, finalId)
  }
  return merged
}
/**
 * Subscriptionを再作成する
 * @param {stripe.subscriptions.ISubscription} subscription
 */
const replaceSubscription = async (subscription: ISubscription) => {
  if (subscription.items.data.length > 1) return
  
  await stripe.subscriptions.del(subscription.id)

  const customerId = typeof subscription.customer === "string" ? subscription.customer : subscription.customer.id
  const options: subscriptions.ISubscriptionCreationOptions ={
    customer: customerId,
    billing_cycle_anchor: moment().add(1, 'month').startOf('month').unix(),
    items: subscription.items.data.map(
      (item): subscriptions.ISubscriptionCreationItem => {
        return {
          plan: item.plan.id,
          quantity: item.quantity,
          metadata: item.metadata
        }
      }
    ),
    metadata: subscription.metadata
  }
  return stripe.subscriptions.create(options)
}
const replaceAllSubscriptions = async (planId: string): Promise<void> => {
  const subscriptions = await getAllSubscription('free')

  const legacyPlanSubscriptions = subscriptions
  .filter(subscription => {
    return subscription.items.data.length < 2
  })
  console.log(`free subscription has a ${legacyPlanSubscriptions.length} items`)

  let p: Promise<void> = Promise.resolve()
  legacyPlanSubscriptions.forEach(subscription => {
    console.log(`subscription id: ${subscription.id}`)
    if (subscription.items.data.length > 1) return
    p = p.then(async() => {
      try {
        const result = await replaceSubscription(subscription)
        if (!result) {
          console.log('SKIPPED')
          return
        }
        console.log('=====')
        console.log(`ID: https://dashboard.stripe.com/test/subscriptions/${result.id}`)
        console.log(`Customer: https://dashboard.stripe.com/test/customers/${result.customer}`)
        console.log(`Period: ${moment.unix(result.current_period_start).toISOString()}`)
      } catch (e) {
        console.log(e)
      }
    })
  })
  p.then(() => {
    console.log('===FINISH===')
  })
}

やっていること

  • subscriptions.listを再帰的に回して対象のsubscriptionを全取得
  • 更新したい部分だけ上書きしてsubscriptions.createを実行
  • 非同期に実行するとスロットリングされるので、foreach + Promiseで逐次実行

参考

Comment