@onesub/server 0.12.1 → 0.13.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.
Files changed (118) hide show
  1. package/dist/__tests__/admin-customer-detail.test.js +146 -0
  2. package/dist/__tests__/admin-customer-detail.test.js.map +1 -0
  3. package/dist/__tests__/admin-list-subscriptions.test.js +192 -0
  4. package/dist/__tests__/admin-list-subscriptions.test.js.map +1 -0
  5. package/dist/__tests__/admin-subscription-detail.test.js +112 -0
  6. package/dist/__tests__/admin-subscription-detail.test.js.map +1 -0
  7. package/dist/__tests__/apple-root-ca.test.js +41 -0
  8. package/dist/__tests__/apple-root-ca.test.js.map +1 -0
  9. package/dist/__tests__/apple-status-api.test.js +345 -0
  10. package/dist/__tests__/apple-status-api.test.js.map +1 -0
  11. package/dist/__tests__/cache.test.js +34 -0
  12. package/dist/__tests__/cache.test.js.map +1 -0
  13. package/dist/__tests__/entitlements.test.js +268 -0
  14. package/dist/__tests__/entitlements.test.js.map +1 -0
  15. package/dist/__tests__/error-codes.test.js +168 -0
  16. package/dist/__tests__/error-codes.test.js.map +1 -0
  17. package/dist/__tests__/fetch-hardening.test.js +192 -0
  18. package/dist/__tests__/fetch-hardening.test.js.map +1 -0
  19. package/dist/__tests__/google-paused.test.js +126 -0
  20. package/dist/__tests__/google-paused.test.js.map +1 -0
  21. package/dist/__tests__/google-price-change.test.js +186 -0
  22. package/dist/__tests__/google-price-change.test.js.map +1 -0
  23. package/dist/__tests__/google-subscriptions-v2.test.js +206 -0
  24. package/dist/__tests__/google-subscriptions-v2.test.js.map +1 -0
  25. package/dist/__tests__/lifecycle-scenarios.test.js +579 -0
  26. package/dist/__tests__/lifecycle-scenarios.test.js.map +1 -0
  27. package/dist/__tests__/lifecycle-states.test.js +423 -0
  28. package/dist/__tests__/lifecycle-states.test.js.map +1 -0
  29. package/dist/__tests__/linked-purchase-token.test.js +227 -0
  30. package/dist/__tests__/linked-purchase-token.test.js.map +1 -0
  31. package/dist/__tests__/metrics.test.js +340 -0
  32. package/dist/__tests__/metrics.test.js.map +1 -0
  33. package/dist/__tests__/mock-provider.test.js +188 -0
  34. package/dist/__tests__/mock-provider.test.js.map +1 -0
  35. package/dist/__tests__/openapi.test.js +32 -0
  36. package/dist/__tests__/openapi.test.js.map +1 -0
  37. package/dist/__tests__/paused-auto-resume.test.js +162 -0
  38. package/dist/__tests__/paused-auto-resume.test.js.map +1 -0
  39. package/dist/__tests__/providers.test.js +321 -0
  40. package/dist/__tests__/providers.test.js.map +1 -0
  41. package/dist/__tests__/redis-store.test.js +117 -0
  42. package/dist/__tests__/redis-store.test.js.map +1 -0
  43. package/dist/__tests__/refund-policy.test.js +317 -0
  44. package/dist/__tests__/refund-policy.test.js.map +1 -0
  45. package/dist/__tests__/schema.test.js +46 -0
  46. package/dist/__tests__/schema.test.js.map +1 -0
  47. package/dist/__tests__/store.test.js +172 -0
  48. package/dist/__tests__/store.test.js.map +1 -0
  49. package/dist/__tests__/test-utils.js +30 -0
  50. package/dist/__tests__/test-utils.js.map +1 -0
  51. package/dist/__tests__/webhook-events.test.js +36 -0
  52. package/dist/__tests__/webhook-events.test.js.map +1 -0
  53. package/dist/__tests__/webhook-queue.test.js +26 -0
  54. package/dist/__tests__/webhook-queue.test.js.map +1 -0
  55. package/dist/__tests__/webhook-refund.test.js +313 -0
  56. package/dist/__tests__/webhook-refund.test.js.map +1 -0
  57. package/dist/cache.js +43 -0
  58. package/dist/cache.js.map +1 -0
  59. package/dist/errors.js +19 -0
  60. package/dist/errors.js.map +1 -0
  61. package/dist/http.js +36 -0
  62. package/dist/http.js.map +1 -0
  63. package/dist/index.cjs +231 -6
  64. package/dist/index.cjs.map +1 -1
  65. package/dist/index.d.ts +1 -1
  66. package/dist/index.d.ts.map +1 -1
  67. package/dist/index.js +231 -8
  68. package/dist/index.js.map +1 -1
  69. package/dist/logger.js +20 -0
  70. package/dist/logger.js.map +1 -0
  71. package/dist/openapi.js +196 -0
  72. package/dist/openapi.js.map +1 -0
  73. package/dist/providers/apple-root-ca.js +38 -0
  74. package/dist/providers/apple-root-ca.js.map +1 -0
  75. package/dist/providers/apple.d.ts +76 -0
  76. package/dist/providers/apple.d.ts.map +1 -1
  77. package/dist/providers/apple.js +510 -0
  78. package/dist/providers/apple.js.map +1 -0
  79. package/dist/providers/google.d.ts +25 -0
  80. package/dist/providers/google.d.ts.map +1 -1
  81. package/dist/providers/google.js +522 -0
  82. package/dist/providers/google.js.map +1 -0
  83. package/dist/providers/mock.js +93 -0
  84. package/dist/providers/mock.js.map +1 -0
  85. package/dist/routes/admin.d.ts.map +1 -1
  86. package/dist/routes/admin.js +287 -0
  87. package/dist/routes/admin.js.map +1 -0
  88. package/dist/routes/apple-offer.d.ts +21 -0
  89. package/dist/routes/apple-offer.d.ts.map +1 -0
  90. package/dist/routes/entitlements.js +130 -0
  91. package/dist/routes/entitlements.js.map +1 -0
  92. package/dist/routes/metrics.js +281 -0
  93. package/dist/routes/metrics.js.map +1 -0
  94. package/dist/routes/purchase.js +187 -0
  95. package/dist/routes/purchase.js.map +1 -0
  96. package/dist/routes/status.js +55 -0
  97. package/dist/routes/status.js.map +1 -0
  98. package/dist/routes/validate.js +70 -0
  99. package/dist/routes/validate.js.map +1 -0
  100. package/dist/routes/webhook-apple.d.ts.map +1 -1
  101. package/dist/routes/webhook-google.d.ts.map +1 -1
  102. package/dist/routes/webhook.js +461 -0
  103. package/dist/routes/webhook.js.map +1 -0
  104. package/dist/store.js +145 -0
  105. package/dist/store.js.map +1 -0
  106. package/dist/stores/postgres.js +329 -0
  107. package/dist/stores/postgres.js.map +1 -0
  108. package/dist/stores/redis.js +255 -0
  109. package/dist/stores/redis.js.map +1 -0
  110. package/dist/stores/schema.js +67 -0
  111. package/dist/stores/schema.js.map +1 -0
  112. package/dist/tracing.js +74 -0
  113. package/dist/tracing.js.map +1 -0
  114. package/dist/webhook-events.js +57 -0
  115. package/dist/webhook-events.js.map +1 -0
  116. package/dist/webhook-queue.js +108 -0
  117. package/dist/webhook-queue.js.map +1 -0
  118. package/package.json +2 -2
