@waffo/pancake-ts 0.1.3 → 0.1.7
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/CHANGELOG.md +74 -0
- package/README.md +91 -11
- package/dist/index.cjs +159 -44
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +117 -26
- package/dist/index.d.ts +117 -26
- package/dist/index.js +160 -45
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,80 @@ All notable changes to `@waffo/pancake-ts` will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), versioning follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## [0.1.7] - 2026-03-20
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **Custom webhook public key** — `WaffoPancakeConfig.webhookPublicKey` accepts `string` (shared) or `{ test?, prod? }` (per-environment) to override built-in keys.
|
|
12
|
+
- **Multi-level key resolution** — Webhook public key is resolved per environment: `options.publicKey` (per-call) → config key → `WAFFO_WEBHOOK_{TEST|PROD}_PUBLIC_KEY` env var → `WAFFO_WEBHOOK_PUBLIC_KEY` env var → built-in hardcoded key.
|
|
13
|
+
- **`client.webhooks.verify()`** — New resource namespace on the client instance. Injects config-level keys into the resolution chain automatically; supports per-call override via `options.publicKey`.
|
|
14
|
+
- **`VerifyWebhookOptions.publicKey`** — Per-call override for the standalone `verifyWebhook()` function (highest priority, skips all resolution).
|
|
15
|
+
- **`VerifyWebhookOptions.publicKeys`** — Config-level key(s) for the resolution chain (typically injected by `client.webhooks.verify()`).
|
|
16
|
+
- **`WebhookPublicKeys` type** — `string | { test?: string; prod?: string }`, exported from the package.
|
|
17
|
+
- **Public key normalization** — `normalizePublicKey()` handles the same flexible input formats as `normalizePrivateKey`: literal `\n` from environment variables, Windows `\r\n` line endings, raw base64 without PEM headers, single-line base64, and PKCS#1 (`BEGIN RSA PUBLIC KEY`) format. Applied automatically at every level of the resolution chain.
|
|
18
|
+
|
|
19
|
+
## [0.1.6] - 2026-03-18
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
|
|
23
|
+
- **Types (BREAKING)** — `PriceInfo` removes `taxIncluded` field. Prices now only require `amount` and `taxCategory`. The system internally defaults to tax-exclusive pricing; `taxIncluded` may be re-introduced in a future version.
|
|
24
|
+
- **Types (BREAKING)** — `Store` removes `isPublic` field from response type. `UpdateStoreParams` removes `isPublic` field from input type. Store visibility is no longer configurable (defaults to private).
|
|
25
|
+
|
|
26
|
+
### Migration
|
|
27
|
+
|
|
28
|
+
Remove `taxIncluded` from all `PriceInfo` / `Prices` objects:
|
|
29
|
+
|
|
30
|
+
```diff
|
|
31
|
+
const { product } = await client.onetimeProducts.create({
|
|
32
|
+
storeId: "store_xxx",
|
|
33
|
+
name: "My Product",
|
|
34
|
+
prices: {
|
|
35
|
+
- USD: { amount: 2900, taxIncluded: false, taxCategory: "digital_goods" },
|
|
36
|
+
+ USD: { amount: 2900, taxCategory: "digital_goods" },
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Remove `isPublic` from `stores.update()` calls:
|
|
42
|
+
|
|
43
|
+
```diff
|
|
44
|
+
const { store } = await client.stores.update({
|
|
45
|
+
id: "store_xxx",
|
|
46
|
+
- isPublic: true,
|
|
47
|
+
name: "Updated Name",
|
|
48
|
+
});
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## [0.1.5] - 2026-03-18
|
|
52
|
+
|
|
53
|
+
### Changed
|
|
54
|
+
|
|
55
|
+
- **Types** — `CheckoutThemeSettings` removes `checkoutColorTextSecondary` field (7→6 fields). The secondary text color is now derived server-side from `checkoutColorText` and `checkoutColorCard` via color mixing. Merchants only need to configure 5 base color/radius fields; the remaining 7 PSP variables are computed automatically.
|
|
56
|
+
|
|
57
|
+
### Migration
|
|
58
|
+
|
|
59
|
+
If your code references `checkoutColorTextSecondary`, remove it. The field is no longer accepted by the API and is silently ignored in stored data.
|
|
60
|
+
|
|
61
|
+
```diff
|
|
62
|
+
const theme: CheckoutThemeSettings = {
|
|
63
|
+
checkoutLogo: null,
|
|
64
|
+
checkoutColorPrimary: "#6366f1",
|
|
65
|
+
checkoutColorBackground: "#ffffff",
|
|
66
|
+
checkoutColorCard: "#f9fafb",
|
|
67
|
+
checkoutColorText: "#111827",
|
|
68
|
+
- checkoutColorTextSecondary: "#6b7280",
|
|
69
|
+
checkoutBorderRadius: "0.5rem",
|
|
70
|
+
};
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## [0.1.4] - 2026-03-16
|
|
74
|
+
|
|
75
|
+
### Changed
|
|
76
|
+
|
|
77
|
+
- **Types** — `CheckoutSettings` adds `defaultDarkMode: boolean` field to match API response (store create/update)
|
|
78
|
+
- **Types** — `CreateCheckoutSessionParams` adds optional `darkMode` field for dark mode override
|
|
79
|
+
- **Types** — `CreateCheckoutSessionParams.storeId` changed from optional to required to match API spec
|
|
80
|
+
|
|
7
81
|
## [0.1.3] - 2026-03-11
|
|
8
82
|
|
|
9
83
|
### Added
|
package/README.md
CHANGED
|
@@ -31,8 +31,8 @@ const { product } = await client.onetimeProducts.create({
|
|
|
31
31
|
storeId: store.id,
|
|
32
32
|
name: "E-Book: TypeScript Handbook",
|
|
33
33
|
prices: {
|
|
34
|
-
USD: { amount: 2900,
|
|
35
|
-
EUR: { amount: 2700,
|
|
34
|
+
USD: { amount: 2900, taxCategory: "digital_goods" },
|
|
35
|
+
EUR: { amount: 2700, taxCategory: "digital_goods" },
|
|
36
36
|
},
|
|
37
37
|
});
|
|
38
38
|
|
|
@@ -59,6 +59,7 @@ const result = await client.graphql.query<{ stores: Array<{ id: string; name: st
|
|
|
59
59
|
| `privateKey` | `string` | Yes | RSA private key (see [Private Key Formats](#private-key-formats) below) |
|
|
60
60
|
| `baseUrl` | `string` | No | API base URL (default: `https://waffo-pancake-auth-service.vercel.app`) |
|
|
61
61
|
| `fetch` | `typeof fetch` | No | Custom fetch implementation |
|
|
62
|
+
| `webhookPublicKey` | `string \| { test?, prod? }` | No | Custom webhook public key(s) (see [Webhook Public Key Resolution](#webhook-public-key-resolution) below) |
|
|
62
63
|
|
|
63
64
|
### Private Key Formats
|
|
64
65
|
|
|
@@ -82,6 +83,53 @@ new WaffoPancake({ merchantId: "m_1", privateKey: fs.readFileSync("key.pem", "ut
|
|
|
82
83
|
new WaffoPancake({ merchantId: "m_1", privateKey: rawBase64String }); // raw base64
|
|
83
84
|
```
|
|
84
85
|
|
|
86
|
+
### Webhook Public Key Resolution
|
|
87
|
+
|
|
88
|
+
The SDK resolves the webhook verification public key per environment using a multi-level fallback chain:
|
|
89
|
+
|
|
90
|
+
| Priority | Source | Description |
|
|
91
|
+
|----------|--------|-------------|
|
|
92
|
+
| 1 | `options.publicKey` | Per-call override (highest priority, skips all resolution) |
|
|
93
|
+
| 2 | `config.webhookPublicKey[env]` | Config object per-environment key |
|
|
94
|
+
| 3 | `config.webhookPublicKey` (string) | Config shared key (both environments) |
|
|
95
|
+
| 4 | `WAFFO_WEBHOOK_TEST_PUBLIC_KEY` / `WAFFO_WEBHOOK_PROD_PUBLIC_KEY` | Environment variable per-environment |
|
|
96
|
+
| 5 | `WAFFO_WEBHOOK_PUBLIC_KEY` | Environment variable shared |
|
|
97
|
+
| 6 | Built-in hardcoded key | SDK-embedded Waffo public key (default) |
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
// Shared key for both environments
|
|
101
|
+
new WaffoPancake({ merchantId: "m_1", privateKey: "...", webhookPublicKey: "MIIBIjAN..." });
|
|
102
|
+
|
|
103
|
+
// Per-environment keys
|
|
104
|
+
new WaffoPancake({
|
|
105
|
+
merchantId: "m_1",
|
|
106
|
+
privateKey: "...",
|
|
107
|
+
webhookPublicKey: {
|
|
108
|
+
test: process.env.WAFFO_TEST_PUB_KEY!,
|
|
109
|
+
prod: process.env.WAFFO_PROD_PUB_KEY!,
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
// Or rely on environment variables (no config needed)
|
|
114
|
+
// export WAFFO_WEBHOOK_TEST_PUBLIC_KEY="-----BEGIN PUBLIC KEY-----\n..."
|
|
115
|
+
// export WAFFO_WEBHOOK_PROD_PUBLIC_KEY="-----BEGIN PUBLIC KEY-----\n..."
|
|
116
|
+
new WaffoPancake({ merchantId: "m_1", privateKey: "..." });
|
|
117
|
+
// => SDK auto-reads from env vars, falls back to built-in keys
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Public Key Formats
|
|
121
|
+
|
|
122
|
+
All public key inputs (config, env vars, per-call) accept the same flexible formats as private keys:
|
|
123
|
+
|
|
124
|
+
| Format | Example | Notes |
|
|
125
|
+
|--------|---------|-------|
|
|
126
|
+
| Standard SPKI PEM | `-----BEGIN PUBLIC KEY-----\n...` | Recommended |
|
|
127
|
+
| PKCS#1 PEM | `-----BEGIN RSA PUBLIC KEY-----\n...` | Also accepted |
|
|
128
|
+
| Literal `\n` (env vars) | `"-----BEGIN PUBLIC KEY-----\\nMIIB..."` | Common when stored in `.env` or CI secrets |
|
|
129
|
+
| Windows line endings | `\r\n` | Converted to `\n` |
|
|
130
|
+
| Raw base64 (no headers) | `MIIBIjANBgkqhki...` | Wrapped with SPKI headers automatically |
|
|
131
|
+
| Single-line base64 with headers | Header + all base64 on one line + footer | Re-wrapped to 64-char lines |
|
|
132
|
+
|
|
85
133
|
## Resources
|
|
86
134
|
|
|
87
135
|
| Namespace | Methods | Description |
|
|
@@ -95,6 +143,7 @@ new WaffoPancake({ merchantId: "m_1", privateKey: rawBase64String });
|
|
|
95
143
|
| `client.orders` | `cancelSubscription()` | Order management (pending→canceled, active→canceling) |
|
|
96
144
|
| `client.checkout` | `createSession()` | Create a checkout session with trial toggle, billing detail, and price snapshot |
|
|
97
145
|
| `client.graphql` | `query<T>()` | Typed GraphQL queries (Query only, no Mutations) |
|
|
146
|
+
| `client.webhooks` | `verify<T>()` | Webhook signature verification (uses configured `webhookPublicKey` or built-in keys) |
|
|
98
147
|
|
|
99
148
|
See [API Reference](docs/api-reference.md) for complete parameter tables and return types.
|
|
100
149
|
|
|
@@ -260,9 +309,9 @@ const { product } = await client.onetimeProducts.create({
|
|
|
260
309
|
name: "E-Book: TypeScript Handbook",
|
|
261
310
|
description: "Complete TypeScript guide for developers",
|
|
262
311
|
prices: {
|
|
263
|
-
USD: { amount: 2900,
|
|
264
|
-
EUR: { amount: 2700,
|
|
265
|
-
JPY: { amount: 4500,
|
|
312
|
+
USD: { amount: 2900, taxCategory: TaxCategory.DigitalGoods },
|
|
313
|
+
EUR: { amount: 2700, taxCategory: TaxCategory.DigitalGoods },
|
|
314
|
+
JPY: { amount: 4500, taxCategory: TaxCategory.DigitalGoods },
|
|
266
315
|
},
|
|
267
316
|
media: [{ type: "image", url: "https://example.com/cover.jpg", alt: "Book cover" }],
|
|
268
317
|
metadata: { sku: "ebook-ts-001" },
|
|
@@ -272,7 +321,7 @@ const { product } = await client.onetimeProducts.create({
|
|
|
272
321
|
await client.onetimeProducts.update({
|
|
273
322
|
id: product.id,
|
|
274
323
|
name: "E-Book: TypeScript Handbook v2",
|
|
275
|
-
prices: { USD: { amount: 3900,
|
|
324
|
+
prices: { USD: { amount: 3900, taxCategory: "digital_goods" } },
|
|
276
325
|
});
|
|
277
326
|
|
|
278
327
|
// Publish test version → production
|
|
@@ -291,7 +340,7 @@ const { product } = await client.subscriptionProducts.create({
|
|
|
291
340
|
storeId: "store_xxx",
|
|
292
341
|
name: "Pro Plan",
|
|
293
342
|
billingPeriod: BillingPeriod.Monthly,
|
|
294
|
-
prices: { USD: { amount: 999,
|
|
343
|
+
prices: { USD: { amount: 999, taxCategory: TaxCategory.SaaS } },
|
|
295
344
|
description: "Unlimited access to all features",
|
|
296
345
|
});
|
|
297
346
|
|
|
@@ -348,6 +397,7 @@ const session = await client.checkout.createSession({
|
|
|
348
397
|
|
|
349
398
|
// Subscription with trial and billing detail
|
|
350
399
|
const subSession = await client.checkout.createSession({
|
|
400
|
+
storeId: "store_xxx",
|
|
351
401
|
productId: "prod_yyy",
|
|
352
402
|
productType: CheckoutSessionProductType.Subscription,
|
|
353
403
|
currency: "USD",
|
|
@@ -390,7 +440,9 @@ See [GraphQL Guide](docs/graphql-guide.md) for introspection, filters, paginatio
|
|
|
390
440
|
|
|
391
441
|
## Webhook Verification
|
|
392
442
|
|
|
393
|
-
|
|
443
|
+
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`.
|
|
444
|
+
|
|
445
|
+
### Option A — Standalone Function (built-in keys)
|
|
394
446
|
|
|
395
447
|
```typescript
|
|
396
448
|
import { verifyWebhook, WebhookEventType } from "@waffo/pancake-ts";
|
|
@@ -444,7 +496,34 @@ const event = verifyWebhook(body, sig, { environment: "prod" });
|
|
|
444
496
|
const event = verifyWebhook(body, sig, { toleranceMs: 0 }); // disable replay check
|
|
445
497
|
```
|
|
446
498
|
|
|
447
|
-
|
|
499
|
+
### Option B — Client Instance Method (multi-level key resolution)
|
|
500
|
+
|
|
501
|
+
`client.webhooks.verify()` uses the [multi-level fallback chain](#webhook-public-key-resolution) automatically: config keys → env vars → built-in keys.
|
|
502
|
+
|
|
503
|
+
```typescript
|
|
504
|
+
// Per-environment keys via config
|
|
505
|
+
const client = new WaffoPancake({
|
|
506
|
+
merchantId: process.env.WAFFO_MERCHANT_ID!,
|
|
507
|
+
privateKey: process.env.WAFFO_PRIVATE_KEY!,
|
|
508
|
+
webhookPublicKey: {
|
|
509
|
+
test: process.env.WAFFO_TEST_PUB_KEY!,
|
|
510
|
+
prod: process.env.WAFFO_PROD_PUB_KEY!,
|
|
511
|
+
},
|
|
512
|
+
});
|
|
513
|
+
const event = client.webhooks.verify(rawBody, sig, { environment: "prod" });
|
|
514
|
+
|
|
515
|
+
// Or rely on env vars (WAFFO_WEBHOOK_TEST_PUBLIC_KEY / WAFFO_WEBHOOK_PROD_PUBLIC_KEY)
|
|
516
|
+
const client2 = new WaffoPancake({
|
|
517
|
+
merchantId: process.env.WAFFO_MERCHANT_ID!,
|
|
518
|
+
privateKey: process.env.WAFFO_PRIVATE_KEY!,
|
|
519
|
+
});
|
|
520
|
+
const event2 = client2.webhooks.verify(rawBody, sig); // auto-detect environment
|
|
521
|
+
|
|
522
|
+
// Per-call override (highest priority, skips all resolution)
|
|
523
|
+
const event3 = client.webhooks.verify(rawBody, sig, { publicKey: oneOffKey });
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
See [Webhook Guide](docs/webhook-guide.md) for event types, signature algorithm, public key resolution, and best practices.
|
|
448
527
|
|
|
449
528
|
## Error Handling
|
|
450
529
|
|
|
@@ -503,7 +582,7 @@ Runtime-accessible values. Both `Enum.Value` and string literal syntax are suppo
|
|
|
503
582
|
|
|
504
583
|
### Types
|
|
505
584
|
|
|
506
|
-
See [API Reference — Types](docs/api-reference.md#types) for the full list
|
|
585
|
+
Key types: `WaffoPancakeConfig`, `WebhookPublicKeys`, `VerifyWebhookOptions`, `WebhookEvent<T>`, `Store`, `OnetimeProductDetail`, `SubscriptionProductDetail`, `CheckoutSessionResult`, `GraphQLResponse<T>`, and 30+ more. See [API Reference — Types](docs/api-reference.md#types) for the full list.
|
|
507
586
|
|
|
508
587
|
## Development
|
|
509
588
|
|
|
@@ -536,7 +615,8 @@ src/
|
|
|
536
615
|
├── subscription-product-groups.ts
|
|
537
616
|
├── orders.ts
|
|
538
617
|
├── checkout.ts
|
|
539
|
-
|
|
618
|
+
├── graphql.ts
|
|
619
|
+
└── webhooks.ts
|
|
540
620
|
docs/
|
|
541
621
|
├── api-reference.md # Complete API reference
|
|
542
622
|
├── graphql-guide.md # GraphQL usage guide
|
package/dist/index.cjs
CHANGED
|
@@ -63,6 +63,10 @@ var PKCS8_HEADER = "-----BEGIN PRIVATE KEY-----";
|
|
|
63
63
|
var PKCS8_FOOTER = "-----END PRIVATE KEY-----";
|
|
64
64
|
var PKCS1_HEADER = "-----BEGIN RSA PRIVATE KEY-----";
|
|
65
65
|
var PKCS1_FOOTER = "-----END RSA PRIVATE KEY-----";
|
|
66
|
+
var SPKI_HEADER = "-----BEGIN PUBLIC KEY-----";
|
|
67
|
+
var SPKI_FOOTER = "-----END PUBLIC KEY-----";
|
|
68
|
+
var PKCS1_PUB_HEADER = "-----BEGIN RSA PUBLIC KEY-----";
|
|
69
|
+
var PKCS1_PUB_FOOTER = "-----END RSA PUBLIC KEY-----";
|
|
66
70
|
function normalizePrivateKey(raw) {
|
|
67
71
|
if (!raw || !raw.trim()) {
|
|
68
72
|
throw new Error(
|
|
@@ -108,6 +112,51 @@ ${PKCS8_FOOTER}`;
|
|
|
108
112
|
}
|
|
109
113
|
return pem;
|
|
110
114
|
}
|
|
115
|
+
function normalizePublicKey(raw) {
|
|
116
|
+
if (!raw || !raw.trim()) {
|
|
117
|
+
throw new Error(
|
|
118
|
+
"Public key is empty. Provide an RSA public key in PEM format."
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
let pem = raw.replace(/\\n/g, "\n").replace(/\r\n/g, "\n");
|
|
122
|
+
pem = pem.trim();
|
|
123
|
+
const hasSpkiHeader = pem.includes(SPKI_HEADER);
|
|
124
|
+
const hasPkcs1PubHeader = pem.includes(PKCS1_PUB_HEADER);
|
|
125
|
+
const hasHeader = hasSpkiHeader || hasPkcs1PubHeader;
|
|
126
|
+
if (hasHeader) {
|
|
127
|
+
const base64 = pem.replace(/-----BEGIN (?:RSA )?PUBLIC KEY-----/g, "").replace(/-----END (?:RSA )?PUBLIC KEY-----/g, "").replace(/\s+/g, "");
|
|
128
|
+
if (!base64) {
|
|
129
|
+
throw new Error(
|
|
130
|
+
"Public key contains PEM headers but no key data. Check the key content."
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
const header = hasPkcs1PubHeader ? PKCS1_PUB_HEADER : SPKI_HEADER;
|
|
134
|
+
const footer = hasPkcs1PubHeader ? PKCS1_PUB_FOOTER : SPKI_FOOTER;
|
|
135
|
+
const wrapped = base64.match(/.{1,64}/g).join("\n");
|
|
136
|
+
pem = `${header}
|
|
137
|
+
${wrapped}
|
|
138
|
+
${footer}`;
|
|
139
|
+
} else {
|
|
140
|
+
const base64 = pem.replace(/\s+/g, "");
|
|
141
|
+
if (!/^[A-Za-z0-9+/]+=*$/.test(base64)) {
|
|
142
|
+
throw new Error(
|
|
143
|
+
"Public key is not valid PEM or base64. Expected an RSA public key in PEM format or raw base64."
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
const wrapped = base64.match(/.{1,64}/g).join("\n");
|
|
147
|
+
pem = `${SPKI_HEADER}
|
|
148
|
+
${wrapped}
|
|
149
|
+
${SPKI_FOOTER}`;
|
|
150
|
+
}
|
|
151
|
+
try {
|
|
152
|
+
(0, import_node_crypto.createPublicKey)(pem);
|
|
153
|
+
} catch {
|
|
154
|
+
throw new Error(
|
|
155
|
+
"Public key could not be parsed. Ensure it is a valid RSA public key in SPKI or PKCS#1 (PEM) format."
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
return pem;
|
|
159
|
+
}
|
|
111
160
|
function signRequest(method, path, timestamp, body, privateKey) {
|
|
112
161
|
const bodyHash = (0, import_node_crypto.createHash)("sha256").update(body).digest("base64");
|
|
113
162
|
const canonicalRequest = `${method}
|
|
@@ -259,7 +308,7 @@ var OnetimeProductsResource = class {
|
|
|
259
308
|
* const { product } = await client.onetimeProducts.create({
|
|
260
309
|
* storeId: "store_xxx",
|
|
261
310
|
* name: "E-Book",
|
|
262
|
-
* prices: { USD: { amount: 2900,
|
|
311
|
+
* prices: { USD: { amount: 2900, taxCategory: "digital_goods" } },
|
|
263
312
|
* });
|
|
264
313
|
*/
|
|
265
314
|
async create(params) {
|
|
@@ -275,7 +324,7 @@ var OnetimeProductsResource = class {
|
|
|
275
324
|
* const { product } = await client.onetimeProducts.update({
|
|
276
325
|
* id: "prod_xxx",
|
|
277
326
|
* name: "E-Book v2",
|
|
278
|
-
* prices: { USD: { amount: 3900,
|
|
327
|
+
* prices: { USD: { amount: 3900, taxCategory: "digital_goods" } },
|
|
279
328
|
* });
|
|
280
329
|
*/
|
|
281
330
|
async update(params) {
|
|
@@ -416,7 +465,6 @@ var StoresResource = class {
|
|
|
416
465
|
* const { store } = await client.stores.update({
|
|
417
466
|
* id: "store_xxx",
|
|
418
467
|
* name: "Updated Name",
|
|
419
|
-
* supportEmail: "help@example.com",
|
|
420
468
|
* });
|
|
421
469
|
*/
|
|
422
470
|
async update(params) {
|
|
@@ -515,7 +563,7 @@ var SubscriptionProductsResource = class {
|
|
|
515
563
|
* storeId: "store_xxx",
|
|
516
564
|
* name: "Pro Plan",
|
|
517
565
|
* billingPeriod: "monthly",
|
|
518
|
-
* prices: { USD: { amount: 999,
|
|
566
|
+
* prices: { USD: { amount: 999, taxCategory: "saas" } },
|
|
519
567
|
* });
|
|
520
568
|
*/
|
|
521
569
|
async create(params) {
|
|
@@ -532,7 +580,7 @@ var SubscriptionProductsResource = class {
|
|
|
532
580
|
* id: "prod_xxx",
|
|
533
581
|
* name: "Pro Plan v2",
|
|
534
582
|
* billingPeriod: "monthly",
|
|
535
|
-
* prices: { USD: { amount: 1499,
|
|
583
|
+
* prices: { USD: { amount: 1499, taxCategory: "saas" } },
|
|
536
584
|
* });
|
|
537
585
|
*/
|
|
538
586
|
async update(params) {
|
|
@@ -567,32 +615,6 @@ var SubscriptionProductsResource = class {
|
|
|
567
615
|
}
|
|
568
616
|
};
|
|
569
617
|
|
|
570
|
-
// src/client.ts
|
|
571
|
-
var WaffoPancake = class {
|
|
572
|
-
http;
|
|
573
|
-
auth;
|
|
574
|
-
stores;
|
|
575
|
-
storeMerchants;
|
|
576
|
-
onetimeProducts;
|
|
577
|
-
subscriptionProducts;
|
|
578
|
-
subscriptionProductGroups;
|
|
579
|
-
orders;
|
|
580
|
-
checkout;
|
|
581
|
-
graphql;
|
|
582
|
-
constructor(config) {
|
|
583
|
-
this.http = new HttpClient(config);
|
|
584
|
-
this.auth = new AuthResource(this.http);
|
|
585
|
-
this.stores = new StoresResource(this.http);
|
|
586
|
-
this.storeMerchants = new StoreMerchantsResource(this.http);
|
|
587
|
-
this.onetimeProducts = new OnetimeProductsResource(this.http);
|
|
588
|
-
this.subscriptionProducts = new SubscriptionProductsResource(this.http);
|
|
589
|
-
this.subscriptionProductGroups = new SubscriptionProductGroupsResource(this.http);
|
|
590
|
-
this.orders = new OrdersResource(this.http);
|
|
591
|
-
this.checkout = new CheckoutResource(this.http);
|
|
592
|
-
this.graphql = new GraphQLResource(this.http);
|
|
593
|
-
}
|
|
594
|
-
};
|
|
595
|
-
|
|
596
618
|
// src/webhooks.ts
|
|
597
619
|
var import_node_crypto3 = require("crypto");
|
|
598
620
|
var DEFAULT_TOLERANCE_MS = 5 * 60 * 1e3;
|
|
@@ -632,6 +654,23 @@ function rsaVerify(signatureInput, v1, publicKey) {
|
|
|
632
654
|
verifier.update(signatureInput);
|
|
633
655
|
return verifier.verify(publicKey, v1, "base64");
|
|
634
656
|
}
|
|
657
|
+
function resolveKeyForEnv(env, configKeys) {
|
|
658
|
+
if (typeof configKeys === "string") {
|
|
659
|
+
return normalizePublicKey(configKeys);
|
|
660
|
+
}
|
|
661
|
+
if (configKeys?.[env]) {
|
|
662
|
+
return normalizePublicKey(configKeys[env]);
|
|
663
|
+
}
|
|
664
|
+
const envSpecific = env === "test" ? process.env.WAFFO_WEBHOOK_TEST_PUBLIC_KEY : process.env.WAFFO_WEBHOOK_PROD_PUBLIC_KEY;
|
|
665
|
+
if (envSpecific) {
|
|
666
|
+
return normalizePublicKey(envSpecific);
|
|
667
|
+
}
|
|
668
|
+
const generic = process.env.WAFFO_WEBHOOK_PUBLIC_KEY;
|
|
669
|
+
if (generic) {
|
|
670
|
+
return normalizePublicKey(generic);
|
|
671
|
+
}
|
|
672
|
+
return env === "test" ? TEST_PUBLIC_KEY : PROD_PUBLIC_KEY;
|
|
673
|
+
}
|
|
635
674
|
function verifyWebhook(payload, signatureHeader, options) {
|
|
636
675
|
if (!signatureHeader) {
|
|
637
676
|
throw new Error("Missing X-Waffo-Signature header");
|
|
@@ -651,27 +690,103 @@ function verifyWebhook(payload, signatureHeader, options) {
|
|
|
651
690
|
}
|
|
652
691
|
}
|
|
653
692
|
const signatureInput = `${t}.${payload}`;
|
|
654
|
-
const
|
|
655
|
-
if (
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
} else if (env === "prod") {
|
|
660
|
-
if (!rsaVerify(signatureInput, v1, PROD_PUBLIC_KEY)) {
|
|
661
|
-
throw new Error("Invalid webhook signature (prod key)");
|
|
693
|
+
const directKey = options?.publicKey;
|
|
694
|
+
if (directKey) {
|
|
695
|
+
const normalizedKey = normalizePublicKey(directKey);
|
|
696
|
+
if (!rsaVerify(signatureInput, v1, normalizedKey)) {
|
|
697
|
+
throw new Error("Invalid webhook signature (custom key)");
|
|
662
698
|
}
|
|
663
699
|
} else {
|
|
664
|
-
const
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
700
|
+
const configKeys = options?.publicKeys;
|
|
701
|
+
const env = options?.environment;
|
|
702
|
+
if (env === "test" || env === "prod") {
|
|
703
|
+
const key = resolveKeyForEnv(env, configKeys);
|
|
704
|
+
if (!rsaVerify(signatureInput, v1, key)) {
|
|
705
|
+
throw new Error(`Invalid webhook signature (${env} key)`);
|
|
706
|
+
}
|
|
707
|
+
} else {
|
|
708
|
+
const prodKey = resolveKeyForEnv("prod", configKeys);
|
|
709
|
+
if (!rsaVerify(signatureInput, v1, prodKey)) {
|
|
710
|
+
const testKey = resolveKeyForEnv("test", configKeys);
|
|
711
|
+
if (!rsaVerify(signatureInput, v1, testKey)) {
|
|
712
|
+
throw new Error("Invalid webhook signature (tried both prod and test keys)");
|
|
713
|
+
}
|
|
669
714
|
}
|
|
670
715
|
}
|
|
671
716
|
}
|
|
672
717
|
return JSON.parse(payload);
|
|
673
718
|
}
|
|
674
719
|
|
|
720
|
+
// src/resources/webhooks.ts
|
|
721
|
+
var WebhooksResource = class {
|
|
722
|
+
/** @param publicKeys - Optional config-level public key(s) from WaffoPancakeConfig */
|
|
723
|
+
constructor(publicKeys) {
|
|
724
|
+
this.publicKeys = publicKeys;
|
|
725
|
+
}
|
|
726
|
+
/**
|
|
727
|
+
* Verify and parse an incoming webhook event.
|
|
728
|
+
*
|
|
729
|
+
* Key resolution order:
|
|
730
|
+
* 1. `options.publicKey` — per-call override (highest priority)
|
|
731
|
+
* 2. `config.webhookPublicKey[env]` or `config.webhookPublicKey` (string)
|
|
732
|
+
* 3. `WAFFO_WEBHOOK_{TEST|PROD}_PUBLIC_KEY` environment variable
|
|
733
|
+
* 4. `WAFFO_WEBHOOK_PUBLIC_KEY` environment variable
|
|
734
|
+
* 5. Built-in hardcoded key
|
|
735
|
+
*
|
|
736
|
+
* @param payload - Raw request body string (must be unparsed)
|
|
737
|
+
* @param signatureHeader - Value of the `X-Waffo-Signature` header
|
|
738
|
+
* @param options - Verification options (optional)
|
|
739
|
+
* @returns Parsed webhook event
|
|
740
|
+
* @throws Error if signature is invalid, header is malformed, or timestamp is stale
|
|
741
|
+
*
|
|
742
|
+
* @example
|
|
743
|
+
* const event = client.webhooks.verify(rawBody, signatureHeader);
|
|
744
|
+
*
|
|
745
|
+
* @example
|
|
746
|
+
* // Specify environment
|
|
747
|
+
* const event = client.webhooks.verify(rawBody, sig, { environment: "test" });
|
|
748
|
+
*
|
|
749
|
+
* @example
|
|
750
|
+
* // Per-call key override
|
|
751
|
+
* const event = client.webhooks.verify(rawBody, sig, { publicKey: oneOffKey });
|
|
752
|
+
*/
|
|
753
|
+
verify(payload, signatureHeader, options) {
|
|
754
|
+
const mergedOptions = {
|
|
755
|
+
...options,
|
|
756
|
+
publicKeys: options?.publicKeys ?? this.publicKeys
|
|
757
|
+
};
|
|
758
|
+
return verifyWebhook(payload, signatureHeader, mergedOptions);
|
|
759
|
+
}
|
|
760
|
+
};
|
|
761
|
+
|
|
762
|
+
// src/client.ts
|
|
763
|
+
var WaffoPancake = class {
|
|
764
|
+
http;
|
|
765
|
+
auth;
|
|
766
|
+
stores;
|
|
767
|
+
storeMerchants;
|
|
768
|
+
onetimeProducts;
|
|
769
|
+
subscriptionProducts;
|
|
770
|
+
subscriptionProductGroups;
|
|
771
|
+
orders;
|
|
772
|
+
checkout;
|
|
773
|
+
graphql;
|
|
774
|
+
webhooks;
|
|
775
|
+
constructor(config) {
|
|
776
|
+
this.http = new HttpClient(config);
|
|
777
|
+
this.auth = new AuthResource(this.http);
|
|
778
|
+
this.stores = new StoresResource(this.http);
|
|
779
|
+
this.storeMerchants = new StoreMerchantsResource(this.http);
|
|
780
|
+
this.onetimeProducts = new OnetimeProductsResource(this.http);
|
|
781
|
+
this.subscriptionProducts = new SubscriptionProductsResource(this.http);
|
|
782
|
+
this.subscriptionProductGroups = new SubscriptionProductGroupsResource(this.http);
|
|
783
|
+
this.orders = new OrdersResource(this.http);
|
|
784
|
+
this.checkout = new CheckoutResource(this.http);
|
|
785
|
+
this.graphql = new GraphQLResource(this.http);
|
|
786
|
+
this.webhooks = new WebhooksResource(config.webhookPublicKey);
|
|
787
|
+
}
|
|
788
|
+
};
|
|
789
|
+
|
|
675
790
|
// src/types.ts
|
|
676
791
|
var Environment = /* @__PURE__ */ ((Environment2) => {
|
|
677
792
|
Environment2["Test"] = "test";
|