@onesub/server 0.12.0 → 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.
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@ import express, { Router } from 'express';
2
2
  import { PURCHASE_TYPE, ROUTES, DEFAULT_PORT, ONESUB_ERROR_CODE, SUBSCRIPTION_STATUS, MOCK_RECEIPT_PREFIX } from '@onesub/shared';
3
3
  import { z } from 'zod';
4
4
  import { decodeJwt, decodeProtectedHeader, importX509, jwtVerify, importPKCS8, SignJWT, createRemoteJWKSet } from 'jose';
5
- import { X509Certificate, createHash } from 'crypto';
5
+ import { X509Certificate, randomUUID, createSign, createHash } from 'crypto';
6
6
 
7
7
  // src/index.ts
8
8
 
@@ -470,7 +470,9 @@ async function decodeAppleNotification(payload, skipJwsVerification = false) {
470
470
  environment,
471
471
  status,
472
472
  willRenew,
473
- expiresAt: tx.expiresDate ? new Date(tx.expiresDate).toISOString() : null
473
+ expiresAt: tx.expiresDate ? new Date(tx.expiresDate).toISOString() : null,
474
+ appAccountToken: tx.appAccountToken ?? null,
475
+ inAppOwnershipType: tx.inAppOwnershipType ?? null
474
476
  };
475
477
  }
476
478
  var appleJwtMintPromises = /* @__PURE__ */ new Map();
@@ -616,6 +618,91 @@ async function fetchAppleSubscriptionStatus(originalTransactionId, config, optio
616
618
  willRenew: renewal?.autoRenewStatus === 1
617
619
  };
618
620
  }
621
+ async function fetchAppleTransactionHistory(originalTransactionId, config, options) {
622
+ if (config.mockMode) return null;
623
+ let jwt;
624
+ try {
625
+ jwt = await makeAppleApiJwt(config);
626
+ } catch (err) {
627
+ log.warn("[onesub/apple] Cannot fetch transaction history \u2014 JWT mint failed:", err);
628
+ return null;
629
+ }
630
+ const host = options?.sandbox ? "api.storekit-sandbox.itunes.apple.com" : "api.storekit.itunes.apple.com";
631
+ const results = [];
632
+ let revision;
633
+ for (; ; ) {
634
+ const url = new URL(`https://${host}/inApps/v2/history/${encodeURIComponent(originalTransactionId)}`);
635
+ if (revision) url.searchParams.set("revision", revision);
636
+ let page;
637
+ try {
638
+ const resp = await fetchWithTimeout(url.toString(), {
639
+ headers: { Authorization: `Bearer ${jwt}` }
640
+ });
641
+ if (!resp.ok) {
642
+ const text = await resp.text();
643
+ log.warn(`[onesub/apple] Transaction History API error ${resp.status}: ${text}`);
644
+ return null;
645
+ }
646
+ page = await resp.json();
647
+ } catch (err) {
648
+ log.warn("[onesub/apple] Transaction History API network error:", err);
649
+ return null;
650
+ }
651
+ for (const signed of page.signedTransactions ?? []) {
652
+ try {
653
+ const tx = await decodeJws(signed, config.skipJwsVerification);
654
+ if (!tx.originalTransactionId || !tx.transactionId || !tx.productId) continue;
655
+ results.push({
656
+ originalTransactionId: tx.originalTransactionId,
657
+ transactionId: tx.transactionId,
658
+ productId: tx.productId,
659
+ type: tx.type ?? "Unknown",
660
+ purchasedAt: tx.purchaseDate ? new Date(tx.purchaseDate).toISOString() : (/* @__PURE__ */ new Date()).toISOString(),
661
+ expiresAt: tx.expiresDate ? new Date(tx.expiresDate).toISOString() : null,
662
+ appAccountToken: tx.appAccountToken ?? null,
663
+ inAppOwnershipType: tx.inAppOwnershipType ?? null,
664
+ revocationDate: tx.revocationDate ? new Date(tx.revocationDate).toISOString() : null
665
+ });
666
+ } catch {
667
+ }
668
+ }
669
+ if (!page.hasMore) break;
670
+ revision = page.revision;
671
+ }
672
+ return results;
673
+ }
674
+ async function signApplePromotionalOffer(input, config) {
675
+ const { offerKeyId, offerPrivateKey } = config;
676
+ if (!offerKeyId || !offerPrivateKey) {
677
+ throw new Error("[onesub/apple] Promotional Offer signing requires config.apple.offerKeyId and config.apple.offerPrivateKey");
678
+ }
679
+ const nonce = randomUUID().toLowerCase();
680
+ const timestamp = Date.now();
681
+ const separator = "\u2063";
682
+ const message = [
683
+ input.bundleId,
684
+ offerKeyId,
685
+ input.productId,
686
+ input.offerId,
687
+ input.applicationUsername,
688
+ nonce,
689
+ String(timestamp)
690
+ ].join(separator);
691
+ if (!offerPrivateKey.includes("BEGIN") || !offerPrivateKey.includes("END")) {
692
+ throw new Error("[onesub/apple] offerPrivateKey does not appear to be PEM-encoded");
693
+ }
694
+ let signatureBuffer;
695
+ try {
696
+ const sign = createSign("SHA256");
697
+ sign.update(message);
698
+ sign.end();
699
+ signatureBuffer = sign.sign(offerPrivateKey);
700
+ } catch (err) {
701
+ throw new Error(`[onesub/apple] Failed to sign promotional offer \u2014 check that offerPrivateKey is a valid ES256 PEM key: ${err.message}`);
702
+ }
703
+ const signature = signatureBuffer.toString("base64");
704
+ return { keyId: offerKeyId, nonce, timestamp, signature };
705
+ }
619
706
  var GOOGLE_NOTIFICATION_TYPE = {
620
707
  SUBSCRIPTION_RECOVERED: 1,
621
708
  SUBSCRIPTION_RENEWED: 2,
@@ -678,8 +765,8 @@ async function getAccessToken(serviceAccountKey) {
678
765
  })
679
766
  ).toString("base64url");
680
767
  const signingInput = `${header}.${payload}`;
681
- const { createSign } = await import('crypto');
682
- const sign = createSign("RSA-SHA256");
768
+ const { createSign: createSign2 } = await import('crypto');
769
+ const sign = createSign2("RSA-SHA256");
683
770
  sign.update(signingInput);
