@dodopayments/opencode-plugin 0.5.1 → 0.6.1

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 CHANGED
@@ -1,23 +1,13 @@
1
1
  {
2
2
  "mcpServers": {
3
3
  "dodopayments-api": {
4
- "type": "stdio",
5
- "command": "npx",
6
- "args": [
7
- "-y",
8
- "mcp-remote@latest",
9
- "https://mcp.dodopayments.com/mcp"
10
- ],
4
+ "type": "http",
5
+ "url": "https://mcp.dodopayments.com/mcp",
11
6
  "enabled": true
12
7
  },
13
8
  "dodo-knowledge": {
14
- "type": "stdio",
15
- "command": "npx",
16
- "args": [
17
- "-y",
18
- "mcp-remote@latest",
19
- "https://knowledge.dodopayments.com/mcp"
20
- ],
9
+ "type": "http",
10
+ "url": "https://knowledge.dodopayments.com/mcp",
21
11
  "enabled": true
22
12
  }
23
13
  }
package/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
  [![npm](https://img.shields.io/npm/v/@dodopayments/opencode-plugin.svg?style=flat-square)](https://www.npmjs.com/package/@dodopayments/opencode-plugin)
6
6
  [![Discord](https://img.shields.io/discord/1305511580854779984?label=discord&style=flat-square)](https://discord.gg/bYqAp4ayYh)
7
7
 
8
- The official Dodo Payments plugin for AI coding agents. Installs seventeen integration skills and two MCP servers across **Claude Code**, **Codex CLI**, **Cursor**, **VS Code / GitHub Copilot**, and **OpenCode** from a single source of truth.
8
+ The official Dodo Payments plugin for AI coding agents. Installs the Dodo Payments integration skills and two MCP servers across **Claude Code**, **Codex CLI**, **Cursor**, **VS Code / GitHub Copilot**, **Grok Build**, **Meta Muse Code**, **Gemini CLI**, **Antigravity**, **Junie**, **OpenCode**, and more, from a single source of truth.
9
9
 
10
10
  This plugin conforms to the [Agent Plugins 1.0.0](https://agent-plugins.org/specification) specification: a root [`plugin.json`](./plugin.json), skills as immediate children of [`skills/`](./skills), and MCP servers in [`mcp.json`](./mcp.json). Clients with native Agent Plugins support load it directly; the provider-specific manifests in this repo are generated compatibility shims for clients that do not.
11
11
 
@@ -13,10 +13,28 @@ This plugin conforms to the [Agent Plugins 1.0.0](https://agent-plugins.org/spec
13
13
 
14
14
  - **Dodo Payments API MCP server** - Live API access (payments, subscriptions, customers, products, refunds, licenses, usage). Authenticates via browser OAuth, no local credentials required.
15
15
  - **Dodo Knowledge MCP server** - No credentials. Semantic search over the current Dodo Payments documentation.
16
- - **Seventeen agent skills** - Written as `SKILL.md` files with YAML frontmatter. Your agent loads the relevant skill on its own when a task calls for it.
16
+ - **Agent skills** - Integration guides (listed [below](#included-skills)), written as `SKILL.md` files with YAML frontmatter. Your agent loads the relevant skill on its own when a task calls for it.
17
17
 
18
18
  ## Install
19
19
 
20
+ | Client | Skills | MCP | How |
21
+ |---|---|---|---|
22
+ | Claude Code | ✅ | ✅ | [Marketplace](#claude-code) |
23
+ | Codex CLI / ChatGPT | ✅ | ✅ | [Marketplace](#codex-cli) |
24
+ | Cursor (and Grok Bot) | ✅ | ✅ | [Clone](#cursor) |
25
+ | VS Code / GitHub Copilot | ✅ | ✅ | [Plugins view](#vs-code--github-copilot) · [Copilot CLI](./docs/clients/more-clients.md) |
26
+ | Grok Build | ✅ | ✅ | [`grok plugin install`](./docs/clients/grok.md) |
27
+ | grok.com, xAI API | — | ✅ | [Connector / API tool](./docs/clients/grok.md) |
28
+ | Meta Muse Code | ✅ | ✅ | [`muse plugins install`](./docs/clients/muse.md) |
29
+ | Gemini CLI | ✅ | ✅ | [Extension](#gemini-cli) |
30
+ | Google Antigravity | ✅ | ✅ | [`agy plugin install`](./docs/clients/antigravity-and-junie.md#antigravity) |
31
+ | JetBrains Junie | ✅ | ✅ | [Extension](./docs/clients/antigravity-and-junie.md#junie) |
32
+ | Kiro | ✅ | ✅ | [Power](#kiro) |
33
+ | OpenCode | ✅ | ✅ | [npm](#opencode) |
34
+ | Qwen Code, Devin, Goose, Factory Droid, Augment, OpenHands, OpenClaw, Hermes | ✅ | ✅ | [More clients](./docs/clients/more-clients.md) |
35
+ | Cline, Kilo, Zed, Warp, Amp, Continue, Kimi, … | ✅ | ✅ | [`npx skills` + `npx add-mcp`](./docs/clients/more-clients.md#skills-and-mcp-separately) |
36
+ | Claude.ai, ChatGPT, Perplexity, Mistral | — | ✅ | [Custom connector](./docs/clients/more-clients.md#mcp-only-assistants) |
37
+
20
38
  ### Claude Code
21
39
 
22
40
  ```bash
@@ -75,16 +93,13 @@ git clone https://github.com/dodopayments/dodo-agent-plugin.git
75
93
 
76
94
  Point Kiro at the cloned folder. Skills load from `skills/`, MCP servers from `mcp.json`, and Kiro-specific presentation comes from the `dev.kiro` extension namespace in `plugin.json`.
77
95
 
78
- ### Gemini CLI (MCP only)
79
-
80
- Gemini CLI has no agent-skill primitive, so **only the two MCP servers are available** - the seventeen skills are not. `dodo-knowledge` still covers a good share of what the skills provide, and it stays current automatically.
96
+ ### Gemini CLI
81
97
 
82
98
  ```bash
83
- git clone https://github.com/dodopayments/dodo-agent-plugin.git \
84
- ~/.gemini/extensions/dodopayments
99
+ gemini extensions install https://github.com/dodopayments/dodo-agent-plugin
85
100
  ```
86
101
 
87
- Restart Gemini CLI. `gemini-extension.json` at the repo root is the manifest.
102
+ `gemini-extension.json` at the repo root is the manifest, and Gemini auto-discovers the `skills/` directory next to it. Verified on Gemini CLI 0.63.0: `gemini skills list` shows every skill and `gemini mcp list` shows both servers.
88
103
 
89
104
  ### VS Code / GitHub Copilot
90
105
 
@@ -94,7 +109,7 @@ git clone https://github.com/dodopayments/dodo-agent-plugin.git
94
109
 
95
110
  Then open the Chat view, go to **Plugins**, and add the cloned folder. Skills load from `skills/`, and both MCP servers load from `.mcp.json`.
96
111
 
97
- > VS Code 1.125.1 does not key off the Agent Plugins `$schema` - the string appears nowhere in its bundle. Its loader picks a manifest by probing, in order, `.plugin/plugin.json`, then `.claude-plugin/plugin.json`, then a root `plugin.json`, and defaults MCP to `.mcp.json` rather than `mcp.json`. Because this repo ships a generated `.claude-plugin/plugin.json`, VS Code loads it through that branch. Everything works - seventeen skills and two MCP servers - but via the compatibility manifests rather than the spec ones, so VS Code gets the `mcp-remote` bridge rather than the native transports in `mcp.json`.
112
+ > VS Code 1.125.1 does not key off the Agent Plugins `$schema` - the string appears nowhere in its bundle. Its loader picks a manifest by probing, in order, `.plugin/plugin.json`, then `.claude-plugin/plugin.json`, then a root `plugin.json`, and defaults MCP to `.mcp.json` rather than `mcp.json`. Because this repo ships a generated `.claude-plugin/plugin.json`, VS Code loads it through that branch. Everything works - all skills and both MCP servers - via the compatibility manifests rather than the spec ones.
98
113
 
99
114
  ### OpenCode
100
115
 
@@ -107,7 +122,7 @@ OpenCode distributes via npm. Add the plugin to your `opencode.json`:
107
122
  }
108
123
  ```
109
124
 
110
- Restart OpenCode. Both MCP servers (`dodopayments-api`, `dodo-knowledge`) are registered automatically via the plugin's `config` hook. No manual `mcp` block required.
125
+ Restart OpenCode. Both MCP servers (`dodopayments-api`, `dodo-knowledge`) are registered automatically via the plugin's `config` hook as native remote servers. No manual `mcp` block required. Run `opencode mcp auth dodopayments-api` to sign in to the API server.
111
126
 
112
127
  **Skills need the package installed locally plus one extra line.** OpenCode does not scan installed packages for skills, so point it at the package's `skills/` directory yourself. `skills.paths` entries resolve against the project directory, so the package must be present in the project's `node_modules` - OpenCode's own plugin cache is not the same location:
113
128
 
@@ -127,7 +142,7 @@ npm install --save-dev @dodopayments/opencode-plugin
127
142
 
128
143
  An absolute path works too, and avoids the local-install requirement.
129
144
 
130
- Verify with `opencode run "List every skill available to you by name."` - you should see all seventeen. A skills path that does not exist is ignored silently, so check rather than assume.
145
+ Verify with `opencode run "List every skill available to you by name."` - you should see every skill listed under [Included Skills](#included-skills). A skills path that does not exist is ignored silently, so check rather than assume.
131
146
 
132
147
  > Versions before 0.5.0 documented these skills as auto-discovered. They were not: nothing in OpenCode scans an installed package, so OpenCode users had MCP servers but no skills. Setting `config.skills` from the plugin's `config` hook does not fix this either - the skill index is built before `config` hooks run, so it never registers anything.
133
148
 
@@ -160,6 +175,7 @@ If you prefer the local stdio API server with your own API key instead of the de
160
175
  | `dodo-best-practices` | SDK setup, environments, API keys, and the canonical checkout-to-webhook architecture |
161
176
  | `framework-adapters` | Official `@dodopayments/*` handlers for Next.js, Express, Hono, Astro, Remix, SvelteKit, Nuxt, Fastify, TanStack, Bun, Convex |
162
177
  | `testing-and-go-live` | Test mode, test payment methods, webhook testing, production launch checklist |
178
+ | `dodo-mcp-usage` | When to use the documentation vs API MCP servers, test-mode safety rules, auth, troubleshooting |
163
179
 
164
180
  **Accepting payments**
165
181
 
@@ -209,7 +225,7 @@ Skills source: [`dodopayments/skills`](https://github.com/dodopayments/skills),
209
225
  | `dodopayments-api` | Live API access (payments, subscriptions, customers, products, refunds, licenses, usage) | OAuth (browser) |
210
226
  | `dodo-knowledge` | Semantic search over the Dodo Payments documentation | None |
211
227
 
212
- Both servers speak Streamable HTTP. The canonical `mcp.json` declares them natively (`type: "streamable-http"`), which is what spec-native clients such as Codex CLI and Cursor use. The generated compatibility manifests — `.mcp.json`, read by Claude Code, VS Code and Cursor's legacy path — wire the same two endpoints through `mcp-remote` instead, so they run in clients that cannot yet dial Streamable HTTP directly.
228
+ Both servers speak Streamable HTTP over HTTPS, and every manifest dials them natively - no `npx mcp-remote` bridge and no Node dependency. The canonical `mcp.json` uses the spec's `type: "streamable-http"`; the generated `.mcp.json` (Claude Code, Cursor, VS Code, Grok Build, Devin, Goose, Droid) uses the same endpoints spelled `type: "http"`, the name that dialect uses; Muse, Antigravity, Junie and Gemini get their own spellings from the generator.
213
229
 
214
230
  ## Configure (optional, Claude Code)
215
231
 
@@ -269,9 +285,8 @@ These clients load MCPs from the static `.mcp.json` shipped with the plugin. To
269
285
  {
270
286
  "mcpServers": {
271
287
  "dodopayments-api": {
272
- "type": "stdio",
273
- "command": "npx",
274
- "args": ["-y", "mcp-remote@latest", "https://mcp.dodopayments.com/mcp"],
288
+ "type": "http",
289
+ "url": "https://mcp.dodopayments.com/mcp",
275
290
  "enabled": false
276
291
  }
277
292
  }
@@ -327,9 +342,9 @@ npm run verify # generated artifacts in sync + Agent Plugins conformance
327
342
  |---|---|
328
343
  | `plugin.json` | **Canonical.** Agent Plugins v1.0.0 manifest and the version source of truth |
329
344
  | `mcp.json` | **Canonical.** Agent Plugins v1.0.0 MCP config |
330
- | `skills/` | **Canonical.** Seventeen skills, vendored as real files |
345
+ | `skills/` | **Canonical.** Skills, vendored as real files; the declared set lives in `.skills-source.json` |
331
346
  | `overlays/*.json` | Hand-authored provider extras the closed spec schema cannot express |
332
- | `.claude-plugin/`, `.cursor-plugin/`, `.agents/`, `.mcp.json`, `plugins/dodopayments/` | **Generated.** Do not hand-edit - run `npm run build` |
347
+ | `.claude-plugin/`, `.cursor-plugin/`, `.codex-plugin/`, `.muse-plugin/`, `.agents/`, `.mcp.json`, `gemini-extension.json`, `plugins/dodopayments/`, `providers/` | **Generated.** Do not hand-edit - run `npm run build` |
333
348
  | `scripts/build.mjs` | The single generator (`--check` for drift) |
334
349
  | `scripts/conformance.mjs` | Agent Plugins conformance validator |
335
350
  | `.skills-source.json` | Upstream provenance for the vendored skills |
@@ -343,7 +358,7 @@ The repo is configured to publish the OpenCode npm package on every GitHub Relea
343
358
  **One-time setup (already done for this repo):**
344
359
 
345
360
  - npm scope `@dodopayments` exists and is owned by Dodo Payments.
346
- - GitHub Actions secret `NPM_TOKEN` is provisioned with publish rights to the `@dodopayments` scope.
361
+ - npm Trusted Publishing (OIDC) is configured for `@dodopayments/opencode-plugin` against `publish-opencode.yml`. No npm token is stored in the repo.
347
362
 
348
363
  **Release workflow:**
349
364
 
@@ -358,7 +373,7 @@ The repo is configured to publish the OpenCode npm package on every GitHub Relea
358
373
 
359
374
  **CI checks:**
360
375
 
361
- - `Verify` runs on every pull request and push to `main`: artifact drift, Agent Plugins conformance, live JSON Schema validation, a "seventeen skills, zero symlinks" assertion, and an npm payload check.
376
+ - `Verify` runs on every pull request and push to `main`: artifact drift, Agent Plugins conformance, live JSON Schema validation, an "every declared skill, zero symlinks" assertion, per-client manifest checks (Muse, Antigravity, Junie, Gemini), and an npm payload check.
362
377
  - The release workflow re-runs the same gates before publishing.
363
378
 
364
379
  ## Resources
package/mcp.json ADDED
@@ -0,0 +1,13 @@
1
+ {
2
+ "$schema": "https://agent-plugins.org/schemas/1.0.0/mcp.schema.json",
3
+ "mcpServers": {
4
+ "dodopayments-api": {
5
+ "type": "streamable-http",
6
+ "url": "https://mcp.dodopayments.com/mcp"
7
+ },
8
+ "dodo-knowledge": {
9
+ "type": "streamable-http",
10
+ "url": "https://knowledge.dodopayments.com/mcp"
11
+ }
12
+ }
13
+ }
@@ -1,6 +1,6 @@
1
1
  # @dodopayments/opencode-plugin
2
2
 
3
- Official OpenCode plugin for Dodo Payments. Ships seventeen integration skills and auto-registers two MCP servers.
3
+ Official OpenCode plugin for Dodo Payments. Ships the Dodo Payments integration skills and auto-registers two MCP servers.
4
4
 
5
5
  This package is part of [`dodopayments/dodo-agent-plugin`](https://github.com/dodopayments/dodo-agent-plugin) - the universal Dodo Payments plugin that also installs into Claude Code, Codex, and Cursor.
6
6
 
@@ -25,7 +25,7 @@ npm install --save-dev @dodopayments/opencode-plugin
25
25
  Restart OpenCode.
26
26
 
27
27
  - Both MCP servers are registered automatically via the plugin's `config` hook.
28
- - The `skills.paths` entry is what makes the seventeen skills visible - OpenCode does not scan installed packages for skills. The path resolves against the **project** directory, not OpenCode's plugin cache, which is why the local install above is required. An absolute path works too and avoids that requirement.
28
+ - The `skills.paths` entry is what makes the skills visible - OpenCode does not scan installed packages for skills. The path resolves against the **project** directory, not OpenCode's plugin cache, which is why the local install above is required. An absolute path works too and avoids that requirement.
29
29
  - The plugin module exports only its default function. OpenCode's loader throws `Plugin export is not a function` on any non-function named export, skips the plugin, and swallows the error - leaving you with no MCP servers. Verified on OpenCode 1.18.15.
30
30
  - The first call to `dodopayments-api` opens a browser for OAuth. `dodo-knowledge` needs no auth.
31
31
 
@@ -37,7 +37,7 @@ opencode run "List every skill available to you by name."
37
37
 
38
38
  ## What you get
39
39
 
40
- Seventeen agent skills (auto-loaded when relevant): `dodo-best-practices`, `framework-adapters`, `testing-and-go-live`, `checkout-integration`, `subscription-integration`, `mobile-checkout`, `webhook-integration`, `credit-based-billing`, `usage-based-billing`, `license-keys`, `product-catalog-management`, `discounts-and-promotions`, `localized-pricing`, `customer-management`, `refunds-and-disputes`, `billing-sdk`, `better-auth-integration`.
40
+ Agent skills (auto-loaded when relevant): `dodo-best-practices`, `dodo-mcp-usage`, `framework-adapters`, `testing-and-go-live`, `checkout-integration`, `subscription-integration`, `mobile-checkout`, `webhook-integration`, `credit-based-billing`, `usage-based-billing`, `license-keys`, `product-catalog-management`, `discounts-and-promotions`, `localized-pricing`, `customer-management`, `refunds-and-disputes`, `billing-sdk`, `better-auth-integration`.
41
41
 
42
42
  Two MCP servers (registered automatically):
43
43
 
@@ -30,19 +30,21 @@
30
30
  //
31
31
  // The endpoint URLs below MUST match the canonical mcp.json. This file is
32
32
  // hand-written rather than generated - OpenCode's config shape is its own
33
- // (`type: "local"`, `command: [...]`) - so `build.mjs --check` cannot cover
33
+ // (`type: "remote"`, `url`) - so `build.mjs --check` cannot cover
34
34
  // it. `scripts/conformance.mjs` asserts the URL sets are identical instead.
35
- // The `mcp-remote` bridge is deliberate here, matching the generated
36
- // `.mcp.json`: only the canonical mcp.json uses native streamable-http.
35
+ // `type: "remote"` is OpenCode's native Streamable HTTP transport; OpenCode
36
+ // runs OAuth discovery itself. Verified on 1.18.34 from a project-level
37
+ // opencode.json with these exact entries: dodo-knowledge connects, and
38
+ // dodopayments-api reports "needs authentication" (`opencode mcp auth`).
37
39
  const DODO_MCP_SERVERS = {
38
40
  "dodopayments-api": {
39
- type: "local",
40
- command: ["npx", "-y", "mcp-remote@latest", "https://mcp.dodopayments.com/mcp"],
41
+ type: "remote",
42
+ url: "https://mcp.dodopayments.com/mcp",
41
43
  enabled: true,
42
44
  },
43
45
  "dodo-knowledge": {
44
- type: "local",
45
- command: ["npx", "-y", "mcp-remote@latest", "https://knowledge.dodopayments.com/mcp"],
46
+ type: "remote",
47
+ url: "https://knowledge.dodopayments.com/mcp",
46
48
  enabled: true,
47
49
  },
48
50
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@dodopayments/opencode-plugin",
3
- "version": "0.5.1",
4
- "description": "Dodo Payments skills and MCP server hints for OpenCode.",
3
+ "version": "0.6.1",
4
+ "description": "Dodo Payments agent skills and MCP servers for OpenCode.",
5
5
  "type": "module",
6
6
  "main": "opencode-plugin/index.js",
7
7
  "exports": {
@@ -10,6 +10,7 @@
10
10
  "files": [
11
11
  "opencode-plugin/",
12
12
  "skills/",
13
+ "mcp.json",
13
14
  ".mcp.json",
14
15
  "LICENSE"
15
16
  ],
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: better-auth-integration
3
- description: Guide only for applications using @dodopayments/better-auth, covering authenticated customer sync, checkout, portal access, usage ingestion, and verified webhook callbacks.
3
+ description: Dodo Payments plugin for Better Auth (@dodopayments/better-auth) covering customer sync on signup, authenticated checkout, customer portal, usage ingestion, and verified webhooks. Use when an app uses Better Auth and needs the dodopayments, dodopaymentsClient, checkout, portal, usage, or webhooks plugins; for other auth stacks use framework-adapters.
4
4
  ---
5
5
 
6
6
  # Better Auth Integration
@@ -18,9 +18,11 @@ Use `@dodopayments/better-auth` to synchronize Better Auth users with Dodo Payme
18
18
  ## Install
19
19
 
20
20
  ```bash
21
- npm install @dodopayments/better-auth dodopayments better-auth
21
+ npm install @dodopayments/better-auth dodopayments better-auth zod
22
22
  ```
23
23
 
24
+ Requires Better Auth 1.4 or a later 1.x release.
25
+
24
26
  The package exports the lowercase server functions `dodopayments`, `checkout`, `portal`, `usage`, and `webhooks`. Its client entry point exports `dodopaymentsClient`.
25
27
 
26
28
  ## Server setup
@@ -76,6 +78,13 @@ export const auth = betterAuth({
76
78
 
77
79
  `products` maps an application slug to a Dodo product ID. `authenticatedUsersOnly: true` requires a Better Auth session for checkout. `getCustomerParams` adds application metadata when customer synchronization runs.
78
80
 
81
+ The plugin adds a `dodoCustomerId` field to the Better Auth `user` table. After adding the plugin, update your database schema with the [Better Auth CLI](https://www.better-auth.com/docs/concepts/cli):
82
+
83
+ ```bash
84
+ npx auth generate # generate the ORM schema or SQL migration, then apply it yourself
85
+ npx auth migrate # or apply it directly (built-in Kysely adapter only)
86
+ ```
87
+
79
88
  ## Browser client setup
80
89
 
81
90
  ```typescript
@@ -170,7 +179,7 @@ if (error) {
170
179
  }
171
180
  ```
172
181
 
173
- The plugin derives `customer_id` from the authenticated session. `event_id` is the event's idempotency key; reuse it when retrying the same event.
182
+ Both `usage.ingest` and `usage.meters.list` require a signed-in user with a verified email address. The plugin derives `customer_id` from the authenticated session. `event_id` is the event's idempotency key; reuse it when retrying the same event.
174
183
 
175
184
  ## Webhook endpoint
176
185
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: billing-sdk
3
- description: Guide for building billing UI with BillingSDK - the open-source React component library for pricing tables, subscription management, usage meters, invoice history, and customer portal flows wired to Dodo Payments.
3
+ description: BillingSDK, the open-source React and shadcn component library for Dodo Payments billing UI. Use when building pricing tables, pricing pages, account billing pages, usage meters, credit balances, invoice history, or cancel and upgrade UI in React or Next.js, or running @billingsdk/cli init; keep Dodo API calls server-side.
4
4
  ---
5
5
 
6
6
  # BillingSDK
@@ -75,8 +75,10 @@ From the current official registry, grouped by purpose:
75
75
  | Payments | `payment-details`, `payment-details-two`, `payment-method-selector`, `payment-card`, `payment-failure` |
76
76
  | Promotion and trials | `banner`, `limited-offer-dialog`, `trial-expiry-card` |
77
77
 
78
- `pricing-table-one` is the only block with an officially published prop example, reproduced below. For
79
- every other block, run `add` and read the generated file's props type.
78
+ Most blocks have a published usage example and props table on their page at
79
+ [billingsdk.com/docs/components](https://billingsdk.com/docs/components) (for example `subscription-management`
80
+ and `billing-settings`); `pricing-table-one` is reproduced below. The generated file's props type is still
81
+ authoritative for the version you installed, so read it after running `add`.
80
82
 
81
83
  ## Server client
82
84
 
@@ -103,44 +105,75 @@ are `https://test.dodopayments.com` and `https://live.dodopayments.com`.
103
105
 
104
106
  ### 1. Plan catalogue
105
107
 
106
- Keep the product ids and the display copy in one module. The `price` here is a display number rendered by
107
- the component — the amount actually charged comes from the Dodo product catalogue, and Dodo API amounts
108
- are always in the smallest currency unit (cents). Treat this file as a mirror of the dashboard and
109
- reconcile it when you change a price.
108
+ `pricing-table-one` takes BillingSDK's `Plan[]`, not an arbitrary shape. Adding the component generates
109
+ `lib/billingsdk-config.ts`, which exports the `Plan` interface and a sample `plans` array — edit that array
110
+ rather than defining your own type. Prices are display **strings** (`monthlyPrice`, `yearlyPrice`) and
111
+ `features` are objects. The amount actually charged comes from the Dodo product catalogue (always in the
112
+ smallest currency unit), so treat this file as a mirror of the dashboard and reconcile it when you change a
113
+ price.
110
114
 
111
115
  ```typescript
112
- // lib/plans.ts
113
- export type PlanId = 'starter' | 'pro';
114
-
116
+ // lib/billingsdk-config.ts (generated by `add pricing-table-one`; edit the plans)
115
117
  export interface Plan {
116
- id: PlanId;
118
+ id: string;
117
119
  title: string;
118
- price: number;
119
- period: string;
120
- features: string[];
121
- popular: boolean;
120
+ description: string;
121
+ highlight?: boolean;
122
+ type?: 'monthly' | 'yearly';
123
+ currency?: string;
124
+ monthlyPrice: string;
125
+ yearlyPrice: string;
126
+ buttonText: string;
127
+ badge?: string;
128
+ features: {
129
+ name: string;
130
+ icon: string;
131
+ iconColor?: string;
132
+ }[];
122
133
  }
123
134
 
124
- export const PLANS: readonly Plan[] = [
135
+ export const plans: Plan[] = [
125
136
  {
126
137
  id: 'starter',
127
138
  title: 'Starter',
128
- price: 9,
129
- period: 'month',
130
- features: ['100 requests', 'Basic support', '1 project'],
131
- popular: false,
139
+ description: 'For individuals getting started.',
140
+ currency: '$',
141
+ monthlyPrice: '9',
142
+ yearlyPrice: '90',
143
+ buttonText: 'Choose Starter',
144
+ features: [
145
+ { name: '100 requests', icon: 'check' },
146
+ { name: 'Basic support', icon: 'check' },
147
+ { name: '1 project', icon: 'check' },
148
+ ],
132
149
  },
133
150
  {
134
151
  id: 'pro',
135
152
  title: 'Pro',
136
- price: 29,
137
- period: 'month',
138
- features: ['Unlimited requests', 'Priority support', '10 projects'],
139
- popular: true,
153
+ description: 'For teams running in production.',
154
+ currency: '$',
155
+ monthlyPrice: '29',
156
+ yearlyPrice: '290',
157
+ buttonText: 'Choose Pro',
158
+ badge: 'Most popular',
159
+ highlight: true,
160
+ features: [
161
+ { name: 'Unlimited requests', icon: 'check' },
162
+ { name: 'Priority support', icon: 'check' },
163
+ { name: '10 projects', icon: 'check' },
164
+ ],
140
165
  },
141
166
  ];
142
167
  ```
143
168
 
169
+ The generated file is the contract for your installed version; if its `Plan` differs from the above, follow
170
+ the file. `onPlanSelect` receives only the plan `id`, so the ids here are the slugs the server maps below.
171
+
172
+ This example sells **monthly plans only**: `yearlyPrice` is required by the `Plan` type and is displayed,
173
+ but the server map below has only monthly products. To sell yearly too, create the yearly products in Dodo,
174
+ track the table's billing-period toggle in your own state (it is not passed to `onPlanSelect`), send it with
175
+ the plan id, and key the server map by plan and period (for example `pro:yearly`).
176
+
144
177
  ### 2. Product id map — server only
145
178
 
146
179
  Never let the browser choose an arbitrary `product_id`. Map the public plan slug to a `pdt_` id on the
@@ -149,16 +182,16 @@ server and reject anything else.
149
182
  ```typescript
150
183
  // lib/plan-products.server.ts
151
184
  import 'server-only';
152
- import type { PlanId } from './plans';
153
185
 
154
- const PRODUCT_IDS: Record<PlanId, string> = {
186
+ // Keys must match the `id`s in lib/billingsdk-config.ts.
187
+ const PRODUCT_IDS: Readonly<Record<string, string>> = {
155
188
  starter: 'pdt_starter_monthly',
156
189
  pro: 'pdt_pro_monthly',
157
190
  };
158
191
 
159
192
  export function productIdForPlan(value: unknown): string | null {
160
- if (typeof value !== 'string') return null;
161
- return PRODUCT_IDS[value as PlanId] ?? null;
193
+ if (typeof value !== 'string' || !Object.hasOwn(PRODUCT_IDS, value)) return null;
194
+ return PRODUCT_IDS[value];
162
195
  }
163
196
  ```
164
197
 
@@ -220,7 +253,7 @@ integrations. Checkout parameters are covered in depth in the `checkout-integrat
220
253
 
221
254
  import { useState } from 'react';
222
255
  import { PricingTableOne } from '@/components/billingsdk/pricing-table-one';
223
- import { PLANS } from '@/lib/plans';
256
+ import { plans } from '@/lib/billingsdk-config';
224
257
 
225
258
  export function PricingPlans() {
226
259
  const [error, setError] = useState<string | null>(null);
@@ -249,7 +282,7 @@ export function PricingPlans() {
249
282
  return (
250
283
  <>
251
284
  <PricingTableOne
252
- plans={PLANS}
285
+ plans={plans}
253
286
  title="Choose your plan"
254
287
  description="Select the plan that works best for you"
255
288
  onPlanSelect={handlePlanSelect}
@@ -458,14 +491,14 @@ signature verification is covered in the `webhook-integration` skill.
458
491
  **Accepting a raw `product_id` from the request body.** A caller can then check out against any product in
459
492
  your catalogue, including internal or discounted ones. Map an opaque plan slug to a `pdt_` id server-side.
460
493
 
461
- **Hardcoding prices in the client.** `PLANS[].price` is display text. When you change a price in the Dodo
494
+ **Hardcoding prices in the client.** `plans[].monthlyPrice` / `yearlyPrice` are display text. When you change a price in the Dodo
462
495
  dashboard, the checkout charges the new amount while the pricing page keeps advertising the old one.
463
- Reconcile `lib/plans.ts` with the dashboard as part of any pricing change.
496
+ Reconcile `lib/billingsdk-config.ts` with the dashboard as part of any pricing change.
464
497
 
465
498
  **`npm install billingsdk`.** The root repository package is private. Install through
466
499
  `@billingsdk/cli` or the shadcn registry instead.
467
500
 
468
- **Guessing prop names.** `pricing-table-one` is the only block with a published prop example. Open the
501
+ **Guessing prop names.** Check the block's page on billingsdk.com/docs/components, then open the
469
502
  generated file under `components/billingsdk/` and read the exported props type before wiring anything else.
470
503
 
471
504
  **Using `DODO_PAYMENTS_WEBHOOK_SECRET`.** The SDK reads `DODO_PAYMENTS_WEBHOOK_KEY` for the `webhookKey`
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: checkout-integration
3
- description: Guide for starting hosted Checkout Sessions, payment links, and overlay or inline checkout for one-time and recurring products; use subscription-integration for post-checkout lifecycle management.
3
+ description: Dodo Payments checkout via hosted Checkout Sessions (client.checkoutSessions.create), static payment links, and overlay or inline checkout for one-time and subscription products. Use when building a pay button, checkout page, trial signup, custom fields, discount codes at checkout, or return_url redirects; use subscription-integration for post-checkout lifecycle.
4
4
  ---
5
5
 
6
6
  # Dodo Payments Checkout Integration
@@ -38,7 +38,7 @@ Dodo Payments offers three ways to collect payment:
38
38
 
39
39
  **Checkout Session:** A single-use session that generates a hosted checkout URL. Expires after 24 hours (or 15 minutes if `confirm=true`).
40
40
 
41
- **Return URL:** Where the customer lands after payment. Query parameters include `status=success` and `session_id`.
41
+ **Return URL:** Where the customer lands after payment. Dodo appends `payment_id` (one-time) or `subscription_id` (subscription), `status`, and when available `license_key` and `email`. It is a UI hint only, never proof of payment.
42
42
 
43
43
  **Entitlement on webhook:** The browser redirect is not the source of truth. Always verify payment via webhook before granting access. See `webhook-integration` skill for verification.
44
44
 
@@ -637,31 +637,27 @@ async function createCheckoutUrl(planId: PlanId, quantity = 1): Promise<string>
637
637
  return data.checkoutUrl;
638
638
  }
639
639
 
640
- export async function openOverlayCheckout(planId: PlanId): Promise<void> {
641
- const checkoutUrl = await createCheckoutUrl(planId);
642
-
640
+ // Call ONCE when the app loads (e.g. in your root component or entry point),
641
+ // not before every checkout. Pick the display type your page uses.
642
+ export function initCheckout(displayType: 'overlay' | 'inline'): void {
643
643
  DodoPayments.Initialize({
644
644
  mode: 'test', // or 'live'
645
- displayType: 'overlay',
645
+ displayType,
646
646
  onEvent: (event) => {
647
647
  console.log('Checkout event:', event);
648
648
  }
649
649
  });
650
+ }
650
651
 
652
+ // Requires initCheckout('overlay') to have run at app load.
653
+ export async function openOverlayCheckout(planId: PlanId): Promise<void> {
654
+ const checkoutUrl = await createCheckoutUrl(planId);
651
655
  DodoPayments.Checkout.open({ checkoutUrl });
652
656
  }
653
657
 
658
+ // Requires initCheckout('inline') to have run at app load.
654
659
  export async function openInlineCheckout(planId: PlanId): Promise<void> {
655
660
  const checkoutUrl = await createCheckoutUrl(planId);
656
-
657
- DodoPayments.Initialize({
658
- mode: 'test', // or 'live'
659
- displayType: 'inline',
660
- onEvent: (event) => {
661
- console.log('Checkout event:', event);
662
- }
663
- });
664
-
665
661
  DodoPayments.Checkout.open({
666
662
  checkoutUrl,
667
663
  elementId: 'dodo-inline-checkout'
@@ -685,6 +681,8 @@ No-code shareable links. No server-side API call needed.
685
681
  https://checkout.dodopayments.com/buy/{productid}
686
682
  ```
687
683
 
684
+ In test mode the host is `test.checkout.dodopayments.com` (for example `https://test.checkout.dodopayments.com/buy/{productid}`); a test-mode product does not exist on the live host.
685
+
688
686
  Example:
689
687
  ```text
690
688
  https://checkout.dodopayments.com/buy/pdt_example
@@ -789,12 +787,20 @@ const session = await client.checkoutSessions.create({
789
787
  After payment, the customer is redirected to your `return_url` with query parameters:
790
788
 
791
789
  ```text
792
- https://yoursite.com/success?status=success&session_id=cks_123
790
+ # One-time payment
791
+ https://yoursite.com/success?payment_id=pay_xxx&status=succeeded&email=customer%40example.com
792
+
793
+ # Subscription (with license keys)
794
+ https://yoursite.com/success?subscription_id=sub_xxx&status=active&license_key=LK-001,LK-002&email=customer%40example.com
793
795
  ```
794
796
 
795
797
  Query parameters:
796
- - `status`: `success` or `failed`
797
- - `session_id`: Checkout session ID
798
+ - `payment_id` (one-time payments) or `subscription_id` (subscriptions)
799
+ - `status`: `succeeded` (one-time) or `active` (subscription) on success; `failed` if declined; `processing` (or a `requires_*` value) if it settles later; `expired` if the session expired
800
+ - `license_key`: present when the product issues license keys (comma-separated if several)
801
+ - `email`: present when the customer has an email on record
802
+
803
+ There is no `session_id` parameter. Treat `processing`/missing status as "unknown" and wait for the webhook.
798
804
 
799
805
  ### Verify Payment Server-Side
800
806
 
@@ -818,8 +824,8 @@ Do not use `client.payments.create()` or `client.subscriptions.create()` for new
818
824
  **3. Forgetting the `environment` flag**
819
825
  The default is `live_mode`. Always set `environment: 'test_mode'` during development to avoid charging real cards.
820
826
 
821
- **4. Assuming only one discount-code form is valid**
822
- Both `discount_code` (a string) and `discount_codes` (an array) are valid Checkout Session parameters.
827
+ **4. Using the deprecated `discount_code`**
828
+ Use `discount_codes` (an array). The singular `discount_code` string is deprecated (still accepted for backward compatibility) and cannot be combined with `discount_codes` in the same request.
823
829
 
824
830
  **5. Amounts in wrong unit**
825
831
  All amounts are in the smallest currency unit (cents for USD). $10 is `1000`, not `10`.