recur-tw 0.15.0 → 0.16.0

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/dist/server.cjs CHANGED
@@ -1,5 +1,11 @@
1
1
  'use strict';
2
2
 
3
+ var crypto = require('crypto');
4
+
5
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
6
+
7
+ var crypto__default = /*#__PURE__*/_interopDefault(crypto);
8
+
3
9
  var __defProp = Object.defineProperty;
4
10
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
5
11
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
@@ -21,6 +27,12 @@ var RecurAPIError = class extends Error {
21
27
  this.details = error.details;
22
28
  }
23
29
  };
30
+ var WebhookSignatureVerificationError = class extends Error {
31
+ constructor(message) {
32
+ super(message);
33
+ this.name = "WebhookSignatureVerificationError";
34
+ }
35
+ };
24
36
 
25
37
  // package.json
26
38
  var package_default = {
@@ -277,6 +289,80 @@ var Entitlements = class {
277
289
  return { entitlements };
278
290
  }
279
291
  };
292
+ var Webhooks = class {
293
+ // Accept config for consistency with other resources (future API methods)
294
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
295
+ constructor(_config) {
296
+ }
297
+ /**
298
+ * Verify a webhook signature and return the parsed event
299
+ *
300
+ * @param payload - The raw request body as a string (use `await request.text()`, NOT `JSON.stringify(await request.json())`)
301
+ * @param signature - The value of the `X-Recur-Signature` header
302
+ * @param secret - The webhook signing secret from your dashboard
303
+ * @returns The parsed webhook event
304
+ * @throws {WebhookSignatureVerificationError} If the signature is missing or invalid
305
+ *
306
+ * @example
307
+ * ```typescript
308
+ * import { Recur } from 'recur-tw/server';
309
+ *
310
+ * const recur = new Recur(process.env.RECUR_SECRET_KEY!);
311
+ *
312
+ * export async function POST(request: Request) {
313
+ * const payload = await request.text();
314
+ * const signature = request.headers.get('x-recur-signature');
315
+ *
316
+ * try {
317
+ * const event = recur.webhooks.verify(
318
+ * payload,
319
+ * signature,
320
+ * process.env.RECUR_WEBHOOK_SECRET!,
321
+ * );
322
+ * // Handle event based on event.type
323
+ * switch (event.type) {
324
+ * case 'checkout.completed':
325
+ * break;
326
+ * case 'subscription.cancelled':
327
+ * break;
328
+ * }
329
+ * return new Response('OK', { status: 200 });
330
+ * } catch (err) {
331
+ * return new Response('Invalid signature', { status: 401 });
332
+ * }
333
+ * }
334
+ * ```
335
+ */
336
+ verify(payload, signature, secret) {
337
+ if (!signature) {
338
+ throw new WebhookSignatureVerificationError(
339
+ "Missing webhook signature. Expected the `X-Recur-Signature` header."
340
+ );
341
+ }
342
+ if (!secret) {
343
+ throw new WebhookSignatureVerificationError(
344
+ "Missing webhook secret. Pass your webhook signing secret as the third argument."
345
+ );
346
+ }
347
+ const expectedSignature = crypto__default.default.createHmac("sha256", secret).update(payload).digest("base64");
348
+ let isValid;
349
+ try {
350
+ isValid = crypto__default.default.timingSafeEqual(
351
+ Buffer.from(signature),
352
+ Buffer.from(expectedSignature)
353
+ );
354
+ } catch {
355
+ isValid = false;
356
+ }
357
+ if (!isValid) {
358
+ throw new WebhookSignatureVerificationError(
359
+ "Webhook signature verification failed. Ensure you are using the raw request body and the correct signing secret."
360
+ );
361
+ }
362
+ const event = JSON.parse(payload);
363
+ return event;
364
+ }
365
+ };
280
366
 
281
367
  // src/server/recur.ts
