@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 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