@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.
- package/dist/__tests__/admin-customer-detail.test.js +146 -0
- package/dist/__tests__/admin-customer-detail.test.js.map +1 -0
- package/dist/__tests__/admin-list-subscriptions.test.js +192 -0
- package/dist/__tests__/admin-list-subscriptions.test.js.map +1 -0
- package/dist/__tests__/admin-subscription-detail.test.js +112 -0
- package/dist/__tests__/admin-subscription-detail.test.js.map +1 -0
- package/dist/__tests__/apple-root-ca.test.js +41 -0
- package/dist/__tests__/apple-root-ca.test.js.map +1 -0
- package/dist/__tests__/apple-status-api.test.js +345 -0
- package/dist/__tests__/apple-status-api.test.js.map +1 -0
- package/dist/__tests__/cache.test.js +34 -0
- package/dist/__tests__/cache.test.js.map +1 -0
- package/dist/__tests__/entitlements.test.js +268 -0
- package/dist/__tests__/entitlements.test.js.map +1 -0
- package/dist/__tests__/error-codes.test.js +168 -0
- package/dist/__tests__/error-codes.test.js.map +1 -0
- package/dist/__tests__/fetch-hardening.test.js +192 -0
- package/dist/__tests__/fetch-hardening.test.js.map +1 -0
- package/dist/__tests__/google-paused.test.js +126 -0
- package/dist/__tests__/google-paused.test.js.map +1 -0
- package/dist/__tests__/google-price-change.test.js +186 -0
- package/dist/__tests__/google-price-change.test.js.map +1 -0
- package/dist/__tests__/google-subscriptions-v2.test.js +206 -0
- package/dist/__tests__/google-subscriptions-v2.test.js.map +1 -0
- package/dist/__tests__/lifecycle-scenarios.test.js +579 -0
- package/dist/__tests__/lifecycle-scenarios.test.js.map +1 -0
- package/dist/__tests__/lifecycle-states.test.js +423 -0
- package/dist/__tests__/lifecycle-states.test.js.map +1 -0
- package/dist/__tests__/linked-purchase-token.test.js +227 -0
- package/dist/__tests__/linked-purchase-token.test.js.map +1 -0
- package/dist/__tests__/metrics.test.js +340 -0
- package/dist/__tests__/metrics.test.js.map +1 -0
- package/dist/__tests__/mock-provider.test.js +188 -0
- package/dist/__tests__/mock-provider.test.js.map +1 -0
- package/dist/__tests__/openapi.test.js +32 -0
- package/dist/__tests__/openapi.test.js.map +1 -0
- package/dist/__tests__/paused-auto-resume.test.js +162 -0
- package/dist/__tests__/paused-auto-resume.test.js.map +1 -0
- package/dist/__tests__/providers.test.js +321 -0
- package/dist/__tests__/providers.test.js.map +1 -0
- package/dist/__tests__/redis-store.test.js +117 -0
- package/dist/__tests__/redis-store.test.js.map +1 -0
- package/dist/__tests__/refund-policy.test.js +317 -0
- package/dist/__tests__/refund-policy.test.js.map +1 -0
- package/dist/__tests__/schema.test.js +46 -0
- package/dist/__tests__/schema.test.js.map +1 -0
- package/dist/__tests__/store.test.js +172 -0
- package/dist/__tests__/store.test.js.map +1 -0
- package/dist/__tests__/test-utils.js +30 -0
- package/dist/__tests__/test-utils.js.map +1 -0
- package/dist/__tests__/webhook-events.test.js +36 -0
- package/dist/__tests__/webhook-events.test.js.map +1 -0
- package/dist/__tests__/webhook-queue.test.js +26 -0
- package/dist/__tests__/webhook-queue.test.js.map +1 -0
- package/dist/__tests__/webhook-refund.test.js +313 -0
- package/dist/__tests__/webhook-refund.test.js.map +1 -0
- package/dist/cache.js +43 -0
- package/dist/cache.js.map +1 -0
- package/dist/errors.js +19 -0
- package/dist/errors.js.map +1 -0
- package/dist/http.js +36 -0
- package/dist/http.js.map +1 -0
- package/dist/index.cjs +231 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +231 -8
- package/dist/index.js.map +1 -1
- package/dist/logger.js +20 -0
- package/dist/logger.js.map +1 -0
- package/dist/openapi.js +196 -0
- package/dist/openapi.js.map +1 -0
- package/dist/providers/apple-root-ca.js +38 -0
- package/dist/providers/apple-root-ca.js.map +1 -0
- package/dist/providers/apple.d.ts +76 -0
- package/dist/providers/apple.d.ts.map +1 -1
- package/dist/providers/apple.js +510 -0
- package/dist/providers/apple.js.map +1 -0
- package/dist/providers/google.d.ts +25 -0
- package/dist/providers/google.d.ts.map +1 -1
- package/dist/providers/google.js +522 -0
- package/dist/providers/google.js.map +1 -0
- package/dist/providers/mock.js +93 -0
- package/dist/providers/mock.js.map +1 -0
- package/dist/routes/admin.d.ts.map +1 -1
- package/dist/routes/admin.js +287 -0
- package/dist/routes/admin.js.map +1 -0
- package/dist/routes/apple-offer.d.ts +21 -0
- package/dist/routes/apple-offer.d.ts.map +1 -0
- package/dist/routes/entitlements.js +130 -0
- package/dist/routes/entitlements.js.map +1 -0
- package/dist/routes/metrics.js +281 -0
- package/dist/routes/metrics.js.map +1 -0
- package/dist/routes/purchase.js +187 -0
- package/dist/routes/purchase.js.map +1 -0
- package/dist/routes/status.js +55 -0
- package/dist/routes/status.js.map +1 -0
- package/dist/routes/validate.js +70 -0
- package/dist/routes/validate.js.map +1 -0
- package/dist/routes/webhook-apple.d.ts.map +1 -1
- package/dist/routes/webhook-google.d.ts.map +1 -1
- package/dist/routes/webhook.js +461 -0
- package/dist/routes/webhook.js.map +1 -0
- package/dist/store.js +145 -0
- package/dist/store.js.map +1 -0
- package/dist/stores/postgres.js +329 -0
- package/dist/stores/postgres.js.map +1 -0
- package/dist/stores/redis.js +255 -0
- package/dist/stores/redis.js.map +1 -0
- package/dist/stores/schema.js +67 -0
- package/dist/stores/schema.js.map +1 -0
- package/dist/tracing.js +74 -0
- package/dist/tracing.js.map +1 -0
- package/dist/webhook-events.js +57 -0
- package/dist/webhook-events.js.map +1 -0
- package/dist/webhook-queue.js +108 -0
- package/dist/webhook-queue.js.map +1 -0
- 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 =
|
|
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
|
|
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;
|