282
368
  var Recur = class {
@@ -292,6 +378,19 @@ var Recur = class {
292
378
  * Portal resource for managing customer portal sessions
293
379
  */
294
380
  __publicField(this, "portal");
381
+ /**
382
+ * Webhooks resource for verifying webhook signatures
383
+ *
384
+ * @example
385
+ * ```typescript
386
+ * const event = recur.webhooks.verify(
387
+ * payload,
388
+ * signature,
389
+ * process.env.RECUR_WEBHOOK_SECRET!,
390
+ * );
391
+ * ```
392
+ */
393
+ __publicField(this, "webhooks");
295
394
  /**
296
395
  * Entitlements resource for checking customer subscription access
297
396
  *
@@ -325,9 +424,11 @@ var Recur = class {
325
424
  baseUrl: options?.baseUrl || "https://api.recur.tw"
326
425
  };
327
426
  this.portal = new Portal(this.config);
427
+ this.webhooks = new Webhooks(this.config);
328
428
  this.entitlements = new Entitlements(this.config);
329
429
  }
330
430
  };
331
431
 
332
432
  exports.Recur = Recur;
333
433
  exports.RecurAPIError = RecurAPIError;
434
+ exports.WebhookSignatureVerificationError = WebhookSignatureVerificationError;
package/dist/server.d.cts CHANGED
@@ -250,6 +250,40 @@ interface EntitlementListResult {
250
250
  */
251
251
  entitlements: Entitlement[];
252
252
  }
253
+ /**
254
+ * Known webhook event types
255
+ *
256
+ * Uses a union with `(string & {})` to allow unknown event types
257
+ * while still providing autocomplete for known ones.
258
+ */
259
+ type WebhookEventType = 'checkout.completed' | 'subscription.created' | 'subscription.activated' | 'subscription.updated' | 'subscription.cancelled' | 'subscription.expired' | 'subscription.trial_ending' | 'subscription.payment_failed' | 'invoice.created' | 'invoice.paid' | 'invoice.payment_failed' | 'invoice.refunded' | 'customer.created' | 'customer.updated' | 'order.paid' | (string & {});
260
+ /**
261
+ * A webhook event received from Recur
262
+ */
263
+ interface WebhookEvent {
264
+ /**
265
+ * Unique event identifier
266
+ */
267
+ id: string;
268
+ /**
269
+ * Event type
270
+ */
271
+ type: WebhookEventType;
272
+ /**
273
+ * ISO 8601 timestamp of when the event was created
274
+ */
275
+ timestamp: string;
276
+ /**
277
+ * Event-specific data payload
278
+ */
279
+ data: Record<string, unknown>;
280
+ }
281
+ /**
282
+ * Error thrown when webhook signature verification fails
283
+ */
284
+ declare class WebhookSignatureVerificationError extends Error {
285
+ constructor(message: string);
286
+ }
253
287
 
254
288
  /**
255
289
  * Portal Sessions Resource
@@ -415,6 +449,57 @@ declare class Entitlements {
415
449
  list(options: EntitlementListOptions): Promise<EntitlementListResult>;
416
450
  }
417
451
 
452
+ /**
453
+ * Webhooks Resource
454
+ *
455
+ * Provides webhook signature verification for securing webhook endpoints.
456
+ * Uses HMAC-SHA256 with Base64 encoding, matching the Recur webhook infrastructure.
457
+ */
458
+
459
+ declare class Webhooks {
460
+ constructor(_config: RecurConfig);
461
+ /**
462
+ * Verify a webhook signature and return the parsed event
463
+ *
464
+ * @param payload - The raw request body as a string (use `await request.text()`, NOT `JSON.stringify(await request.json())`)
465
+ * @param signature - The value of the `X-Recur-Signature` header
466
+ * @param secret - The webhook signing secret from your dashboard
467
+ * @returns The parsed webhook event
468
+ * @throws {WebhookSignatureVerificationError} If the signature is missing or invalid
469
+ *
470
+ * @example
471
+ * ```typescript
472
+ * import { Recur } from 'recur-tw/server';
473
+ *
474
+ * const recur = new Recur(process.env.RECUR_SECRET_KEY!);
475
+ *
476
+ * export async function POST(request: Request) {
477
+ * const payload = await request.text();
478
+ * const signature = request.headers.get('x-recur-signature');
479
+ *
480
+ * try {
481
+ * const event = recur.webhooks.verify(
482
+ * payload,
483
+ * signature,
484
+ * process.env.RECUR_WEBHOOK_SECRET!,
485
+ * );
486
+ * // Handle event based on event.type
487
+ * switch (event.type) {
488
+ * case 'checkout.completed':
489
+ * break;
490
+ * case 'subscription.cancelled':
491
+ * break;
492
+ * }
493
+ * return new Response('OK', { status: 200 });
494
+ * } catch (err) {
495
+ * return new Response('Invalid signature', { status: 401 });
496
+ * }
497
+ * }
498
+ * ```
499
+ */
500
+ verify(payload: string, signature: string | null, secret: string): WebhookEvent;
501
+ }
502
+
418
503
  /**
419
504
  * Recur Server SDK
420
505
  *
@@ -441,6 +526,19 @@ declare class Recur {
441
526
  * Portal resource for managing customer portal sessions
442
527
  */
443
528
  readonly portal: Portal;
529
+ /**
530
+ * Webhooks resource for verifying webhook signatures
531
+ *
532
+ * @example
533
+ * ```typescript
534
+ * const event = recur.webhooks.verify(
535
+ * payload,
536
+ * signature,
537
+ * process.env.RECUR_WEBHOOK_SECRET!,
538
+ * );
539
+ * ```
540
+ */
541
+ readonly webhooks: Webhooks;
444
542
  /**
445
543
  * Entitlements resource for checking customer subscription access
446
544
  *
@@ -468,4 +566,4 @@ declare class Recur {
468
566
  constructor(secretKey: string, options?: Omit<RecurConfig, 'secretKey'>);
469
567
  }
470
568
 
471
- export { type ApiErrorCode, type CustomerIdentifier, type DuplicateSubscriptionDetails, type Entitlement, type EntitlementCheckOptions, type EntitlementCheckResult, type EntitlementListOptions, type EntitlementListResult, type EntitlementStatus, type EntitlementSubscription, type PortalSession, type PortalSessionCreateParams, Recur, RecurAPIError, type RecurConfig, type RecurError };
569
+ export { type ApiErrorCode, type CustomerIdentifier, type DuplicateSubscriptionDetails, type Entitlement, type EntitlementCheckOptions, type EntitlementCheckResult, type EntitlementListOptions, type EntitlementListResult, type EntitlementStatus, type EntitlementSubscription, type PortalSession, type PortalSessionCreateParams, Recur, RecurAPIError, type RecurConfig, type RecurError, type WebhookEvent, type WebhookEventType, WebhookSignatureVerificationError };
package/dist/server.d.ts CHANGED
@@ -250,6 +250,40 @@ interface EntitlementListResult {
250
250
  */
251
251
  entitlements: Entitlement[];
252
252
  }
