@waffo/pancake-ts 0.1.5 → 0.1.9

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/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  TypeScript SDK for the Waffo Pancake Merchant of Record (MoR) payment platform.
4
4
 
5
- - Zero runtime dependencies, ESM-only, Node >= 18
5
+ - Zero runtime dependencies, ESM + CJS, Node >= 20
6
6
  - Automatic RSA-SHA256 request signing with deterministic idempotency keys
7
7
  - Full TypeScript type definitions (15 enums, 40+ interfaces)
8
8
  - Webhook verification with embedded public keys (test/prod)
@@ -15,224 +15,223 @@ npm install @waffo/pancake-ts
15
15
 
16
16
  ## Quick Start
17
17
 
18
+ > Most merchants create stores and products in the [Dashboard](https://pancake.waffo.ai/dashboard). The SDK is primarily used for **checkout integration** — redirecting buyers from your site to the Waffo checkout page.
19
+
18
20
  ```typescript
19
21
  import { WaffoPancake } from "@waffo/pancake-ts";
20
22
 
23
+ // Merchant ID and API Key are available in Dashboard > Settings > Developers
21
24
  const client = new WaffoPancake({
22
- merchantId: process.env.WAFFO_MERCHANT_ID!,
25
+ merchantId: process.env.WAFFO_MERCHANT_ID!, // MER_{base62} format
23
26
  privateKey: process.env.WAFFO_PRIVATE_KEY!,
24
27
  });
25
28
 
26
- // Create a store
27
- const { store } = await client.stores.create({ name: "My Store" });
28
-
29
- // Create a one-time product with multi-currency pricing
30
- const { product } = await client.onetimeProducts.create({
31
- storeId: store.id,
32
- name: "E-Book: TypeScript Handbook",
33
- prices: {
34
- USD: { amount: 2900, taxCategory: "digital_goods" },
35
- EUR: { amount: 2700, taxCategory: "digital_goods" },
36
- },
37
- });
38
-
39
- // Create a checkout session and redirect the buyer
40
- const session = await client.checkout.createSession({
41
- storeId: store.id,
42
- productId: product.id,
29
+ // Create a checkout session — one call handles token + session + URL
30
+ const result = await client.checkout.authenticated.create({
31
+ storeId: "STO_xxx", // from Dashboard > Stores
32
+ productId: "PROD_xxx", // from Dashboard > Products
43
33
  productType: "onetime",
44
34
  currency: "USD",
35
+ buyerIdentity: req.user.email, // your user's identity
45
36
  });
46
- // => redirect buyer to session.checkoutUrl
47
37
 
48
- // Query data via GraphQL (Query only, no Mutations)
49
- const result = await client.graphql.query<{ stores: Array<{ id: string; name: string }> }>({
50
- query: `query { stores { id name status } }`,
51
- });
38
+ // Redirect buyer to the checkout page (opens in new tab)
39
+ res.json({ checkoutUrl: result.checkoutUrl });
40
+ // => checkoutUrl includes #token=... (form pre-filled)
52
41
  ```
53
42
 
54
43
  ## Configuration
55
44
 
56
45
  | Parameter | Type | Required | Description |
57
46
  |-----------|------|----------|-------------|
58
- | `merchantId` | `string` | Yes | Merchant ID, sent as `X-Merchant-Id` header |
59
- | `privateKey` | `string` | Yes | RSA private key (see [Private Key Formats](#private-key-formats) below) |
60
- | `baseUrl` | `string` | No | API base URL (default: `https://waffo-pancake-auth-service.vercel.app`) |
47
+ | `merchantId` | `string` | Yes | Merchant ID in `MER_{base62}` format |
48
+ | `privateKey` | `string` | Yes | RSA private key in PEM format (auto-normalized, see [docs](docs/api-reference.md)) |
49
+ | `baseUrl` | `string` | No | API base URL override |
61
50
  | `fetch` | `typeof fetch` | No | Custom fetch implementation |
62
- | `webhookPublicKey` | `string` | No | Custom RSA public key for webhook verification (see [Public Key Formats](#public-key-formats) below). Overrides built-in keys when set. |
63
-
64
- ### Private Key Formats
65
-
66
- The SDK automatically normalizes `privateKey` at construction time, so all of the following formats are accepted:
67
-
68
- | Format | Example | Notes |
69
- |--------|---------|-------|
70
- | Standard PKCS#8 PEM | `-----BEGIN PRIVATE KEY-----\n...` | Recommended |
71
- | PKCS#1 PEM | `-----BEGIN RSA PRIVATE KEY-----\n...` | Also accepted |
72
- | Literal `\n` (env vars) | `"-----BEGIN PRIVATE KEY-----\\nMIIE..."` | Common when stored in `.env` or CI secrets |
73
- | Windows line endings | `\r\n` | Converted to `\n` |
74
- | Raw base64 (no headers) | `MIIEvQIBADANBgkqhki...` | Wrapped with PKCS#8 headers automatically |
75
- | Single-line base64 with headers | Header + all base64 on one line + footer | Re-wrapped to 64-char lines |
51
+ | `webhookPublicKey` | `string \| { test?, prod? }` | No | Custom webhook public key(s) |
76
52
 
77
- If the key is invalid or empty, the constructor throws a descriptive error immediately rather than failing silently on the first API call.
53
+ The SDK auto-normalizes key formats: standard PEM, PKCS#1, literal `\n` from env vars, raw base64, and Windows line endings are all accepted.
78
54
 
79
- ```typescript
80
- // All of these work:
81
- new WaffoPancake({ merchantId: "m_1", privateKey: process.env.PRIVATE_KEY! }); // .env with literal \n
82
- new WaffoPancake({ merchantId: "m_1", privateKey: fs.readFileSync("key.pem", "utf8") }); // file read
83
- new WaffoPancake({ merchantId: "m_1", privateKey: rawBase64String }); // raw base64
84
- ```
85
-
86
- ### Public Key Formats
87
-
88
- The `webhookPublicKey` option (and the `publicKey` field in `VerifyWebhookOptions`) accepts the same flexible formats as private keys:
89
-
90
- | Format | Example | Notes |
91
- |--------|---------|-------|
92
- | Standard SPKI PEM | `-----BEGIN PUBLIC KEY-----\n...` | Recommended |
93
- | PKCS#1 PEM | `-----BEGIN RSA PUBLIC KEY-----\n...` | Also accepted |
94
- | Literal `\n` (env vars) | `"-----BEGIN PUBLIC KEY-----\\nMIIB..."` | Common when stored in `.env` or CI secrets |
95
- | Windows line endings | `\r\n` | Converted to `\n` |
96
- | Raw base64 (no headers) | `MIIBIjANBgkqhki...` | Wrapped with SPKI headers automatically |
97
- | Single-line base64 with headers | Header + all base64 on one line + footer | Re-wrapped to 64-char lines |
98
-
99
- ## Resources
55
+ ## Checkout Integration
100
56
 
101
- | Namespace | Methods | Description |
102
- |-----------|---------|-------------|
103
- | `client.auth` | `issueSessionToken()` | Issue a buyer session token (JWT) |
104
- | `client.stores` | `create()` `update()` `delete()` | Store management (webhook, notification, checkout settings) |
105
- | `client.storeMerchants` | `add()` `remove()` `updateRole()` | Store member management (coming soon, returns 501) |
106
- | `client.onetimeProducts` | `create()` `update()` `publish()` `updateStatus()` | One-time product CRUD with multi-currency pricing and version management |
107
- | `client.subscriptionProducts` | `create()` `update()` `publish()` `updateStatus()` | Subscription product CRUD with billing period and version management |
108
- | `client.subscriptionProductGroups` | `create()` `update()` `delete()` `publish()` | Product groups for shared trial and plan switching |
109
- | `client.orders` | `cancelSubscription()` | Order management (pending→canceled, active→canceling) |
110
- | `client.checkout` | `createSession()` | Create a checkout session with trial toggle, billing detail, and price snapshot |
111
- | `client.graphql` | `query<T>()` | Typed GraphQL queries (Query only, no Mutations) |
112
- | `client.webhooks` | `verify<T>()` | Webhook signature verification (uses configured `webhookPublicKey` or built-in keys) |
113
-
114
- See [API Reference](docs/api-reference.md) for complete parameter tables and return types.
57
+ Waffo supports two checkout modes based on whether the merchant knows the buyer's identity:
115
58
 
116
- ## Checkout Integration
59
+ - **Merchants with their own sites** know who the buyer is — they have user accounts, login systems, or collect buyer info before checkout. The merchant provides the buyer's identity upfront, and the checkout form arrives pre-filled.
60
+ - **Template stores and shared links** have no prior buyer context — the buyer arrives directly at the checkout page and fills in their own details.
117
61
 
118
- Guide buyers from your site to the Waffo checkout page in three steps:
62
+ | Mode | Method | Buyer Identity | Form State | Use Case |
63
+ |------|--------|---------------|------------|----------|
64
+ | **Authenticated** | `checkout.authenticated.create()` | Merchant provides | Pre-filled | Merchant sites with user accounts |
65
+ | **Anonymous** | `checkout.anonymous.create()` | Not provided | Empty | Template stores, one-time purchase links |
119
66
 
120
- ```
121
- 1. Issue Session Token → Obtain a buyer identity credential (JWT)
122
- 2. Create Checkout Session → Create a session and get the checkout URL
123
- 3. Open Checkout Page → Open the checkout in a new browser tab
124
- ```
67
+ > **We recommend authenticated checkout whenever possible.** The most important reason: authenticated checkout binds the order to the `buyerIdentity` you provide, which is a **merchant-controlled stable identifier**. Even if the buyer changes the email on the checkout form, the order is still tied to the identity you specified. In anonymous mode, the buyer self-reports their email on the form — if they enter a different address, the system treats them as a new user, which means **previous orders become unlinked** and **subscription trial periods can be exploited** (a new email = a new user = a fresh trial).
68
+ >
69
+ > Anonymous checkout also uses the `shopper` role, which can **only create orders** (no cancellation, subscription management, or refund tickets) with a **1-minute single-use session**.
70
+ >
71
+ > | | Authenticated (`customer`) | Anonymous (`shopper`) |
72
+ > |---|---|---|
73
+ > | **Identity** | Merchant-provided, stable across orders | Self-reported email, may vary |
74
+ > | **Permissions** | Create orders, cancel orders, manage subscriptions, submit refund tickets | Create orders **only** |
75
+ > | **Session** | 5-minute TTL, auto-refreshes on each API call | 1-minute TTL, **single-use** (consumed on first API call) |
76
+ > | **Subscriptions** | Fully supported — buyers can manage, cancel, or reactivate | Not practical — buyer has no session to manage the subscription afterward |
125
77
 
126
- ### Step 1 — Issue a Session Token
78
+ ### Authenticated Checkout (Recommended)
127
79
 
128
- Your backend requests a Session Token on behalf of the buyer. The token carries the buyer's identity and is used by the checkout page to load order details and place orders.
80
+ The merchant provides buyer identity the SDK issues a session token, creates a checkout session, and returns a checkout URL with the token appended as a URL fragment. One call does everything.
129
81
 
130
82
  ```typescript
131
- const { token } = await client.auth.issueSessionToken({
132
- storeId: "store_xxx",
83
+ const result = await client.checkout.authenticated.create({
84
+ storeId: "STO_xxx",
85
+ productId: "PROD_xxx",
86
+ productType: "onetime",
87
+ currency: "USD",
133
88
  buyerIdentity: "customer@example.com",
89
+ // Optional: pre-fill billing details
90
+ billingDetail: { country: "US", isBusiness: false },
134
91
  });
92
+ // result.checkoutUrl = "https://pancake.waffo.ai/store/{slug}/checkout/{sessionId}#token={JWT}"
93
+
94
+ // Frontend — open in a new tab (recommended)
95
+ window.open(result.checkoutUrl, "_blank", "noopener,noreferrer");
135
96
  ```
136
97
 
137
- ### Step 2 Create a Checkout Session
98
+ The token is passed via the URL fragment (after `#`), which is never sent to the server and never appears in the `Referer` header.
138
99
 
139
- Create a checkout session with your API Key. The response includes a checkout URL with the token embedded in the URL fragment.
100
+ ### Anonymous Checkout
140
101
 
141
- ```typescript
142
- import { CheckoutSessionProductType } from "@waffo/pancake-ts";
102
+ No buyer identity required — the buyer fills in billing details manually on the checkout page.
143
103
 
144
- const session = await client.checkout.createSession({
145
- storeId: "store_xxx",
146
- productId: "prod_xxx",
147
- productType: CheckoutSessionProductType.Onetime,
104
+ ```typescript
105
+ const result = await client.checkout.anonymous.create({
106
+ storeId: "STO_xxx",
107
+ productId: "PROD_xxx",
108
+ productType: "onetime",
148
109
  currency: "USD",
149
- buyerEmail: "customer@example.com",
150
- successUrl: "https://example.com/thank-you",
151
110
  });
152
- // session.checkoutUrl format:
153
- // https://waffo.ai/store/{slug}/checkout/{sessionId}#token={JWT}
154
- ```
111
+ // result.checkoutUrl = "https://pancake.waffo.ai/store/{slug}/checkout/{sessionId}"
155
112
 
156
- The token is passed via the URL fragment (after `#`), which is never sent to the server and never appears in the `Referer` header.
113
+ window.open(result.checkoutUrl, "_blank", "noopener,noreferrer");
114
+ ```
157
115
 
158
- ### Step 3 — Open Checkout Page (New Tab)
116
+ ### Opening the Checkout Page
159
117
 
160
- **We recommend opening the checkout page in a new tab** rather than navigating in the current page. Benefits:
118
+ **We recommend opening the checkout page in a new tab** rather than navigating in the current page:
161
119
 
162
120
  - Buyers can return to your site immediately after payment or if they close the checkout tab
163
121
  - Merchant page state (cart, forms, scroll position) is preserved
164
122
  - Payment flow is decoupled from the browsing experience, reducing checkout abandonment
165
123
 
166
124
  ```typescript
167
- // Frontend — recommended: open in a new tab
168
- window.open(session.checkoutUrl, "_blank", "noopener,noreferrer");
125
+ // Recommended: open in a new tab
126
+ window.open(result.checkoutUrl, "_blank", "noopener,noreferrer");
169
127
 
170
128
  // Or via an <a> tag
171
129
  // <a href={checkoutUrl} target="_blank" rel="noopener noreferrer">Proceed to Checkout</a>
172
130
  ```
173
131
 
174
- > **Not recommended:** `window.location.href = session.checkoutUrl` replaces the current page, preventing buyers from returning to your site without browser back navigation.
132
+ > **Not recommended:** `window.location.href = result.checkoutUrl` replaces the current page, preventing buyers from returning to your site without browser back navigation.
133
+
134
+ See [API Reference — Checkout](docs/api-reference.md#checkout) for full parameter tables and `BillingDetail` field requirements.
135
+
136
+ ## Webhook Verification
137
+
138
+ After a buyer completes payment, Waffo sends webhook events to your server. The SDK provides two ways to verify signatures:
175
139
 
176
- ### Complete Example (Express)
140
+ ### Standalone Function (built-in keys)
177
141
 
178
142
  ```typescript
179
- import express from "express";
180
- import { WaffoPancake, CheckoutSessionProductType } from "@waffo/pancake-ts";
143
+ import { verifyWebhook, WebhookEventType } from "@waffo/pancake-ts";
181
144
 
182
- const client = new WaffoPancake({
183
- merchantId: process.env.WAFFO_MERCHANT_ID!,
184
- privateKey: process.env.WAFFO_PRIVATE_KEY!,
185
- });
145
+ // Express (IMPORTANT: use raw body — parsed JSON breaks signature verification)
146
+ app.post("/webhooks", express.raw({ type: "application/json" }), (req, res) => {
147
+ try {
148
+ const event = verifyWebhook(
149
+ req.body.toString("utf-8"),
150
+ req.headers["x-waffo-signature"] as string,
151
+ );
152
+
153
+ // Respond immediately, process asynchronously
154
+ res.status(200).send("OK");
186
155
 
187
- const app = express();
188
-
189
- app.post("/api/checkout", async (req, res) => {
190
- const { productId, currency, buyerEmail } = req.body;
191
-
192
- // Step 1: Issue session token
193
- const { token } = await client.auth.issueSessionToken({
194
- storeId: "store_xxx",
195
- buyerIdentity: buyerEmail,
196
- });
197
-
198
- // Step 2: Create checkout session
199
- const session = await client.checkout.createSession({
200
- storeId: "store_xxx",
201
- productId,
202
- productType: CheckoutSessionProductType.Onetime,
203
- currency,
204
- buyerEmail,
205
- successUrl: "https://example.com/thank-you",
206
- });
207
-
208
- // Return URL to frontend (frontend opens in new tab)
209
- res.json({ checkoutUrl: session.checkoutUrl });
156
+ switch (event.eventType) {
157
+ case WebhookEventType.OrderCompleted:
158
+ console.log(`Order ${event.data.orderId} completed`);
159
+ break;
160
+ case WebhookEventType.SubscriptionActivated:
161
+ console.log(`Subscription activated for ${event.data.buyerEmail}`);
162
+ break;
163
+ }
164
+ } catch {
165
+ res.status(401).send("Invalid signature");
166
+ }
210
167
  });
168
+
169
+ // Next.js App Router
170
+ export async function POST(request: Request) {
171
+ const body = await request.text();
172
+ const sig = request.headers.get("x-waffo-signature");
173
+ try {
174
+ const event = verifyWebhook(body, sig);
175
+ return new Response("OK");
176
+ } catch {
177
+ return new Response("Invalid signature", { status: 401 });
178
+ }
179
+ }
211
180
  ```
212
181
 
182
+ ### Client Instance Method (multi-level key resolution)
183
+
213
184
  ```typescript
214
- // Frontend
215
- const res = await fetch("/api/checkout", {
216
- method: "POST",
217
- headers: { "Content-Type": "application/json" },
218
- body: JSON.stringify({ productId: "prod_xxx", currency: "USD", buyerEmail: "customer@example.com" }),
185
+ const client = new WaffoPancake({
186
+ merchantId: "MER_xxx",
187
+ privateKey: "...",
188
+ webhookPublicKey: {
189
+ test: process.env.WAFFO_TEST_PUB_KEY!,
190
+ prod: process.env.WAFFO_PROD_PUB_KEY!,
191
+ },
219
192
  });
220
- const { checkoutUrl } = await res.json();
221
- window.open(checkoutUrl, "_blank", "noopener,noreferrer");
193
+ const event = client.webhooks.verify(rawBody, sig, { environment: "prod" });
222
194
  ```
223
195
 
224
- ## Usage Examples
196
+ See [Webhook Guide](docs/webhook-guide.md) for event types, dual-environment key architecture, key resolution chain, retry mechanism, and best practices.
225
197
 
226
- ### AuthIssue a Buyer Session Token
198
+ ## GraphQLTyped Queries
227
199
 
228
200
  ```typescript
229
- const { token, expiresAt } = await client.auth.issueSessionToken({
230
- storeId: "store_xxx",
231
- buyerIdentity: "customer@example.com",
201
+ // Simple query
202
+ interface StoresQuery {
203
+ stores: Array<{ id: string; name: string; status: string }>;
204
+ }
205
+ const result = await client.graphql.query<StoresQuery>({
206
+ query: `query { stores { id name status } }`,
207
+ });
208
+
209
+ // Query with variables
210
+ const product = await client.graphql.query({
211
+ query: `query ($id: ID!) { onetimeProduct(id: $id) { id name prices } }`,
212
+ variables: { id: "PROD_xxx" },
213
+ });
214
+
215
+ // Nested relationships in a single request
216
+ const detail = await client.graphql.query({
217
+ query: `query ($id: ID!) {
218
+ store(id: $id) {
219
+ id name
220
+ onetimeProducts { id name status prices }
221
+ subscriptionProducts { id name billingPeriod status }
222
+ }
223
+ }`,
224
+ variables: { id: "STO_xxx" },
232
225
  });
233
226
  ```
234
227
 
235
- ### Stores Create, Update, Delete
228
+ See [GraphQL Guide](docs/graphql-guide.md) for filters, analytics queries, delivery logs, and more.
229
+
230
+ ## Programmatic Store & Product Management
231
+
232
+ > Most merchants manage stores and products in the [Dashboard](https://pancake.waffo.ai/dashboard). The following APIs are for merchants who need programmatic automation.
233
+
234
+ ### Stores
236
235
 
237
236
  ```typescript
238
237
  // Create a store
@@ -264,20 +263,20 @@ const { store: updated } = await client.stores.update({
264
263
  const { store: deleted } = await client.stores.delete({ id: store.id });
265
264
  ```
266
265
 
267
- ### Onetime Products — Create, Update, Publish
266
+ ### Products
268
267
 
269
268
  ```typescript
270
- import { TaxCategory, ProductVersionStatus } from "@waffo/pancake-ts";
269
+ import { TaxCategory, BillingPeriod, ProductVersionStatus } from "@waffo/pancake-ts";
271
270
 
272
- // Create with multi-currency pricing
271
+ // One-time product with multi-currency pricing
273
272
  const { product } = await client.onetimeProducts.create({
274
- storeId: "store_xxx",
273
+ storeId: "STO_xxx",
275
274
  name: "E-Book: TypeScript Handbook",
276
275
  description: "Complete TypeScript guide for developers",
277
276
  prices: {
278
- USD: { amount: 2900, taxCategory: TaxCategory.DigitalGoods },
279
- EUR: { amount: 2700, taxCategory: TaxCategory.DigitalGoods },
280
- JPY: { amount: 4500, taxCategory: TaxCategory.DigitalGoods },
277
+ USD: { amount: "29.00", taxCategory: TaxCategory.DigitalGoods },
278
+ EUR: { amount: "27.00", taxCategory: TaxCategory.DigitalGoods },
279
+ JPY: { amount: "4500", taxCategory: TaxCategory.DigitalGoods },
281
280
  },
282
281
  media: [{ type: "image", url: "https://example.com/cover.jpg", alt: "Book cover" }],
283
282
  metadata: { sku: "ebook-ts-001" },
@@ -287,7 +286,7 @@ const { product } = await client.onetimeProducts.create({
287
286
  await client.onetimeProducts.update({
288
287
  id: product.id,
289
288
  name: "E-Book: TypeScript Handbook v2",
290
- prices: { USD: { amount: 3900, taxCategory: "digital_goods" } },
289
+ prices: { USD: { amount: "39.00", taxCategory: "digital_goods" } },
291
290
  });
292
291
 
293
292
  // Publish test version → production
@@ -295,40 +294,32 @@ await client.onetimeProducts.publish({ id: product.id });
295
294
 
296
295
  // Deactivate
297
296
  await client.onetimeProducts.updateStatus({ id: product.id, status: ProductVersionStatus.Inactive });
298
- ```
299
-
300
- ### Subscription Products — Create with Billing Period
301
-
302
- ```typescript
303
- import { BillingPeriod, TaxCategory } from "@waffo/pancake-ts";
304
297
 
305
- const { product } = await client.subscriptionProducts.create({
306
- storeId: "store_xxx",
298
+ // Subscription product
299
+ const { product: sub } = await client.subscriptionProducts.create({
300
+ storeId: "STO_xxx",
307
301
  name: "Pro Plan",
308
302
  billingPeriod: BillingPeriod.Monthly,
309
- prices: { USD: { amount: 999, taxCategory: TaxCategory.SaaS } },
310
- description: "Unlimited access to all features",
303
+ prices: { USD: { amount: "9.99", taxCategory: TaxCategory.SaaS } },
311
304
  });
312
-
313
- // Same update/publish/updateStatus pattern as onetime products
314
- await client.subscriptionProducts.publish({ id: product.id });
305
+ await client.subscriptionProducts.publish({ id: sub.id });
315
306
  ```
316
307
 
317
- ### Subscription Product Groups — Shared Trial & Plan Switching
308
+ ### Subscription Product Groups
318
309
 
319
310
  ```typescript
320
311
  // Create a group linking related subscription tiers
321
312
  const { group } = await client.subscriptionProductGroups.create({
322
- storeId: "store_xxx",
313
+ storeId: "STO_xxx",
323
314
  name: "Pro Plans",
324
315
  rules: { sharedTrial: true },
325
- productIds: ["prod_aaa", "prod_bbb"],
316
+ productIds: ["PROD_aaa", "PROD_bbb"],
326
317
  });
327
318
 
328
319
  // Update members (full replacement, not merge)
329
320
  await client.subscriptionProductGroups.update({
330
321
  id: group.id,
331
- productIds: ["prod_aaa", "prod_bbb", "prod_ccc"],
322
+ productIds: ["PROD_aaa", "PROD_bbb", "PROD_ccc"],
332
323
  });
333
324
 
334
325
  // Publish / delete
@@ -336,160 +327,15 @@ await client.subscriptionProductGroups.publish({ id: group.id });
336
327
  await client.subscriptionProductGroups.delete({ id: group.id });
337
328
  ```
338
329
 
339
- ### Orders — Cancel a Subscription
330
+ ### Orders
340
331
 
341
332
  ```typescript
342
333
  const { orderId, status } = await client.orders.cancelSubscription({
343
- orderId: "order_xxx",
334
+ orderId: "ORD_xxx",
344
335
  });
345
336
  // status: "canceled" (was pending) or "canceling" (was active, PSP notified)
346
337
  ```
347
338
 
348
- ### Checkout — Create a Session
349
-
350
- ```typescript
351
- import { CheckoutSessionProductType } from "@waffo/pancake-ts";
352
-
353
- // One-time product checkout
354
- const session = await client.checkout.createSession({
355
- storeId: "store_xxx",
356
- productId: "prod_xxx",
357
- productType: CheckoutSessionProductType.Onetime,
358
- currency: "USD",
359
- buyerEmail: "customer@example.com",
360
- successUrl: "https://example.com/thank-you",
361
- });
362
- // => redirect buyer to session.checkoutUrl
363
-
364
- // Subscription with trial and billing detail
365
- const subSession = await client.checkout.createSession({
366
- storeId: "store_xxx",
367
- productId: "prod_yyy",
368
- productType: CheckoutSessionProductType.Subscription,
369
- currency: "USD",
370
- withTrial: true,
371
- billingDetail: { country: "US", isBusiness: false, state: "CA", postcode: "94105" },
372
- });
373
- ```
374
-
375
- ### GraphQL — Typed Queries
376
-
377
- ```typescript
378
- // Simple query
379
- interface StoresQuery {
380
- stores: Array<{ id: string; name: string; status: string }>;
381
- }
382
- const result = await client.graphql.query<StoresQuery>({
383
- query: `query { stores { id name status } }`,
384
- });
385
-
386
- // Query with variables
387
- const product = await client.graphql.query({
388
- query: `query ($id: ID!) { onetimeProduct(id: $id) { id name prices } }`,
389
- variables: { id: "prod_xxx" },
390
- });
391
-
392
- // Nested relationships in a single request
393
- const detail = await client.graphql.query({
394
- query: `query ($id: ID!) {
395
- store(id: $id) {
396
- id name
397
- onetimeProducts { id name status prices }
398
- subscriptionProducts { id name billingPeriod status }
399
- }
400
- }`,
401
- variables: { id: "store_xxx" },
402
- });
403
- ```
404
-
405
- See [GraphQL Guide](docs/graphql-guide.md) for introspection, filters, pagination, and more examples.
406
-
407
- ## Webhook Verification
408
-
409
- Two ways to verify webhooks: the **standalone function** `verifyWebhook()` with built-in public keys, or the **client instance method** `client.webhooks.verify()` which uses the configured `webhookPublicKey`.
410
-
411
- ### Option A — Standalone Function (built-in keys)
412
-
413
- ```typescript
414
- import { verifyWebhook, WebhookEventType } from "@waffo/pancake-ts";
415
-
416
- // Express (IMPORTANT: use raw body — parsed JSON breaks signature verification)
417
- app.post("/webhooks", express.raw({ type: "application/json" }), (req, res) => {
418
- try {
419
- const event = verifyWebhook(
420
- req.body.toString("utf-8"),
421
- req.headers["x-waffo-signature"] as string,
422
- );
423
-
424
- // Respond immediately, process asynchronously
425
- res.status(200).send("OK");
426
-
427
- // Use event.id for idempotent deduplication
428
- switch (event.eventType) {
429
- case WebhookEventType.OrderCompleted:
430
- console.log(`Order ${event.data.orderId} completed`);
431
- break;
432
- case WebhookEventType.SubscriptionActivated:
433
- console.log(`Subscription activated for ${event.data.buyerEmail}`);
434
- break;
435
- case WebhookEventType.SubscriptionCanceled:
436
- console.log(`Subscription canceled: ${event.data.orderId}`);
437
- break;
438
- case WebhookEventType.RefundSucceeded:
439
- console.log(`Refund ${event.data.amount} ${event.data.currency}`);
440
- break;
441
- }
442
- } catch {
443
- res.status(401).send("Invalid signature");
444
- }
445
- });
446
-
447
- // Next.js App Router
448
- export async function POST(request: Request) {
449
- const body = await request.text();
450
- const sig = request.headers.get("x-waffo-signature");
451
- try {
452
- const event = verifyWebhook(body, sig);
453
- // handle event ...
454
- return new Response("OK");
455
- } catch {
456
- return new Response("Invalid signature", { status: 401 });
457
- }
458
- }
459
-
460
- // Options: specify environment, disable/customize replay protection
461
- const event = verifyWebhook(body, sig, { environment: "prod" });
462
- const event = verifyWebhook(body, sig, { toleranceMs: 0 }); // disable replay check
463
- ```
464
-
465
- ### Option B — Client Instance Method (custom public key)
466
-
467
- When you provide a `webhookPublicKey` in the client config, `client.webhooks.verify()` uses that key automatically. Useful for self-hosted deployments or custom key rotation.
468
-
469
- ```typescript
470
- const client = new WaffoPancake({
471
- merchantId: process.env.WAFFO_MERCHANT_ID!,
472
- privateKey: process.env.WAFFO_PRIVATE_KEY!,
473
- webhookPublicKey: process.env.WAFFO_WEBHOOK_PUBLIC_KEY!, // any format accepted
474
- });
475
-
476
- // Uses the configured public key — no need to pass it per call
477
- const event = client.webhooks.verify(rawBody, signatureHeader);
478
-
479
- // You can still override per call if needed
480
- const event = client.webhooks.verify(rawBody, sig, { publicKey: anotherKey });
481
- ```
482
-
483
- ### Standalone Function with Custom Key
484
-
485
- You can also pass a custom key directly to the standalone function without creating a client:
486
-
487
- ```typescript
488
- const event = verifyWebhook(body, sig, { publicKey: process.env.MY_PUBLIC_KEY! });
489
- ```
490
-
491
- See [Webhook Guide](docs/webhook-guide.md) for all 10 event types, signature algorithm, and best practices.
492
-
493
339
  ## Error Handling
494
340
 
495
341
  API errors throw `WaffoPancakeError` with the HTTP status code and a call-stack-ordered errors array.
@@ -508,25 +354,44 @@ try {
508
354
  }
509
355
  ```
510
356
 
357
+ ## Resources
358
+
359
+ | Namespace | Methods | Description |
360
+ |-----------|---------|-------------|
361
+ | `client.checkout.authenticated` | `create()` | Authenticated checkout (recommended) |
362
+ | `client.checkout.anonymous` | `create()` | Anonymous checkout |
363
+ | `client.checkout` | `createSession()` | Low-level checkout session |
364
+ | `client.webhooks` | `verify<T>()` | Webhook signature verification |
365
+ | `client.graphql` | `query<T>()` | Typed GraphQL queries |
366
+ | `client.auth` | `issueSessionToken()` | Issue a buyer session token (JWT) |
367
+ | `client.stores` | `create()` `update()` `delete()` | Store management |
368
+ | `client.storeMerchants` | `add()` `remove()` `updateRole()` | Store members (coming soon) |
369
+ | `client.onetimeProducts` | `create()` `update()` `publish()` `updateStatus()` | One-time products |
370
+ | `client.subscriptionProducts` | `create()` `update()` `publish()` `updateStatus()` | Subscription products |
371
+ | `client.subscriptionProductGroups` | `create()` `update()` `delete()` `publish()` | Product groups |
372
+ | `client.orders` | `cancelSubscription()` | Order management |
373
+
374
+ ## Documentation
375
+
376
+ | Document | Content |
377
+ |----------|---------|
378
+ | [API Reference](docs/api-reference.md) | Complete method reference — parameters, return types, `BillingDetail` fields |
379
+ | [GraphQL Guide](docs/graphql-guide.md) | Queries, filters, analytics, introspection, delivery logs |
380
+ | [Webhook Guide](docs/webhook-guide.md) | Signature verification, event types, key resolution, retry mechanism |
381
+ | [Changelog](CHANGELOG.md) | Version history and migration guides |
382
+
511
383
  ## Exports
512
384
 
513
- ### Classes
385
+ ### Classes & Functions
514
386
 
515
387
  | Export | Description |
516
388
  |--------|-------------|
517
389
  | `WaffoPancake` | SDK client with auto-signed requests |
518
390
  | `WaffoPancakeError` | API error with status and call-stack errors |
519
-
520
- ### Functions
521
-
522
- | Export | Description |
523
- |--------|-------------|
524
- | `verifyWebhook` | RSA-SHA256 webhook signature verification |
391
+ | `verifyWebhook` | Standalone webhook signature verification |
525
392
 
526
393
  ### Enums
527
394
 
528
- Runtime-accessible values. Both `Enum.Value` and string literal syntax are supported.
529
-
530
395
  | Export | Values |
531
396
  |--------|--------|
532
397
  | `Environment` | `Test`, `Prod` |
@@ -536,18 +401,18 @@ Runtime-accessible values. Both `Enum.Value` and string literal syntax are suppo
536
401
  | `EntityStatus` | `Active`, `Inactive`, `Suspended` |
537
402
  | `StoreRole` | `Owner`, `Admin`, `Member` |
538
403
  | `OnetimeOrderStatus` | `Pending`, `Completed`, `Canceled` |
539
- | `SubscriptionOrderStatus` | `Pending`, `Active`, `Canceling`, `Canceled`, `PastDue`, `Expired` |
404
+ | `SubscriptionOrderStatus` | `Pending`, `Active`, `Canceling`, `PastDue`, `Closed`, `Canceled`, `Expired` |
540
405
  | `PaymentStatus` | `Pending`, `Succeeded`, `Failed`, `Canceled` |
541
406
  | `RefundTicketStatus` | `Pending`, `Approved`, `Rejected`, `Processing`, `Succeeded`, `Failed` |
542
407
  | `RefundStatus` | `Succeeded`, `Failed` |
543
408
  | `MediaType` | `Image`, `Video` |
544
409
  | `CheckoutSessionProductType` | `Onetime`, `Subscription` |
545
- | `ErrorLayer` | `Gateway`, `User`, `Store`, `Product`, `Order`, `GraphQL`, `Resource`, `Email` |
410
+ | `ErrorLayer` | `Gateway`, `User`, `Store`, `Product`, `Order`, `Ticket`, `GraphQL`, `Resource`, `Email` |
546
411
  | `WebhookEventType` | `OrderCompleted`, `SubscriptionActivated`, `SubscriptionPaymentSucceeded`, `SubscriptionCanceling`, `SubscriptionUncanceled`, `SubscriptionUpdated`, `SubscriptionCanceled`, `SubscriptionPastDue`, `RefundSucceeded`, `RefundFailed` |
547
412
 
548
413
  ### Types
549
414
 
550
- See [API Reference — Types](docs/api-reference.md#types) for the full list of 40+ exported interfaces.
415
+ Key types: `WaffoPancakeConfig`, `AuthenticatedCheckoutParams`, `AuthenticatedCheckoutResult`, `AnonymousCheckoutParams`, `CheckoutSessionResult`, `Store`, `OnetimeProductDetail`, `SubscriptionProductDetail`, `WebhookEvent<T>`, `GraphQLResponse<T>`, and 30+ more. See [API Reference](docs/api-reference.md#types) for the full list.
551
416
 
552
417
  ## Development
553
418
 
@@ -556,7 +421,7 @@ npm run lint # ESLint 9 (TypeScript ESLint + import order + JSDoc)
556
421
  npm run test # Vitest
557
422
  npm run test:watch # Vitest in watch mode
558
423
  npm run test:coverage # Vitest with v8 coverage
559
- npm run build # TypeScript compilation to dist/
424
+ npm run build # tsup ESM + CJS + DTS
560
425
  ```
561
426
 
562
427
  ## Project Structure
@@ -580,11 +445,13 @@ src/
580
445
  ├── subscription-product-groups.ts
581
446
  ├── orders.ts
582
447
  ├── checkout.ts
448
+ ├── checkout-anonymous.ts
449
+ ├── checkout-authenticated.ts
583
450
  ├── graphql.ts
584
451
  └── webhooks.ts
585
452
  docs/
586
453
  ├── api-reference.md # Complete API reference
587
- ├── graphql-guide.md # GraphQL usage guide
454
+ ├── graphql-guide.md # GraphQL queries & analytics
588
455
  └── webhook-guide.md # Webhook verification guide
589
456
  ```
590
457