customer-management について
DodoPaymentsを利用する開発者向けのスキルで、顧客情報、ホスト型セルフサービスポータル、支払い方法、ウォレット、エンタイトルメントを扱います。顧客管理の操作と、冪等性を備えたウォレット台帳の記録を説明します。
主な機能
- 01顧客情報の作成・一覧・取得・更新
- 02有効期限付きポータルセッションの作成
- 03保存済み支払い方法の一覧・削除
- 04ウォレット残高の確認と冪等な記録
- 05顧客エンタイトルメントとクレジット残高の取得
活用シーン
- 01アプリのユーザーとDodo顧客の紐付け
- 02請求書やサブスクリプションのセルフサービス提供
- 03ウォレットへの入金や返金の記録
- 04顧客の付与機能やクレジット残高の確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ dodopayments/ skills --skill customer-managementインストール後、Claude Code で/customer-managementを入力して読み込まれたか確認します。
詳細
顧客管理
顧客レコード、ホスト型セルフサービス・ポータル、保存済みの支払い方法、および実通貨のウォレット台帳を構築します。このスキルでは、CRUD 操作、支払い方法の管理、有効期限付きのポータルセッション、冪等性を備えたウォレット取引など、顧客のライフサイクル全体を扱います。
このスキルを使う場面
- 顧客レコードの作成、一覧表示、取得、更新
- 請求書、サブスクリプション、支払い方法を利用できるセルフサービスの顧客ポータルの構築
- 保存済みの支払い方法と顧客の支払い履歴の管理
- クレジットや返金のための顧客ウォレット残高と台帳エントリーの実装
- アプリのユーザーレコードと Dodo の顧客の紐付け
- 顧客のエンタイトルメントとクレジット残高の確認
基本概念
顧客は、支払い、サブスクリプション、ポータルへのアクセスをまとめる Dodo のレコードです。各顧客には ID(プレフィックス cus_)、メールアドレス、名前、および任意のメタデータがあります。
顧客ポータルは、有効期限付きのホスト型セッションです。顧客はここで、請求書の表示、サブスクリプションのキャンセル、プランの変更、支払い方法の更新、保留中のサブスクリプションの復旧、ライセンスキーのダウンロードをセルフサービスで行えます。セッションを作成し、そのリンクにリダイレクトします。
支払い方法は、顧客に紐付けられた保存済みのカードやその他の支払い手段です。一覧表示や削除ができます。作成はチェックアウト時に行われます。
顧客ウォレットには、顧客が今後の購入に使える実通貨(USD、INR)の残高が保持されます。利用クレジットとは異なるものです(credit-based-billing スキルを参照)。ウォレットの台帳エントリーでは、二重請求を防ぐため、idempotency_key による冪等性が確保されます。
エンタイトルメントは、機能やファイルへの付与です。listCreditEntitlements はクレジット残高を返し、listEntitlements は機能やファイルへの付与を返し、listEntitlementGrants は個々の付与と取り消し状況を一覧表示します。
顧客の CRUD
顧客レコードを作成、一覧表示、取得、更新します。
import DodoPayments from 'dodopayments';
const client = new DodoPayments({
bearerToken: process.env.DODO_PAYMENTS_API_KEY,
environment: 'test_mode',
});
// Create a customer
const customer = await client.customers.create({
email: '[email protected]',
name: 'Alice Chen',
metadata: {
userId: 'user_12345',
tier: 'premium',
},
});
console.log(customer.customer_id); // cus_...
// List customers
const customers = await client.customers.list({
page_size: 10,
});
// Retrieve a customer
const retrieved = await client.customers.retrieve('cus_abc123');
// Update a customer
await client.customers.update('cus_abc123', {
name: 'Alice Chen-Smith',
metadata: { tier: 'enterprise' },
});
顧客ポータル
有効期限付きのセッションを作成し、顧客をホスト型ポータルにリダイレクトします。ポータルでは、顧客は請求書の表示、サブスクリプションのキャンセル、プランの変更、支払い方法の更新、保留中のサブスクリプションの復旧、ライセンスキーのダウンロードを行えます。
SDK からブラウザーで呼び出さないでください。秘密 API キーが使われるためです。サーバールートでセッションを作成し、リンクのみを返してください。
// Server route: create a portal session
const session = await client.customers.customerPortal.create('cus_abc123', {
return_url: 'https://yourapp.com/dashboard',
// send_email: true, // optionally also email the link to the customer
});
return Response.json({ link: session.link });
// Browser: redirect the customer
const res = await fetch('/api/portal', { method: 'POST' });
const { link } = await res.json();
window.location.href = link;
ポータルセッションのリンクは 24 時間有効です。send_email: true を渡すと、Dodo から顧客にリンクがメールで送信されます。顧客ができない操作は次のとおりです。
- 新しいサブスクリプションの作成(その場合はチェックアウトを使用してください)
- 自分で返金すること
- メールアドレスや名前の変更(サポートに問い合わせてください)
- 請求書以外の請求履歴へのアクセス
これらの機能を自分で再構築すると、ポータルと同じ機能を重複して実装することになります。代わりにポータルを使用してください。
保存済みの支払い方法
顧客に保存されている支払い方法を一覧表示し、削除します。
// List payment methods for a customer
const methods = await client.customers.retrievePaymentMethods('cus_abc123');
console.log(methods);
// {
// items: [{ payment_method_id: 'pm_xyz789', payment_method: 'card' }]
// }
// Delete a payment method
await client.customers.deletePaymentMethod('pm_xyz789', {
customer_id: 'cus_abc123',
});
顧客がチェックアウト時にカードの保存を選択すると、支払い方法が作成されます。API から直接作成することはできません。
顧客ウォレット
ウォレットには、顧客が今後の購入に使える実通貨(USD、INR)の残高が保持されます。利用クレジットとは別のものです。ウォレット残高を一覧表示し、冪等性を備えた台帳エントリーを作成します。
// List wallets for a customer
const wallets = await client.customers.wallets.list('cus_abc123');
console.log(wallets);
// {
// items: [
// { currency: 'USD', balance: 5000, customer_id: 'cus_abc123', ... },
// { currency: 'INR', balance: 100000, customer_id: 'cus_abc123', ... }
// ],
// total_balance_usd: 17000
// }
// List ledger entries
const entries = await client.customers.wallets.ledgerEntries.list('cus_abc123', {
page_size: 20,
});
// Create a ledger entry (credit or debit)
// Amounts are in the smallest currency unit (cents for USD, paise for INR)
await client.customers.wallets.ledgerEntries.create('cus_abc123', {
amount: 1000, // $10.00 in USD
currency: 'USD',
entry_type: 'credit', // or 'debit'
reason: 'Refund for order #12345',
idempotency_key: 'refund_order_12345_v1',
});
冪等性は非常に重要です。 台帳エントリーの作成後にネットワーク障害が発生した場合、同じ idempotency_key で再試行すると、重複して請求される代わりに既存のエントリーが返されます。取引ごとに、常に一意で安定したキーを使用してください。
ウォレットは実通貨を扱います。冪等性キーを指定せずにエントリーを作成しないでください。また、確認なしにウォレット残高をクライアントに公開しないでください。
顧客のエンタイトルメントの確認
エンタイトルメントは、機能やファイルへの付与です。方法は次の 3 つです。
// List credit entitlements (credit balances)
const credits = await client.customers.listCreditEntitlements('cus_abc123');
console.log(credits);
// {
// items: [{
// credit_entitlement_id: 'cred_ent_1',
// balance: '5000',
// name: 'API Calls',
// overage: '0',
// unit: 'calls'
// }]
// }
// List feature/file entitlements
const features = await client.customers.listEntitlements('cus_abc123');
console.log(features);
// {
// items: [
// {
// grant_id: 'entg_feature_1',
// entitlement_id: 'ent_feature_1',
// entitlement_name: 'Pro Features',
// integration_type: 'feature_flag',
// status: 'delivered',
// created_at: '2026-01-01T00:00:00Z',
// updated_at: '2026-01-01T00:00:00Z'
// }
// ]
// }
// List individual grants (with revocation status)
const grants = await client.customers.listEntitlementGrants('cus_abc123', {
page_size: 50,
});
console.log(grants);
// {
// items: [{
// id: 'entg_feature_1',
// entitlement_id: 'ent_feature_1',
// status: 'Delivered',
// revoked_at: null
// }]
// }
アプリのユーザーと Dodo の顧客の紐付け
Dodo の顧客 ID をユーザーレコードに保存するか、メタデータを使って紐付けます。
// Option 1: Store the Dodo customer ID in your database
const customer = await client.customers.create({
email: user.email,
name: user.name,
});
await db.users.update(user.id, { dodo_customer_id: customer.customer_id });
// Option 2: Use metadata to store your user ID
const customer = await client.customers.create({
email: user.email,
name: user.name,
metadata: {
app_user_id: user.id,
},
});
// Later, retrieve by email (not a direct API call, but common pattern)
// You must store the mapping yourself or query Dodo's list endpoint
const customers = await client.customers.list({ page_size: 100 });
const found = customers.items.find(c => c.email === user.email);
メールアドレスの一致に関する注意点: アプリでユーザーがメールアドレスを変更しても、Dodo の顧客メールアドレスは自動的には更新されません。メールアドレスの変更は、必ず client.customers.update() で明示的に同期してください。
よくある間違い
チェックアウトごとに顧客を重複して作成すること。 customer: { email } を渡しても、毎回新しい顧客が作成されるわけではありません。デフォルトでは、チェックアウトはメールアドレスを使って既存の顧客を検索し、その顧客にセッションを紐付けます。重複が発生するのは、feature_flags.always_create_new_customer: true を設定した場合、またはチェックアウトのたびに customers.create を呼び出した場合です。顧客は一度だけ作成する(または最初のチェックアウト時に作成させる)ようにし、その customer_id を保存して、後続のチェックアウトで渡してください。
// WRONG: forces a new customer record on every checkout
const session = await client.checkoutSessions.create({
product_cart: [{ product_id: 'pdt_abc', quantity: 1 }],
customer: { email: user.email },
feature_flags: { always_create_new_customer: true },
});
// RIGHT: reuse the stored customer ID, or fall back to email (matched to an existing customer by default)
const session = await client.checkoutSessions.create({
product_cart: [{ product_id: 'pdt_abc', quantity: 1 }],
customer: user.dodo_customer_id
? { customer_id: user.dodo_customer_id }
: { email: user.email },
});
ポータルを使わず、独自の課金 UI を構築すること。 ポータルは、請求書、解約、プラン変更、支払い方法の更新、保留中の支払いの回復に対応しています。これらを再構築すると、作業が重複し、エッジケースへの対応も漏れてしまいます。
冪等性キーを使わずにウォレット残高を変更すること。 再試行によってエントリーが重複し、顧客に二重請求されます。
// WRONG
await client.customers.wallets.ledgerEntries.create('cus_abc123', {
amount: 1000,
currency: 'USD',
entry_type: 'credit',
reason: 'Refund',
// No idempotency_key
});
// RIGHT
await client.customers.wallets.ledgerEntries.create('cus_abc123', {
amount: 1000,
currency: 'USD',
entry_type: 'credit',
reason: 'Refund',
idempotency_key: 'refund_order_12345_v1',
});
ポータルリンクを一般公開すること。 ポータルセッションには有効期限があり、顧客 ID に紐付いています。セッションをログに記録したり、URL で共有したりしないでください。サーバーサイドで生成し、直ちにリダイレクトしてください。
ウォレットとクレジットを混同すること。 ウォレットには実際のお金(USD、INR)が保持されます。クレジットは使用量ベースの利用権です。クレジットの管理については、credit-based-billing スキルを参照してください。