package/dist/index.cjs CHANGED
@@ -477,7 +477,9 @@ async function decodeAppleNotification(payload, skipJwsVerification = false) {
477
477
  environment,
478
478
  status,
479
479
  willRenew,
480
- expiresAt: tx.expiresDate ? new Date(tx.expiresDate).toISOString() : null
480
+ expiresAt: tx.expiresDate ? new Date(tx.expiresDate).toISOString() : null,
481
+ appAccountToken: tx.appAccountToken ?? null,
482
+ inAppOwnershipType: tx.inAppOwnershipType ?? null
481
483
  };
482
484
  }
483
485
  var appleJwtMintPromises = /* @__PURE__ */ new Map();
@@ -623,6 +625,91 @@ async function fetchAppleSubscriptionStatus(originalTransactionId, config, optio
623
625
  willRenew: renewal?.autoRenewStatus === 1
624
626
  };
625
627
  }
628
+ async function fetchAppleTransactionHistory(originalTransactionId, config, options) {
629
+ if (config.mockMode) return null;
630
+ let jwt;
631
+ try {
632
+ jwt = await makeAppleApiJwt(config);
633
+ } catch (err) {
634
+ log.warn("[onesub/apple] Cannot fetch transaction history \u2014 JWT mint failed:", err);
635
+ return null;
636
+ }
637
+ const host = options?.sandbox ? "api.storekit-sandbox.itunes.apple.com" : "api.storekit.itunes.apple.com";
638
+ const results = [];
639
+ let revision;
640
+ for (; ; ) {
641
+ const url = new URL(`https://${host}/inApps/v2/history/${encodeURIComponent(originalTransactionId)}`);
642
+ if (revision) url.searchParams.set("revision", revision);
643
+ let page;
644
+ try {
645
+ const resp = await fetchWithTimeout(url.toString(), {
646
+ headers: { Authorization: `Bearer ${jwt}` }
647
+ });
648
+ if (!resp.ok) {
649
+ const text = await resp.text();
650
+ log.warn(`[onesub/apple] Transaction History API error ${resp.status}: ${text}`);
651
+ return null;
652
+ }
653
+ page = await resp.json();
654
+ } catch (err) {
655
+ log.warn("[onesub/apple] Transaction History API network error:", err);
656
+ return null;
657
+ }
658
+ for (const signed of page.signedTransactions ?? []) {
659
+ try {
660
+ const tx = await decodeJws(signed, config.skipJwsVerification);
661
+ if (!tx.originalTransactionId || !tx.transactionId || !tx.productId) continue;
662
+ results.push({
663
+ originalTransactionId: tx.originalTransactionId,
664
+ transactionId: tx.transactionId,
665
+ productId: tx.productId,
666
+ type: tx.type ?? "Unknown",
667
+ purchasedAt: tx.purchaseDate ? new Date(tx.purchaseDate).toISOString() : (/* @__PURE__ */ new Date()).toISOString(),
668
+ expiresAt: tx.expiresDate ? new Date(tx.expiresDate).toISOString() : null,
669
+ appAccountToken: tx.appAccountToken ?? null,
670
+ inAppOwnershipType: tx.inAppOwnershipType ?? null,
671
+ revocationDate: tx.revocationDate ? new Date(tx.revocationDate).toISOString() : null
672
+ });
673
+ } catch {
674
+ }
675
+ }
676
+ if (!page.hasMore) break;
677
+ revision = page.revision;
678
+ }
679
+ return results;
680
+ }
681
+ async function signApplePromotionalOffer(input, config) {
682
+ const { offerKeyId, offerPrivateKey } = config;
683
+ if (!offerKeyId || !offerPrivateKey) {
684
+ throw new Error("[onesub/apple] Promotional Offer signing requires config.apple.offerKeyId and config.apple.offerPrivateKey");
685
+ }
686
+ const nonce = crypto.randomUUID().toLowerCase();
687
+ const timestamp = Date.now();
688
+ const separator = "\u2063";
689
+ const message = [
690
+ input.bundleId,
691
+ offerKeyId,
692
+ input.productId,
693
+ input.offerId,
694
+ input.applicationUsername,
695
+ nonce,
696
+ String(timestamp)
697
+ ].join(separator);
698
+ if (!offerPrivateKey.includes("BEGIN") || !offerPrivateKey.includes("END")) {
699
+ throw new Error("[onesub/apple] offerPrivateKey does not appear to be PEM-encoded");
700
+ }
701
+ let signatureBuffer;
702
+ try {
703
+ const sign = crypto.createSign("SHA256");
704
+ sign.update(message);
705
+ sign.end();
706
+ signatureBuffer = sign.sign(offerPrivateKey);
707
+ } catch (err) {
708
+ throw new Error(`[onesub/apple] Failed to sign promotional offer \u2014 check that offerPrivateKey is a valid ES256 PEM key: ${err.message}`);
709
+ }
710
+ const signature = signatureBuffer.toString("base64");
711
+ return { keyId: offerKeyId, nonce, timestamp, signature };
712
+ }
626
713
  var GOOGLE_NOTIFICATION_TYPE = {
627
714
  SUBSCRIPTION_RECOVERED: 1,
628
715
  SUBSCRIPTION_RENEWED: 2,
@@ -685,8 +772,8 @@ async function getAccessToken(serviceAccountKey) {
685
772
  })
