@dodopayments/opencode-plugin 0.2.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.
- package/.mcp.json +22 -0
- package/LICENSE +21 -0
- package/README.md +195 -0
- package/opencode-plugin/README.md +42 -0
- package/opencode-plugin/index.js +30 -0
- package/opencode.json +28 -0
- package/package.json +53 -0
- package/skills/best-practices/SKILL.md +315 -0
- package/skills/billing-sdk/SKILL.md +501 -0
- package/skills/checkout-integration/SKILL.md +599 -0
- package/skills/credit-based-billing/SKILL.md +942 -0
- package/skills/license-keys/SKILL.md +591 -0
- package/skills/subscription-integration/SKILL.md +571 -0
- package/skills/usage-based-billing/SKILL.md +680 -0
- package/skills/webhook-integration/SKILL.md +529 -0
|
@@ -0,0 +1,501 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: billing-sdk
|
|
3
|
+
description: Guide for using BillingSDK - open-source React components for pricing tables, subscription management, and billing UI with Dodo Payments.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# BillingSDK Integration
|
|
7
|
+
|
|
8
|
+
**Reference: [docs.dodopayments.com/developer-resources/billingsdk](https://docs.dodopayments.com/developer-resources/billingsdk) | [billingsdk.com](https://billingsdk.com)**
|
|
9
|
+
|
|
10
|
+
BillingSDK provides open-source, customizable React components for billing interfaces - pricing tables, subscription management, usage meters, and more.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Overview
|
|
15
|
+
|
|
16
|
+
BillingSDK offers:
|
|
17
|
+
- **React Components**: Pre-built, customizable billing components
|
|
18
|
+
- **CLI Tooling**: Project initialization and component management
|
|
19
|
+
- **Framework Support**: Next.js, Express.js, Hono, Fastify, React
|
|
20
|
+
- **Payment Provider**: Full integration with Dodo Payments
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Quick Start Options
|
|
25
|
+
|
|
26
|
+
### Option 1: New Project (Recommended)
|
|
27
|
+
Complete project setup with framework configuration and API routes:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npx @billingsdk/cli init
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
The CLI will:
|
|
34
|
+
- Configure your framework (Next.js App Router)
|
|
35
|
+
- Set up Dodo Payments integration
|
|
36
|
+
- Generate API routes for checkout, customers, webhooks
|
|
37
|
+
- Install dependencies
|
|
38
|
+
- Create configuration files
|
|
39
|
+
|
|
40
|
+
### Option 2: Add to Existing Project
|
|
41
|
+
Add individual components using the CLI:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
npx @billingsdk/cli add pricing-table-one
|
|
45
|
+
npx @billingsdk/cli add subscription-management
|
|
46
|
+
npx @billingsdk/cli add usage-meter-circle
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Option 3: Manual via shadcn/ui
|
|
50
|
+
Install directly using shadcn registry:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
npx shadcn@latest add @billingsdk/pricing-table-one
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## CLI Reference
|
|
59
|
+
|
|
60
|
+
### Initialize Project
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
npx @billingsdk/cli init
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Interactive setup prompts:
|
|
67
|
+
1. Select framework (Next.js, Express.js, Hono, Fastify, React)
|
|
68
|
+
2. Select payment provider (Dodo Payments)
|
|
69
|
+
3. Configure project settings
|
|
70
|
+
|
|
71
|
+
### Add Components
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
npx @billingsdk/cli add <component-name>
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
**Available components:**
|
|
78
|
+
- `pricing-table-one` - Simple pricing table
|
|
79
|
+
- `pricing-table-two` - Feature-rich pricing table
|
|
80
|
+
- `subscription-management` - Manage active subscriptions
|
|
81
|
+
- `usage-meter-circle` - Circular usage visualization
|
|
82
|
+
- More components available...
|
|
83
|
+
|
|
84
|
+
### What happens when adding:
|
|
85
|
+
1. Downloads component from registry
|
|
86
|
+
2. Installs files to `components/billingsdk/`
|
|
87
|
+
3. Updates project configuration
|
|
88
|
+
4. Installs additional dependencies
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## Components
|
|
93
|
+
|
|
94
|
+
### Pricing Table One
|
|
95
|
+
|
|
96
|
+
Simple, clean pricing table for displaying plans.
|
|
97
|
+
|
|
98
|
+
**Installation:**
|
|
99
|
+
```bash
|
|
100
|
+
npx @billingsdk/cli add pricing-table-one
|
|
101
|
+
# or
|
|
102
|
+
npx shadcn@latest add @billingsdk/pricing-table-one
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
**Usage:**
|
|
106
|
+
```tsx
|
|
107
|
+
import { PricingTableOne } from "@/components/billingsdk/pricing-table-one";
|
|
108
|
+
|
|
109
|
+
const plans = [
|
|
110
|
+
{
|
|
111
|
+
id: 'prod_free',
|
|
112
|
+
name: 'Free',
|
|
113
|
+
price: 0,
|
|
114
|
+
interval: 'month',
|
|
115
|
+
features: ['5 projects', 'Basic support'],
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
id: 'prod_pro',
|
|
119
|
+
name: 'Pro',
|
|
120
|
+
price: 29,
|
|
121
|
+
interval: 'month',
|
|
122
|
+
features: ['Unlimited projects', 'Priority support', 'API access'],
|
|
123
|
+
popular: true,
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
id: 'prod_enterprise',
|
|
127
|
+
name: 'Enterprise',
|
|
128
|
+
price: 99,
|
|
129
|
+
interval: 'month',
|
|
130
|
+
features: ['Everything in Pro', 'Custom integrations', 'Dedicated support'],
|
|
131
|
+
},
|
|
132
|
+
];
|
|
133
|
+
|
|
134
|
+
export function PricingPage() {
|
|
135
|
+
const handleSelectPlan = async (planId: string) => {
|
|
136
|
+
// Create checkout session
|
|
137
|
+
const response = await fetch('/api/checkout', {
|
|
138
|
+
method: 'POST',
|
|
139
|
+
headers: { 'Content-Type': 'application/json' },
|
|
140
|
+
body: JSON.stringify({ productId: planId }),
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
const { checkoutUrl } = await response.json();
|
|
144
|
+
window.location.href = checkoutUrl;
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
return (
|
|
148
|
+
<PricingTableOne
|
|
149
|
+
plans={plans}
|
|
150
|
+
onSelectPlan={handleSelectPlan}
|
|
151
|
+
/>
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Pricing Table Two
|
|
157
|
+
|
|
158
|
+
Feature-comparison pricing table with toggle for monthly/yearly.
|
|
159
|
+
|
|
160
|
+
**Installation:**
|
|
161
|
+
```bash
|
|
162
|
+
npx @billingsdk/cli add pricing-table-two
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
**Usage:**
|
|
166
|
+
```tsx
|
|
167
|
+
import { PricingTableTwo } from "@/components/billingsdk/pricing-table-two";
|
|
168
|
+
|
|
169
|
+
const plans = [
|
|
170
|
+
{
|
|
171
|
+
id: 'prod_starter_monthly',
|
|
172
|
+
yearlyId: 'prod_starter_yearly',
|
|
173
|
+
name: 'Starter',
|
|
174
|
+
monthlyPrice: 19,
|
|
175
|
+
yearlyPrice: 190,
|
|
176
|
+
features: [
|
|
177
|
+
{ name: 'Projects', value: '10' },
|
|
178
|
+
{ name: 'Storage', value: '5 GB' },
|
|
179
|
+
{ name: 'Support', value: 'Email' },
|
|
180
|
+
],
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
id: 'prod_pro_monthly',
|
|
184
|
+
yearlyId: 'prod_pro_yearly',
|
|
185
|
+
name: 'Pro',
|
|
186
|
+
monthlyPrice: 49,
|
|
187
|
+
yearlyPrice: 490,
|
|
188
|
+
popular: true,
|
|
189
|
+
features: [
|
|
190
|
+
{ name: 'Projects', value: 'Unlimited' },
|
|
191
|
+
{ name: 'Storage', value: '50 GB' },
|
|
192
|
+
{ name: 'Support', value: 'Priority' },
|
|
193
|
+
],
|
|
194
|
+
},
|
|
195
|
+
];
|
|
196
|
+
|
|
197
|
+
export function PricingPage() {
|
|
198
|
+
return (
|
|
199
|
+
<PricingTableTwo
|
|
200
|
+
plans={plans}
|
|
201
|
+
onSelectPlan={(planId, billingInterval) => {
|
|
202
|
+
console.log(`Selected: ${planId}, Interval: ${billingInterval}`);
|
|
203
|
+
}}
|
|
204
|
+
/>
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### Subscription Management
|
|
210
|
+
|
|
211
|
+
Allow users to view and manage their subscription.
|
|
212
|
+
|
|
213
|
+
**Installation:**
|
|
214
|
+
```bash
|
|
215
|
+
npx @billingsdk/cli add subscription-management
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
**Usage:**
|
|
219
|
+
```tsx
|
|
220
|
+
import { SubscriptionManagement } from "@/components/billingsdk/subscription-management";
|
|
221
|
+
|
|
222
|
+
export function AccountPage() {
|
|
223
|
+
const subscription = {
|
|
224
|
+
plan: 'Pro',
|
|
225
|
+
status: 'active',
|
|
226
|
+
currentPeriodEnd: '2025-02-21',
|
|
227
|
+
amount: 49,
|
|
228
|
+
interval: 'month',
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
return (
|
|
232
|
+
<SubscriptionManagement
|
|
233
|
+
subscription={subscription}
|
|
234
|
+
onManageBilling={async () => {
|
|
235
|
+
// Open customer portal
|
|
236
|
+
const response = await fetch('/api/portal', { method: 'POST' });
|
|
237
|
+
const { url } = await response.json();
|
|
238
|
+
window.location.href = url;
|
|
239
|
+
}}
|
|
240
|
+
onCancelSubscription={async () => {
|
|
241
|
+
if (confirm('Are you sure you want to cancel?')) {
|
|
242
|
+
await fetch('/api/subscription/cancel', { method: 'POST' });
|
|
243
|
+
}
|
|
244
|
+
}}
|
|
245
|
+
/>
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
### Usage Meter
|
|
251
|
+
|
|
252
|
+
Display usage-based billing metrics.
|
|
253
|
+
|
|
254
|
+
**Installation:**
|
|
255
|
+
```bash
|
|
256
|
+
npx @billingsdk/cli add usage-meter-circle
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
**Usage:**
|
|
260
|
+
```tsx
|
|
261
|
+
import { UsageMeterCircle } from "@/components/billingsdk/usage-meter-circle";
|
|
262
|
+
|
|
263
|
+
export function UsageDashboard() {
|
|
264
|
+
return (
|
|
265
|
+
<div className="grid grid-cols-3 gap-4">
|
|
266
|
+
<UsageMeterCircle
|
|
267
|
+
label="API Calls"
|
|
268
|
+
current={8500}
|
|
269
|
+
limit={10000}
|
|
270
|
+
unit="calls"
|
|
271
|
+
/>
|
|
272
|
+
<UsageMeterCircle
|
|
273
|
+
label="Storage"
|
|
274
|
+
current={3.2}
|
|
275
|
+
limit={5}
|
|
276
|
+
unit="GB"
|
|
277
|
+
/>
|
|
278
|
+
<UsageMeterCircle
|
|
279
|
+
label="Bandwidth"
|
|
280
|
+
current={45}
|
|
281
|
+
limit={100}
|
|
282
|
+
unit="GB"
|
|
283
|
+
/>
|
|
284
|
+
</div>
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
---
|
|
290
|
+
|
|
291
|
+
## Next.js Integration
|
|
292
|
+
|
|
293
|
+
### Project Structure (after `init`)
|
|
294
|
+
|
|
295
|
+
```
|
|
296
|
+
your-project/
|
|
297
|
+
├── app/
|
|
298
|
+
│ ├── api/
|
|
299
|
+
│ │ ├── checkout/
|
|
300
|
+
│ │ │ └── route.ts
|
|
301
|
+
│ │ ├── portal/
|
|
302
|
+
│ │ │ └── route.ts
|
|
303
|
+
│ │ └── webhooks/
|
|
304
|
+
│ │ └── dodo/
|
|
305
|
+
│ │ └── route.ts
|
|
306
|
+
│ └── pricing/
|
|
307
|
+
│ └── page.tsx
|
|
308
|
+
├── components/
|
|
309
|
+
│ └── billingsdk/
|
|
310
|
+
│ ├── pricing-table-one.tsx
|
|
311
|
+
│ └── subscription-management.tsx
|
|
312
|
+
├── lib/
|
|
313
|
+
│ ├── dodo.ts
|
|
314
|
+
│ └── billingsdk-config.ts
|
|
315
|
+
└── .env.local
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
### Generated API Routes
|
|
319
|
+
|
|
320
|
+
**Checkout Route (`app/api/checkout/route.ts`):**
|
|
321
|
+
```typescript
|
|
322
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
323
|
+
import { dodo } from '@/lib/dodo';
|
|
324
|
+
|
|
325
|
+
export async function POST(req: NextRequest) {
|
|
326
|
+
const { productId, email } = await req.json();
|
|
327
|
+
|
|
328
|
+
const session = await dodo.checkoutSessions.create({
|
|
329
|
+
product_cart: [{ product_id: productId, quantity: 1 }],
|
|
330
|
+
customer: { email },
|
|
331
|
+
return_url: `${process.env.NEXT_PUBLIC_APP_URL}/success`,
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
return NextResponse.json({ checkoutUrl: session.checkout_url });
|
|
335
|
+
}
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
**Portal Route (`app/api/portal/route.ts`):**
|
|
339
|
+
```typescript
|
|
340
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
341
|
+
import { dodo } from '@/lib/dodo';
|
|
342
|
+
import { getSession } from '@/lib/auth';
|
|
343
|
+
|
|
344
|
+
export async function POST(req: NextRequest) {
|
|
345
|
+
const session = await getSession();
|
|
346
|
+
|
|
347
|
+
const portal = await dodo.customers.createPortalSession({
|
|
348
|
+
customer_id: session.user.customerId,
|
|
349
|
+
return_url: `${process.env.NEXT_PUBLIC_APP_URL}/account`,
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
return NextResponse.json({ url: portal.url });
|
|
353
|
+
}
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
### Configuration File
|
|
357
|
+
|
|
358
|
+
**`lib/billingsdk-config.ts`:**
|
|
359
|
+
```typescript
|
|
360
|
+
export const plans = [
|
|
361
|
+
{
|
|
362
|
+
id: process.env.NEXT_PUBLIC_PLAN_FREE_ID!,
|
|
363
|
+
name: 'Free',
|
|
364
|
+
description: 'Perfect for trying out',
|
|
365
|
+
price: 0,
|
|
366
|
+
interval: 'month' as const,
|
|
367
|
+
features: [
|
|
368
|
+
'5 projects',
|
|
369
|
+
'1 GB storage',
|
|
370
|
+
'Community support',
|
|
371
|
+
],
|
|
372
|
+
},
|
|
373
|
+
{
|
|
374
|
+
id: process.env.NEXT_PUBLIC_PLAN_PRO_ID!,
|
|
375
|
+
name: 'Pro',
|
|
376
|
+
description: 'For professionals',
|
|
377
|
+
price: 29,
|
|
378
|
+
interval: 'month' as const,
|
|
379
|
+
popular: true,
|
|
380
|
+
features: [
|
|
381
|
+
'Unlimited projects',
|
|
382
|
+
'50 GB storage',
|
|
383
|
+
'Priority support',
|
|
384
|
+
'API access',
|
|
385
|
+
],
|
|
386
|
+
},
|
|
387
|
+
];
|
|
388
|
+
|
|
389
|
+
export const config = {
|
|
390
|
+
returnUrl: process.env.NEXT_PUBLIC_APP_URL + '/success',
|
|
391
|
+
portalReturnUrl: process.env.NEXT_PUBLIC_APP_URL + '/account',
|
|
392
|
+
};
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
---
|
|
396
|
+
|
|
397
|
+
## Customization
|
|
398
|
+
|
|
399
|
+
### Styling with Tailwind
|
|
400
|
+
|
|
401
|
+
Components use Tailwind CSS and shadcn/ui patterns. Customize via:
|
|
402
|
+
|
|
403
|
+
1. **Theme variables** in `globals.css`
|
|
404
|
+
2. **Direct class overrides** on components
|
|
405
|
+
3. **Component source modification** (files are local)
|
|
406
|
+
|
|
407
|
+
**Example - Custom colors:**
|
|
408
|
+
```css
|
|
409
|
+
/* globals.css */
|
|
410
|
+
@layer base {
|
|
411
|
+
:root {
|
|
412
|
+
--primary: 220 90% 56%;
|
|
413
|
+
--primary-foreground: 0 0% 100%;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
### Component Props
|
|
419
|
+
|
|
420
|
+
Most components accept standard styling props:
|
|
421
|
+
|
|
422
|
+
```tsx
|
|
423
|
+
<PricingTableOne
|
|
424
|
+
plans={plans}
|
|
425
|
+
onSelectPlan={handleSelect}
|
|
426
|
+
className="max-w-4xl mx-auto"
|
|
427
|
+
containerClassName="gap-8"
|
|
428
|
+
cardClassName="border-2"
|
|
429
|
+
/>
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
---
|
|
433
|
+
|
|
434
|
+
## Environment Variables
|
|
435
|
+
|
|
436
|
+
```bash
|
|
437
|
+
# .env.local
|
|
438
|
+
|
|
439
|
+
# Dodo Payments
|
|
440
|
+
DODO_PAYMENTS_API_KEY=sk_live_xxxxx
|
|
441
|
+
DODO_PAYMENTS_WEBHOOK_SECRET=whsec_xxxxx
|
|
442
|
+
|
|
443
|
+
# Product IDs (from dashboard)
|
|
444
|
+
NEXT_PUBLIC_PLAN_FREE_ID=prod_xxxxx
|
|
445
|
+
NEXT_PUBLIC_PLAN_PRO_ID=prod_xxxxx
|
|
446
|
+
NEXT_PUBLIC_PLAN_ENTERPRISE_ID=prod_xxxxx
|
|
447
|
+
|
|
448
|
+
# App
|
|
449
|
+
NEXT_PUBLIC_APP_URL=https://yoursite.com
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
---
|
|
453
|
+
|
|
454
|
+
## Best Practices
|
|
455
|
+
|
|
456
|
+
### 1. Use Product IDs from Environment
|
|
457
|
+
Keep product IDs in environment variables for easy staging/production switching.
|
|
458
|
+
|
|
459
|
+
### 2. Handle Loading States
|
|
460
|
+
Components should show loading states during checkout:
|
|
461
|
+
|
|
462
|
+
```tsx
|
|
463
|
+
const [loading, setLoading] = useState(false);
|
|
464
|
+
|
|
465
|
+
const handleSelect = async (planId: string) => {
|
|
466
|
+
setLoading(true);
|
|
467
|
+
try {
|
|
468
|
+
const response = await fetch('/api/checkout', {...});
|
|
469
|
+
const { checkoutUrl } = await response.json();
|
|
470
|
+
window.location.href = checkoutUrl;
|
|
471
|
+
} finally {
|
|
472
|
+
setLoading(false);
|
|
473
|
+
}
|
|
474
|
+
};
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
### 3. Server-Side Data Fetching
|
|
478
|
+
Fetch subscription data server-side when possible:
|
|
479
|
+
|
|
480
|
+
```tsx
|
|
481
|
+
// app/account/page.tsx
|
|
482
|
+
import { getSubscription } from '@/lib/subscription';
|
|
483
|
+
|
|
484
|
+
export default async function AccountPage() {
|
|
485
|
+
const subscription = await getSubscription();
|
|
486
|
+
|
|
487
|
+
return <SubscriptionManagement subscription={subscription} />;
|
|
488
|
+
}
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
### 4. Implement Webhooks
|
|
492
|
+
Always use webhooks as source of truth for subscription status, not client-side data.
|
|
493
|
+
|
|
494
|
+
---
|
|
495
|
+
|
|
496
|
+
## Resources
|
|
497
|
+
|
|
498
|
+
- [BillingSDK Documentation](https://billingsdk.com/docs)
|
|
499
|
+
- [Dodo Payments Integration](https://docs.dodopayments.com/developer-resources/billingsdk)
|
|
500
|
+
- [Component Gallery](https://billingsdk.com/docs/components)
|
|
501
|
+
- [GitHub Repository](https://github.com/dodopayments/billingsdk)
|