JavaScriptNode.jsSaaS / FaaSStripeTypeScript

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

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

参考

ブックマークや限定記事(予定)など

WP Kyotoサポーター募集中

WordPressやフロントエンドアプリのホスティング、Algolia・AWSなどのサービス利用料を支援する「WP Kyotoサポーター」を募集しています。
月額または年額の有料プランを契約すると、ブックマーク機能などのサポーター限定機能がご利用いただけます。

14日間のトライアルも用意しておりますので、「このサイトよく見るな」という方はぜひご検討ください。

広告ここから
広告ここまで

Related Category posts