686
773
  ).toString("base64url");
687
774
  const signingInput = `${header}.${payload}`;
688
- const { createSign } = await import('crypto');
689
- const sign = createSign("RSA-SHA256");
775
+ const { createSign: createSign2 } = await import('crypto');
776
+ const sign = createSign2("RSA-SHA256");
690
777
  sign.update(signingInput);
691
778
  const signature = sign.sign(key.private_key, "base64url");
692
779
  const assertion = `${signingInput}.${signature}`;
@@ -958,6 +1045,22 @@ function decodeGoogleVoidedNotification(payload) {
958
1045
  packageName: notification.packageName
959
1046
  };
960
1047
  }
1048
+ function decodeGoogleOneTimeProductNotification(payload) {
1049
+ let notification;
1050
+ try {
1051
+ const json = Buffer.from(payload.message.data, "base64").toString("utf-8");
1052
+ notification = JSON.parse(json);
1053
+ } catch {
1054
+ return null;
1055
+ }
1056
+ if (!notification.oneTimeProductNotification) return null;
1057
+ const { notificationType, purchaseToken, sku } = notification.oneTimeProductNotification;
1058
+ if (notificationType !== 1 && notificationType !== 2) {
1059
+ log.warn("[onesub/google] Unknown oneTimeProductNotification type:", notificationType);
1060
+ return null;
1061
+ }
1062
+ return { notificationType, purchaseToken, sku, packageName: notification.packageName };
1063
+ }
961
1064
  function isGoogleActiveNotification(notificationType) {
962
1065
  return notificationType === GOOGLE_NOTIFICATION_TYPE.SUBSCRIPTION_PURCHASED || notificationType === GOOGLE_NOTIFICATION_TYPE.SUBSCRIPTION_RENEWED || notificationType === GOOGLE_NOTIFICATION_TYPE.SUBSCRIPTION_RECOVERED || notificationType === GOOGLE_NOTIFICATION_TYPE.SUBSCRIPTION_RESTARTED;
963
1066
  }
