@waffo/pancake-ts 0.1.2 → 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 +78 -0
- package/README.md +185 -10
- package/dist/index.cjs +186 -45
- 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 +187 -46
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -7,6 +7,8 @@ interface WaffoPancakeConfig {
|
|
|
7
7
|
baseUrl?: string;
|
|
8
8
|
/** Custom fetch implementation (default: global fetch) */
|
|
9
9
|
fetch?: typeof fetch;
|
|
10
|
+
/** Custom RSA public key (PEM) for webhook signature verification. When set, overrides the built-in Waffo public keys. */
|
|
11
|
+
webhookPublicKey?: string;
|
|
10
12
|
}
|
|
11
13
|
/**
|
|
12
14
|
* Single error object within the `errors` array.
|
|
@@ -239,7 +241,6 @@ interface CheckoutThemeSettings {
|
|
|
239
241
|
checkoutColorBackground: string;
|
|
240
242
|
checkoutColorCard: string;
|
|
241
243
|
checkoutColorText: string;
|
|
242
|
-
checkoutColorTextSecondary: string;
|
|
243
244
|
checkoutBorderRadius: string;
|
|
244
245
|
}
|
|
245
246
|
/**
|
|
@@ -247,6 +248,7 @@ interface CheckoutThemeSettings {
|
|
|
247
248
|
* @see waffo-pancake-store-service/app/lib/types.ts
|
|
248
249
|
*/
|
|
249
250
|
interface CheckoutSettings {
|
|
251
|
+
defaultDarkMode: boolean;
|
|
250
252
|
light: CheckoutThemeSettings;
|
|
251
253
|
dark: CheckoutThemeSettings;
|
|
252
254
|
}
|
|
@@ -262,7 +264,6 @@ interface Store {
|
|
|
262
264
|
supportEmail: string | null;
|
|
263
265
|
website: string | null;
|
|
264
266
|
slug: string | null;
|
|
265
|
-
isPublic: boolean;
|
|
266
267
|
prodEnabled: boolean;
|
|
267
268
|
webhookSettings: WebhookSettings | null;
|
|
268
269
|
notificationSettings: NotificationSettings | null;
|
|
@@ -283,9 +284,6 @@ interface UpdateStoreParams {
|
|
|
283
284
|
name?: string;
|
|
284
285
|
status?: EntityStatus;
|
|
285
286
|
logo?: string | null;
|
|
286
|
-
supportEmail?: string | null;
|
|
287
|
-
website?: string | null;
|
|
288
|
-
isPublic?: boolean;
|
|
289
287
|
webhookSettings?: WebhookSettings | null;
|
|
290
288
|
notificationSettings?: NotificationSettings | null;
|
|
291
289
|
checkoutSettings?: CheckoutSettings | null;
|
|
@@ -343,17 +341,15 @@ interface UpdateRoleResult {
|
|
|
343
341
|
*
|
|
344
342
|
* @example
|
|
345
343
|
* // USD $9.99
|
|
346
|
-
* { amount: 999,
|
|
344
|
+
* { amount: 999, taxCategory: "saas" }
|
|
347
345
|
*
|
|
348
346
|
* @example
|
|
349
347
|
* // JPY ¥1000
|
|
350
|
-
* { amount: 1000,
|
|
348
|
+
* { amount: 1000, taxCategory: "software" }
|
|
351
349
|
*/
|
|
352
350
|
interface PriceInfo {
|
|
353
351
|
/** Price amount in smallest currency unit */
|
|
354
352
|
amount: number;
|
|
355
|
-
/** Whether the price is tax-inclusive */
|
|
356
|
-
taxIncluded: boolean;
|
|
357
353
|
/** Tax category */
|
|
358
354
|
taxCategory: TaxCategory;
|
|
359
355
|
}
|
|
@@ -364,8 +360,8 @@ interface PriceInfo {
|
|
|
364
360
|
*
|
|
365
361
|
* @example
|
|
366
362
|
* {
|
|
367
|
-
* "USD": { amount: 999,
|
|
368
|
-
* "EUR": { amount: 899,
|
|
363
|
+
* "USD": { amount: 999, taxCategory: "saas" },
|
|
364
|
+
* "EUR": { amount: 899, taxCategory: "saas" }
|
|
369
365
|
* }
|
|
370
366
|
*/
|
|
371
367
|
type Prices = Record<string, PriceInfo>;
|
|
@@ -576,13 +572,13 @@ interface BillingDetail {
|
|
|
576
572
|
country: string;
|
|
577
573
|
/** Whether this is a business purchase */
|
|
578
574
|
isBusiness: boolean;
|
|
579
|
-
/** Postal / ZIP code */
|
|
575
|
+
/** Postal / ZIP code (required for US, at least one of postcode/state for CA) */
|
|
580
576
|
postcode?: string;
|
|
581
|
-
/** State / province code (
|
|
577
|
+
/** State / province code (at least one of state/postcode for CA) */
|
|
582
578
|
state?: string;
|
|
583
|
-
/** Business name (
|
|
579
|
+
/** Business name (recommended for invoicing, does not affect tax calculation) */
|
|
584
580
|
businessName?: string;
|
|
585
|
-
/** Tax ID
|
|
581
|
+
/** Tax ID / VAT number (EU businesses: triggers reverse charge 0% when provided) */
|
|
586
582
|
taxId?: string;
|
|
587
583
|
}
|
|
588
584
|
/**
|
|
@@ -591,7 +587,7 @@ interface BillingDetail {
|
|
|
591
587
|
*/
|
|
592
588
|
interface CreateCheckoutSessionParams {
|
|
593
589
|
/** Store ID */
|
|
594
|
-
storeId
|
|
590
|
+
storeId: string;
|
|
595
591
|
/** Product ID */
|
|
596
592
|
productId: string;
|
|
597
593
|
/** Product type */
|
|
@@ -608,8 +604,10 @@ interface CreateCheckoutSessionParams {
|
|
|
608
604
|
billingDetail?: BillingDetail;
|
|
609
605
|
/** Redirect URL after successful payment */
|
|
610
606
|
successUrl?: string;
|
|
611
|
-
/** Session expiration in seconds (default:
|
|
607
|
+
/** Session expiration in seconds (default: 45 minutes) */
|
|
612
608
|
expiresInSeconds?: number;
|
|
609
|
+
/** Dark mode override (true=dark, false=light, omit=use store default) */
|
|
610
|
+
darkMode?: boolean;
|
|
613
611
|
/** Custom metadata */
|
|
614
612
|
metadata?: Record<string, string>;
|
|
615
613
|
}
|
|
@@ -718,6 +716,7 @@ interface VerifyWebhookOptions {
|
|
|
718
716
|
/**
|
|
719
717
|
* Specify which environment's public key to use for verification.
|
|
720
718
|
* When omitted, both keys are tried automatically (prod first).
|
|
719
|
+
* Ignored when `publicKey` is provided.
|
|
721
720
|
*/
|
|
722
721
|
environment?: `${Environment}`;
|
|
723
722
|
/**
|
|
@@ -726,6 +725,11 @@ interface VerifyWebhookOptions {
|
|
|
726
725
|
* @default 300000 (5 minutes)
|
|
727
726
|
*/
|
|
728
727
|
toleranceMs?: number;
|
|
728
|
+
/**
|
|
729
|
+
* Custom RSA public key (PEM) for signature verification.
|
|
730
|
+
* When provided, overrides both built-in keys and the `environment` option.
|
|
731
|
+
*/
|
|
732
|
+
publicKey?: string;
|
|
729
733
|
}
|
|
730
734
|
|
|
731
735
|
/**
|
|
@@ -836,7 +840,7 @@ declare class OnetimeProductsResource {
|
|
|
836
840
|
* const { product } = await client.onetimeProducts.create({
|
|
837
841
|
* storeId: "store_xxx",
|
|
838
842
|
* name: "E-Book",
|
|
839
|
-
* prices: { USD: { amount: 2900,
|
|
843
|
+
* prices: { USD: { amount: 2900, taxCategory: "digital_goods" } },
|
|
840
844
|
* });
|
|
841
845
|
*/
|
|
842
846
|
create(params: CreateOnetimeProductParams): Promise<{
|
|
@@ -852,7 +856,7 @@ declare class OnetimeProductsResource {
|
|
|
852
856
|
* const { product } = await client.onetimeProducts.update({
|
|
853
857
|
* id: "prod_xxx",
|
|
854
858
|
* name: "E-Book v2",
|
|
855
|
-
* prices: { USD: { amount: 3900,
|
|
859
|
+
* prices: { USD: { amount: 3900, taxCategory: "digital_goods" } },
|
|
856
860
|
* });
|
|
857
861
|
*/
|
|
858
862
|
update(params: UpdateOnetimeProductParams): Promise<{
|
|
@@ -982,7 +986,6 @@ declare class StoresResource {
|
|
|
982
986
|
* const { store } = await client.stores.update({
|
|
983
987
|
* id: "store_xxx",
|
|
984
988
|
* name: "Updated Name",
|
|
985
|
-
* supportEmail: "help@example.com",
|
|
986
989
|
* });
|
|
987
990
|
*/
|
|
988
991
|
update(params: UpdateStoreParams): Promise<{
|
|
@@ -1079,7 +1082,7 @@ declare class SubscriptionProductsResource {
|
|
|
1079
1082
|
* storeId: "store_xxx",
|
|
1080
1083
|
* name: "Pro Plan",
|
|
1081
1084
|
* billingPeriod: "monthly",
|
|
1082
|
-
* prices: { USD: { amount: 999,
|
|
1085
|
+
* prices: { USD: { amount: 999, taxCategory: "saas" } },
|
|
1083
1086
|
* });
|
|
1084
1087
|
*/
|
|
1085
1088
|
create(params: CreateSubscriptionProductParams): Promise<{
|
|
@@ -1096,7 +1099,7 @@ declare class SubscriptionProductsResource {
|
|
|
1096
1099
|
* id: "prod_xxx",
|
|
1097
1100
|
* name: "Pro Plan v2",
|
|
1098
1101
|
* billingPeriod: "monthly",
|
|
1099
|
-
* prices: { USD: { amount: 1499,
|
|
1102
|
+
* prices: { USD: { amount: 1499, taxCategory: "saas" } },
|
|
1100
1103
|
* });
|
|
1101
1104
|
*/
|
|
1102
1105
|
update(params: UpdateSubscriptionProductParams): Promise<{
|
|
@@ -1131,6 +1134,33 @@ declare class SubscriptionProductsResource {
|
|
|
1131
1134
|
}>;
|
|
1132
1135
|
}
|
|
1133
1136
|
|
|
1137
|
+
/** Webhook signature verification resource. */
|
|
1138
|
+
declare class WebhooksResource {
|
|
1139
|
+
private readonly publicKey;
|
|
1140
|
+
/** @param publicKey - Optional custom RSA public key (PEM or raw base64) */
|
|
1141
|
+
constructor(publicKey: string | undefined);
|
|
1142
|
+
/**
|
|
1143
|
+
* Verify and parse an incoming webhook event.
|
|
1144
|
+
*
|
|
1145
|
+
* When the client was created with a `webhookPublicKey`, that key is used
|
|
1146
|
+
* automatically. You can still override per-call via `options.publicKey`.
|
|
1147
|
+
*
|
|
1148
|
+
* @param payload - Raw request body string (must be unparsed)
|
|
1149
|
+
* @param signatureHeader - Value of the `X-Waffo-Signature` header
|
|
1150
|
+
* @param options - Verification options (optional)
|
|
1151
|
+
* @returns Parsed webhook event
|
|
1152
|
+
* @throws Error if signature is invalid, header is malformed, or timestamp is stale
|
|
1153
|
+
*
|
|
1154
|
+
* @example
|
|
1155
|
+
* const event = client.webhooks.verify(rawBody, signatureHeader);
|
|
1156
|
+
*
|
|
1157
|
+
* @example
|
|
1158
|
+
* // Override tolerance per call
|
|
1159
|
+
* const event = client.webhooks.verify(rawBody, sig, { toleranceMs: 0 });
|
|
1160
|
+
*/
|
|
1161
|
+
verify<T = Record<string, unknown>>(payload: string, signatureHeader: string | undefined | null, options?: VerifyWebhookOptions): WebhookEvent<T>;
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1134
1164
|
/**
|
|
1135
1165
|
* Waffo Pancake TypeScript SDK client.
|
|
1136
1166
|
*
|
|
@@ -1152,7 +1182,7 @@ declare class SubscriptionProductsResource {
|
|
|
1152
1182
|
* const { product } = await client.onetimeProducts.create({
|
|
1153
1183
|
* storeId: store.id,
|
|
1154
1184
|
* name: "E-Book",
|
|
1155
|
-
* prices: { USD: { amount: 2900,
|
|
1185
|
+
* prices: { USD: { amount: 2900, taxCategory: "digital_goods" } },
|
|
1156
1186
|
* });
|
|
1157
1187
|
*
|
|
1158
1188
|
* // Create a checkout session
|
|
@@ -1168,6 +1198,15 @@ declare class SubscriptionProductsResource {
|
|
|
1168
1198
|
* const result = await client.graphql.query({
|
|
1169
1199
|
* query: `query { stores { id name status } }`,
|
|
1170
1200
|
* });
|
|
1201
|
+
*
|
|
1202
|
+
* @example
|
|
1203
|
+
* // Use a custom public key for webhook verification
|
|
1204
|
+
* const client = new WaffoPancake({
|
|
1205
|
+
* merchantId: "...",
|
|
1206
|
+
* privateKey: "...",
|
|
1207
|
+
* webhookPublicKey: myCustomPublicKeyPem,
|
|
1208
|
+
* });
|
|
1209
|
+
* const event = client.webhooks.verify(rawBody, signatureHeader);
|
|
1171
1210
|
*/
|
|
1172
1211
|
declare class WaffoPancake {
|
|
1173
1212
|
private readonly http;
|
|
@@ -1180,6 +1219,7 @@ declare class WaffoPancake {
|
|
|
1180
1219
|
readonly orders: OrdersResource;
|
|
1181
1220
|
readonly checkout: CheckoutResource;
|
|
1182
1221
|
readonly graphql: GraphQLResource;
|
|
1222
|
+
readonly webhooks: WebhooksResource;
|
|
1183
1223
|
constructor(config: WaffoPancakeConfig);
|
|
1184
1224
|
}
|
|
1185
1225
|
|
package/dist/index.js
CHANGED
|
@@ -15,7 +15,105 @@ var WaffoPancakeError = class extends Error {
|
|
|
15
15
|
};
|
|
16
16
|
|
|
17
17
|
// src/signing.ts
|
|
18
|
-
import { createHash, createSign } from "crypto";
|
|
18
|
+
import { createHash, createPrivateKey, createPublicKey, createSign } from "crypto";
|
|
19
|
+
var PKCS8_HEADER = "-----BEGIN PRIVATE KEY-----";
|
|
20
|
+
var PKCS8_FOOTER = "-----END PRIVATE KEY-----";
|
|
21
|
+
var PKCS1_HEADER = "-----BEGIN RSA PRIVATE KEY-----";
|
|
22
|
+
var PKCS1_FOOTER = "-----END RSA PRIVATE KEY-----";
|
|
23
|
+
var SPKI_HEADER = "-----BEGIN PUBLIC KEY-----";
|
|
24
|
+
var SPKI_FOOTER = "-----END PUBLIC KEY-----";
|
|
25
|
+
var PKCS1_PUB_HEADER = "-----BEGIN RSA PUBLIC KEY-----";
|
|
26
|
+
var PKCS1_PUB_FOOTER = "-----END RSA PUBLIC KEY-----";
|
|
27
|
+
function normalizePrivateKey(raw) {
|
|
28
|
+
if (!raw || !raw.trim()) {
|
|
29
|
+
throw new Error(
|
|
30
|
+
"Private key is empty. Provide an RSA private key in PEM format."
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
let pem = raw.replace(/\\n/g, "\n").replace(/\r\n/g, "\n");
|
|
34
|
+
pem = pem.trim();
|
|
35
|
+
const hasPkcs8Header = pem.includes(PKCS8_HEADER);
|
|
36
|
+
const hasPkcs1Header = pem.includes(PKCS1_HEADER);
|
|
37
|
+
const hasHeader = hasPkcs8Header || hasPkcs1Header;
|
|
38
|
+
if (hasHeader) {
|
|
39
|
+
const base64 = pem.replace(/-----BEGIN (?:RSA )?PRIVATE KEY-----/g, "").replace(/-----END (?:RSA )?PRIVATE KEY-----/g, "").replace(/\s+/g, "");
|
|
40
|
+
if (!base64) {
|
|
41
|
+
throw new Error(
|
|
42
|
+
"Private key contains PEM headers but no key data. Check the key content."
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
const header = hasPkcs1Header ? PKCS1_HEADER : PKCS8_HEADER;
|
|
46
|
+
const footer = hasPkcs1Header ? PKCS1_FOOTER : PKCS8_FOOTER;
|
|
47
|
+
const wrapped = base64.match(/.{1,64}/g).join("\n");
|
|
48
|
+
pem = `${header}
|
|
49
|
+
${wrapped}
|
|
50
|
+
${footer}`;
|
|
51
|
+
} else {
|
|
52
|
+
const base64 = pem.replace(/\s+/g, "");
|
|
53
|
+
if (!/^[A-Za-z0-9+/]+=*$/.test(base64)) {
|
|
54
|
+
throw new Error(
|
|
55
|
+
"Private key is not valid PEM or base64. Expected an RSA private key in PEM format or raw base64."
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
const wrapped = base64.match(/.{1,64}/g).join("\n");
|
|
59
|
+
pem = `${PKCS8_HEADER}
|
|
60
|
+
${wrapped}
|
|
61
|
+
${PKCS8_FOOTER}`;
|
|
62
|
+
}
|
|
63
|
+
try {
|
|
64
|
+
createPrivateKey(pem);
|
|
65
|
+
} catch {
|
|
66
|
+
throw new Error(
|
|
67
|
+
"Private key could not be parsed. Ensure it is a valid RSA private key in PKCS#8 or PKCS#1 (PEM) format."
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
return pem;
|
|
71
|
+
}
|
|
72
|
+
function normalizePublicKey(raw) {
|
|
73
|
+
if (!raw || !raw.trim()) {
|
|
74
|
+
throw new Error(
|
|
75
|
+
"Public key is empty. Provide an RSA public key in PEM format."
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
let pem = raw.replace(/\\n/g, "\n").replace(/\r\n/g, "\n");
|
|
79
|
+
pem = pem.trim();
|
|
80
|
+
const hasSpkiHeader = pem.includes(SPKI_HEADER);
|
|
81
|
+
const hasPkcs1PubHeader = pem.includes(PKCS1_PUB_HEADER);
|
|
82
|
+
const hasHeader = hasSpkiHeader || hasPkcs1PubHeader;
|
|
83
|
+
if (hasHeader) {
|
|
84
|
+
const base64 = pem.replace(/-----BEGIN (?:RSA )?PUBLIC KEY-----/g, "").replace(/-----END (?:RSA )?PUBLIC KEY-----/g, "").replace(/\s+/g, "");
|
|
85
|
+
if (!base64) {
|
|
86
|
+
throw new Error(
|
|
87
|
+
"Public key contains PEM headers but no key data. Check the key content."
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
const header = hasPkcs1PubHeader ? PKCS1_PUB_HEADER : SPKI_HEADER;
|
|
91
|
+
const footer = hasPkcs1PubHeader ? PKCS1_PUB_FOOTER : SPKI_FOOTER;
|
|
92
|
+
const wrapped = base64.match(/.{1,64}/g).join("\n");
|
|
93
|
+
pem = `${header}
|
|
94
|
+
${wrapped}
|
|
95
|
+
${footer}`;
|
|
96
|
+
} else {
|
|
97
|
+
const base64 = pem.replace(/\s+/g, "");
|
|
98
|
+
if (!/^[A-Za-z0-9+/]+=*$/.test(base64)) {
|
|
99
|
+
throw new Error(
|
|
100
|
+
"Public key is not valid PEM or base64. Expected an RSA public key in PEM format or raw base64."
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
const wrapped = base64.match(/.{1,64}/g).join("\n");
|
|
104
|
+
pem = `${SPKI_HEADER}
|
|
105
|
+
${wrapped}
|
|
106
|
+
${SPKI_FOOTER}`;
|
|
107
|
+
}
|
|
108
|
+
try {
|
|
109
|
+
createPublicKey(pem);
|
|
110
|
+
} catch {
|
|
111
|
+
throw new Error(
|
|
112
|
+
"Public key could not be parsed. Ensure it is a valid RSA public key in SPKI or PKCS#1 (PEM) format."
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
return pem;
|
|
116
|
+
}
|
|
19
117
|
function signRequest(method, path, timestamp, body, privateKey) {
|
|
20
118
|
const bodyHash = createHash("sha256").update(body).digest("base64");
|
|
21
119
|
const canonicalRequest = `${method}
|
|
@@ -36,7 +134,7 @@ var HttpClient = class {
|
|
|
36
134
|
_fetch;
|
|
37
135
|
constructor(config) {
|
|
38
136
|
this.merchantId = config.merchantId;
|
|
39
|
-
this.privateKey = config.privateKey;
|
|
137
|
+
this.privateKey = normalizePrivateKey(config.privateKey);
|
|
40
138
|
this.baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
41
139
|
this._fetch = config.fetch ?? fetch;
|
|
42
140
|
}
|
|
@@ -167,7 +265,7 @@ var OnetimeProductsResource = class {
|
|
|
167
265
|
* const { product } = await client.onetimeProducts.create({
|
|
168
266
|
* storeId: "store_xxx",
|
|
169
267
|
* name: "E-Book",
|
|
170
|
-
* prices: { USD: { amount: 2900,
|
|
268
|
+
* prices: { USD: { amount: 2900, taxCategory: "digital_goods" } },
|
|
171
269
|
* });
|
|
172
270
|
*/
|
|
173
271
|
async create(params) {
|
|
@@ -183,7 +281,7 @@ var OnetimeProductsResource = class {
|
|
|
183
281
|
* const { product } = await client.onetimeProducts.update({
|
|
184
282
|
* id: "prod_xxx",
|
|
185
283
|
* name: "E-Book v2",
|
|
186
|
-
* prices: { USD: { amount: 3900,
|
|
284
|
+
* prices: { USD: { amount: 3900, taxCategory: "digital_goods" } },
|
|
187
285
|
* });
|
|
188
286
|
*/
|
|
189
287
|
async update(params) {
|
|
@@ -324,7 +422,6 @@ var StoresResource = class {
|
|
|
324
422
|
* const { store } = await client.stores.update({
|
|
325
423
|
* id: "store_xxx",
|
|
326
424
|
* name: "Updated Name",
|
|
327
|
-
* supportEmail: "help@example.com",
|
|
328
425
|
* });
|
|
329
426
|
*/
|
|
330
427
|
async update(params) {
|
|
@@ -423,7 +520,7 @@ var SubscriptionProductsResource = class {
|
|
|
423
520
|
* storeId: "store_xxx",
|
|
424
521
|
* name: "Pro Plan",
|
|
425
522
|
* billingPeriod: "monthly",
|
|
426
|
-
* prices: { USD: { amount: 999,
|
|
523
|
+
* prices: { USD: { amount: 999, taxCategory: "saas" } },
|
|
427
524
|
* });
|
|
428
525
|
*/
|
|
429
526
|
async create(params) {
|
|
@@ -440,7 +537,7 @@ var SubscriptionProductsResource = class {
|
|
|
440
537
|
* id: "prod_xxx",
|
|
441
538
|
* name: "Pro Plan v2",
|
|
442
539
|
* billingPeriod: "monthly",
|
|
443
|
-
* prices: { USD: { amount: 1499,
|
|
540
|
+
* prices: { USD: { amount: 1499, taxCategory: "saas" } },
|
|
444
541
|
* });
|
|
445
542
|
*/
|
|
446
543
|
async update(params) {
|
|
@@ -475,32 +572,6 @@ var SubscriptionProductsResource = class {
|
|
|
475
572
|
}
|
|
476
573
|
};
|
|
477
574
|
|
|
478
|
-
// src/client.ts
|
|
479
|
-
var WaffoPancake = class {
|
|
480
|
-
http;
|
|
481
|
-
auth;
|
|
482
|
-
stores;
|
|
483
|
-
storeMerchants;
|
|
484
|
-
onetimeProducts;
|
|
485
|
-
subscriptionProducts;
|
|
486
|
-
subscriptionProductGroups;
|
|
487
|
-
orders;
|
|
488
|
-
checkout;
|
|
489
|
-
graphql;
|
|
490
|
-
constructor(config) {
|
|
491
|
-
this.http = new HttpClient(config);
|
|
492
|
-
this.auth = new AuthResource(this.http);
|
|
493
|
-
this.stores = new StoresResource(this.http);
|
|
494
|
-
this.storeMerchants = new StoreMerchantsResource(this.http);
|
|
495
|
-
this.onetimeProducts = new OnetimeProductsResource(this.http);
|
|
496
|
-
this.subscriptionProducts = new SubscriptionProductsResource(this.http);
|
|
497
|
-
this.subscriptionProductGroups = new SubscriptionProductGroupsResource(this.http);
|
|
498
|
-
this.orders = new OrdersResource(this.http);
|
|
499
|
-
this.checkout = new CheckoutResource(this.http);
|
|
500
|
-
this.graphql = new GraphQLResource(this.http);
|
|
501
|
-
}
|
|
502
|
-
};
|
|
503
|
-
|
|
504
575
|
// src/webhooks.ts
|
|
505
576
|
import { createVerify } from "crypto";
|
|
506
577
|
var DEFAULT_TOLERANCE_MS = 5 * 60 * 1e3;
|
|
@@ -559,27 +630,97 @@ function verifyWebhook(payload, signatureHeader, options) {
|
|
|
559
630
|
}
|
|
560
631
|
}
|
|
561
632
|
const signatureInput = `${t}.${payload}`;
|
|
562
|
-
const
|
|
563
|
-
if (
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
} else if (env === "prod") {
|
|
568
|
-
if (!rsaVerify(signatureInput, v1, PROD_PUBLIC_KEY)) {
|
|
569
|
-
throw new Error("Invalid webhook signature (prod key)");
|
|
633
|
+
const customKey = options?.publicKey;
|
|
634
|
+
if (customKey) {
|
|
635
|
+
const normalizedKey = normalizePublicKey(customKey);
|
|
636
|
+
if (!rsaVerify(signatureInput, v1, normalizedKey)) {
|
|
637
|
+
throw new Error("Invalid webhook signature (custom key)");
|
|
570
638
|
}
|
|
571
639
|
} else {
|
|
572
|
-
const
|
|
573
|
-
if (
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
640
|
+
const env = options?.environment;
|
|
641
|
+
if (env === "test") {
|
|
642
|
+
if (!rsaVerify(signatureInput, v1, TEST_PUBLIC_KEY)) {
|
|
643
|
+
throw new Error("Invalid webhook signature (test key)");
|
|
644
|
+
}
|
|
645
|
+
} else if (env === "prod") {
|
|
646
|
+
if (!rsaVerify(signatureInput, v1, PROD_PUBLIC_KEY)) {
|
|
647
|
+
throw new Error("Invalid webhook signature (prod key)");
|
|
648
|
+
}
|
|
649
|
+
} else {
|
|
650
|
+
const prodValid = rsaVerify(signatureInput, v1, PROD_PUBLIC_KEY);
|
|
651
|
+
if (!prodValid) {
|
|
652
|
+
const testValid = rsaVerify(signatureInput, v1, TEST_PUBLIC_KEY);
|
|
653
|
+
if (!testValid) {
|
|
654
|
+
throw new Error("Invalid webhook signature (tried both prod and test keys)");
|
|
655
|
+
}
|
|
577
656
|
}
|
|
578
657
|
}
|
|
579
658
|
}
|
|
580
659
|
return JSON.parse(payload);
|
|
581
660
|
}
|
|
582
661
|
|
|
662
|
+
// src/resources/webhooks.ts
|
|
663
|
+
var WebhooksResource = class {
|
|
664
|
+
/** @param publicKey - Optional custom RSA public key (PEM or raw base64) */
|
|
665
|
+
constructor(publicKey) {
|
|
666
|
+
this.publicKey = publicKey;
|
|
667
|
+
}
|
|
668
|
+
/**
|
|
669
|
+
* Verify and parse an incoming webhook event.
|
|
670
|
+
*
|
|
671
|
+
* When the client was created with a `webhookPublicKey`, that key is used
|
|
672
|
+
* automatically. You can still override per-call via `options.publicKey`.
|
|
673
|
+
*
|
|
674
|
+
* @param payload - Raw request body string (must be unparsed)
|
|
675
|
+
* @param signatureHeader - Value of the `X-Waffo-Signature` header
|
|
676
|
+
* @param options - Verification options (optional)
|
|
677
|
+
* @returns Parsed webhook event
|
|
678
|
+
* @throws Error if signature is invalid, header is malformed, or timestamp is stale
|
|
679
|
+
*
|
|
680
|
+
* @example
|
|
681
|
+
* const event = client.webhooks.verify(rawBody, signatureHeader);
|
|
682
|
+
*
|
|
683
|
+
* @example
|
|
684
|
+
* // Override tolerance per call
|
|
685
|
+
* const event = client.webhooks.verify(rawBody, sig, { toleranceMs: 0 });
|
|
686
|
+
*/
|
|
687
|
+
verify(payload, signatureHeader, options) {
|
|
688
|
+
const mergedOptions = {
|
|
689
|
+
...options,
|
|
690
|
+
publicKey: options?.publicKey ?? this.publicKey
|
|
691
|
+
};
|
|
692
|
+
return verifyWebhook(payload, signatureHeader, mergedOptions);
|
|
693
|
+
}
|
|
694
|
+
};
|
|
695
|
+
|
|
696
|
+
// src/client.ts
|
|
697
|
+
var WaffoPancake = class {
|
|
698
|
+
http;
|
|
699
|
+
auth;
|
|
700
|
+
stores;
|
|
701
|
+
storeMerchants;
|
|
702
|
+
onetimeProducts;
|
|
703
|
+
subscriptionProducts;
|
|
704
|
+
subscriptionProductGroups;
|
|
705
|
+
orders;
|
|
706
|
+
checkout;
|
|
707
|
+
graphql;
|
|
708
|
+
webhooks;
|
|
709
|
+
constructor(config) {
|
|
710
|
+
this.http = new HttpClient(config);
|
|
711
|
+
this.auth = new AuthResource(this.http);
|
|
712
|
+
this.stores = new StoresResource(this.http);
|
|
713
|
+
this.storeMerchants = new StoreMerchantsResource(this.http);
|
|
714
|
+
this.onetimeProducts = new OnetimeProductsResource(this.http);
|
|
715
|
+
this.subscriptionProducts = new SubscriptionProductsResource(this.http);
|
|
716
|
+
this.subscriptionProductGroups = new SubscriptionProductGroupsResource(this.http);
|
|
717
|
+
this.orders = new OrdersResource(this.http);
|
|
718
|
+
this.checkout = new CheckoutResource(this.http);
|
|
719
|
+
this.graphql = new GraphQLResource(this.http);
|
|
720
|
+
this.webhooks = new WebhooksResource(config.webhookPublicKey);
|
|
721
|
+
}
|
|
722
|
+
};
|
|
723
|
+
|
|
583
724
|
// src/types.ts
|
|
584
725
|
var Environment = /* @__PURE__ */ ((Environment2) => {
|
|
585
726
|
Environment2["Test"] = "test";
|