253
+ /**
254
+ * Known webhook event types
255
+ *
256
+ * Uses a union with `(string & {})` to allow unknown event types
257
+ * while still providing autocomplete for known ones.
258
+ */
259
+ type WebhookEventType = 'checkout.completed' | 'subscription.created' | 'subscription.activated' | 'subscription.updated' | 'subscription.cancelled' | 'subscription.expired' | 'subscription.trial_ending' | 'subscription.payment_failed' | 'invoice.created' | 'invoice.paid' | 'invoice.payment_failed' | 'invoice.refunded' | 'customer.created' | 'customer.updated' | 'order.paid' | (string & {});
260
+ /**
261
+ * A webhook event received from Recur
262
+ */
263
+ interface WebhookEvent {
264
+ /**
265
+ * Unique event identifier
266
+ */
267
+ id: string;
268
+ /**
269
+ * Event type
270
+ */
271
+ type: WebhookEventType;
272
+ /**
273
+ * ISO 8601 timestamp of when the event was created
274
+ */
275
+ timestamp: string;
276
+ /**
277
+ * Event-specific data payload
278
+ */
279
+ data: Record<string, unknown>;
280
+ }
281
+ /**
282
+ * Error thrown when webhook signature verification fails
283
+ */
284
+ declare class WebhookSignatureVerificationError extends Error {
285
+ constructor(message: string);
286
+ }
253
287
 
