@waffo/pancake-ts 0.1.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/README.md +418 -0
- package/dist/index.cjs +757 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1258 -0
- package/dist/index.d.ts +1258 -0
- package/dist/index.js +713 -0
- package/dist/index.js.map +1 -0
- package/package.json +51 -0
package/README.md
ADDED
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
# @waffo/pancake-ts
|
|
2
|
+
|
|
3
|
+
TypeScript SDK for the Waffo Pancake Merchant of Record (MoR) payment platform.
|
|
4
|
+
|
|
5
|
+
- Zero runtime dependencies, ESM-only, Node >= 18
|
|
6
|
+
- Automatic RSA-SHA256 request signing with deterministic idempotency keys
|
|
7
|
+
- Full TypeScript type definitions (15 enums, 40+ interfaces)
|
|
8
|
+
- Webhook verification with embedded public keys (test/prod)
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm install @waffo/pancake-ts
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Quick Start
|
|
17
|
+
|
|
18
|
+
```typescript
|
|
19
|
+
import { WaffoPancake } from "@waffo/pancake-ts";
|
|
20
|
+
|
|
21
|
+
const client = new WaffoPancake({
|
|
22
|
+
merchantId: process.env.WAFFO_MERCHANT_ID!,
|
|
23
|
+
privateKey: process.env.WAFFO_PRIVATE_KEY!,
|
|
24
|
+
});
|
|
25
|
+
|
|
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, taxIncluded: false, taxCategory: "digital_goods" },
|
|
35
|
+
EUR: { amount: 2700, taxIncluded: true, 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,
|
|
43
|
+
productType: "onetime",
|
|
44
|
+
currency: "USD",
|
|
45
|
+
});
|
|
46
|
+
// => redirect buyer to session.checkoutUrl
|
|
47
|
+
|
|
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
|
+
});
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Configuration
|
|
55
|
+
|
|
56
|
+
| Parameter | Type | Required | Description |
|
|
57
|
+
|-----------|------|----------|-------------|
|
|
58
|
+
| `merchantId` | `string` | Yes | Merchant ID, sent as `X-Merchant-Id` header |
|
|
59
|
+
| `privateKey` | `string` | Yes | RSA private key in PEM format for request signing |
|
|
60
|
+
| `baseUrl` | `string` | No | API base URL (default: `https://waffo-pancake-auth-service.vercel.app`) |
|
|
61
|
+
| `fetch` | `typeof fetch` | No | Custom fetch implementation |
|
|
62
|
+
|
|
63
|
+
## Resources
|
|
64
|
+
|
|
65
|
+
| Namespace | Methods | Description |
|
|
66
|
+
|-----------|---------|-------------|
|
|
67
|
+
| `client.auth` | `issueSessionToken()` | Issue a buyer session token (JWT) |
|
|
68
|
+
| `client.stores` | `create()` `update()` `delete()` | Store management (webhook, notification, checkout settings) |
|
|
69
|
+
| `client.storeMerchants` | `add()` `remove()` `updateRole()` | Store member management (coming soon, returns 501) |
|
|
70
|
+
| `client.onetimeProducts` | `create()` `update()` `publish()` `updateStatus()` | One-time product CRUD with multi-currency pricing and version management |
|
|
71
|
+
| `client.subscriptionProducts` | `create()` `update()` `publish()` `updateStatus()` | Subscription product CRUD with billing period and version management |
|
|
72
|
+
| `client.subscriptionProductGroups` | `create()` `update()` `delete()` `publish()` | Product groups for shared trial and plan switching |
|
|
73
|
+
| `client.orders` | `cancelSubscription()` | Order management (pending→canceled, active→canceling) |
|
|
74
|
+
| `client.checkout` | `createSession()` | Create a checkout session with trial toggle, billing detail, and price snapshot |
|
|
75
|
+
| `client.graphql` | `query<T>()` | Typed GraphQL queries (Query only, no Mutations) |
|
|
76
|
+
|
|
77
|
+
See [API Reference](docs/api-reference.md) for complete parameter tables and return types.
|
|
78
|
+
|
|
79
|
+
## Usage Examples
|
|
80
|
+
|
|
81
|
+
### Auth — Issue a Buyer Session Token
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
const { token, expiresAt } = await client.auth.issueSessionToken({
|
|
85
|
+
storeId: "store_xxx",
|
|
86
|
+
buyerIdentity: "customer@example.com",
|
|
87
|
+
});
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Stores — Create, Update, Delete
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
// Create a store
|
|
94
|
+
const { store } = await client.stores.create({ name: "My Store" });
|
|
95
|
+
|
|
96
|
+
// Update settings (webhook, notification, checkout theme)
|
|
97
|
+
const { store: updated } = await client.stores.update({
|
|
98
|
+
id: store.id,
|
|
99
|
+
supportEmail: "help@example.com",
|
|
100
|
+
webhookSettings: {
|
|
101
|
+
testWebhookUrl: "https://example.com/webhooks",
|
|
102
|
+
prodWebhookUrl: null,
|
|
103
|
+
testEvents: ["order.completed", "subscription.activated"],
|
|
104
|
+
prodEvents: [],
|
|
105
|
+
},
|
|
106
|
+
notificationSettings: {
|
|
107
|
+
emailOrderConfirmation: true,
|
|
108
|
+
emailSubscriptionConfirmation: true,
|
|
109
|
+
emailSubscriptionCycled: true,
|
|
110
|
+
emailSubscriptionCanceled: true,
|
|
111
|
+
emailSubscriptionRevoked: true,
|
|
112
|
+
emailSubscriptionPastDue: true,
|
|
113
|
+
notifyNewOrders: true,
|
|
114
|
+
notifyNewSubscriptions: true,
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
// Soft-delete
|
|
119
|
+
const { store: deleted } = await client.stores.delete({ id: store.id });
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Onetime Products — Create, Update, Publish
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
import { TaxCategory, ProductVersionStatus } from "@waffo/pancake-ts";
|
|
126
|
+
|
|
127
|
+
// Create with multi-currency pricing
|
|
128
|
+
const { product } = await client.onetimeProducts.create({
|
|
129
|
+
storeId: "store_xxx",
|
|
130
|
+
name: "E-Book: TypeScript Handbook",
|
|
131
|
+
description: "Complete TypeScript guide for developers",
|
|
132
|
+
prices: {
|
|
133
|
+
USD: { amount: 2900, taxIncluded: false, taxCategory: TaxCategory.DigitalGoods },
|
|
134
|
+
EUR: { amount: 2700, taxIncluded: true, taxCategory: TaxCategory.DigitalGoods },
|
|
135
|
+
JPY: { amount: 4500, taxIncluded: true, taxCategory: TaxCategory.DigitalGoods },
|
|
136
|
+
},
|
|
137
|
+
media: [{ type: "image", url: "https://example.com/cover.jpg", alt: "Book cover" }],
|
|
138
|
+
metadata: { sku: "ebook-ts-001" },
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
// Update (creates a new immutable version; skips if unchanged)
|
|
142
|
+
await client.onetimeProducts.update({
|
|
143
|
+
id: product.id,
|
|
144
|
+
name: "E-Book: TypeScript Handbook v2",
|
|
145
|
+
prices: { USD: { amount: 3900, taxIncluded: false, taxCategory: "digital_goods" } },
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// Publish test version → production
|
|
149
|
+
await client.onetimeProducts.publish({ id: product.id });
|
|
150
|
+
|
|
151
|
+
// Deactivate
|
|
152
|
+
await client.onetimeProducts.updateStatus({ id: product.id, status: ProductVersionStatus.Inactive });
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### Subscription Products — Create with Billing Period
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
import { BillingPeriod, TaxCategory } from "@waffo/pancake-ts";
|
|
159
|
+
|
|
160
|
+
const { product } = await client.subscriptionProducts.create({
|
|
161
|
+
storeId: "store_xxx",
|
|
162
|
+
name: "Pro Plan",
|
|
163
|
+
billingPeriod: BillingPeriod.Monthly,
|
|
164
|
+
prices: { USD: { amount: 999, taxIncluded: false, taxCategory: TaxCategory.SaaS } },
|
|
165
|
+
description: "Unlimited access to all features",
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// Same update/publish/updateStatus pattern as onetime products
|
|
169
|
+
await client.subscriptionProducts.publish({ id: product.id });
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### Subscription Product Groups — Shared Trial & Plan Switching
|
|
173
|
+
|
|
174
|
+
```typescript
|
|
175
|
+
// Create a group linking related subscription tiers
|
|
176
|
+
const { group } = await client.subscriptionProductGroups.create({
|
|
177
|
+
storeId: "store_xxx",
|
|
178
|
+
name: "Pro Plans",
|
|
179
|
+
rules: { sharedTrial: true },
|
|
180
|
+
productIds: ["prod_aaa", "prod_bbb"],
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
// Update members (full replacement, not merge)
|
|
184
|
+
await client.subscriptionProductGroups.update({
|
|
185
|
+
id: group.id,
|
|
186
|
+
productIds: ["prod_aaa", "prod_bbb", "prod_ccc"],
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
// Publish / delete
|
|
190
|
+
await client.subscriptionProductGroups.publish({ id: group.id });
|
|
191
|
+
await client.subscriptionProductGroups.delete({ id: group.id });
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### Orders — Cancel a Subscription
|
|
195
|
+
|
|
196
|
+
```typescript
|
|
197
|
+
const { orderId, status } = await client.orders.cancelSubscription({
|
|
198
|
+
orderId: "order_xxx",
|
|
199
|
+
});
|
|
200
|
+
// status: "canceled" (was pending) or "canceling" (was active, PSP notified)
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### Checkout — Create a Session
|
|
204
|
+
|
|
205
|
+
```typescript
|
|
206
|
+
import { CheckoutSessionProductType } from "@waffo/pancake-ts";
|
|
207
|
+
|
|
208
|
+
// One-time product checkout
|
|
209
|
+
const session = await client.checkout.createSession({
|
|
210
|
+
storeId: "store_xxx",
|
|
211
|
+
productId: "prod_xxx",
|
|
212
|
+
productType: CheckoutSessionProductType.Onetime,
|
|
213
|
+
currency: "USD",
|
|
214
|
+
buyerEmail: "customer@example.com",
|
|
215
|
+
successUrl: "https://example.com/thank-you",
|
|
216
|
+
});
|
|
217
|
+
// => redirect buyer to session.checkoutUrl
|
|
218
|
+
|
|
219
|
+
// Subscription with trial and billing detail
|
|
220
|
+
const subSession = await client.checkout.createSession({
|
|
221
|
+
productId: "prod_yyy",
|
|
222
|
+
productType: CheckoutSessionProductType.Subscription,
|
|
223
|
+
currency: "USD",
|
|
224
|
+
withTrial: true,
|
|
225
|
+
billingDetail: { country: "US", isBusiness: false, state: "CA", postcode: "94105" },
|
|
226
|
+
});
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### GraphQL — Typed Queries
|
|
230
|
+
|
|
231
|
+
```typescript
|
|
232
|
+
// Simple query
|
|
233
|
+
interface StoresQuery {
|
|
234
|
+
stores: Array<{ id: string; name: string; status: string }>;
|
|
235
|
+
}
|
|
236
|
+
const result = await client.graphql.query<StoresQuery>({
|
|
237
|
+
query: `query { stores { id name status } }`,
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
// Query with variables
|
|
241
|
+
const product = await client.graphql.query({
|
|
242
|
+
query: `query ($id: ID!) { onetimeProduct(id: $id) { id name prices } }`,
|
|
243
|
+
variables: { id: "prod_xxx" },
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
// Nested relationships in a single request
|
|
247
|
+
const detail = await client.graphql.query({
|
|
248
|
+
query: `query ($id: ID!) {
|
|
249
|
+
store(id: $id) {
|
|
250
|
+
id name
|
|
251
|
+
onetimeProducts { id name status prices }
|
|
252
|
+
subscriptionProducts { id name billingPeriod status }
|
|
253
|
+
}
|
|
254
|
+
}`,
|
|
255
|
+
variables: { id: "store_xxx" },
|
|
256
|
+
});
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
See [GraphQL Guide](docs/graphql-guide.md) for introspection, filters, pagination, and more examples.
|
|
260
|
+
|
|
261
|
+
## Webhook Verification
|
|
262
|
+
|
|
263
|
+
The SDK exports a standalone `verifyWebhook()` function with **embedded RSA-SHA256 public keys** for both test and production environments. No need to manage keys yourself.
|
|
264
|
+
|
|
265
|
+
```typescript
|
|
266
|
+
import { verifyWebhook, WebhookEventType } from "@waffo/pancake-ts";
|
|
267
|
+
|
|
268
|
+
// Express (IMPORTANT: use raw body — parsed JSON breaks signature verification)
|
|
269
|
+
app.post("/webhooks", express.raw({ type: "application/json" }), (req, res) => {
|
|
270
|
+
try {
|
|
271
|
+
const event = verifyWebhook(
|
|
272
|
+
req.body.toString("utf-8"),
|
|
273
|
+
req.headers["x-waffo-signature"] as string,
|
|
274
|
+
);
|
|
275
|
+
|
|
276
|
+
// Respond immediately, process asynchronously
|
|
277
|
+
res.status(200).send("OK");
|
|
278
|
+
|
|
279
|
+
// Use event.id for idempotent deduplication
|
|
280
|
+
switch (event.eventType) {
|
|
281
|
+
case WebhookEventType.OrderCompleted:
|
|
282
|
+
console.log(`Order ${event.data.orderId} completed`);
|
|
283
|
+
break;
|
|
284
|
+
case WebhookEventType.SubscriptionActivated:
|
|
285
|
+
console.log(`Subscription activated for ${event.data.buyerEmail}`);
|
|
286
|
+
break;
|
|
287
|
+
case WebhookEventType.SubscriptionCanceled:
|
|
288
|
+
console.log(`Subscription canceled: ${event.data.orderId}`);
|
|
289
|
+
break;
|
|
290
|
+
case WebhookEventType.RefundSucceeded:
|
|
291
|
+
console.log(`Refund ${event.data.amount} ${event.data.currency}`);
|
|
292
|
+
break;
|
|
293
|
+
}
|
|
294
|
+
} catch {
|
|
295
|
+
res.status(401).send("Invalid signature");
|
|
296
|
+
}
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
// Next.js App Router
|
|
300
|
+
export async function POST(request: Request) {
|
|
301
|
+
const body = await request.text();
|
|
302
|
+
const sig = request.headers.get("x-waffo-signature");
|
|
303
|
+
try {
|
|
304
|
+
const event = verifyWebhook(body, sig);
|
|
305
|
+
// handle event ...
|
|
306
|
+
return new Response("OK");
|
|
307
|
+
} catch {
|
|
308
|
+
return new Response("Invalid signature", { status: 401 });
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Options: specify environment, disable/customize replay protection
|
|
313
|
+
const event = verifyWebhook(body, sig, { environment: "prod" });
|
|
314
|
+
const event = verifyWebhook(body, sig, { toleranceMs: 0 }); // disable replay check
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
See [Webhook Guide](docs/webhook-guide.md) for all 10 event types, signature algorithm, and best practices.
|
|
318
|
+
|
|
319
|
+
## Error Handling
|
|
320
|
+
|
|
321
|
+
API errors throw `WaffoPancakeError` with the HTTP status code and a call-stack-ordered errors array.
|
|
322
|
+
|
|
323
|
+
```typescript
|
|
324
|
+
import { WaffoPancakeError } from "@waffo/pancake-ts";
|
|
325
|
+
|
|
326
|
+
try {
|
|
327
|
+
await client.stores.create({ name: "" });
|
|
328
|
+
} catch (err) {
|
|
329
|
+
if (err instanceof WaffoPancakeError) {
|
|
330
|
+
console.log(err.status); // 400
|
|
331
|
+
console.log(err.errors); // [{ message: "...", layer: "store" }, ...]
|
|
332
|
+
// errors[0] = deepest layer, errors[n] = outermost layer
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
## Exports
|
|
338
|
+
|
|
339
|
+
### Classes
|
|
340
|
+
|
|
341
|
+
| Export | Description |
|
|
342
|
+
|--------|-------------|
|
|
343
|
+
| `WaffoPancake` | SDK client with auto-signed requests |
|
|
344
|
+
| `WaffoPancakeError` | API error with status and call-stack errors |
|
|
345
|
+
|
|
346
|
+
### Functions
|
|
347
|
+
|
|
348
|
+
| Export | Description |
|
|
349
|
+
|--------|-------------|
|
|
350
|
+
| `verifyWebhook` | RSA-SHA256 webhook signature verification |
|
|
351
|
+
|
|
352
|
+
### Enums
|
|
353
|
+
|
|
354
|
+
Runtime-accessible values. Both `Enum.Value` and string literal syntax are supported.
|
|
355
|
+
|
|
356
|
+
| Export | Values |
|
|
357
|
+
|--------|--------|
|
|
358
|
+
| `Environment` | `Test`, `Prod` |
|
|
359
|
+
| `TaxCategory` | `DigitalGoods`, `SaaS`, `Software`, `Ebook`, `OnlineCourse`, `Consulting`, `ProfessionalService` |
|
|
360
|
+
| `BillingPeriod` | `Weekly`, `Monthly`, `Quarterly`, `Yearly` |
|
|
361
|
+
| `ProductVersionStatus` | `Active`, `Inactive` |
|
|
362
|
+
| `EntityStatus` | `Active`, `Inactive`, `Suspended` |
|
|
363
|
+
| `StoreRole` | `Owner`, `Admin`, `Member` |
|
|
364
|
+
| `OnetimeOrderStatus` | `Pending`, `Completed`, `Canceled` |
|
|
365
|
+
| `SubscriptionOrderStatus` | `Pending`, `Active`, `Canceling`, `Canceled`, `PastDue`, `Expired` |
|
|
366
|
+
| `PaymentStatus` | `Pending`, `Succeeded`, `Failed`, `Canceled` |
|
|
367
|
+
| `RefundTicketStatus` | `Pending`, `Approved`, `Rejected`, `Processing`, `Succeeded`, `Failed` |
|
|
368
|
+
| `RefundStatus` | `Succeeded`, `Failed` |
|
|
369
|
+
| `MediaType` | `Image`, `Video` |
|
|
370
|
+
| `CheckoutSessionProductType` | `Onetime`, `Subscription` |
|
|
371
|
+
| `ErrorLayer` | `Gateway`, `User`, `Store`, `Product`, `Order`, `GraphQL`, `Resource`, `Email` |
|
|
372
|
+
| `WebhookEventType` | `OrderCompleted`, `SubscriptionActivated`, `SubscriptionPaymentSucceeded`, `SubscriptionCanceling`, `SubscriptionUncanceled`, `SubscriptionUpdated`, `SubscriptionCanceled`, `SubscriptionPastDue`, `RefundSucceeded`, `RefundFailed` |
|
|
373
|
+
|
|
374
|
+
### Types
|
|
375
|
+
|
|
376
|
+
See [API Reference — Types](docs/api-reference.md#types) for the full list of 40+ exported interfaces.
|
|
377
|
+
|
|
378
|
+
## Development
|
|
379
|
+
|
|
380
|
+
```bash
|
|
381
|
+
npm run lint # ESLint 9 (TypeScript ESLint + import order + JSDoc)
|
|
382
|
+
npm run test # Vitest
|
|
383
|
+
npm run test:watch # Vitest in watch mode
|
|
384
|
+
npm run test:coverage # Vitest with v8 coverage
|
|
385
|
+
npm run build # TypeScript compilation to dist/
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
## Project Structure
|
|
389
|
+
|
|
390
|
+
```
|
|
391
|
+
src/
|
|
392
|
+
├── index.ts # Unified export entry
|
|
393
|
+
├── client.ts # WaffoPancake main class
|
|
394
|
+
├── http-client.ts # HTTP client (auto-signing + idempotency)
|
|
395
|
+
├── signing.ts # RSA-SHA256 request signing
|
|
396
|
+
├── errors.ts # WaffoPancakeError
|
|
397
|
+
├── webhooks.ts # Webhook verification (embedded keys)
|
|
398
|
+
├── types.ts # Type definitions & enums
|
|
399
|
+
├── __tests__/ # Test suite
|
|
400
|
+
└── resources/ # API resource classes
|
|
401
|
+
├── auth.ts
|
|
402
|
+
├── stores.ts
|
|
403
|
+
├── store-merchants.ts
|
|
404
|
+
├── onetime-products.ts
|
|
405
|
+
├── subscription-products.ts
|
|
406
|
+
├── subscription-product-groups.ts
|
|
407
|
+
├── orders.ts
|
|
408
|
+
├── checkout.ts
|
|
409
|
+
└── graphql.ts
|
|
410
|
+
docs/
|
|
411
|
+
├── api-reference.md # Complete API reference
|
|
412
|
+
├── graphql-guide.md # GraphQL usage guide
|
|
413
|
+
└── webhook-guide.md # Webhook verification guide
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
## License
|
|
417
|
+
|
|
418
|
+
MIT
|