@dodopayments/opencode-plugin 0.3.2 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,281 @@
1
+ ---
2
+ name: customer-management
3
+ description: Guide for managing customer records, saved payment methods, monetary wallets, and hosted customer-portal sessions; subscription lifecycle and custom billing UI are covered separately.
4
+ ---
5
+
6
+ # Customer Management
7
+
8
+ 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.
9
+
10
+ ## When to use this skill
11
+
12
+ - Creating, listing, retrieving, or updating customer records
13
+ - Building a self-service customer portal for invoices, subscriptions, and payment methods
14
+ - Managing saved payment methods and customer payment history
15
+ - Implementing customer wallet balances and ledger entries for credits or refunds
16
+ - Linking your app's user records to Dodo customers
17
+ - Reading customer entitlements and credit balances
18
+
19
+ ## Core concepts
20
+
21
+ **Customers** are records in Dodo that group payments, subscriptions, and portal access. Each customer has an ID (prefix `cus_`), email, name, and optional metadata.
22
+
23
+ **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.
24
+
25
+ **Payment Methods** are saved cards or other payment instruments linked to a customer. You can list and delete them; creation happens during checkout.
26
+
27
+ **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.
28
+
29
+ **Entitlements** are feature or file grants. `listCreditEntitlements` returns credit balances; `listEntitlements` returns feature/file grants; `listEntitlementGrants` lists individual grants with revocation status.
30
+
31
+ ## Customer CRUD
32
+
33
+ Create, list, retrieve, and update customer records.
34
+
35
+ ```typescript
36
+ import DodoPayments from 'dodopayments';
37
+
38
+ const client = new DodoPayments({
39
+ bearerToken: process.env.DODO_PAYMENTS_API_KEY,
40
+ environment: 'test_mode',
41
+ });
42
+
43
+ // Create a customer
44
+ const customer = await client.customers.create({
45
+ email: 'alice@example.com',
46
+ name: 'Alice Chen',
47
+ metadata: {
48
+ userId: 'user_12345',
49
+ tier: 'premium',
50
+ },
51
+ });
52
+ console.log(customer.customer_id); // cus_...
53
+
54
+ // List customers
55
+ const customers = await client.customers.list({
56
+ page_size: 10,
57
+ });
58
+
59
+ // Retrieve a customer
60
+ const retrieved = await client.customers.retrieve('cus_abc123');
61
+
62
+ // Update a customer
63
+ await client.customers.update('cus_abc123', {
64
+ name: 'Alice Chen-Smith',
65
+ metadata: { tier: 'enterprise' },
66
+ });
67
+ ```
68
+
69
+ ## Customer Portal
70
+
71
+ Create a time-bound session that redirects customers to a hosted portal. The portal is read-only for most actions; customers can view invoices, cancel subscriptions, change plans, update payment methods, recover on-hold subscriptions, and download license keys.
72
+
73
+ ```typescript
74
+ // Create a portal session
75
+ const session = await client.customers.customerPortal.create('cus_abc123', {
76
+ return_url: 'https://yourapp.com/dashboard',
77
+ });
78
+
79
+ // Redirect the customer
80
+ window.location.href = session.link;
81
+ ```
82
+
83
+ The portal session link expires after a short time. Customers cannot:
84
+ - Create new subscriptions (use checkout for that)
85
+ - Refund themselves
86
+ - Change their email or name (contact support)
87
+ - Access billing history beyond invoices
88
+
89
+ If you need to rebuild any of these, you're duplicating the portal. Use it instead.
90
+
91
+ ## Saved Payment Methods
92
+
93
+ List and delete payment methods saved to a customer.
94
+
95
+ ```typescript
96
+ // List payment methods for a customer
97
+ const methods = await client.customers.retrievePaymentMethods('cus_abc123');
98
+ console.log(methods);
99
+ // {
100
+ // items: [{ payment_method_id: 'pm_xyz789', payment_method: 'card' }]
101
+ // }
102
+
103
+ // Delete a payment method
104
+ await client.customers.deletePaymentMethod('pm_xyz789', {
105
+ customer_id: 'cus_abc123',
106
+ });
107
+ ```
108
+
109
+ Payment methods are created during checkout when the customer opts to save their card. You cannot create them directly via the API.
110
+
111
+ ## Customer Wallets
112
+
113
+ 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.
114
+
115
+ ```typescript
116
+ // List wallets for a customer
117
+ const wallets = await client.customers.wallets.list('cus_abc123');
118
+ console.log(wallets);
119
+ // {
120
+ // items: [
121
+ // { currency: 'USD', balance: 5000, customer_id: 'cus_abc123', ... },
122
+ // { currency: 'INR', balance: 100000, customer_id: 'cus_abc123', ... }
123
+ // ],
124
+ // total_balance_usd: 17000
125
+ // }
126
+
127
+ // List ledger entries
128
+ const entries = await client.customers.wallets.ledgerEntries.list('cus_abc123', {
129
+ page_size: 20,
130
+ });
131
+
132
+ // Create a ledger entry (credit or debit)
133
+ // Amounts are in the smallest currency unit (cents for USD, paise for INR)
134
+ await client.customers.wallets.ledgerEntries.create('cus_abc123', {
135
+ amount: 1000, // $10.00 in USD
136
+ currency: 'USD',
137
+ entry_type: 'credit', // or 'debit'
138
+ reason: 'Refund for order #12345',
139
+ idempotency_key: 'refund_order_12345_v1',
140
+ });
141
+ ```
142
+
143
+ **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.
144
+
145
+ Wallets are real money. Do not create entries without idempotency keys, and do not expose wallet balances to the client without verification.
146
+
147
+ ## Reading Customer Entitlements
148
+
149
+ Entitlements are feature or file grants. There are three methods:
150
+
151
+ ```typescript
152
+ // List credit entitlements (credit balances)
153
+ const credits = await client.customers.listCreditEntitlements('cus_abc123');
154
+ console.log(credits);
155
+ // {
156
+ // items: [{
157
+ // credit_entitlement_id: 'cred_ent_1',
158
+ // balance: '5000',
159
+ // name: 'API Calls',
160
+ // overage: '0',
161
+ // unit: 'calls'
162
+ // }]
163
+ // }
164
+
165
+ // List feature/file entitlements
166
+ const features = await client.customers.listEntitlements('cus_abc123');
167
+ console.log(features);
168
+ // {
169
+ // items: [
170
+ // {
171
+ // grant_id: 'entg_feature_1',
172
+ // entitlement_id: 'ent_feature_1',
173
+ // entitlement_name: 'Pro Features',
174
+ // integration_type: 'feature_flag',
175
+ // status: 'delivered',
176
+ // created_at: '2026-01-01T00:00:00Z',
177
+ // updated_at: '2026-01-01T00:00:00Z'
178
+ // }
179
+ // ]
180
+ // }
181
+
182
+ // List individual grants (with revocation status)
183
+ const grants = await client.customers.listEntitlementGrants('cus_abc123', {
184
+ page_size: 50,
185
+ });
186
+ console.log(grants);
187
+ // {
188
+ // items: [{
189
+ // id: 'entg_feature_1',
190
+ // entitlement_id: 'ent_feature_1',
191
+ // status: 'Delivered',
192
+ // revoked_at: null
193
+ // }]
194
+ // }
195
+ ```
196
+
197
+ ## Linking Your App's Users to Dodo Customers
198
+
199
+ Store the Dodo customer ID in your user record, or use metadata to link them.
200
+
201
+ ```typescript
202
+ // Option 1: Store the Dodo customer ID in your database
203
+ const customer = await client.customers.create({
204
+ email: user.email,
205
+ name: user.name,
206
+ });
207
+ await db.users.update(user.id, { dodo_customer_id: customer.customer_id });
208
+
209
+ // Option 2: Use metadata to store your user ID
210
+ const customer = await client.customers.create({
211
+ email: user.email,
212
+ name: user.name,
213
+ metadata: {
214
+ app_user_id: user.id,
215
+ },
216
+ });
217
+
218
+ // Later, retrieve by email (not a direct API call, but common pattern)
219
+ // You must store the mapping yourself or query Dodo's list endpoint
220
+ const customers = await client.customers.list({ page_size: 100 });
221
+ const found = customers.items.find(c => c.email === user.email);
222
+ ```
223
+
224
+ **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()`.
225
+
226
+ ## Common mistakes
227
+
228
+ **Creating duplicate customers per checkout.** Each checkout should reuse an existing `customer_id` or create the customer once. Creating a new customer for every transaction fragments your customer data.
229
+
230
+ ```typescript
231
+ // WRONG
232
+ const session = await client.checkoutSessions.create({
233
+ product_cart: [...],
234
+ customer: { email: user.email }, // Creates a new customer each time
235
+ });
236
+
237
+ // RIGHT
238
+ const customer = await client.customers.create({
239
+ email: user.email,
240
+ name: user.name,
241
+ });
242
+ const session = await client.checkoutSessions.create({
243
+ product_cart: [...],
244
+ customer: { customer_id: customer.customer_id }, // Reuse the customer
245
+ });
246
+ ```
247
+
248
+ **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.
249
+
250
+ **Mutating wallet balances without an idempotency key.** Retries will create duplicate entries and charge the customer twice.
251
+
252
+ ```typescript
253
+ // WRONG
254
+ await client.customers.wallets.ledgerEntries.create('cus_abc123', {
255
+ amount: 1000,
256
+ currency: 'USD',
257
+ entry_type: 'credit',
258
+ reason: 'Refund',
259
+ // No idempotency_key
260
+ });
261
+
262
+ // RIGHT
263
+ await client.customers.wallets.ledgerEntries.create('cus_abc123', {
264
+ amount: 1000,
265
+ currency: 'USD',
266
+ entry_type: 'credit',
267
+ reason: 'Refund',
268
+ idempotency_key: 'refund_order_12345_v1',
269
+ });
270
+ ```
271
+
272
+ **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.
273
+
274
+ **Confusing wallets with credits.** Wallets hold real money (USD, INR). Credits are usage-based entitlements. See the `credit-based-billing` skill for credit management.
275
+
276
+ ## Resources
277
+
278
+ - [Customer Management](https://docs.dodopayments.com/features/customers)
279
+ - [Customer Portal](https://docs.dodopayments.com/features/customer-portal)
280
+ - [Customer Wallets](https://docs.dodopayments.com/features/customer-wallet)
281
+ - [Customers API Reference](https://docs.dodopayments.com/api-reference/customers)