Skip to main content

Blog

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

Posted over 2 years ago
この記事をシェア:

請求サイクルを変更する必要が出てきたのですが、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で逐次実行

参考

Tools to Support Stripe Development

We provide helpful tools to extend the Stripe Dashboard and streamline development and testing.

View All Tools

Support This Project

If you find this content helpful, consider supporting the project through GitHub Sponsors. Your support helps maintain and improve these tools.