@onesub/server 0.25.0 → 0.26.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__/route-log-fields.test.d.ts +20 -0
- package/dist/__tests__/route-log-fields.test.d.ts.map +1 -0
- package/dist/apps.d.ts.map +1 -1
- package/dist/index.cjs +118 -68
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +118 -68
- package/dist/index.js.map +1 -1
- package/dist/log-format.d.ts +2 -0
- package/dist/log-format.d.ts.map +1 -1
- package/dist/routes/admin.d.ts.map +1 -1
- package/dist/routes/purchase.d.ts.map +1 -1
- package/dist/routes/validate.d.ts.map +1 -1
- package/dist/routes/webhook-apple.d.ts.map +1 -1
- package/dist/routes/webhook-google.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -442,7 +442,7 @@ function outcomePasses(outcome, tag) {
|
|
|
442
442
|
throw new Error(`[onesub/mock/${tag}] simulated upstream network error`);
|
|
443
443
|
}
|
|
444
444
|
if (outcome.kind === "valid" || outcome.kind === "sandbox") return true;
|
|
445
|
-
log.warn(
|
|
445
|
+
log.warn("[onesub/mock] receipt rejected", { provider: tag, outcome: outcome.kind });
|
|
446
446
|
return false;
|
|
447
447
|
}
|
|
448
448
|
function deterministicTransactionId(prefix, receipt) {
|
|
@@ -1494,7 +1494,10 @@ function buildAppRegistry(config) {
|
|
|
1494
1494
|
// that would validate one app's receipt against another's credentials.
|
|
1495
1495
|
(config.apple || config.google || apps.length === 1 ? apps[0] : void 0);
|
|
1496
1496
|
if (apps.length > 1) {
|
|
1497
|
-
log.info("[onesub] Multi-app mode
|
|
1497
|
+
log.info("[onesub] Multi-app mode", {
|
|
1498
|
+
appIds: apps.map(appName),
|
|
1499
|
+
defaultAppId: defaultApp ? appName(defaultApp) : null
|
|
1500
|
+
});
|
|
1498
1501
|
}
|
|
1499
1502
|
function match(hint) {
|
|
1500
1503
|
if (hint.appId) {
|
|
@@ -1502,13 +1505,13 @@ function buildAppRegistry(config) {
|
|
|
1502
1505
|
(a) => a.id === hint.appId || a.apple?.bundleId === hint.appId || a.google?.packageName === hint.appId
|
|
1503
1506
|
);
|
|
1504
1507
|
if (byId) return byId;
|
|
1505
|
-
log.warn("[onesub] Unknown appId
|
|
1508
|
+
log.warn("[onesub] Unknown appId", { appId: hint.appId });
|
|
1506
1509
|
return void 0;
|
|
1507
1510
|
}
|
|
1508
1511
|
if (hint.bundleId) {
|
|
1509
1512
|
const byBundle = apps.find((a) => a.apple?.bundleId === hint.bundleId);
|
|
1510
1513
|
if (byBundle) return byBundle;
|
|
1511
|
-
log.warn("[onesub] No app configured for bundleId
|
|
1514
|
+
log.warn("[onesub] No app configured for bundleId", { bundleId: hint.bundleId });
|
|
1512
1515
|
return void 0;
|
|
1513
1516
|
}
|
|
1514
1517
|
return defaultApp;
|
|
@@ -1596,9 +1599,10 @@ function createValidateRouter(config, store) {
|
|
|
1596
1599
|
delete sub.boundAccountId;
|
|
1597
1600
|
const bindingMismatch = platform === "apple" ? boundAccountId && boundAccountId.toLowerCase() !== userId.toLowerCase() : boundAccountId && boundAccountId !== userId;
|
|
1598
1601
|
if (bindingMismatch) {
|
|
1599
|
-
log.warn(
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
+
log.warn("[onesub/validate] account binding mismatch \u2014 receipt token does not match userId", {
|
|
1603
|
+
originalTransactionId: sub.originalTransactionId,
|
|
1604
|
+
userId
|
|
1605
|
+
});
|
|
1602
1606
|
sendError(
|
|
1603
1607
|
res,
|
|
1604
1608
|
409,
|
|
@@ -1611,7 +1615,7 @@ function createValidateRouter(config, store) {
|
|
|
1611
1615
|
const isSandbox = sub.sandbox === true;
|
|
1612
1616
|
delete sub.sandbox;
|
|
1613
1617
|
if (isSandbox && getTestOverride(userId) === false) {
|
|
1614
|
-
log.warn(
|
|
1618
|
+
log.warn("[onesub/validate] sandbox test override active \u2014 forcing not-entitled", { userId });
|
|
1615
1619
|
sub.status = SUBSCRIPTION_STATUS.EXPIRED;
|
|
1616
1620
|
sub.willRenew = false;
|
|
1617
1621
|
}
|
|
@@ -1623,7 +1627,7 @@ function createValidateRouter(config, store) {
|
|
|
1623
1627
|
const response = { valid: true, subscription: sub };
|
|
1624
1628
|
res.status(200).json(response);
|
|
1625
1629
|
} catch (err2) {
|
|
1626
|
-
log.error("[onesub/validate] Unexpected error
|
|
1630
|
+
log.error("[onesub/validate] Unexpected error", { userId, productId, platform, err: err2 });
|
|
1627
1631
|
sendError(res, 500, ONESUB_ERROR_CODE.INTERNAL_ERROR, "Internal server error during receipt validation", NO_SUB);
|
|
1628
1632
|
}
|
|
1629
1633
|
});
|
|
@@ -1655,7 +1659,7 @@ function createStatusRouter(store) {
|
|
|
1655
1659
|
const response = { active, subscription: sub };
|
|
1656
1660
|
res.status(200).json(response);
|
|
1657
1661
|
} catch (err2) {
|
|
1658
|
-
log.error("[onesub/status] Store error
|
|
1662
|
+
log.error("[onesub/status] Store error", { userId, err: err2 });
|
|
1659
1663
|
sendError(res, 500, ONESUB_ERROR_CODE.STORE_ERROR, "Internal server error", NO_SUB2);
|
|
1660
1664
|
}
|
|
1661
1665
|
});
|
|
@@ -1763,7 +1767,7 @@ async function processAppleNotification(work, config, store, purchaseStore) {
|
|
|
1763
1767
|
});
|
|
1764
1768
|
}
|
|
1765
1769
|
} catch (err2) {
|
|
1766
|
-
log.warn("[onesub/webhook/apple] consumptionInfoProvider failed
|
|
1770
|
+
log.warn("[onesub/webhook/apple] consumptionInfoProvider failed", { transactionId, err: err2 });
|
|
1767
1771
|
}
|
|
1768
1772
|
})();
|
|
1769
1773
|
}
|
|
@@ -1776,7 +1780,7 @@ async function processAppleNotification(work, config, store, purchaseStore) {
|
|
|
1776
1780
|
const lookupId = transactionId ?? originalTransactionId;
|
|
1777
1781
|
const removed = await purchaseStore.deletePurchaseByTransactionId(lookupId);
|
|
1778
1782
|
if (!removed) {
|
|
1779
|
-
log.warn("[onesub/webhook/apple] IAP refund for unknown transaction
|
|
1783
|
+
log.warn("[onesub/webhook/apple] IAP refund for unknown transaction", { transactionId: lookupId });
|
|
1780
1784
|
}
|
|
1781
1785
|
return;
|
|
1782
1786
|
}
|
|
@@ -1786,7 +1790,10 @@ async function processAppleNotification(work, config, store, purchaseStore) {
|
|
|
1786
1790
|
const keepEntitlement = isSubscriptionRefund && config.refundPolicy === "until_expiry";
|
|
1787
1791
|
const correctedUserId = appAccountToken && existing.userId === originalTransactionId ? appAccountToken : existing.userId;
|
|
1788
1792
|
if (correctedUserId !== existing.userId) {
|
|
1789
|
-
log.info("[onesub/webhook/apple] correcting userId from originalTransactionId to appAccountToken
|
|
1793
|
+
log.info("[onesub/webhook/apple] correcting userId from originalTransactionId to appAccountToken", {
|
|
1794
|
+
originalTransactionId,
|
|
1795
|
+
userId: correctedUserId
|
|
1796
|
+
});
|
|
1790
1797
|
}
|
|
1791
1798
|
const updated = keepEntitlement ? { ...existing, userId: correctedUserId, willRenew: false } : {
|
|
1792
1799
|
...existing,
|
|
@@ -1804,20 +1811,22 @@ async function processAppleNotification(work, config, store, purchaseStore) {
|
|
|
1804
1811
|
fresh.userId = appAccountToken ?? originalTransactionId;
|
|
1805
1812
|
if (inAppOwnershipType === "FAMILY_SHARED") {
|
|
1806
1813
|
const source = appAccountToken ? "appAccountToken" : "originalTransactionId (fallback)";
|
|
1807
|
-
log.info(
|
|
1814
|
+
log.info("[onesub/webhook/apple] FAMILY_SHARED", {
|
|
1815
|
+
originalTransactionId,
|
|
1816
|
+
userId: fresh.userId,
|
|
1817
|
+
userIdSource: source
|
|
1818
|
+
});
|
|
1808
1819
|
}
|
|
1809
1820
|
await store.save(fresh);
|
|
1810
1821
|
} else {
|
|
1811
|
-
log.warn(
|
|
1812
|
-
"[onesub/webhook/apple] Unknown transaction and Status API returned no record:",
|
|
1822
|
+
log.warn("[onesub/webhook/apple] Unknown transaction and Status API returned no record", {
|
|
1813
1823
|
originalTransactionId
|
|
1814
|
-
);
|
|
1824
|
+
});
|
|
1815
1825
|
}
|
|
1816
1826
|
} else {
|
|
1817
|
-
log.warn(
|
|
1818
|
-
"[onesub/webhook/apple] Received notification for unknown transaction (no API creds to recover):",
|
|
1827
|
+
log.warn("[onesub/webhook/apple] Received notification for unknown transaction (no API creds to recover)", {
|
|
1819
1828
|
originalTransactionId
|
|
1820
|
-
);
|
|
1829
|
+
});
|
|
1821
1830
|
}
|
|
1822
1831
|
}
|
|
1823
1832
|
async function handleAppleWebhook(req, res, config, store, purchaseStore, webhookEventStore, webhookQueue) {
|
|
@@ -1833,7 +1842,7 @@ async function handleAppleWebhook(req, res, config, store, purchaseStore, webhoo
|
|
|
1833
1842
|
config.apple?.skipJwsVerification
|
|
1834
1843
|
);
|
|
1835
1844
|
} catch (err2) {
|
|
1836
|
-
log.error("[onesub/webhook/apple] Failed to decode signedPayload
|
|
1845
|
+
log.error("[onesub/webhook/apple] Failed to decode signedPayload", { err: err2 });
|
|
1837
1846
|
sendError(res, 400, ONESUB_ERROR_CODE.INVALID_SIGNED_PAYLOAD, "Invalid signedPayload");
|
|
1838
1847
|
return;
|
|
1839
1848
|
}
|
|
@@ -1841,7 +1850,9 @@ async function handleAppleWebhook(req, res, config, store, purchaseStore, webhoo
|
|
|
1841
1850
|
if (webhookEventStore && typeof payload.notificationUUID === "string") {
|
|
1842
1851
|
const fresh = await webhookEventStore.markIfNew("apple", payload.notificationUUID);
|
|
1843
1852
|
if (!fresh) {
|
|
1844
|
-
log.info("[onesub/webhook/apple] dedupe: already processed",
|
|
1853
|
+
log.info("[onesub/webhook/apple] dedupe: already processed", {
|
|
1854
|
+
notificationUUID: payload.notificationUUID
|
|
1855
|
+
});
|
|
1845
1856
|
res.status(200).json({ received: true, deduped: true });
|
|
1846
1857
|
return;
|
|
1847
1858
|
}
|
|
@@ -1854,7 +1865,7 @@ async function handleAppleWebhook(req, res, config, store, purchaseStore, webhoo
|
|
|
1854
1865
|
}
|
|
1855
1866
|
const notifiedApp = getAppRegistry(config).configFor({ bundleId: decoded.bundleId });
|
|
1856
1867
|
if (decoded.bundleId && !notifiedApp.apple) {
|
|
1857
|
-
log.warn("[onesub/webhook/apple] No app configured for bundleId
|
|
1868
|
+
log.warn("[onesub/webhook/apple] No app configured for bundleId", { bundleId: decoded.bundleId });
|
|
1858
1869
|
sendError(res, 400, ONESUB_ERROR_CODE.BUNDLE_ID_MISMATCH, "Bundle ID mismatch");
|
|
1859
1870
|
return;
|
|
1860
1871
|
}
|
|
@@ -1874,7 +1885,10 @@ async function handleAppleWebhook(req, res, config, store, purchaseStore, webhoo
|
|
|
1874
1885
|
});
|
|
1875
1886
|
res.status(200).json({ received: true, queued: true });
|
|
1876
1887
|
} catch (err2) {
|
|
1877
|
-
log.error("[onesub/webhook/apple] Failed to enqueue webhook job
|
|
1888
|
+
log.error("[onesub/webhook/apple] Failed to enqueue webhook job", {
|
|
1889
|
+
notificationUUID: payload.notificationUUID,
|
|
1890
|
+
err: err2
|
|
1891
|
+
});
|
|
1878
1892
|
await unmarkWebhookEvent(webhookEventStore, "apple", markedEventId);
|
|
1879
1893
|
sendError(res, 500, ONESUB_ERROR_CODE.WEBHOOK_PROCESSING_FAILED, "Failed to update subscription");
|
|
1880
1894
|
}
|
|
@@ -1884,7 +1898,10 @@ async function handleAppleWebhook(req, res, config, store, purchaseStore, webhoo
|
|
|
1884
1898
|
await processAppleNotification(work, config, store, purchaseStore);
|
|
1885
1899
|
res.status(200).json({ received: true });
|
|
1886
1900
|
} catch (err2) {
|
|
1887
|
-
log.error("[onesub/webhook/apple] Store update error
|
|
1901
|
+
log.error("[onesub/webhook/apple] Store update error", {
|
|
1902
|
+
notificationUUID: payload.notificationUUID,
|
|
1903
|
+
err: err2
|
|
1904
|
+
});
|
|
1888
1905
|
await unmarkWebhookEvent(webhookEventStore, "apple", markedEventId);
|
|
1889
1906
|
sendError(res, 500, ONESUB_ERROR_CODE.WEBHOOK_PROCESSING_FAILED, "Failed to update subscription");
|
|
1890
1907
|
}
|
|
@@ -1919,15 +1936,15 @@ async function verifyGooglePushToken(req, expectedAudience, expectedServiceAccou
|
|
|
1919
1936
|
}
|
|
1920
1937
|
var GOOGLE_FAILURE_MESSAGES = {
|
|
1921
1938
|
voided: {
|
|
1922
|
-
logPrefix: "[onesub/webhook/google] voided notification error
|
|
1939
|
+
logPrefix: "[onesub/webhook/google] voided notification error",
|
|
1923
1940
|
message: "Failed to process voided notification"
|
|
1924
1941
|
},
|
|
1925
1942
|
oneTimeProduct: {
|
|
1926
|
-
logPrefix: "[onesub/webhook/google] oneTimeProduct error
|
|
1943
|
+
logPrefix: "[onesub/webhook/google] oneTimeProduct error",
|
|
1927
1944
|
message: "Failed to process oneTimeProduct notification"
|
|
1928
1945
|
},
|
|
1929
1946
|
subscription: {
|
|
1930
|
-
logPrefix: "[onesub/webhook/google] Error handling notification
|
|
1947
|
+
logPrefix: "[onesub/webhook/google] Error handling notification",
|
|
1931
1948
|
message: "Failed to process notification"
|
|
1932
1949
|
}
|
|
1933
1950
|
};
|
|
@@ -1969,12 +1986,14 @@ async function processGoogleNotification(work, config, store, purchaseStore) {
|
|
|
1969
1986
|
const updated = config.refundPolicy === "until_expiry" ? { ...existing2, willRenew: false } : { ...existing2, status: SUBSCRIPTION_STATUS.CANCELED };
|
|
1970
1987
|
await store.save(updated);
|
|
1971
1988
|
} else {
|
|
1972
|
-
log.warn("[onesub/webhook/google] voided subscription for unknown purchaseToken
|
|
1989
|
+
log.warn("[onesub/webhook/google] voided subscription for unknown purchaseToken", {
|
|
1990
|
+
purchaseToken: voided.purchaseToken
|
|
1991
|
+
});
|
|
1973
1992
|
}
|
|
1974
1993
|
} else {
|
|
1975
1994
|
const removed = await purchaseStore.deletePurchaseByTransactionId(voided.orderId);
|
|
1976
1995
|
if (!removed) {
|
|
1977
|
-
log.warn("[onesub/webhook/google] voided IAP for unknown orderId
|
|
1996
|
+
log.warn("[onesub/webhook/google] voided IAP for unknown orderId", { orderId: voided.orderId });
|
|
1978
1997
|
}
|
|
1979
1998
|
}
|
|
1980
1999
|
return;
|
|
@@ -1982,15 +2001,18 @@ async function processGoogleNotification(work, config, store, purchaseStore) {
|
|
|
1982
2001
|
if (work.kind === "oneTimeProduct") {
|
|
1983
2002
|
const { notificationType: notificationType2, purchaseToken: purchaseToken2, sku } = work.oneTimeProduct;
|
|
1984
2003
|
if (notificationType2 === 1) {
|
|
1985
|
-
log.info("[onesub/webhook/google] oneTimeProduct PURCHASED
|
|
2004
|
+
log.info("[onesub/webhook/google] oneTimeProduct PURCHASED", { productId: sku });
|
|
1986
2005
|
const googleCfg = googleFor(work.oneTimeProduct.packageName);
|
|
1987
2006
|
if (googleCfg?.serviceAccountKey && googleCfg.packageName) {
|
|
1988
2007
|
void acknowledgeGoogleProduct(purchaseToken2, sku, googleCfg).catch(
|
|
1989
|
-
(err2) => log.warn(
|
|
2008
|
+
(err2) => log.warn("[onesub/webhook/google] oneTimeProduct ack failed \u2014 3-day auto-refund risk", {
|
|
2009
|
+
productId: sku,
|
|
2010
|
+
err: err2
|
|
2011
|
+
})
|
|
1990
2012
|
);
|
|
1991
2013
|
}
|
|
1992
2014
|
} else {
|
|
1993
|
-
log.info("[onesub/webhook/google] oneTimeProduct CANCELED (pre-completion)
|
|
2015
|
+
log.info("[onesub/webhook/google] oneTimeProduct CANCELED (pre-completion)", { productId: sku });
|
|
1994
2016
|
}
|
|
1995
2017
|
return;
|
|
1996
2018
|
}
|
|
@@ -2017,7 +2039,7 @@ async function processGoogleNotification(work, config, store, purchaseStore) {
|
|
|
2017
2039
|
const subGoogleCfg = googleFor(packageName);
|
|
2018
2040
|
if (isGooglePriceChangeConfirmedNotification(notificationType) && subGoogleCfg?.onPriceChangeConfirmed) {
|
|
2019
2041
|
const hook = subGoogleCfg.onPriceChangeConfirmed;
|
|
2020
|
-
void Promise.resolve().then(() => hook({ purchaseToken, subscriptionId, packageName })).catch((err2) => log.warn("[onesub/webhook/google] onPriceChangeConfirmed hook failed
|
|
2042
|
+
void Promise.resolve().then(() => hook({ purchaseToken, subscriptionId, packageName })).catch((err2) => log.warn("[onesub/webhook/google] onPriceChangeConfirmed hook failed", { err: err2 }));
|
|
2021
2043
|
}
|
|
2022
2044
|
if (existing) {
|
|
2023
2045
|
let updated = { ...existing, status: finalStatus };
|
|
@@ -2046,7 +2068,11 @@ async function processGoogleNotification(work, config, store, purchaseStore) {
|
|
|
2046
2068
|
const previous = await store.getByTransactionId(fresh.linkedPurchaseToken);
|
|
2047
2069
|
fresh.userId = previous ? previous.userId : boundAccountId ?? purchaseToken;
|
|
2048
2070
|
if (previous) {
|
|
2049
|
-
log.info(
|
|
2071
|
+
log.info("[onesub/webhook/google] inherited userId from linkedPurchaseToken", {
|
|
2072
|
+
userId: previous.userId,
|
|
2073
|
+
linkedPurchaseToken: fresh.linkedPurchaseToken,
|
|
2074
|
+
purchaseToken
|
|
2075
|
+
});
|
|
2050
2076
|
}
|
|
2051
2077
|
} else {
|
|
2052
2078
|
fresh.userId = boundAccountId ?? purchaseToken;
|
|
@@ -2054,7 +2080,9 @@ async function processGoogleNotification(work, config, store, purchaseStore) {
|
|
|
2054
2080
|
await store.save(fresh);
|
|
2055
2081
|
}
|
|
2056
2082
|
} else {
|
|
2057
|
-
log.warn("[onesub/webhook/google] Unknown purchase token and no serviceAccountKey to re-fetch
|
|
2083
|
+
log.warn("[onesub/webhook/google] Unknown purchase token and no serviceAccountKey to re-fetch", {
|
|
2084
|
+
purchaseToken
|
|
2085
|
+
});
|
|
2058
2086
|
}
|
|
2059
2087
|
}
|
|
2060
2088
|
}
|
|
@@ -2082,7 +2110,9 @@ async function handleGoogleWebhook(req, res, config, store, purchaseStore, webho
|
|
|
2082
2110
|
if (webhookEventStore && typeof body.message.messageId === "string") {
|
|
2083
2111
|
const fresh = await webhookEventStore.markIfNew("google", body.message.messageId);
|
|
2084
2112
|
if (!fresh) {
|
|
2085
|
-
log.info("[onesub/webhook/google] dedupe: already processed",
|
|
2113
|
+
log.info("[onesub/webhook/google] dedupe: already processed", {
|
|
2114
|
+
messageId: body.message.messageId
|
|
2115
|
+
});
|
|
2086
2116
|
res.status(200).json({ received: true, deduped: true });
|
|
2087
2117
|
return;
|
|
2088
2118
|
}
|
|
@@ -2093,7 +2123,9 @@ async function handleGoogleWebhook(req, res, config, store, purchaseStore, webho
|
|
|
2093
2123
|
const voided = decodeGoogleVoidedNotification(body);
|
|
2094
2124
|
if (voided) {
|
|
2095
2125
|
if (!servesPackage(voided.packageName)) {
|
|
2096
|
-
log.warn("[onesub/webhook/google] voided package name not served
|
|
2126
|
+
log.warn("[onesub/webhook/google] voided package name not served", {
|
|
2127
|
+
packageName: voided.packageName
|
|
2128
|
+
});
|
|
2097
2129
|
sendError(res, 400, ONESUB_ERROR_CODE.PACKAGE_NAME_MISMATCH, "Package name mismatch");
|
|
2098
2130
|
return;
|
|
2099
2131
|
}
|
|
@@ -2102,7 +2134,9 @@ async function handleGoogleWebhook(req, res, config, store, purchaseStore, webho
|
|
|
2102
2134
|
const oneTimeProduct = decodeGoogleOneTimeProductNotification(body);
|
|
2103
2135
|
if (oneTimeProduct) {
|
|
2104
2136
|
if (!servesPackage(oneTimeProduct.packageName)) {
|
|
2105
|
-
log.warn("[onesub/webhook/google] oneTimeProduct package name not served
|
|
2137
|
+
log.warn("[onesub/webhook/google] oneTimeProduct package name not served", {
|
|
2138
|
+
packageName: oneTimeProduct.packageName
|
|
2139
|
+
});
|
|
2106
2140
|
sendError(res, 400, ONESUB_ERROR_CODE.PACKAGE_NAME_MISMATCH, "Package name mismatch");
|
|
2107
2141
|
return;
|
|
2108
2142
|
}
|
|
@@ -2114,7 +2148,9 @@ async function handleGoogleWebhook(req, res, config, store, purchaseStore, webho
|
|
|
2114
2148
|
return;
|
|
2115
2149
|
}
|
|
2116
2150
|
if (!servesPackage(notification.packageName)) {
|
|
2117
|
-
log.warn("[onesub/webhook/google] Package name not served
|
|
2151
|
+
log.warn("[onesub/webhook/google] Package name not served", {
|
|
2152
|
+
packageName: notification.packageName
|
|
2153
|
+
});
|
|
2118
2154
|
sendError(res, 400, ONESUB_ERROR_CODE.PACKAGE_NAME_MISMATCH, "Package name mismatch");
|
|
2119
2155
|
return;
|
|
2120
2156
|
}
|
|
@@ -2132,7 +2168,11 @@ async function handleGoogleWebhook(req, res, config, store, purchaseStore, webho
|
|
|
2132
2168
|
});
|
|
2133
2169
|
res.status(200).json({ received: true, queued: true });
|
|
2134
2170
|
} catch (err2) {
|
|
2135
|
-
log.error("[onesub/webhook/google] Failed to enqueue webhook job
|
|
2171
|
+
log.error("[onesub/webhook/google] Failed to enqueue webhook job", {
|
|
2172
|
+
kind: work.kind,
|
|
2173
|
+
messageId: body.message.messageId,
|
|
2174
|
+
err: err2
|
|
2175
|
+
});
|
|
2136
2176
|
await unmarkWebhookEvent(webhookEventStore, "google", markedEventId);
|
|
2137
2177
|
sendError(res, 500, ONESUB_ERROR_CODE.WEBHOOK_PROCESSING_FAILED, GOOGLE_FAILURE_MESSAGES[work.kind].message);
|
|
2138
2178
|
}
|
|
@@ -2143,7 +2183,7 @@ async function handleGoogleWebhook(req, res, config, store, purchaseStore, webho
|
|
|
2143
2183
|
res.status(200).json({ received: true });
|
|
2144
2184
|
} catch (err2) {
|
|
2145
2185
|
const { logPrefix, message } = GOOGLE_FAILURE_MESSAGES[work.kind];
|
|
2146
|
-
log.error(logPrefix, err2);
|
|
2186
|
+
log.error(logPrefix, { kind: work.kind, err: err2 });
|
|
2147
2187
|
await unmarkWebhookEvent(webhookEventStore, "google", markedEventId);
|
|
2148
2188
|
sendError(res, 500, ONESUB_ERROR_CODE.WEBHOOK_PROCESSING_FAILED, message);
|
|
2149
2189
|
}
|
|
@@ -2255,9 +2295,10 @@ function createPurchaseRouter(config, purchaseStore) {
|
|
|
2255
2295
|
}
|
|
2256
2296
|
const bindingMismatch = platform === "apple" ? boundAccountId && boundAccountId.toLowerCase() !== userId.toLowerCase() : boundAccountId && boundAccountId !== userId;
|
|
2257
2297
|
if (bindingMismatch) {
|
|
2258
|
-
log.warn(
|
|
2259
|
-
|
|
2260
|
-
|
|
2298
|
+
log.warn("[onesub/purchase] account binding mismatch \u2014 token does not match userId", {
|
|
2299
|
+
transactionId,
|
|
2300
|
+
userId
|
|
2301
|
+
});
|
|
2261
2302
|
sendError(
|
|
2262
2303
|
res,
|
|
2263
2304
|
409,
|
|
@@ -2274,9 +2315,11 @@ function createPurchaseRouter(config, purchaseStore) {
|
|
|
2274
2315
|
if (existing.userId !== userId) {
|
|
2275
2316
|
if (type === PURCHASE_TYPE.NON_CONSUMABLE) {
|
|
2276
2317
|
await purchaseStore.reassignPurchase(transactionId, userId);
|
|
2277
|
-
log.info(
|
|
2278
|
-
|
|
2279
|
-
|
|
2318
|
+
log.info("[onesub/purchase] reassigned transaction to a new user", {
|
|
2319
|
+
transactionId,
|
|
2320
|
+
fromUserId: existing.userId,
|
|
2321
|
+
userId
|
|
2322
|
+
});
|
|
2280
2323
|
} else {
|
|
2281
2324
|
sendError(
|
|
2282
2325
|
res,
|
|
@@ -2320,7 +2363,7 @@ function createPurchaseRouter(config, purchaseStore) {
|
|
|
2320
2363
|
};
|
|
2321
2364
|
res.status(200).json(response);
|
|
2322
2365
|
} catch (err2) {
|
|
2323
|
-
log.error("[onesub/purchase/validate] Unexpected error
|
|
2366
|
+
log.error("[onesub/purchase/validate] Unexpected error", { userId, productId, platform, type, err: err2 });
|
|
2324
2367
|
sendError(res, 500, ONESUB_ERROR_CODE.INTERNAL_ERROR, "Internal server error during purchase validation", NO_PURCHASE);
|
|
2325
2368
|
}
|
|
2326
2369
|
});
|
|
@@ -2335,7 +2378,7 @@ function createPurchaseRouter(config, purchaseStore) {
|
|
|
2335
2378
|
const response = { purchases };
|
|
2336
2379
|
res.status(200).json(response);
|
|
2337
2380
|
} catch (err2) {
|
|
2338
|
-
log.error("[onesub/purchase/status] Store error
|
|
2381
|
+
log.error("[onesub/purchase/status] Store error", { userId, productId, err: err2 });
|
|
2339
2382
|
sendError(res, 500, ONESUB_ERROR_CODE.STORE_ERROR, "Internal server error", { purchases: [] });
|
|
2340
2383
|
}
|
|
2341
2384
|
});
|
|
@@ -2400,7 +2443,7 @@ function createEntitlementRouter(config, store, purchaseStore) {
|
|
|
2400
2443
|
const response = { id, ...status };
|
|
2401
2444
|
res.status(200).json(response);
|
|
2402
2445
|
} catch (err2) {
|
|
2403
|
-
log.error("[onesub/entitlement] evaluation error
|
|
2446
|
+
log.error("[onesub/entitlement] evaluation error", { userId, entitlement: id, err: err2 });
|
|
2404
2447
|
sendError(res, 500, ONESUB_ERROR_CODE.STORE_ERROR, "Internal server error");
|
|
2405
2448
|
}
|
|
2406
2449
|
});
|
|
@@ -2424,7 +2467,7 @@ function createEntitlementRouter(config, store, purchaseStore) {
|
|
|
2424
2467
|
};
|
|
2425
2468
|
res.status(200).json(response);
|
|
2426
2469
|
} catch (err2) {
|
|
2427
|
-
log.error("[onesub/entitlements] evaluation error
|
|
2470
|
+
log.error("[onesub/entitlements] evaluation error", { userId, err: err2 });
|
|
2428
2471
|
sendError(res, 500, ONESUB_ERROR_CODE.STORE_ERROR, "Internal server error", { entitlements: {} });
|
|
2429
2472
|
}
|
|
2430
2473
|
});
|
|
@@ -2538,7 +2581,7 @@ function createAdminRouter(config, purchaseStore, store, webhookQueue) {
|
|
|
2538
2581
|
};
|
|
2539
2582
|
res.status(200).json(response);
|
|
2540
2583
|
} catch (err2) {
|
|
2541
|
-
log.error("[onesub/admin/subscriptions] list error
|
|
2584
|
+
log.error("[onesub/admin/subscriptions] list error", { err: err2 });
|
|
2542
2585
|
sendError(res, 500, ONESUB_ERROR_CODE.STORE_ERROR, "Internal server error");
|
|
2543
2586
|
}
|
|
2544
2587
|
});
|
|
@@ -2558,7 +2601,10 @@ function createAdminRouter(config, purchaseStore, store, webhookQueue) {
|
|
|
2558
2601
|
}
|
|
2559
2602
|
res.status(200).json(sub);
|
|
2560
2603
|
} catch (err2) {
|
|
2561
|
-
log.error("[onesub/admin/subscriptions/:transactionId] detail error
|
|
2604
|
+
log.error("[onesub/admin/subscriptions/:transactionId] detail error", {
|
|
2605
|
+
transactionId: params.transactionId,
|
|
2606
|
+
err: err2
|
|
2607
|
+
});
|
|
2562
2608
|
sendError(res, 500, ONESUB_ERROR_CODE.STORE_ERROR, "Internal server error");
|
|
2563
2609
|
}
|
|
2564
2610
|
});
|
|
@@ -2591,7 +2637,7 @@ function createAdminRouter(config, purchaseStore, store, webhookQueue) {
|
|
|
2591
2637
|
};
|
|
2592
2638
|
res.status(200).json(response);
|
|
2593
2639
|
} catch (err2) {
|
|
2594
|
-
log.error("[onesub/admin/customers/:userId] error
|
|
2640
|
+
log.error("[onesub/admin/customers/:userId] error", { userId: params.userId, err: err2 });
|
|
2595
2641
|
sendError(res, 500, ONESUB_ERROR_CODE.STORE_ERROR, "Internal server error");
|
|
2596
2642
|
}
|
|
2597
2643
|
});
|
|
@@ -2621,7 +2667,10 @@ function createAdminRouter(config, purchaseStore, store, webhookQueue) {
|
|
|
2621
2667
|
await store.save(fresh);
|
|
2622
2668
|
res.status(200).json({ ok: true, subscription: fresh });
|
|
2623
2669
|
} catch (err2) {
|
|
2624
|
-
log.error("[onesub/admin/sync-apple] error
|
|
2670
|
+
log.error("[onesub/admin/sync-apple] error", {
|
|
2671
|
+
originalTransactionId: params.originalTransactionId,
|
|
2672
|
+
err: err2
|
|
2673
|
+
});
|
|
2625
2674
|
sendError(res, 500, ONESUB_ERROR_CODE.INTERNAL_ERROR, "Internal server error");
|
|
2626
2675
|
}
|
|
2627
2676
|
});
|
|
@@ -2636,9 +2685,10 @@ function createAdminRouter(config, purchaseStore, store, webhookQueue) {
|
|
|
2636
2685
|
const body = parseOrSend(res, overrideBodySchema, req.body);
|
|
2637
2686
|
if (!body) return;
|
|
2638
2687
|
setTestOverride(params.userId, body.entitled);
|
|
2639
|
-
log.warn(
|
|
2640
|
-
|
|
2641
|
-
|
|
2688
|
+
log.warn("[onesub/admin] sandbox test override set", {
|
|
2689
|
+
userId: params.userId,
|
|
2690
|
+
entitled: body.entitled
|
|
2691
|
+
});
|
|
2642
2692
|
res.json({ ok: true, userId: params.userId, entitled: body.entitled, sandboxOnly: true });
|
|
2643
2693
|
});
|
|
2644
2694
|
router.delete(ROUTES.ADMIN_TEST_OVERRIDE, (req, res) => {
|
|
@@ -2655,7 +2705,7 @@ function createAdminRouter(config, purchaseStore, store, webhookQueue) {
|
|
|
2655
2705
|
const items = await webhookQueue.listDeadLetters();
|
|
2656
2706
|
res.status(200).json({ items });
|
|
2657
2707
|
} catch (err2) {
|
|
2658
|
-
log.error("[onesub/admin/webhook-deadletters] error
|
|
2708
|
+
log.error("[onesub/admin/webhook-deadletters] error", { err: err2 });
|
|
2659
2709
|
sendError(res, 500, ONESUB_ERROR_CODE.STORE_ERROR, "Internal server error");
|
|
2660
2710
|
}
|
|
2661
2711
|
});
|
|
@@ -2669,7 +2719,7 @@ function createAdminRouter(config, purchaseStore, store, webhookQueue) {
|
|
|
2669
2719
|
await webhookQueue.replayDeadLetter(params.id);
|
|
2670
2720
|
res.status(200).json({ ok: true });
|
|
2671
2721
|
} catch (err2) {
|
|
2672
|
-
log.error("[onesub/admin/webhook-replay] error
|
|
2722
|
+
log.error("[onesub/admin/webhook-replay] error", { deadLetterId: params.id, err: err2 });
|
|
2673
2723
|
sendError(res, 500, ONESUB_ERROR_CODE.STORE_ERROR, "Internal server error");
|
|
2674
2724
|
}
|
|
2675
2725
|
});
|
|
@@ -2839,7 +2889,7 @@ function createMetricsRouter(config, store, purchaseStore) {
|
|
|
2839
2889
|
);
|
|
2840
2890
|
res.status(200).json(response);
|
|
2841
2891
|
} catch (err2) {
|
|
2842
|
-
log.error("[onesub/metrics/active] error
|
|
2892
|
+
log.error("[onesub/metrics/active] error", { err: err2 });
|
|
2843
2893
|
sendError(res, 500, ONESUB_ERROR_CODE.STORE_ERROR, "Internal server error");
|
|
2844
2894
|
}
|
|
2845
2895
|
});
|
|
@@ -2906,7 +2956,7 @@ function createMetricsRouter(config, store, purchaseStore) {
|
|
|
2906
2956
|
};
|
|
2907
2957
|
res.status(200).json(response);
|
|
2908
2958
|
} catch (err2) {
|
|
2909
|
-
log.error(
|
|
2959
|
+
log.error("[onesub/metrics] error", { route: name, err: err2 });
|
|
2910
2960
|
sendError(res, 500, ONESUB_ERROR_CODE.STORE_ERROR, "Internal server error");
|
|
2911
2961
|
}
|
|
2912
2962
|
};
|
|
@@ -2978,7 +3028,7 @@ function createAppleOfferRouter(config) {
|
|
|
2978
3028
|
);
|
|
2979
3029
|
res.status(200).json(result);
|
|
2980
3030
|
} catch (err2) {
|
|
2981
|
-
log.error("[onesub/apple/offer] signing error
|
|
3031
|
+
log.error("[onesub/apple/offer] signing error", { productId: body.productId, err: err2 });
|
|
2982
3032
|
sendError(res, 500, ONESUB_ERROR_CODE.INTERNAL_ERROR, "Offer signing failed");
|
|
2983
3033
|
}
|
|
2984
3034
|
});
|
|
@@ -3050,7 +3100,7 @@ async function createPgPool(connectionString, label) {
|
|
|
3050
3100
|
const Pool = pg.default?.Pool ?? pg.Pool;
|
|
3051
3101
|
const pool = new Pool({ connectionString, max: 10 });
|
|
3052
3102
|
pool.on("error", (err2) => {
|
|
3053
|
-
log.error(
|
|
3103
|
+
log.error("[onesub] Postgres pool error (idle client)", { store: label, err: err2 });
|
|
3054
3104
|
});
|
|
3055
3105
|
return pool;
|
|
3056
3106
|
}
|
|
@@ -3791,7 +3841,7 @@ var BullMQWebhookQueue = class {
|
|
|
3791
3841
|
const { Queue } = await this.getBullMQ();
|
|
3792
3842
|
const queue = new Queue(this.queueName, { connection: this.connection });
|
|
3793
3843
|
queue.on?.("error", (err2) => {
|
|
3794
|
-
log.error("[onesub] BullMQ queue error
|
|
3844
|
+
log.error("[onesub] BullMQ queue error", { err: err2 });
|
|
3795
3845
|
});
|
|
3796
3846
|
return queue;
|
|
3797
3847
|
})();
|
|
@@ -3815,13 +3865,13 @@ var BullMQWebhookQueue = class {
|
|
|
3815
3865
|
}
|
|
3816
3866
|
);
|
|
3817
3867
|
worker.on?.("error", (err2) => {
|
|
3818
|
-
log.error("[onesub] BullMQ worker error
|
|
3868
|
+
log.error("[onesub] BullMQ worker error", { err: err2 });
|
|
3819
3869
|
});
|
|
3820
3870
|
return worker;
|
|
3821
3871
|
})();
|
|
3822
3872
|
this.workerPromise.catch((err2) => {
|
|
3823
3873
|
this.workerStartupError = err2 instanceof Error ? err2 : new Error(String(err2));
|
|
3824
|
-
log.error("[onesub] BullMQ worker failed to start
|
|
3874
|
+
log.error("[onesub] BullMQ worker failed to start", { err: err2 });
|
|
3825
3875
|
});
|
|
3826
3876
|
}
|
|
3827
3877
|
}
|