254
288
  /**
255
289
  * Portal Sessions Resource
@@ -415,6 +449,57 @@ declare class Entitlements {
415
449
  list(options: EntitlementListOptions): Promise<EntitlementListResult>;
416
450
  }
417
451
 
452
+ /**
453
+ * Webhooks Resource
454
+ *
455
+ * Provides webhook signature verification for securing webhook endpoints.
456
+ * Uses HMAC-SHA256 with Base64 encoding, matching the Recur webhook infrastructure.
457
+ */
458
+
459
+ declare class Webhooks {
460
+ constructor(_config: RecurConfig);
461
+ /**
462
+ * Verify a webhook signature and return the parsed event
463
+ *
464
+ * @param payload - The raw request body as a string (use `await request.text()`, NOT `JSON.stringify(await request.json())`)
465
+ * @param signature - The value of the `X-Recur-Signature` header
466
+ * @param secret - The webhook signing secret from your dashboard
467
+ * @returns The parsed webhook event
468
+ * @throws {WebhookSignatureVerificationError} If the signature is missing or invalid
469
+ *
470
+ * @example
471
+ * ```typescript
472
+ * import { Recur } from 'recur-tw/server';
473
+ *
474
+ * const recur = new Recur(process.env.RECUR_SECRET_KEY!);
475
+ *
476
+ * export async function POST(request: Request) {
477
+ * const payload = await request.text();
478
+ * const signature = request.headers.get('x-recur-signature');
479
+ *
480
+ * try {
481
+ * const event = recur.webhooks.verify(
482
+ * payload,
483
+ * signature,
484
+ * process.env.RECUR_WEBHOOK_SECRET!,
485
+ * );
486
+ * // Handle event based on event.type
487
+ * switch (event.type) {
488
+ * case 'checkout.completed':
489
+ * break;
490
+ * case 'subscription.cancelled':
491
+ * break;
492
+ * }
493
+ * return new Response('OK', { status: 200 });
494
+ * } catch (err) {
495
+ * return new Response('Invalid signature', { status: 401 });
496
+ * }
497
+ * }
498
+ * ```
499
+ */
500
+ verify(payload: string, signature: string | null, secret: string): WebhookEvent;
501
+ }
502
+
418
503
  /**
419
504
  * Recur Server SDK
420
505
  *
@@ -441,6 +526,19 @@ declare class Recur {
441
526
  * Portal resource for managing customer portal sessions
442
527
  */
443
528
  readonly portal: Portal;
