@waffo/pancake-ts 0.1.5 → 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 CHANGED
@@ -8,10 +8,13 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), version
8
8
 
9
9
  ### Added
10
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.
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.
15
18
 
16
19
  ## [0.1.6] - 2026-03-18
17
20
 
package/README.md CHANGED
@@ -59,7 +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
+ | `webhookPublicKey` | `string \| { test?, prod? }` | No | Custom webhook public key(s) (see [Webhook Public Key Resolution](#webhook-public-key-resolution) below) |
63
63
 
64
64
  ### Private Key Formats
65
65
 
@@ -83,9 +83,43 @@ new WaffoPancake({ merchantId: "m_1", privateKey: fs.readFileSync("key.pem", "ut
83
83
  new WaffoPancake({ merchantId: "m_1", privateKey: rawBase64String }); // raw base64
84
84
  ```
85
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
+
86
120
  ### Public Key Formats
87
121
 
88
- The `webhookPublicKey` option (and the `publicKey` field in `VerifyWebhookOptions`) accepts the same flexible formats as private keys:
122
+ All public key inputs (config, env vars, per-call) accept the same flexible formats as private keys:
89
123
 
90
124
  | Format | Example | Notes |
91
125
  |--------|---------|-------|
@@ -462,33 +496,34 @@ const event = verifyWebhook(body, sig, { environment: "prod" });
462
496
  const event = verifyWebhook(body, sig, { toleranceMs: 0 }); // disable replay check
463
497
  ```
464
498
 
465
- ### Option B — Client Instance Method (custom public key)
499
+ ### Option B — Client Instance Method (multi-level key resolution)
466
500
 
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.
501
+ `client.webhooks.verify()` uses the [multi-level fallback chain](#webhook-public-key-resolution) automatically: config keys env vars built-in keys.
468
502
 
469
503
  ```typescript
504
+ // Per-environment keys via config
470
505
  const client = new WaffoPancake({
471
506
  merchantId: process.env.WAFFO_MERCHANT_ID!,
472
507
  privateKey: process.env.WAFFO_PRIVATE_KEY!,
473
- webhookPublicKey: process.env.WAFFO_WEBHOOK_PUBLIC_KEY!, // any format accepted
508
+ webhookPublicKey: {
509
+ test: process.env.WAFFO_TEST_PUB_KEY!,
510
+ prod: process.env.WAFFO_PROD_PUB_KEY!,
511
+ },
474
512
  });
513
+ const event = client.webhooks.verify(rawBody, sig, { environment: "prod" });
475
514
 
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:
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
486
521
 
487
- ```typescript
488
- const event = verifyWebhook(body, sig, { publicKey: process.env.MY_PUBLIC_KEY! });
522
+ // Per-call override (highest priority, skips all resolution)
523
+ const event3 = client.webhooks.verify(rawBody, sig, { publicKey: oneOffKey });
489
524
  ```
490
525
 
491
- See [Webhook Guide](docs/webhook-guide.md) for all 10 event types, signature algorithm, and best practices.
526
+ See [Webhook Guide](docs/webhook-guide.md) for event types, signature algorithm, public key resolution, and best practices.
492
527
 
493
528
  ## Error Handling
494
529
 
@@ -547,7 +582,7 @@ Runtime-accessible values. Both `Enum.Value` and string literal syntax are suppo
547
582
 
548
583
  ### Types
549
584
 
550
- See [API Reference — Types](docs/api-reference.md#types) for the full list of 40+ exported interfaces.
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.
551
586
 
552
587
  ## Development
553
588
 
package/dist/index.cjs CHANGED
@@ -654,6 +654,23 @@ function rsaVerify(signatureInput, v1, publicKey) {
654
654
  verifier.update(signatureInput);
655
655
  return verifier.verify(publicKey, v1, "base64");
656
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
+ }
657
674
  function verifyWebhook(payload, signatureHeader, options) {
658
675
  if (!signatureHeader) {
659
676
  throw new Error("Missing X-Waffo-Signature header");
@@ -673,27 +690,25 @@ function verifyWebhook(payload, signatureHeader, options) {
673
690
  }
674
691
  }
675
692
  const signatureInput = `${t}.${payload}`;
676
- const customKey = options?.publicKey;
677
- if (customKey) {
678
- const normalizedKey = normalizePublicKey(customKey);
693
+ const directKey = options?.publicKey;
694
+ if (directKey) {
695
+ const normalizedKey = normalizePublicKey(directKey);
679
696
  if (!rsaVerify(signatureInput, v1, normalizedKey)) {
680
697
  throw new Error("Invalid webhook signature (custom key)");
681
698
  }
682
699
  } else {
700
+ const configKeys = options?.publicKeys;
683
701
  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)");
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)`);
691
706
  }
692
707
  } 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) {
708
+ const prodKey = resolveKeyForEnv("prod", configKeys);
709
+ if (!rsaVerify(signatureInput, v1, prodKey)) {
710
+ const testKey = resolveKeyForEnv("test", configKeys);
711
+ if (!rsaVerify(signatureInput, v1, testKey)) {
697
712
  throw new Error("Invalid webhook signature (tried both prod and test keys)");
698
713
  }
699
714
  }
@@ -704,15 +719,19 @@ function verifyWebhook(payload, signatureHeader, options) {
704
719
 
705
720
  // src/resources/webhooks.ts
706
721
  var WebhooksResource = class {
707
- /** @param publicKey - Optional custom RSA public key (PEM or raw base64) */
708
- constructor(publicKey) {
709
- this.publicKey = publicKey;
722
+ /** @param publicKeys - Optional config-level public key(s) from WaffoPancakeConfig */
723
+ constructor(publicKeys) {
724
+ this.publicKeys = publicKeys;
710
725
  }
711
726
  /**
712
727
  * Verify and parse an incoming webhook event.
713
728
  *
714
- * When the client was created with a `webhookPublicKey`, that key is used
715
- * automatically. You can still override per-call via `options.publicKey`.
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
716
735
  *
717
736
  * @param payload - Raw request body string (must be unparsed)
718
737
  * @param signatureHeader - Value of the `X-Waffo-Signature` header
@@ -724,13 +743,17 @@ var WebhooksResource = class {
724
743
  * const event = client.webhooks.verify(rawBody, signatureHeader);
725
744
  *
726
745
  * @example
727
- * // Override tolerance per call
728
- * const event = client.webhooks.verify(rawBody, sig, { toleranceMs: 0 });
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 });
729
752
  */
730
753
  verify(payload, signatureHeader, options) {
731
754
  const mergedOptions = {
732
755
  ...options,
733
- publicKey: options?.publicKey ?? this.publicKey
756
+ publicKeys: options?.publicKeys ?? this.publicKeys
734
757
  };
735
758
  return verifyWebhook(payload, signatureHeader, mergedOptions);
736
759
  }