@@ -1133,7 +1236,9 @@ async function handleAppleWebhook(req, res, config, store, purchaseStore, webhoo
1133
1236
  environment,
1134
1237
  status,
1135
1238
  willRenew,
1136
- expiresAt
1239
+ expiresAt,
1240
+ appAccountToken,
1241
+ inAppOwnershipType
1137
1242
  } = decoded;
1138
1243
  const notificationType = payload.notificationType;
1139
1244
  const subtype = payload.subtype;
@@ -1177,8 +1282,13 @@ async function handleAppleWebhook(req, res, config, store, purchaseStore, webhoo
1177
1282
  if (existing) {
1178
1283
  const isSubscriptionRefund = isRefundOrRevoke && !isOneTimePurchase && notificationType !== "CONSUMPTION_REQUEST";
1179
1284
  const keepEntitlement = isSubscriptionRefund && config.refundPolicy === "until_expiry";
1180
- const updated = keepEntitlement ? { ...existing, willRenew: false } : {
1285
+ const correctedUserId = appAccountToken && existing.userId === originalTransactionId ? appAccountToken : existing.userId;
1286
+ if (correctedUserId !== existing.userId) {
1287
+ log.info("[onesub/webhook/apple] correcting userId from originalTransactionId to appAccountToken:", correctedUserId);
1288
+ }
1289
+ const updated = keepEntitlement ? { ...existing, userId: correctedUserId, willRenew: false } : {
1181
1290
  ...existing,
1291
+ userId: correctedUserId,
1182
1292
  status: finalStatus,
1183
1293
  willRenew,
1184
1294
  expiresAt: expiresAt ?? existing.expiresAt
@@ -1189,7 +1299,11 @@ async function handleAppleWebhook(req, res, config, store, purchaseStore, webhoo
1189
1299
  sandbox: environment === "Sandbox"
1190
1300
  });
1191
1301
  if (fresh) {
1192
- fresh.userId = originalTransactionId;
1302
+ fresh.userId = appAccountToken ?? originalTransactionId;
1303
+ if (inAppOwnershipType === "FAMILY_SHARED") {
1304
+ const source = appAccountToken ? "appAccountToken" : "originalTransactionId (fallback)";
1305
+ log.info(`[onesub/webhook/apple] FAMILY_SHARED \u2014 userId: ${fresh.userId} (source: ${source})`);
1306
+ }
1193
1307
  await store.save(fresh);
1194
1308
  } else {
1195
1309
  log.warn(
@@ -1277,6 +1391,32 @@ async function handleGoogleWebhook(req, res, config, store, purchaseStore, webho
1277
1391
  }
1278
1392
  return;
1279
1393
  }
1394
+ const oneTimeProduct = decodeGoogleOneTimeProductNotification(body);
1395
+ if (oneTimeProduct) {
1396
+ if (config.google?.packageName && oneTimeProduct.packageName !== config.google.packageName) {
1397
+ log.warn("[onesub/webhook/google] oneTimeProduct package name mismatch:", oneTimeProduct.packageName, "!==", config.google.packageName);
1398
+ sendError(res, 400, shared.ONESUB_ERROR_CODE.PACKAGE_NAME_MISMATCH, "Package name mismatch");
1399
+ return;
1400
+ }
1401
+ try {
1402
+ const { notificationType: notificationType2, purchaseToken: purchaseToken2, sku } = oneTimeProduct;
1403
+ if (notificationType2 === 1) {
1404
+ log.info("[onesub/webhook/google] oneTimeProduct PURCHASED:", sku);
1405
+ if (config.google?.serviceAccountKey && config.google.packageName) {
1406
+ void acknowledgeGoogleProduct(purchaseToken2, sku, config.google).catch(
1407
+ (err) => log.warn(`[onesub/webhook/google] oneTimeProduct ack failed for SKU ${sku} \u2014 3-day auto-refund risk:`, err)
1408
+ );
1409
+ }
1410
+ } else {
1411
+ log.info("[onesub/webhook/google] oneTimeProduct CANCELED (pre-completion):", sku);
1412
+ }
1413
+ res.status(200).json({ received: true });
1414
+ } catch (err) {
1415
+ log.error("[onesub/webhook/google] oneTimeProduct error:", err);
1416
+ sendError(res, 500, shared.ONESUB_ERROR_CODE.WEBHOOK_PROCESSING_FAILED, "Failed to process oneTimeProduct notification");
1417
+ }
1418
+ return;
1419
+ }
1280
1420
  const notification = decodeGoogleNotification(body);
1281
1421
  if (!notification) {
1282
1422
  res.status(200).json({ received: true });
@@ -1788,6 +1928,38 @@ function createAdminRouter(config, purchaseStore, store, webhookQueue) {
1788
1928
  sendError(res, 500, shared.ONESUB_ERROR_CODE.STORE_ERROR, err.message ?? "customer error");
1789
1929
  }
1790
1930
  });
1931
+ const syncAppleParamsSchema = zod.z.object({
1932
+ originalTransactionId: zod.z.string().min(1).max(256)
1933
+ });
1934
+ router.post(shared.ROUTES.ADMIN_SYNC_APPLE, async (req, res) => {
1935
+ let params;
1936
+ try {
1937
+ params = syncAppleParamsSchema.parse(req.params);
1938
+ } catch {
1939
+ sendError(res, 400, shared.ONESUB_ERROR_CODE.INVALID_INPUT, "originalTransactionId required");
1940
+ return;
1941
+ }
1942
+ if (!config.apple?.issuerId || !config.apple?.keyId || !config.apple?.privateKey) {
1943
+ sendError(res, 400, shared.ONESUB_ERROR_CODE.APPLE_CONFIG_MISSING, "Apple API credentials not configured");
1944
+ return;
1945
+ }
1946
+ try {
1947
+ const { originalTransactionId } = params;
1948
+ const existingForEnv = await store.getByTransactionId(originalTransactionId);
1949
+ const isSandbox = req.query["sandbox"] === "true" || existingForEnv?.environment === "Sandbox";
1950
+ const fresh = await fetchAppleSubscriptionStatus(originalTransactionId, config.apple, { sandbox: isSandbox });
1951
+ if (!fresh) {
1952
+ sendError(res, 404, shared.ONESUB_ERROR_CODE.TRANSACTION_NOT_FOUND, "Apple Status API returned no record");
1953
+ return;
1954
+ }
1955
+ const existing = existingForEnv;
1956
+ fresh.userId = existing?.userId ?? originalTransactionId;
1957
+ await store.save(fresh);
1958
+ res.status(200).json({ ok: true, subscription: fresh });
1959
+ } catch (err) {
1960
+ sendError(res, 500, shared.ONESUB_ERROR_CODE.INTERNAL_ERROR, err.message ?? "sync error");
1961
+ }
1962
+ });
1791
1963
  if (webhookQueue?.listDeadLetters) {
1792
1964
  router.get("/onesub/admin/webhook-deadletters", async (_req, res) => {
1793
1965
  try {
@@ -2033,6 +2205,55 @@ function createMetricsRouter(config, store, purchaseStore) {
2033
2205
  });
2034
2206
  return router;
2035
2207
  }
2208
+ var OFFER_SECRET_HEADER = "x-onesub-offer-secret";
2209
+ var offerBodySchema = zod.z.object({
2210
+ productId: zod.z.string().min(1).max(256),
2211
+ offerId: zod.z.string().min(1).max(256),
2212
+ applicationUsername: zod.z.string().min(1).max(256)
2213
+ });
2214
+ function createAppleOfferRouter(config) {
2215
+ const apple = config.apple;
2216
+ if (!apple?.offerKeyId || !apple?.offerPrivateKey) return null;
2217
+ const router = express.Router();
2218
+ router.post("/onesub/apple/offer-signature", async (req, res) => {
2219
+ if (config.adminSecret) {
2220
+ const provided = req.headers[OFFER_SECRET_HEADER];
2221
+ if (typeof provided !== "string" || provided !== config.adminSecret) {
2222
+ sendError(res, 401, shared.ONESUB_ERROR_CODE.UNAUTHORIZED, "Unauthorized");
2223
+ return;
2224
+ }
2225
+ }
2226
+ let body;
2227
+ try {
2228
+ body = offerBodySchema.parse(req.body);
2229
+ } catch (err) {
2230
+ if (err instanceof zod.z.ZodError) {
2231
+ sendZodError(res, err);
2232
+ return;
2233
+ }
2234
+ throw err;
2235
+ }
2236
+ if (!apple.bundleId) {
2237
+ sendError(res, 400, shared.ONESUB_ERROR_CODE.APPLE_CONFIG_MISSING, "config.apple.bundleId is required for offer signing");
2238
+ return;
2239
+ }
2240
+ try {
2241
+ const result = await signApplePromotionalOffer(
2242
+ {
2243
+ bundleId: apple.bundleId,
2244
+ productId: body.productId,
2245
+ offerId: body.offerId,
2246
+ applicationUsername: body.applicationUsername
2247
+ },
2248
+ apple
2249
+ );
2250
+ res.status(200).json(result);
2251
+ } catch (err) {
2252
+ sendError(res, 500, shared.ONESUB_ERROR_CODE.INTERNAL_ERROR, err.message ?? "Offer signing failed");
2253
+ }
2254
+ });
2255
+ return router;
2256
+ }
2036
2257
 
2037
2258
  // src/stores/schema.ts
2038
2259
  var SUBSCRIPTIONS_SCHEMA_SQL = `
@@ -3019,6 +3240,8 @@ function createOneSubMiddleware(config) {
3019
3240
  if (entitlementRouter) router.use(entitlementRouter);
3020
3241
  const metricsRouter = createMetricsRouter(config, store, purchaseStore);
3021
3242
  if (metricsRouter) router.use(metricsRouter);
3243
+ const appleOfferRouter = createAppleOfferRouter(config);
3244
+ if (appleOfferRouter) router.use(appleOfferRouter);
3022
3245
  return router;
3023
3246
  }
3024
3247
  function createOneSubServer(config) {
@@ -3072,11 +3295,13 @@ exports.createOneSubMiddleware = createOneSubMiddleware;
3072
3295
  exports.createOneSubServer = createOneSubServer;
3073
3296
  exports.evaluateEntitlement = evaluateEntitlement;
3074
3297
  exports.fetchAppleSubscriptionStatus = fetchAppleSubscriptionStatus;
3298
+ exports.fetchAppleTransactionHistory = fetchAppleTransactionHistory;
3075
3299
  exports.getDefaultCache = getDefaultCache;
3076
3300
  exports.log = log;
3077
3301
  exports.openapiHandler = openapiHandler;
3078
3302
  exports.setDefaultCache = setDefaultCache;
3079
3303
  exports.setLogger = setLogger;
3304
+ exports.signApplePromotionalOffer = signApplePromotionalOffer;
3080
3305
  exports.validateAppleReceipt = validateAppleReceipt;
3081
3306
  exports.validateGoogleReceipt = validateGoogleReceipt;
3082
3307
  exports.withSpan = withSpan;