跳到主要內容

訂閱

訂閱是週期性收入的基石,讓您能以重複性的排程向客戶收取產品和服務的費用。在 PaymentKit 中,當客戶購買具有週期性價格的產品時,就會建立訂閱。

本指南將引導您了解整個訂閱生命週期,從透過結帳階段(Checkout Session)建立訂閱,到管理其狀態和處理基於用量的計費。在繼續之前,請確保您已建立您的 產品價格

訂閱生命週期

訂閱會根據付款事件、試用期和管理操作而經歷各種狀態。了解此生命週期是有效管理客戶的關鍵。

Subscription Lifecycle Flow

d2
direction: down

style: {
  stroke-width: 2
  font-size: 14
}

Checkout: {
  label: "結帳階段"
  shape: circle
  style: {
    fill: "#e6f7ff"
    stroke: "#91d5ff"
  }
}

Incomplete: {
  label: "未完成"
  style: {
    fill: "#fffbe6"
    stroke: "#ffe58f"
  }
}

Trialing: {
  label: "試用中"
  style: {
    fill: "#f6ffed"
    stroke: "#b7eb8f"
  }
}

Active: {
  label: "啟用中"
  style: {
    fill: "#d4edda"
    stroke: "#28a745"
  }
}

Past-Due: {
  label: "逾期"
  style: {
    fill: "#fff1f0"
    stroke: "#ffccc7"
  }
}

Canceled: {
  label: "已取消"
  style: {
    fill: "#fafafa"
    stroke: "#d9d9d9"
  }
}

Paused: {
  label: "已暫停"
  style: {
    fill: "#e6f7ff"
    stroke: "#91d5ff"
  }
}

Checkout -> Incomplete: "付款失敗"
Checkout -> Trialing: "成功(含試用)"
Checkout -> Active: "成功(無試用)"

Incomplete -> Active: "付款成功"

Trialing -> Active: "試用結束"

Active -> Past-Due: "續訂失敗"
Past-Due -> Active: "付款成功"
Past-Due -> Canceled: "重試失敗"

Active -> Canceled: "由使用者/管理員取消"
Trialing -> Canceled: "由使用者/管理員取消"

Active <-> Paused: "暫停/恢復"

以下是訂閱可能具有的主要狀態:

  • active:訂閱狀態良好且付款皆為最新。
  • trialing:客戶正處於免費試用期。
  • past_due:最近一次付款嘗試失敗,PaymentKit 正在重試收費。
  • canceled:訂閱已被取消且不會續訂。
  • incomplete:訂閱的初始付款失敗。
  • incomplete_expired:初始付款失敗,且重試期已過。
  • paused:訂閱已暫時暫停,不會產生發票。

建立訂閱

訂閱並非直接建立。您需要建立一個 mode'subscription'結帳階段(Checkout Session)。此階段會引導客戶完成付款流程,成功完成後,系統會自動建立一個訂閱。

透過結帳建立訂閱

javascript
import payment from '@blocklet/payment-js';

async function createSubscriptionCheckout() {
  try {
    const session = await payment.checkout.sessions.create({
      success_url: 'https://example.com/success?session_id={CHECKOUT_SESSION_ID}',
      cancel_url: 'https://example.com/cancel',
      mode: 'subscription',
      line_items: [
        { price_id: 'price_xxx', quantity: 1 } // 請替換為您的週期性價格 ID
      ],
      subscription_data: {
        trial_period_days: 14, // 選用:14 天免費試用
        metadata: {
          project_id: 'proj_123',
        },
      },
    });
    console.log('Checkout session created:', session.url);
    // 將您的客戶重新導向至 session.url
  } catch (error) {
    console.error('Error creating checkout session:', error.message);
  }
}

createSubscriptionCheckout();

在此範例中,我們為一個具有週期性價格的產品建立了一個結帳階段。一旦客戶完成付款,將會建立一個新的訂閱,並在首次收費前處於 trialing(試用中)狀態 14 天。

管理現有訂閱

訂閱建立後,您可以使用 payment.subscriptions 資源來管理它。