684
771
  const signature = sign.sign(key.private_key, "base64url");
685
772
  const assertion = `${signingInput}.${signature}`;
@@ -723,6 +810,7 @@ async function fetchProductPurchase(packageName, productId, purchaseToken, acces
723
810
  async function acknowledgeGoogleSubscription(purchaseToken, productId, config) {
724
811
  if (config.mockMode) return;
725
812
  if (!config.serviceAccountKey) return;
813
+ if (!config.packageName) return;
726
814
  let accessToken;
727
815
  try {
728
816
  accessToken = await getCachedAccessToken(config.serviceAccountKey);
@@ -748,6 +836,7 @@ async function acknowledgeGoogleSubscription(purchaseToken, productId, config) {
748
836
  async function acknowledgeGoogleProduct(purchaseToken, productId, config) {
749
837
  if (config.mockMode) return;
750
838
  if (!config.serviceAccountKey) return;
839
+ if (!config.packageName) return;
751
840
  let accessToken;
752
841
  try {
753
842
  accessToken = await getCachedAccessToken(config.serviceAccountKey);
@@ -772,6 +861,7 @@ async function acknowledgeGoogleProduct(purchaseToken, productId, config) {
772
861
  }
773
862
  async function consumeGoogleProductReceipt(purchaseToken, productId, config) {
774
863
  if (!config.serviceAccountKey) return;
864
+ if (!config.packageName) return;
775
865
  let accessToken;
776
866
  try {
777
867
  accessToken = await getCachedAccessToken(config.serviceAccountKey);
@@ -819,6 +909,10 @@ async function validateGoogleReceipt(receipt, productId, config) {
819
909
  log.warn("[onesub/google] No serviceAccountKey provided \u2014 cannot call Play API");
820
910
  return null;
821
911
  }
912
+ if (!config.packageName) {
913
+ log.warn("[onesub/google] No packageName provided \u2014 cannot call Play API");
914
+ return null;
915
+ }
822
916
  let purchase;
823
917
  try {
824
918
  const token = await getCachedAccessToken(config.serviceAccountKey);
@@ -871,6 +965,10 @@ async function validateGoogleProductReceipt(purchaseToken, productId, config, ty
871
965
  log.warn("[onesub/google] No serviceAccountKey \u2014 cannot validate product receipt");
872
966
  return null;
873
967
  }
968
+ if (!config.packageName) {
969
+ log.warn("[onesub/google] No packageName \u2014 cannot validate product receipt");
970
+ return null;
971
+ }
874
972
  let purchase;
875
973
  try {
876
974
  const token = await getCachedAccessToken(config.serviceAccountKey);
@@ -940,6 +1038,22 @@ function decodeGoogleVoidedNotification(payload) {
940
1038
  packageName: notification.packageName
941
1039
  };
942
1040
  }
1041
+ function decodeGoogleOneTimeProductNotification(payload) {
1042
+ let notification;
1043
+ try {
1044
+ const json = Buffer.from(payload.message.data, "base64").toString("utf-8");
1045
+ notification = JSON.parse(json);
1046
+ } catch {
1047
+ return null;
1048
+ }
1049
+ if (!notification.oneTimeProductNotification) return null;
1050
+ const { notificationType, purchaseToken, sku } = notification.oneTimeProductNotification;
1051
+ if (notificationType !== 1 && notificationType !== 2) {
1052
+ log.warn("[onesub/google] Unknown oneTimeProductNotification type:", notificationType);
1053
+ return null;
1054
+ }
1055
+ return { notificationType, purchaseToken, sku, packageName: notification.packageName };
1056
+ }
943
1057
  function isGoogleActiveNotification(notificationType) {
944
1058
  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;
945
1059
  }
@@ -1063,41 +1177,9 @@ function createStatusRouter(store) {
1063
1177
  });
1064
1178
  return router;
1065
1179
  }
1066
- var GOOGLE_JWKS_URL = "https://www.googleapis.com/oauth2/v3/certs";
1067
- var googleJwks = null;
1068
- function getGoogleJwks() {
1069
- if (!googleJwks) {
1070
- googleJwks = createRemoteJWKSet(new URL(GOOGLE_JWKS_URL));
1071
- }
1072
- return googleJwks;
1073
- }
1074
- async function verifyGooglePushToken(req, expectedAudience) {
1075
- const authHeader = req.headers["authorization"];
1076
- if (typeof authHeader !== "string" || !authHeader.startsWith("Bearer ")) {
1077
- return false;
1078
- }
1079
- const token = authHeader.slice("Bearer ".length).trim();
1080
- try {
1081
- await jwtVerify(token, getGoogleJwks(), { audience: expectedAudience });
1082
- return true;
1083
- } catch {
1084
- return false;
1085
- }
1086
- }
1087
- var APPLE_ACTIVE_TYPES = /* @__PURE__ */ new Set([
1088
- "SUBSCRIBED",
1089
- "DID_RENEW",
1090
- "DID_RECOVER",
1091
- "OFFER_REDEEMED"
1092
- ]);
1093
- var APPLE_CANCELED_TYPES = /* @__PURE__ */ new Set([
1094
- "REVOKE",
1095
- "REFUND",
1096
- "CONSUMPTION_REQUEST"
1097
- ]);
1098
- var APPLE_EXPIRED_TYPES = /* @__PURE__ */ new Set([
1099
- "EXPIRED"
1100
- ]);
1180
+ var APPLE_ACTIVE_TYPES = /* @__PURE__ */ new Set(["SUBSCRIBED", "DID_RENEW", "DID_RECOVER", "OFFER_REDEEMED"]);
1181
+ var APPLE_CANCELED_TYPES = /* @__PURE__ */ new Set(["REVOKE", "REFUND", "CONSUMPTION_REQUEST"]);
1182
+ var APPLE_EXPIRED_TYPES = /* @__PURE__ */ new Set(["EXPIRED"]);
1101
1183
  function mapAppleNotificationStatus(notificationType, subtype) {
1102
1184
  if (notificationType === "DID_FAIL_TO_RENEW") {
1103
1185
  return subtype === "GRACE_PERIOD" ? SUBSCRIPTION_STATUS.GRACE_PERIOD : SUBSCRIPTION_STATUS.ON_HOLD;
@@ -1108,283 +1190,315 @@ function mapAppleNotificationStatus(notificationType, subtype) {
1108
1190
  if (APPLE_ACTIVE_TYPES.has(notificationType)) return SUBSCRIPTION_STATUS.ACTIVE;
1109
1191
  return null;
1110
1192
  }
1111
- function createWebhookRouter(config, store, purchaseStore, webhookEventStore) {
1112
- const router = Router();
1113
- router.post(ROUTES.WEBHOOK_APPLE, async (req, res) => {
1114
- const body = req.body;
1115
- if (!body.signedPayload) {
1116
- sendError(res, 400, ONESUB_ERROR_CODE.MISSING_SIGNED_PAYLOAD, "Missing signedPayload");
1117
- return;
1118
- }
1119
- let payload;
1120
- try {
1121
- payload = await decodeJws(
1122
- body.signedPayload,
1123
- config.apple?.skipJwsVerification
1124
- );
1125
- } catch (err) {
1126
- log.error("[onesub/webhook/apple] Failed to decode signedPayload:", err);
1127
- sendError(res, 400, ONESUB_ERROR_CODE.INVALID_SIGNED_PAYLOAD, "Invalid signedPayload");
1193
+ async function handleAppleWebhook(req, res, config, store, purchaseStore, webhookEventStore) {
1194
+ const body = req.body;
1195
+ if (!body.signedPayload) {
1196
+ sendError(res, 400, ONESUB_ERROR_CODE.MISSING_SIGNED_PAYLOAD, "Missing signedPayload");
1197
+ return;
1198
+ }
1199
+ let payload;
1200
+ try {
1201
+ payload = await decodeJws(
1202
+ body.signedPayload,
1203
+ config.apple?.skipJwsVerification
1204
+ );
1205
+ } catch (err) {
1206
+ log.error("[onesub/webhook/apple] Failed to decode signedPayload:", err);
1207
+ sendError(res, 400, ONESUB_ERROR_CODE.INVALID_SIGNED_PAYLOAD, "Invalid signedPayload");
1208
+ return;
1209
+ }
1210
+ if (webhookEventStore && typeof payload.notificationUUID === "string") {
1211
+ const fresh = await webhookEventStore.markIfNew("apple", payload.notificationUUID);
1212
+ if (!fresh) {
1213
+ log.info("[onesub/webhook/apple] dedupe: already processed", payload.notificationUUID);
1214
+ res.status(200).json({ received: true, deduped: true });
1128
1215
  return;
1129
1216
  }
1130
- if (webhookEventStore && typeof payload.notificationUUID === "string") {
1131
- const fresh = await webhookEventStore.markIfNew("apple", payload.notificationUUID);
1132
- if (!fresh) {
1133
- log.info("[onesub/webhook/apple] dedupe: already processed", payload.notificationUUID);
1134
- res.status(200).json({ received: true, deduped: true });
1135
- return;
1217
+ }
1218
+ const decoded = await decodeAppleNotification(payload, config.apple?.skipJwsVerification);
1219
+ if (!decoded) {
1220
+ res.status(200).json({ received: true });
1221
+ return;
1222
+ }
1223
+ const {
1224
+ originalTransactionId,
1225
+ transactionId,
1226
+ type,
1227
+ productId,
1228
+ bundleId,
1229
+ environment,
1230
+ status,
1231
+ willRenew,
1232
+ expiresAt,
1233
+ appAccountToken,
1234
+ inAppOwnershipType
1235
+ } = decoded;
1236
+ const notificationType = payload.notificationType;
1237
+ const subtype = payload.subtype;
1238
+ const mapped = mapAppleNotificationStatus(notificationType, subtype);
1239
+ const finalStatus = mapped ?? status;
1240
+ if (notificationType === "CONSUMPTION_REQUEST" && config.apple?.consumptionInfoProvider && transactionId && productId) {
1241
+ const provider = config.apple.consumptionInfoProvider;
1242
+ const appleConfig = config.apple;
1243
+ void (async () => {
1244
+ try {
1245
+ const info = await provider({
1246
+ transactionId,
1247
+ originalTransactionId,
1248
+ productId,
1249
+ bundleId: bundleId ?? appleConfig.bundleId,
1250
+ environment
1251
+ });
1252
+ if (info) {
1253
+ await sendAppleConsumptionResponse(transactionId, info, appleConfig, {
1254
+ sandbox: environment === "Sandbox"
1255
+ });
1256
+ }
1257
+ } catch (err) {
1258
+ log.warn("[onesub/webhook/apple] consumptionInfoProvider failed:", err);
1259
+ }
1260
+ })();
1261
+ }
1262
+ const isOneTimePurchase = type === "Consumable" || type === "Non-Consumable";
1263
+ const isRefundOrRevoke = APPLE_CANCELED_TYPES.has(notificationType);
1264
+ try {
1265
+ if (isOneTimePurchase && isRefundOrRevoke) {
1266
+ const lookupId = transactionId ?? originalTransactionId;
1267
+ const removed = await purchaseStore.deletePurchaseByTransactionId(lookupId);
1268
+ if (!removed) {
1269
+ log.warn("[onesub/webhook/apple] IAP refund for unknown transaction:", lookupId);
1136
1270
  }
1137
- }
1138
- const decoded = await decodeAppleNotification(payload, config.apple?.skipJwsVerification);
1139
- if (!decoded) {
1140
1271
  res.status(200).json({ received: true });
1141
1272
  return;
1142
1273
  }
1143
- const {
1144
- originalTransactionId,
1145
- transactionId,
1146
- type,
1147
- productId,
1148
- bundleId,
1149
- environment,
1150
- status,
1151
- willRenew,
1152
- expiresAt
1153
- } = decoded;
1154
- const notificationType = payload.notificationType;
1155
- const subtype = payload.subtype;
1156
- const mapped = mapAppleNotificationStatus(notificationType, subtype);
1157
- const finalStatus = mapped ?? status;
1158
- if (notificationType === "CONSUMPTION_REQUEST" && config.apple?.consumptionInfoProvider && transactionId && productId) {
1159
- const provider = config.apple.consumptionInfoProvider;
1160
- const appleConfig = config.apple;
1161
- void (async () => {
1162
- try {
1163
- const info = await provider({
1164
- transactionId,
1165
- originalTransactionId,
1166
- productId,
1167
- bundleId: bundleId ?? appleConfig.bundleId,
1168
- environment
1169
- });
1170
- if (info) {
1171
- await sendAppleConsumptionResponse(transactionId, info, appleConfig, {
1172
- sandbox: environment === "Sandbox"
1173
- });
1174
- }
1175
- } catch (err) {
1176
- log.warn("[onesub/webhook/apple] consumptionInfoProvider failed:", err);
1177
- }
1178
- })();
1179
- }
1180
- const isOneTimePurchase = type === "Consumable" || type === "Non-Consumable";
1181
- const isRefundOrRevoke = APPLE_CANCELED_TYPES.has(notificationType);
1182
- try {
1183
- if (isOneTimePurchase && isRefundOrRevoke) {
1184
- const lookupId = transactionId ?? originalTransactionId;
1185
- const removed = await purchaseStore.deletePurchaseByTransactionId(lookupId);
1186
- if (!removed) {
1187
- log.warn(
1188
- "[onesub/webhook/apple] IAP refund for unknown transaction:",
1189
- lookupId
1190
- );
1191
- }
1192
- res.status(200).json({ received: true });
1193
- return;
1274
+ const existing = await store.getByTransactionId(originalTransactionId);
1275
+ if (existing) {
1276
+ const isSubscriptionRefund = isRefundOrRevoke && !isOneTimePurchase && notificationType !== "CONSUMPTION_REQUEST";
1277
+ const keepEntitlement = isSubscriptionRefund && config.refundPolicy === "until_expiry";
1278
+ const correctedUserId = appAccountToken && existing.userId === originalTransactionId ? appAccountToken : existing.userId;
1279
+ if (correctedUserId !== existing.userId) {
1280
+ log.info("[onesub/webhook/apple] correcting userId from originalTransactionId to appAccountToken:", correctedUserId);
1194
1281
  }
1195
- const existing = await store.getByTransactionId(originalTransactionId);
1196
- if (existing) {
1197
- const isSubscriptionRefund = isRefundOrRevoke && !isOneTimePurchase && notificationType !== "CONSUMPTION_REQUEST";
1198
- const keepEntitlement = isSubscriptionRefund && config.refundPolicy === "until_expiry";
1199
- const updated = keepEntitlement ? { ...existing, willRenew: false } : {
1200
- ...existing,
1201
- status: finalStatus,
1202
- willRenew,
1203
- // expiresAt may be absent on non-subscription payloads — keep the
1204
- // previously-stored value rather than overwriting with null/empty.
1205
- expiresAt: expiresAt ?? existing.expiresAt
1206
- };
1207
- await store.save(updated);
1208
- } else if (config.apple?.issuerId && config.apple?.keyId && config.apple?.privateKey) {
1209
- const fresh = await fetchAppleSubscriptionStatus(originalTransactionId, config.apple, {
1210
- sandbox: environment === "Sandbox"
1211
- });
1212
- if (fresh) {
1213
- fresh.userId = originalTransactionId;
1214
- await store.save(fresh);
1215
- } else {
1216
- log.warn(
1217
- "[onesub/webhook/apple] Unknown transaction and Status API returned no record:",
1218
- originalTransactionId
1219
- );
1282
+ const updated = keepEntitlement ? { ...existing, userId: correctedUserId, willRenew: false } : {
1283
+ ...existing,
1284
+ userId: correctedUserId,
1285
+ status: finalStatus,
1286
+ willRenew,
1287
+ expiresAt: expiresAt ?? existing.expiresAt
1288
+ };
1289
+ await store.save(updated);
1290
+ } else if (config.apple?.issuerId && config.apple?.keyId && config.apple?.privateKey) {
1291
+ const fresh = await fetchAppleSubscriptionStatus(originalTransactionId, config.apple, {
1292
+ sandbox: environment === "Sandbox"
1293
+ });
1294
+ if (fresh) {
1295
+ fresh.userId = appAccountToken ?? originalTransactionId;
1296
+ if (inAppOwnershipType === "FAMILY_SHARED") {
1297
+ const source = appAccountToken ? "appAccountToken" : "originalTransactionId (fallback)";
1298
+ log.info(`[onesub/webhook/apple] FAMILY_SHARED \u2014 userId: ${fresh.userId} (source: ${source})`);
1220
1299
  }
1300
+ await store.save(fresh);
1221
1301
  } else {
1222
1302
  log.warn(
1223
- "[onesub/webhook/apple] Received notification for unknown transaction (no API creds to recover):",
1303
+ "[onesub/webhook/apple] Unknown transaction and Status API returned no record:",
1224
1304
  originalTransactionId
1225
1305
  );
1226
1306
  }
1227
- res.status(200).json({ received: true });
1228
- } catch (err) {
1229
- log.error("[onesub/webhook/apple] Store update error:", err);
1230
- sendError(res, 500, ONESUB_ERROR_CODE.WEBHOOK_PROCESSING_FAILED, "Failed to update subscription");
1307
+ } else {
1308
+ log.warn(
1309
+ "[onesub/webhook/apple] Received notification for unknown transaction (no API creds to recover):",
1310
+ originalTransactionId
1311
+ );
1231
1312
  }
1232
- });
1233
- router.post(ROUTES.WEBHOOK_GOOGLE, async (req, res) => {
1234
- if (config.google?.pushAudience) {
1235
- const authenticated = await verifyGooglePushToken(req, config.google.pushAudience);
1236
- if (!authenticated) {
1237
- sendError(res, 401, ONESUB_ERROR_CODE.UNAUTHORIZED, "Unauthorized");
1238
- return;
1239
- }
1313
+ res.status(200).json({ received: true });
1314
+ } catch (err) {
1315
+ log.error("[onesub/webhook/apple] Store update error:", err);
1316
+ sendError(res, 500, ONESUB_ERROR_CODE.WEBHOOK_PROCESSING_FAILED, "Failed to update subscription");
1317
+ }
1318
+ }
1319
+ var GOOGLE_JWKS_URL = "https://www.googleapis.com/oauth2/v3/certs";
1320
+ var googleJwks = null;
1321
+ function getGoogleJwks() {
1322
+ if (!googleJwks) googleJwks = createRemoteJWKSet(new URL(GOOGLE_JWKS_URL));
1323
+ return googleJwks;
1324
+ }
1325
+ async function verifyGooglePushToken(req, expectedAudience) {
1326
+ const authHeader = req.headers["authorization"];
1327
+ if (typeof authHeader !== "string" || !authHeader.startsWith("Bearer ")) return false;
1328
+ try {
1329
+ await jwtVerify(authHeader.slice("Bearer ".length).trim(), getGoogleJwks(), {
1330
+ audience: expectedAudience
1331
+ });
1332
+ return true;
1333
+ } catch {
1334
+ return false;
1335
+ }
1336
+ }
1337
+ async function handleGoogleWebhook(req, res, config, store, purchaseStore, webhookEventStore) {
1338
+ if (config.google?.pushAudience) {
1339
+ const authenticated = await verifyGooglePushToken(req, config.google.pushAudience);
1340
+ if (!authenticated) {
1341
+ sendError(res, 401, ONESUB_ERROR_CODE.UNAUTHORIZED, "Unauthorized");
1342
+ return;
1240
1343
  }
1241
- const body = req.body;
1242
- if (!body.message?.data) {
1243
- sendError(res, 400, ONESUB_ERROR_CODE.MISSING_MESSAGE_DATA, "Missing message.data");
1344
+ }
1345
+ const body = req.body;
1346
+ if (!body.message?.data) {
1347
+ sendError(res, 400, ONESUB_ERROR_CODE.MISSING_MESSAGE_DATA, "Missing message.data");
1348
+ return;
1349
+ }
1350
+ if (webhookEventStore && typeof body.message.messageId === "string") {
1351
+ const fresh = await webhookEventStore.markIfNew("google", body.message.messageId);
1352
+ if (!fresh) {
1353
+ log.info("[onesub/webhook/google] dedupe: already processed", body.message.messageId);
1354
+ res.status(200).json({ received: true, deduped: true });
1244
1355
  return;
1245
1356
  }
1246
- if (webhookEventStore && typeof body.message.messageId === "string") {
1247
- const fresh = await webhookEventStore.markIfNew("google", body.message.messageId);
1248
- if (!fresh) {
1249
- log.info("[onesub/webhook/google] dedupe: already processed", body.message.messageId);
1250
- res.status(200).json({ received: true, deduped: true });
1251
- return;
1252
- }
1357
+ }
1358
+ const voided = decodeGoogleVoidedNotification(body);
1359
+ if (voided) {
1360
+ if (config.google?.packageName && voided.packageName !== config.google.packageName) {
1361
+ log.warn("[onesub/webhook/google] voided package name mismatch:", voided.packageName, "!==", config.google.packageName);
1362
+ sendError(res, 400, ONESUB_ERROR_CODE.PACKAGE_NAME_MISMATCH, "Package name mismatch");
1363
+ return;
1253
1364
  }
1254
- const voided = decodeGoogleVoidedNotification(body);
1255
- if (voided) {
1256
- if (config.google?.packageName && voided.packageName !== config.google.packageName) {
1257
- log.warn(
1258
- "[onesub/webhook/google] voided package name mismatch:",
1259
- voided.packageName,
1260
- "!==",
1261
- config.google.packageName
1262
- );
1263
- sendError(res, 400, ONESUB_ERROR_CODE.PACKAGE_NAME_MISMATCH, "Package name mismatch");
1264
- return;
1265
- }
1266
- try {
1267
- if (voided.productType === 1) {
1268
- const existing = await store.getByTransactionId(voided.purchaseToken);
1269
- if (existing) {
1270
- const updated = config.refundPolicy === "until_expiry" ? { ...existing, willRenew: false } : { ...existing, status: SUBSCRIPTION_STATUS.CANCELED };
1271
- await store.save(updated);
1272
- } else {
1273
- log.warn(
1274
- "[onesub/webhook/google] voided subscription for unknown purchaseToken:",
1275
- voided.purchaseToken
1276
- );
1277
- }
1365
+ try {
1366
+ if (voided.productType === 1) {
1367
+ const existing = await store.getByTransactionId(voided.purchaseToken);
1368
+ if (existing) {
1369
+ const updated = config.refundPolicy === "until_expiry" ? { ...existing, willRenew: false } : { ...existing, status: SUBSCRIPTION_STATUS.CANCELED };
1370
+ await store.save(updated);
1278
1371
  } else {
1279
- const removed = await purchaseStore.deletePurchaseByTransactionId(voided.orderId);
1280
- if (!removed) {
1281
- log.warn(
1282
- "[onesub/webhook/google] voided IAP for unknown orderId:",
1283
- voided.orderId
1284
- );
1285
- }
1372
+ log.warn("[onesub/webhook/google] voided subscription for unknown purchaseToken:", voided.purchaseToken);
1373
+ }
1374
+ } else {
1375
+ const removed = await purchaseStore.deletePurchaseByTransactionId(voided.orderId);
1376
+ if (!removed) {
1377
+ log.warn("[onesub/webhook/google] voided IAP for unknown orderId:", voided.orderId);
1286
1378
  }
1287
- res.status(200).json({ received: true });
1288
- } catch (err) {
1289
- log.error("[onesub/webhook/google] voided notification error:", err);
1290
- sendError(res, 500, ONESUB_ERROR_CODE.WEBHOOK_PROCESSING_FAILED, "Failed to process voided notification");
1291
1379
  }
1292
- return;
1293
- }
1294
- const notification = decodeGoogleNotification(body);
1295
- if (!notification) {
1296
1380
  res.status(200).json({ received: true });
1297
- return;
1381
+ } catch (err) {
1382
+ log.error("[onesub/webhook/google] voided notification error:", err);
1383
+ sendError(res, 500, ONESUB_ERROR_CODE.WEBHOOK_PROCESSING_FAILED, "Failed to process voided notification");
1298
1384
  }
1299
- const { notificationType, purchaseToken, subscriptionId, packageName } = notification;
1300
- if (config.google?.packageName && packageName !== config.google.packageName) {
1301
- log.warn(
1302
- "[onesub/webhook/google] Package name mismatch:",
1303
- packageName,
1304
- "!==",
1305
- config.google.packageName
1306
- );
1385
+ return;
1386
+ }
1387
+ const oneTimeProduct = decodeGoogleOneTimeProductNotification(body);
1388
+ if (oneTimeProduct) {
1389
+ if (config.google?.packageName && oneTimeProduct.packageName !== config.google.packageName) {
1390
+ log.warn("[onesub/webhook/google] oneTimeProduct package name mismatch:", oneTimeProduct.packageName, "!==", config.google.packageName);
1307
1391
  sendError(res, 400, ONESUB_ERROR_CODE.PACKAGE_NAME_MISMATCH, "Package name mismatch");
1308
1392
  return;
1309
1393
  }
1310
1394
  try {
1311
- const existing = await store.getByTransactionId(purchaseToken);
1312
- let finalStatus;
1313
- if (isGoogleActiveNotification(notificationType)) {
1314
- finalStatus = SUBSCRIPTION_STATUS.ACTIVE;
1315
- } else if (isGoogleGracePeriodNotification(notificationType)) {
1316
- finalStatus = SUBSCRIPTION_STATUS.GRACE_PERIOD;
1317
- } else if (isGoogleOnHoldNotification(notificationType)) {
1318
- finalStatus = SUBSCRIPTION_STATUS.ON_HOLD;
1319
- } else if (isGooglePausedNotification(notificationType)) {
1320
- finalStatus = SUBSCRIPTION_STATUS.PAUSED;
1321
- } else if (isGooglePriceChangeConfirmedNotification(notificationType)) {
1322
- finalStatus = SUBSCRIPTION_STATUS.ACTIVE;
1323
- } else if (isGoogleCanceledNotification(notificationType)) {
1324
- finalStatus = SUBSCRIPTION_STATUS.CANCELED;
1325
- } else if (isGoogleExpiredNotification(notificationType)) {
1326
- finalStatus = SUBSCRIPTION_STATUS.EXPIRED;
1395
+ const { notificationType: notificationType2, purchaseToken: purchaseToken2, sku } = oneTimeProduct;
1396
+ if (notificationType2 === 1) {
1397
+ log.info("[onesub/webhook/google] oneTimeProduct PURCHASED:", sku);
1398
+ if (config.google?.serviceAccountKey && config.google.packageName) {
1399
+ void acknowledgeGoogleProduct(purchaseToken2, sku, config.google).catch(
1400
+ (err) => log.warn(`[onesub/webhook/google] oneTimeProduct ack failed for SKU ${sku} \u2014 3-day auto-refund risk:`, err)
1401
+ );
1402
+ }
1327
1403
  } else {
1328
- finalStatus = SUBSCRIPTION_STATUS.ACTIVE;
1329
- }
1330
- if (isGooglePriceChangeConfirmedNotification(notificationType) && config.google?.onPriceChangeConfirmed) {
1331
- const hook = config.google.onPriceChangeConfirmed;
1332
- void Promise.resolve().then(() => hook({ purchaseToken, subscriptionId, packageName })).catch((err) => log.warn("[onesub/webhook/google] onPriceChangeConfirmed hook failed:", err));
1404
+ log.info("[onesub/webhook/google] oneTimeProduct CANCELED (pre-completion):", sku);
1333
1405
  }
1334
- if (existing) {
1335
- let updated = { ...existing, status: finalStatus };
1336
- if (config.google?.serviceAccountKey) {
1337
- const fresh = await validateGoogleReceipt(purchaseToken, subscriptionId, config.google);
1338
- if (fresh) {
1339
- const preserveNotificationStatus = finalStatus === SUBSCRIPTION_STATUS.GRACE_PERIOD || finalStatus === SUBSCRIPTION_STATUS.ON_HOLD;
1340
- updated = {
1341
- ...existing,
1342
- status: preserveNotificationStatus ? finalStatus : fresh.status,
1343
- expiresAt: fresh.expiresAt,
1344
- willRenew: fresh.willRenew,
1345
- // Propagate v2-only fields that fresh may have updated. Without
1346
- // these, paused→active transitions left autoResumeTime stale and
1347
- // upgrade chains lost their linkedPurchaseToken on subsequent
1348
- // notifications. Use ?? undefined so cleared values become undefined
1349
- // (e.g. resume from paused autoResumeTime should disappear).
1350
- autoResumeTime: fresh.autoResumeTime,
1351
- linkedPurchaseToken: fresh.linkedPurchaseToken ?? existing.linkedPurchaseToken
1352
- };
1353
- }
1406
+ res.status(200).json({ received: true });
1407
+ } catch (err) {
1408
+ log.error("[onesub/webhook/google] oneTimeProduct error:", err);
1409
+ sendError(res, 500, ONESUB_ERROR_CODE.WEBHOOK_PROCESSING_FAILED, "Failed to process oneTimeProduct notification");
1410
+ }
1411
+ return;
1412
+ }
1413
+ const notification = decodeGoogleNotification(body);
1414
+ if (!notification) {
1415
+ res.status(200).json({ received: true });
1416
+ return;
1417
+ }
1418
+ const { notificationType, purchaseToken, subscriptionId, packageName } = notification;
1419
+ if (config.google?.packageName && packageName !== config.google.packageName) {
1420
+ log.warn("[onesub/webhook/google] Package name mismatch:", packageName, "!==", config.google.packageName);
1421
+ sendError(res, 400, ONESUB_ERROR_CODE.PACKAGE_NAME_MISMATCH, "Package name mismatch");
1422
+ return;
1423
+ }
1424
+ try {
1425
+ const existing = await store.getByTransactionId(purchaseToken);
1426
+ let finalStatus;
1427
+ if (isGoogleActiveNotification(notificationType)) {
1428
+ finalStatus = SUBSCRIPTION_STATUS.ACTIVE;
1429
+ } else if (isGoogleGracePeriodNotification(notificationType)) {
1430
+ finalStatus = SUBSCRIPTION_STATUS.GRACE_PERIOD;
1431
+ } else if (isGoogleOnHoldNotification(notificationType)) {
1432
+ finalStatus = SUBSCRIPTION_STATUS.ON_HOLD;
1433
+ } else if (isGooglePausedNotification(notificationType)) {
1434
+ finalStatus = SUBSCRIPTION_STATUS.PAUSED;
1435
+ } else if (isGooglePriceChangeConfirmedNotification(notificationType)) {
1436
+ finalStatus = SUBSCRIPTION_STATUS.ACTIVE;
1437
+ } else if (isGoogleCanceledNotification(notificationType)) {
1438
+ finalStatus = SUBSCRIPTION_STATUS.CANCELED;
1439
+ } else if (isGoogleExpiredNotification(notificationType)) {
1440
+ finalStatus = SUBSCRIPTION_STATUS.EXPIRED;
1441
+ } else {
1442
+ finalStatus = SUBSCRIPTION_STATUS.ACTIVE;
1443
+ }
1444
+ if (isGooglePriceChangeConfirmedNotification(notificationType) && config.google?.onPriceChangeConfirmed) {
1445
+ const hook = config.google.onPriceChangeConfirmed;
1446
+ void Promise.resolve().then(() => hook({ purchaseToken, subscriptionId, packageName })).catch((err) => log.warn("[onesub/webhook/google] onPriceChangeConfirmed hook failed:", err));
1447
+ }
1448
+ if (existing) {
1449
+ let updated = { ...existing, status: finalStatus };
1450
+ if (config.google?.serviceAccountKey) {
1451
+ const fresh = await validateGoogleReceipt(purchaseToken, subscriptionId, config.google);
1452
+ if (fresh) {
1453
+ const preserveNotificationStatus = finalStatus === SUBSCRIPTION_STATUS.GRACE_PERIOD || finalStatus === SUBSCRIPTION_STATUS.ON_HOLD;
1454
+ updated = {
1455
+ ...existing,
1456
+ status: preserveNotificationStatus ? finalStatus : fresh.status,
1457
+ expiresAt: fresh.expiresAt,
1458
+ willRenew: fresh.willRenew,
1459
+ autoResumeTime: fresh.autoResumeTime,
1460
+ linkedPurchaseToken: fresh.linkedPurchaseToken ?? existing.linkedPurchaseToken
1461
+ };
1354
1462
  }
1355
- await store.save(updated);
1356
- } else {
1357
- if (config.google?.serviceAccountKey) {
1358
- const fresh = await validateGoogleReceipt(purchaseToken, subscriptionId, config.google);
1359
- if (fresh) {
1360
- if (fresh.linkedPurchaseToken) {
1361
- const previous = await store.getByTransactionId(fresh.linkedPurchaseToken);
1362
- if (previous) {
1363
- fresh.userId = previous.userId;
1364
- log.info(
1365
- `[onesub/webhook/google] inherited userId ${previous.userId} from linkedPurchaseToken ${fresh.linkedPurchaseToken} \u2192 new token ${purchaseToken}`
1366
- );
1367
- } else {
1368
- fresh.userId = purchaseToken;
1369
- }
1370
- } else {
1371
- fresh.userId = purchaseToken;
1463
+ }
1464
+ await store.save(updated);
1465
+ } else {
1466
+ if (config.google?.serviceAccountKey) {
1467
+ const fresh = await validateGoogleReceipt(purchaseToken, subscriptionId, config.google);
1468
+ if (fresh) {
1469
+ if (fresh.linkedPurchaseToken) {
1470
+ const previous = await store.getByTransactionId(fresh.linkedPurchaseToken);
1471
+ fresh.userId = previous ? previous.userId : purchaseToken;
1472
+ if (previous) {
1473
+ log.info(`[onesub/webhook/google] inherited userId ${previous.userId} from linkedPurchaseToken ${fresh.linkedPurchaseToken} \u2192 new token ${purchaseToken}`);
1372
1474
  }
1373
- await store.save(fresh);
1475
+ } else {
1476
+ fresh.userId = purchaseToken;
1374
1477
  }
1375
- } else {
1376
- log.warn(
1377
- "[onesub/webhook/google] Unknown purchase token and no serviceAccountKey to re-fetch:",
1378
- purchaseToken
1379
- );
1478
+ await store.save(fresh);
1380
1479
  }
1480
+ } else {
1481
+ log.warn("[onesub/webhook/google] Unknown purchase token and no serviceAccountKey to re-fetch:", purchaseToken);
1381
1482
  }
1382
- res.status(200).json({ received: true });
1383
- } catch (err) {
1384
- log.error("[onesub/webhook/google] Error handling notification:", err);
1385
- sendError(res, 500, ONESUB_ERROR_CODE.WEBHOOK_PROCESSING_FAILED, "Failed to process notification");
1386
1483
  }
1387
- });
1484
+ res.status(200).json({ received: true });
1485
+ } catch (err) {
1486
+ log.error("[onesub/webhook/google] Error handling notification:", err);
1487
+ sendError(res, 500, ONESUB_ERROR_CODE.WEBHOOK_PROCESSING_FAILED, "Failed to process notification");
1488
+ }
1489
+ }
1490
+
1491
+ // src/routes/webhook.ts
1492
+ function createWebhookRouter(config, store, purchaseStore, webhookEventStore) {
1493
+ const router = Router();
1494
+ router.post(
1495
+ ROUTES.WEBHOOK_APPLE,
1496
+ (req, res) => handleAppleWebhook(req, res, config, store, purchaseStore, webhookEventStore)
1497
+ );
1498
+ router.post(
1499
+ ROUTES.WEBHOOK_GOOGLE,
1500
+ (req, res) => handleGoogleWebhook(req, res, config, store, purchaseStore, webhookEventStore)
1501
+ );
1388
1502
  return router;
1389
1503
  }
1390
1504
  var NO_PURCHASE = { valid: false, purchase: null };
@@ -1807,6 +1921,38 @@ function createAdminRouter(config, purchaseStore, store, webhookQueue) {
1807
1921
  sendError(res, 500, ONESUB_ERROR_CODE.STORE_ERROR, err.message ?? "customer error");
1808
1922
  }
1809
1923
  });
1924
+ const syncAppleParamsSchema = z.object({
1925
+ originalTransactionId: z.string().min(1).max(256)
1926
+ });
1927
+ router.post(ROUTES.ADMIN_SYNC_APPLE, async (req, res) => {
1928
+ let params;
1929
+ try {
1930
+ params = syncAppleParamsSchema.parse(req.params);
1931
+ } catch {
1932
+ sendError(res, 400, ONESUB_ERROR_CODE.INVALID_INPUT, "originalTransactionId required");
1933
+ return;
1934
+ }
1935
+ if (!config.apple?.issuerId || !config.apple?.keyId || !config.apple?.privateKey) {
1936
+ sendError(res, 400, ONESUB_ERROR_CODE.APPLE_CONFIG_MISSING, "Apple API credentials not configured");
1937
+ return;
1938
+ }
1939
+ try {
1940
+ const { originalTransactionId } = params;
1941
+ const existingForEnv = await store.getByTransactionId(originalTransactionId);
1942
+ const isSandbox = req.query["sandbox"] === "true" || existingForEnv?.environment === "Sandbox";
1943
+ const fresh = await fetchAppleSubscriptionStatus(originalTransactionId, config.apple, { sandbox: isSandbox });
1944
+ if (!fresh) {
1945
+ sendError(res, 404, ONESUB_ERROR_CODE.TRANSACTION_NOT_FOUND, "Apple Status API returned no record");
1946
+ return;
1947
+ }
1948
+ const existing = existingForEnv;
1949
+ fresh.userId = existing?.userId ?? originalTransactionId;
1950
+ await store.save(fresh);
1951
+ res.status(200).json({ ok: true, subscription: fresh });
1952
+ } catch (err) {
1953
+ sendError(res, 500, ONESUB_ERROR_CODE.INTERNAL_ERROR, err.message ?? "sync error");
1954
+ }
1955
+ });
1810
1956
  if (webhookQueue?.listDeadLetters) {
1811
1957
  router.get("/onesub/admin/webhook-deadletters", async (_req, res) => {
1812
1958
  try {
@@ -2052,6 +2198,55 @@ function createMetricsRouter(config, store, purchaseStore) {
2052
2198
  });
2053
2199
  return router;
2054
2200
  }
2201
+ var OFFER_SECRET_HEADER = "x-onesub-offer-secret";
2202
+ var offerBodySchema = z.object({
2203
+ productId: z.string().min(1).max(256),
2204
+ offerId: z.string().min(1).max(256),
2205
+ applicationUsername: z.string().min(1).max(256)
2206
+ });
2207
+ function createAppleOfferRouter(config) {
2208
+ const apple = config.apple;
2209
+ if (!apple?.offerKeyId || !apple?.offerPrivateKey) return null;
2210
+ const router = Router();
2211
+ router.post("/onesub/apple/offer-signature", async (req, res) => {
2212
+ if (config.adminSecret) {
2213
+ const provided = req.headers[OFFER_SECRET_HEADER];
2214
+ if (typeof provided !== "string" || provided !== config.adminSecret) {
2215
+ sendError(res, 401, ONESUB_ERROR_CODE.UNAUTHORIZED, "Unauthorized");
2216
+ return;
2217
+ }
2218
+ }
2219
+ let body;
2220
+ try {
2221
+ body = offerBodySchema.parse(req.body);
2222
+ } catch (err) {
2223
+ if (err instanceof z.ZodError) {
2224
+ sendZodError(res, err);
2225
+ return;
2226
+ }
2227
+ throw err;
2228
+ }
2229
+ if (!apple.bundleId) {
2230
+ sendError(res, 400, ONESUB_ERROR_CODE.APPLE_CONFIG_MISSING, "config.apple.bundleId is required for offer signing");
2231
+ return;
2232
+ }
2233
+ try {
2234
+ const result = await signApplePromotionalOffer(
2235
+ {
2236
+ bundleId: apple.bundleId,
2237
+ productId: body.productId,
2238
+ offerId: body.offerId,
2239
+ applicationUsername: body.applicationUsername
2240
+ },
2241
+ apple
2242
+ );
2243
+ res.status(200).json(result);
2244
+ } catch (err) {
2245
+ sendError(res, 500, ONESUB_ERROR_CODE.INTERNAL_ERROR, err.message ?? "Offer signing failed");
2246
+ }
2247
+ });
2248
+ return router;
2249
+ }
2055
2250
 
2056
2251
  // src/stores/schema.ts
2057
2252
  var SUBSCRIPTIONS_SCHEMA_SQL = `
@@ -3038,6 +3233,8 @@ function createOneSubMiddleware(config) {
3038
3233
  if (entitlementRouter) router.use(entitlementRouter);
3039
3234
  const metricsRouter = createMetricsRouter(config, store, purchaseStore);
3040
3235
  if (metricsRouter) router.use(metricsRouter);
3236
+ const appleOfferRouter = createAppleOfferRouter(config);
3237
+ if (appleOfferRouter) router.use(appleOfferRouter);
3041
3238
  return router;
3042
3239
  }
3043
3240
  function createOneSubServer(config) {
@@ -3048,7 +3245,6 @@ function createOneSubServer(config) {
3048
3245
  app.use(createOneSubMiddleware(config));
3049
3246
  return app;
3050
3247
  }
3051
- var index_default = createOneSubMiddleware;
3052
3248
  var isMain = typeof process !== "undefined" && process.argv[1] != null && new URL(import.meta.url).pathname.endsWith(process.argv[1].replace(/\\/g, "/"));
3053
3249
  if (isMain) {
3054
3250
  const config = {
@@ -3074,6 +3270,6 @@ if (isMain) {
3074
3270
  });
3075
3271
  }
3076
3272
 
3077
- export { BullMQWebhookQueue, CacheWebhookEventStore, InMemoryCacheAdapter, InMemoryPurchaseStore, InMemorySubscriptionStore, InMemoryWebhookEventStore, InProcessWebhookQueue, ONESUB_OPENAPI, PostgresPurchaseStore, PostgresSubscriptionStore, RedisCacheAdapter, RedisPurchaseStore, RedisSubscriptionStore, RedisWebhookEventStore, createOneSubMiddleware, createOneSubServer, index_default as default, evaluateEntitlement, fetchAppleSubscriptionStatus, getDefaultCache, log, openapiHandler, setDefaultCache, setLogger, validateAppleReceipt, validateGoogleReceipt, withSpan };
3273
+ export { BullMQWebhookQueue, CacheWebhookEventStore, InMemoryCacheAdapter, InMemoryPurchaseStore, InMemorySubscriptionStore, InMemoryWebhookEventStore, InProcessWebhookQueue, ONESUB_OPENAPI, PostgresPurchaseStore, PostgresSubscriptionStore, RedisCacheAdapter, RedisPurchaseStore, RedisSubscriptionStore, RedisWebhookEventStore, createOneSubMiddleware, createOneSubServer, evaluateEntitlement, fetchAppleSubscriptionStatus, fetchAppleTransactionHistory, getDefaultCache, log, openapiHandler, setDefaultCache, setLogger, signApplePromotionalOffer, validateAppleReceipt, validateGoogleReceipt, withSpan };
3078
3274
  //# sourceMappingURL=index.js.map
3079
3275
  //# sourceMappingURL=index.js.map