529
+ /**
530
+ * Webhooks resource for verifying webhook signatures
531
+ *
532
+ * @example
533
+ * ```typescript
534
+ * const event = recur.webhooks.verify(
535
+ * payload,
536
+ * signature,
537
+ * process.env.RECUR_WEBHOOK_SECRET!,
538
+ * );
539
+ * ```
540
+ */
541
+ readonly webhooks: Webhooks;
444
542
  /**
445
543
  * Entitlements resource for checking customer subscription access
446
544
  *
@@ -468,4 +566,4 @@ declare class Recur {
468
566
  constructor(secretKey: string, options?: Omit<RecurConfig, 'secretKey'>);
469
567
  }
470
568
 
471
- export { type ApiErrorCode, type CustomerIdentifier, type DuplicateSubscriptionDetails, type Entitlement, type EntitlementCheckOptions, type EntitlementCheckResult, type EntitlementListOptions, type EntitlementListResult, type EntitlementStatus, type EntitlementSubscription, type PortalSession, type PortalSessionCreateParams, Recur, RecurAPIError, type RecurConfig, type RecurError };
569
+ export { type ApiErrorCode, type CustomerIdentifier, type DuplicateSubscriptionDetails, type Entitlement, type EntitlementCheckOptions, type EntitlementCheckResult, type EntitlementListOptions, type EntitlementListResult, type EntitlementStatus, type EntitlementSubscription, type PortalSession, type PortalSessionCreateParams, Recur, RecurAPIError, type RecurConfig, type RecurError, type WebhookEvent, type WebhookEventType, WebhookSignatureVerificationError };
package/dist/server.js CHANGED
@@ -1,3 +1,5 @@
1
+ import crypto from 'crypto';
2
+
1
3
  var __defProp = Object.defineProperty;
2
4
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
5
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
@@ -19,6 +21,12 @@ var RecurAPIError = class extends Error {
19
21
  this.details = error.details;
20
22
  }
21
23
  };
24
+ var WebhookSignatureVerificationError = class extends Error {
25
+ constructor(message) {
26
+ super(message);
27
+ this.name = "WebhookSignatureVerificationError";
28
+ }
29
+ };
22
30
 
23
31
  // package.json
24
32
  var package_default = {
@@ -275,6 +283,80 @@ var Entitlements = class {
275
283
  return { entitlements };
276
284
  }
277
285
  };
286
+ var Webhooks = class {
287
+ // Accept config for consistency with other resources (future API methods)
288
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
289
+ constructor(_config) {
290
+ }
291
+ /**
292
+ * Verify a webhook signature and return the parsed event
293
+ *
294
+ * @param payload - The raw request body as a string (use `await request.text()`, NOT `JSON.stringify(await request.json())`)
295
+ * @param signature - The value of the `X-Recur-Signature` header
296
+ * @param secret - The webhook signing secret from your dashboard
297
+ * @returns The parsed webhook event
298
+ * @throws {WebhookSignatureVerificationError} If the signature is missing or invalid
299
+ *
300
+ * @example
301
+ * ```typescript
302
+ * import { Recur } from 'recur-tw/server';
303
+ *
304
+ * const recur = new Recur(process.env.RECUR_SECRET_KEY!);
305
+ *
306
+ * export async function POST(request: Request) {
307
+ * const payload = await request.text();
308
+ * const signature = request.headers.get('x-recur-signature');
309
+ *
310
+ * try {
311
+ * const event = recur.webhooks.verify(
312
+ * payload,
313
+ * signature,
314
+ * process.env.RECUR_WEBHOOK_SECRET!,
315
+ * );
316
+ * // Handle event based on event.type
317
+ * switch (event.type) {
318
+ * case 'checkout.completed':
319
+ * break;
320
+ * case 'subscription.cancelled':
321
+ * break;
322
+ * }
323
+ * return new Response('OK', { status: 200 });
324
+ * } catch (err) {
325
+ * return new Response('Invalid signature', { status: 401 });
326
+ * }
327
+ * }
328
+ * ```
329
+ */
330
+ verify(payload, signature, secret) {
331
+ if (!signature) {
332
+ throw new WebhookSignatureVerificationError(
333
+ "Missing webhook signature. Expected the `X-Recur-Signature` header."
334
+ );
335
+ }
336
+ if (!secret) {
337
+ throw new WebhookSignatureVerificationError(
338
+ "Missing webhook secret. Pass your webhook signing secret as the third argument."
339
+ );
340
+ }
341
+ const expectedSignature = crypto.createHmac("sha256", secret).update(payload).digest("base64");
342
+ let isValid;
343
+ try {
344
+ isValid = crypto.timingSafeEqual(
345
+ Buffer.from(signature),
346
+ Buffer.from(expectedSignature)
347
+ );
348
+ } catch {
349
+ isValid = false;
350
+ }
351
+ if (!isValid) {
352
+ throw new WebhookSignatureVerificationError(
353
+ "Webhook signature verification failed. Ensure you are using the raw request body and the correct signing secret."
354
+ );
355
+ }
356
+ const event = JSON.parse(payload);
357
+ return event;
358
+ }
359
+ };
278
360
 
279
361
  // src/server/recur.ts
280
362
  var Recur = class {
@@ -290,6 +372,19 @@ var Recur = class {
290
372
  * Portal resource for managing customer portal sessions
291
373
  */
292
374
  __publicField(this, "portal");
375
+ /**
376
+ * Webhooks resource for verifying webhook signatures
377
+ *
378
+ * @example
379
+ * ```typescript
380
+ * const event = recur.webhooks.verify(
381
+ * payload,
382
+ * signature,
383
+ * process.env.RECUR_WEBHOOK_SECRET!,
384
+ * );
385
+ * ```
386
+ */
387
+ __publicField(this, "webhooks");
293
388
  /**
294
389
  * Entitlements resource for checking customer subscription access
295
390
  *
@@ -323,8 +418,9 @@ var Recur = class {
323
418
  baseUrl: options?.baseUrl || "https://api.recur.tw"
324
419
  };
325
420
  this.portal = new Portal(this.config);
421
+ this.webhooks = new Webhooks(this.config);
326
422
  this.entitlements = new Entitlements(this.config);
327
423
  }
328
424
  };
329
425
 
330
- export { Recur, RecurAPIError };
426
+ export { Recur, RecurAPIError, WebhookSignatureVerificationError };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "recur-tw",
3
- "version": "0.15.0",
3
+ "version": "0.16.0",
4
4
  "description": "React & Vanilla JS SDK for embedding subscription checkout flows (Taiwan / PAYUNi)",
5
5
  "type": "module",
6
6
  "private": false,