檢索訂閱

使用訂閱 ID 來獲取特定訂閱的詳細資訊。

檢索訂閱

javascript
async function getSubscription(subscriptionId) {
  try {
    const subscription = await payment.subscriptions.retrieve(subscriptionId);
    console.log(`Status of ${subscription.id}: ${subscription.status}`);
    console.log('Current period ends:', new Date(subscription.current_period_end * 1000));
  } catch (error) {
    console.error('Error retrieving subscription:', error.message);
  }
}

getSubscription('sub_xxx'); // 請替換為有效的訂閱 ID

列出訂閱

您可以列出所有訂閱,或根據客戶 ID 或狀態等屬性進行篩選。

列出訂閱

javascript
async function listActiveSubscriptionsForCustomer(customerId) {
  try {
    const subscriptions = await payment.subscriptions.list({
      customer_id: customerId,
      status: 'active',
      activeFirst: true,
      order: 'created_at:DESC',
    });

    console.log(`Found ${subscriptions.total} active subscriptions for customer ${customerId}:`);
    subscriptions.data.forEach(sub => {
      console.log(`- ID: ${sub.id}, Status: ${sub.status}`);
    });
  } catch (error) {
    console.error('Error listing subscriptions:', error.message);
  }
}

listActiveSubscriptionsForCustomer('cus_xxx'); // 請替換為有效的客戶 ID

取消訂閱

取消訂閱是一個常見的需求。您可以選擇立即取消,或在目前計費週期結束時取消。

取消訂閱

javascript
async function cancelSubscription(subscriptionId) {
  try {
    const subscription = await payment.subscriptions.cancel(subscriptionId, {
      at: 'current_period_end', // 訂閱將保持啟用狀態,直到計費週期結束。
      // 使用 'now' 可立即取消。
      reason: 'cancellation_requested',
      feedback: 'User no longer needs the service.',
    });
    console.log(`Subscription ${subscription.id} scheduled for cancellation.`);
  } catch (error) {
    console.error('Error canceling subscription:', error.message);
  }
}

cancelSubscription('sub_xxx'); // 請替換為有效的訂閱 ID

暫停和恢復訂閱

對於需要暫時中止服務的情況,您可以暫停訂閱,並在之後恢復它。

暫停和恢復

javascript
async function manageSubscriptionPause(subscriptionId) {
  try {
    // 暫停訂閱
    let subscription = await payment.subscriptions.pause(subscriptionId);
    console.log(`Subscription ${subscription.id} is now ${subscription.status}.`);

    // 一段時間後,恢復訂閱
    subscription = await payment.subscriptions.resume(subscriptionId);
    console.log(`Subscription ${subscription.id} is now ${subscription.status}.`);

  } catch (error) {
    console.error('Error managing subscription pause state:', error.message);
  }
}

manageSubscriptionPause('sub_xxx'); // 請替換為有效的訂閱 ID

回報計量計費的用量

如果您的訂閱使用的價格 usage_type'metered',您必須在整個計費週期內回報用量。這可以透過為特定的訂閱項目建立用量記錄來完成。

回報用量

javascript
async function reportApiUsage(subscriptionItemId) {
  try {
    const usageRecord = await payment.subscriptionItems.createUsageRecord({
      subscription_item_id: subscriptionItemId,
      quantity: 100, // 要回報的用量
      action: 'increment', // 'increment' 會在現有用量上增加,'set' 則會覆寫。
      timestamp: Math.floor(Date.now() / 1000), // 用量發生的時間
    });
    console.log('Usage record created:', usageRecord.id);
  } catch (error) {
    console.error('Error reporting usage:', error.message);
  }
}

// 您可以在訂閱物件上找到 subscription_item_id。
reportApiUsage('si_xxx'); // 請替換為有效的訂閱項目 ID

在計費週期結束時,PaymentKit 會將所有回報的用量加總,並據此向客戶收費。

後續步驟

您現在對如何管理訂閱有了扎實的了解。若要深入研究,請探索詳細的 API 文件,並學習如何監聽與訂閱相關的事件。