@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/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(`[onesub/mock/${tag}] receipt rejected`, { outcome: outcome.kind });
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:", apps.map(appName).join(", "), "| default:", defaultApp ? appName(defaultApp) : "(none)");
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:", hint.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:", hint.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
- `[onesub/validate] account binding mismatch for ${sub.originalTransactionId}: receipt token does not match userId ${userId}`
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(`[onesub/validate] sandbox test override active for ${userId}: forcing not-entitled`);
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:", err2);
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:", err2);
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:", err2);
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:", lookupId);
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:", correctedUserId);
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(`[onesub/webhook/apple] FAMILY_SHARED \u2014 userId: ${fresh.userId} (source: ${source})`);
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:", err2);
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", payload.notificationUUID);
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:", decoded.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:", err2);
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:", err2);
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:", voided.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:", voided.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:", sku);
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(`[onesub/webhook/google] oneTimeProduct ack failed for SKU ${sku} \u2014 3-day auto-refund risk:`, err2)
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):", sku);
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:", err2));
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(`[onesub/webhook/google] inherited userId ${previous.userId} from linkedPurchaseToken ${fresh.linkedPurchaseToken} \u2192 new token ${purchaseToken}`);
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:", purchaseToken);
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", body.message.messageId);
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:", voided.packageName);
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:", oneTimeProduct.packageName);
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:", notification.packageName);
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:", err2);
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
- `[onesub/purchase] account binding mismatch for transaction ${transactionId}: token does not match userId ${userId}`
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
- `[onesub/purchase] reassigned transaction ${transactionId} from ${existing.userId} to ${userId}`
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:", err2);
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:", err2);
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:", err2);
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:", err2);
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:", err2);
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:", err2);
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:", err2);
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:", err2);
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
- `[onesub/admin] sandbox test override set for ${params.userId}: entitled=${body.entitled}`
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:", err2);
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:", err2);
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:", err2);
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(`[onesub/metrics/${name}] error:`, err2);
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:", err2);
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(`[onesub] ${label} pool error (idle client):`, err2);
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:", err2);
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:", err2);
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:", err2);
3874
+ log.error("[onesub] BullMQ worker failed to start", { err: err2 });
3825
3875
  });
3826
3876
  }
3827
3877
  }