@waffo/pancake-ts 0.1.3 → 0.1.5
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 +71 -0
- package/README.md +54 -9
- package/dist/index.cjs +136 -44
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +63 -23
- package/dist/index.d.ts +63 -23
- package/dist/index.js +137 -45
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,77 @@ 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` accepts optional `webhookPublicKey` to override built-in Waffo public keys for webhook signature verification. Useful for self-hosted deployments or custom key rotation.
|
|
12
|
+
- **`client.webhooks.verify()`** — New resource namespace on the client instance. Uses the configured `webhookPublicKey` automatically; supports per-call override via `options.publicKey`.
|
|
13
|
+
- **`VerifyWebhookOptions.publicKey`** — The standalone `verifyWebhook()` function also accepts a custom public key per call, taking precedence over built-in keys and the `environment` option.
|
|
14
|
+
- **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 when a custom public key is used.
|
|
15
|
+
|
|
16
|
+
## [0.1.6] - 2026-03-18
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
|
|
20
|
+
- **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.
|
|
21
|
+
- **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).
|
|
22
|
+
|
|
23
|
+
### Migration
|
|
24
|
+
|
|
25
|
+
Remove `taxIncluded` from all `PriceInfo` / `Prices` objects:
|
|
26
|
+
|
|
27
|
+
```diff
|
|
28
|
+
const { product } = await client.onetimeProducts.create({
|
|
29
|
+
storeId: "store_xxx",
|
|
30
|
+
name: "My Product",
|
|
31
|
+
prices: {
|
|
32
|
+
- USD: { amount: 2900, taxIncluded: false, taxCategory: "digital_goods" },
|
|
33
|
+
+ USD: { amount: 2900, taxCategory: "digital_goods" },
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Remove `isPublic` from `stores.update()` calls:
|
|
39
|
+
|
|
40
|
+
```diff
|
|
41
|
+
const { store } = await client.stores.update({
|
|
42
|
+
id: "store_xxx",
|
|
43
|
+
- isPublic: true,
|
|
44
|
+
name: "Updated Name",
|
|
45
|
+
});
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## [0.1.5] - 2026-03-18
|
|
49
|
+
|
|
50
|
+
### Changed
|
|
51
|
+
|
|
52
|
+
- **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.
|
|
53
|
+
|
|
54
|
+
### Migration
|
|
55
|
+
|
|
56
|
+
If your code references `checkoutColorTextSecondary`, remove it. The field is no longer accepted by the API and is silently ignored in stored data.
|
|
57
|
+
|
|
58
|
+
```diff
|
|
59
|
+
const theme: CheckoutThemeSettings = {
|
|
60
|
+
checkoutLogo: null,
|
|
61
|
+
checkoutColorPrimary: "#6366f1",
|
|
62
|
+
checkoutColorBackground: "#ffffff",
|
|
63
|
+
checkoutColorCard: "#f9fafb",
|
|
64
|
+
checkoutColorText: "#111827",
|
|
65
|
+
- checkoutColorTextSecondary: "#6b7280",
|
|
66
|
+
checkoutBorderRadius: "0.5rem",
|
|
67
|
+
};
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## [0.1.4] - 2026-03-16
|
|
71
|
+
|
|
72
|
+
### Changed
|
|
73
|
+
|
|
74
|
+
- **Types** — `CheckoutSettings` adds `defaultDarkMode: boolean` field to match API response (store create/update)
|
|
75
|
+
- **Types** — `CreateCheckoutSessionParams` adds optional `darkMode` field for dark mode override
|
|
76
|
+
- **Types** — `CreateCheckoutSessionParams.storeId` changed from optional to required to match API spec
|
|
77
|
+
|
|
7
78
|
## [0.1.3] - 2026-03-11
|
|
8
79
|
|
|
9
80
|
### 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` | No | Custom RSA public key for webhook verification (see [Public Key Formats](#public-key-formats) below). Overrides built-in keys when set. |
|
|
62
63
|
|
|
63
64
|
### Private Key Formats
|
|
64
65
|
|
|
@@ -82,6 +83,19 @@ 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
|
+
### 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
|
+
|
|
85
99
|
## Resources
|
|
86
100
|
|
|
87
101
|
| Namespace | Methods | Description |
|
|
@@ -95,6 +109,7 @@ new WaffoPancake({ merchantId: "m_1", privateKey: rawBase64String });
|
|
|
95
109
|
| `client.orders` | `cancelSubscription()` | Order management (pending→canceled, active→canceling) |
|
|
96
110
|
| `client.checkout` | `createSession()` | Create a checkout session with trial toggle, billing detail, and price snapshot |
|
|
97
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) |
|
|
98
113
|
|
|
99
114
|
See [API Reference](docs/api-reference.md) for complete parameter tables and return types.
|
|
100
115
|
|
|
@@ -260,9 +275,9 @@ const { product } = await client.onetimeProducts.create({
|
|
|
260
275
|
name: "E-Book: TypeScript Handbook",
|
|
261
276
|
description: "Complete TypeScript guide for developers",
|
|
262
277
|
prices: {
|
|
263
|
-
USD: { amount: 2900,
|
|
264
|
-
EUR: { amount: 2700,
|
|
265
|
-
JPY: { amount: 4500,
|
|
278
|
+
USD: { amount: 2900, taxCategory: TaxCategory.DigitalGoods },
|
|
279
|
+
EUR: { amount: 2700, taxCategory: TaxCategory.DigitalGoods },
|
|
280
|
+
JPY: { amount: 4500, taxCategory: TaxCategory.DigitalGoods },
|
|
266
281
|
},
|
|
267
282
|
media: [{ type: "image", url: "https://example.com/cover.jpg", alt: "Book cover" }],
|
|
268
283
|
metadata: { sku: "ebook-ts-001" },
|
|
@@ -272,7 +287,7 @@ const { product } = await client.onetimeProducts.create({
|
|
|
272
287
|
await client.onetimeProducts.update({
|
|
273
288
|
id: product.id,
|
|
274
289
|
name: "E-Book: TypeScript Handbook v2",
|
|
275
|
-
prices: { USD: { amount: 3900,
|
|
290
|
+
prices: { USD: { amount: 3900, taxCategory: "digital_goods" } },
|
|
276
291
|
});
|
|
277
292
|
|
|
278
293
|
// Publish test version → production
|
|
@@ -291,7 +306,7 @@ const { product } = await client.subscriptionProducts.create({
|
|
|
291
306
|
storeId: "store_xxx",
|
|
292
307
|
name: "Pro Plan",
|
|
293
308
|
billingPeriod: BillingPeriod.Monthly,
|
|
294
|
-
prices: { USD: { amount: 999,
|
|
309
|
+
prices: { USD: { amount: 999, taxCategory: TaxCategory.SaaS } },
|
|
295
310
|
description: "Unlimited access to all features",
|
|
296
311
|
});
|
|
297
312
|
|
|
@@ -348,6 +363,7 @@ const session = await client.checkout.createSession({
|
|
|
348
363
|
|
|
349
364
|
// Subscription with trial and billing detail
|
|
350
365
|
const subSession = await client.checkout.createSession({
|
|
366
|
+
storeId: "store_xxx",
|
|
351
367
|
productId: "prod_yyy",
|
|
352
368
|
productType: CheckoutSessionProductType.Subscription,
|
|
353
369
|
currency: "USD",
|
|
@@ -390,7 +406,9 @@ See [GraphQL Guide](docs/graphql-guide.md) for introspection, filters, paginatio
|
|
|
390
406
|
|
|
391
407
|
## Webhook Verification
|
|
392
408
|
|
|
393
|
-
|
|
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)
|
|
394
412
|
|
|
395
413
|
```typescript
|
|
396
414
|
import { verifyWebhook, WebhookEventType } from "@waffo/pancake-ts";
|
|
@@ -444,6 +462,32 @@ const event = verifyWebhook(body, sig, { environment: "prod" });
|
|
|
444
462
|
const event = verifyWebhook(body, sig, { toleranceMs: 0 }); // disable replay check
|
|
445
463
|
```
|
|
446
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
|
+
|
|
447
491
|
See [Webhook Guide](docs/webhook-guide.md) for all 10 event types, signature algorithm, and best practices.
|
|
448
492
|
|
|
449
493
|
## Error Handling
|
|
@@ -536,7 +580,8 @@ src/
|
|
|
536
580
|
├── subscription-product-groups.ts
|
|
537
581
|
├── orders.ts
|
|
538
582
|
├── checkout.ts
|
|
539
|
-
|
|
583
|
+
├── graphql.ts
|
|
584
|
+
└── webhooks.ts
|
|
540
585
|
docs/
|
|
541
586
|
├── api-reference.md # Complete API reference
|
|
542
587
|
├── 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;
|
|
@@ -651,27 +673,97 @@ function verifyWebhook(payload, signatureHeader, options) {
|
|
|
651
673
|
}
|
|
652
674
|
}
|
|
653
675
|
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)");
|
|
676
|
+
const customKey = options?.publicKey;
|
|
677
|
+
if (customKey) {
|
|
678
|
+
const normalizedKey = normalizePublicKey(customKey);
|
|
679
|
+
if (!rsaVerify(signatureInput, v1, normalizedKey)) {
|
|
680
|
+
throw new Error("Invalid webhook signature (custom key)");
|
|
662
681
|
}
|
|
663
682
|
} else {
|
|
664
|
-
const
|
|
665
|
-
if (
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
683
|
+
const env = options?.environment;
|
|
684
|
+
if (env === "test") {
|
|
685
|
+
if (!rsaVerify(signatureInput, v1, TEST_PUBLIC_KEY)) {
|
|
686
|
+
throw new Error("Invalid webhook signature (test key)");
|
|
687
|
+
}
|
|
688
|
+
} else if (env === "prod") {
|
|
689
|
+
if (!rsaVerify(signatureInput, v1, PROD_PUBLIC_KEY)) {
|
|
690
|
+
throw new Error("Invalid webhook signature (prod key)");
|
|
691
|
+
}
|
|
692
|
+
} else {
|
|
693
|
+
const prodValid = rsaVerify(signatureInput, v1, PROD_PUBLIC_KEY);
|
|
694
|
+
if (!prodValid) {
|
|
695
|
+
const testValid = rsaVerify(signatureInput, v1, TEST_PUBLIC_KEY);
|
|
696
|
+
if (!testValid) {
|
|
697
|
+
throw new Error("Invalid webhook signature (tried both prod and test keys)");
|
|
698
|
+
}
|
|
669
699
|
}
|
|
670
700
|
}
|
|
671
701
|
}
|
|
672
702
|
return JSON.parse(payload);
|
|
673
703
|
}
|
|
674
704
|
|
|
705
|
+
// src/resources/webhooks.ts
|
|
706
|
+
var WebhooksResource = class {
|
|
707
|
+
/** @param publicKey - Optional custom RSA public key (PEM or raw base64) */
|
|
708
|
+
constructor(publicKey) {
|
|
709
|
+
this.publicKey = publicKey;
|
|
710
|
+
}
|
|
711
|
+
/**
|
|
712
|
+
* Verify and parse an incoming webhook event.
|
|
713
|
+
*
|
|
714
|
+
* When the client was created with a `webhookPublicKey`, that key is used
|
|
715
|
+
* automatically. You can still override per-call via `options.publicKey`.
|
|
716
|
+
*
|
|
717
|
+
* @param payload - Raw request body string (must be unparsed)
|
|
718
|
+
* @param signatureHeader - Value of the `X-Waffo-Signature` header
|
|
719
|
+
* @param options - Verification options (optional)
|
|
720
|
+
* @returns Parsed webhook event
|
|
721
|
+
* @throws Error if signature is invalid, header is malformed, or timestamp is stale
|
|
722
|
+
*
|
|
723
|
+
* @example
|
|
724
|
+
* const event = client.webhooks.verify(rawBody, signatureHeader);
|
|
725
|
+
*
|
|
726
|
+
* @example
|
|
727
|
+
* // Override tolerance per call
|
|
728
|
+
* const event = client.webhooks.verify(rawBody, sig, { toleranceMs: 0 });
|
|
729
|
+
*/
|
|
730
|
+
verify(payload, signatureHeader, options) {
|
|
731
|
+
const mergedOptions = {
|
|
732
|
+
...options,
|
|
733
|
+
publicKey: options?.publicKey ?? this.publicKey
|
|
734
|
+
};
|
|
735
|
+
return verifyWebhook(payload, signatureHeader, mergedOptions);
|
|
736
|
+
}
|
|
737
|
+
};
|
|
738
|
+
|
|
739
|
+
// src/client.ts
|
|
740
|
+
var WaffoPancake = class {
|
|
741
|
+
http;
|
|
742
|
+
auth;
|
|
743
|
+
stores;
|
|
744
|
+
storeMerchants;
|
|
745
|
+
onetimeProducts;
|
|
746
|
+
subscriptionProducts;
|
|
747
|
+
subscriptionProductGroups;
|
|
748
|
+
orders;
|
|
749
|
+
checkout;
|
|
750
|
+
graphql;
|
|
751
|
+
webhooks;
|
|
752
|
+
constructor(config) {
|
|
753
|
+
this.http = new HttpClient(config);
|
|
754
|
+
this.auth = new AuthResource(this.http);
|
|
755
|
+
this.stores = new StoresResource(this.http);
|
|
756
|
+
this.storeMerchants = new StoreMerchantsResource(this.http);
|
|
757
|
+
this.onetimeProducts = new OnetimeProductsResource(this.http);
|
|
758
|
+
this.subscriptionProducts = new SubscriptionProductsResource(this.http);
|
|
759
|
+
this.subscriptionProductGroups = new SubscriptionProductGroupsResource(this.http);
|
|
760
|
+
this.orders = new OrdersResource(this.http);
|
|
761
|
+
this.checkout = new CheckoutResource(this.http);
|
|
762
|
+
this.graphql = new GraphQLResource(this.http);
|
|
763
|
+
this.webhooks = new WebhooksResource(config.webhookPublicKey);
|
|
764
|
+
}
|
|
765
|
+
};
|
|
766
|
+
|
|
675
767
|
// src/types.ts
|
|
676
768
|
var Environment = /* @__PURE__ */ ((Environment2) => {
|
|
677
769
|
Environment2["Test"] = "test";
|