About customer-management
A skill for developers using DodoPayments to manage customer records, hosted self-service portals, payment methods, wallets, and entitlements. It covers lifecycle operations and idempotent wallet ledger entries.
Key features
- 01Create, list, retrieve, and update customers
- 02Create time-limited self-service portal sessions
- 03List and delete saved payment methods
- 04Read wallet balances and create idempotent entries
- 05Retrieve customer entitlements and credit balances
Use cases
- 01Link app users to Dodo customer records
- 02Offer invoice and subscription self-service
- 03Record wallet credits or refunds
- 04Check customer grants and credit balances
Install
Copy the command and run it in a terminal.
npx skills add https:/ / github.com/ dodopayments/ skills --skill customer-managementAfter installing, type/customer-managementin Claude Code to confirm it loaded.
Details
From the project READMECustomer Management
Build customer records, hosted self-service portals, saved payment methods, and real-money wallet ledgers. This skill covers the full customer lifecycle: CRUD operations, payment method management, time-bound portal sessions, and idempotent wallet transactions.
When to use this skill
- Creating, listing, retrieving, or updating customer records
- Building a self-service customer portal for invoices, subscriptions, and payment methods
- Managing saved payment methods and customer payment history
- Implementing customer wallet balances and ledger entries for credits or refunds
- Linking your app's user records to Dodo customers
- Reading customer entitlements and credit balances
Core concepts
Customers are records in Dodo that group payments, subscriptions, and portal access. Each customer has an ID (prefix cus_), email, name, and optional metadata.
Customer Portal is a hosted, time-bound session that lets customers self-serve: view invoices, cancel subscriptions, change plans, update payment methods, recover on-hold subscriptions, and download license keys. You create a session and redirect to its link.
Payment Methods are saved cards or other payment instruments linked to a customer. You can list and delete them; creation happens during checkout.
Customer Wallets hold real-money balances (USD, INR) that customers can spend on future purchases. They are NOT the same as usage credits (see credit-based-billing skill). Wallet ledger entries are idempotent via idempotency_key to prevent duplicate charges.
Entitlements are feature or file grants. listCreditEntitlements returns credit balances; listEntitlements returns feature/file grants; listEntitlementGrants lists individual grants with revocation status.
Customer CRUD
Create, list, retrieve, and update customer records.
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' },
});
Customer Portal
Create a time-bound session that redirects customers to a hosted portal. In the portal, customers can view invoices, cancel subscriptions, change plans, update payment methods, recover on-hold subscriptions, and download license keys.
Never call the SDK from the browser: it uses your secret API key. Create the session in a server route and return only the link.
// 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;
The portal session link is valid for 24 hours. Pass send_email: true to have Dodo also email the link to the customer. Customers cannot:
- Create new subscriptions (use checkout for that)
- Refund themselves
- Change their email or name (contact support)
- Access billing history beyond invoices
If you need to rebuild any of these, you're duplicating the portal. Use it instead.
Saved Payment Methods
List and delete payment methods saved to a customer.
// 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',
});
Payment methods are created during checkout when the customer opts to save their card. You cannot create them directly via the API.
Customer Wallets
Wallets hold real-money balances (USD, INR) that customers can spend on future purchases. They are distinct from usage credits. List wallet balances and create idempotent ledger entries.
// 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 is critical. If your network fails after creating a ledger entry, retrying with the same idempotency_key returns the existing entry instead of creating a duplicate charge. Always use a stable, unique key per transaction.
Wallets are real money. Do not create entries without idempotency keys, and do not expose wallet balances to the client without verification.
Reading Customer Entitlements
Entitlements are feature or file grants. There are three methods:
// 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
// }]
// }
Linking Your App's Users to Dodo Customers
Store the Dodo customer ID in your user record, or use metadata to link them.
// 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);
Email matching pitfall: if a user changes their email in your app, the Dodo customer email won't update automatically. Always sync email changes explicitly via client.customers.update().
Common mistakes
Creating duplicate customers per checkout. Passing customer: { email } does not create a new customer each time: by default, checkout uses the email to find an existing customer and attaches the session to it. Duplicates come from setting feature_flags.always_create_new_customer: true, or from calling customers.create before every checkout. Create the customer once (or let the first checkout create it), store its customer_id, and pass that on later checkouts.
// 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 },
});
Building a bespoke billing UI instead of using the portal. The portal handles invoices, cancellations, plan changes, payment method updates, and on-hold recovery. If you rebuild these, you're duplicating work and missing edge cases.
Mutating wallet balances without an idempotency key. Retries will create duplicate entries and charge the customer twice.
// 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',
});
Exposing portal links publicly. Portal sessions are time-bound and tied to a customer ID. Don't log them or share them in URLs; generate them server-side and redirect immediately.
Confusing wallets with credits. Wallets hold real money (USD, INR). Credits are usage-based entitlements. See the credit-based-billing skill for credit management.