@onesub/server 0.24.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) {
@@ -617,22 +617,23 @@ async function validateAppleReceipt(receipt, config) {
617
617
  } catch (err2) {
618
618
  const preview = receipt.slice(0, 60);
619
619
  const parts = receipt.split(".").length;
620
- log.warn(
621
- "[onesub/apple] Failed to decode receipt as JWS:",
622
- err2?.message ?? err2,
623
- `| preview: "${preview}..." (len=${receipt.length}, parts=${parts})`
624
- );
620
+ log.warn("[onesub/apple] Failed to decode receipt as JWS", {
621
+ receiptPreview: preview,
622
+ receiptLength: receipt.length,
623
+ jwsParts: parts,
624
+ err: err2
625
+ });
625
626
  return null;
626
627
  }
627
628
  if (!tx.originalTransactionId || !tx.productId || !tx.expiresDate) {
628
629
  return null;
629
630
  }
630
631
  if (!tx.bundleId || tx.bundleId !== config.bundleId) {
631
- log.warn("[onesub/apple] Bundle ID mismatch:", tx.bundleId, "!==", config.bundleId);
632
+ log.warn("[onesub/apple] Bundle ID mismatch", { bundleId: tx.bundleId, expected: config.bundleId });
632
633
  return null;
633
634
  }
634
635
  if (process.env["NODE_ENV"] === "production" && tx.environment !== "Production" && process.env["ONESUB_ALLOW_SANDBOX"] !== "true") {
635
- log.warn("[onesub/apple] Sandbox receipt rejected in production:", tx.environment);
636
+ log.warn("[onesub/apple] Sandbox receipt rejected in production", { environment: tx.environment });
636
637
  return null;
637
638
  }
638
639
  const status = deriveStatus(tx, null);
@@ -672,23 +673,25 @@ async function validateAppleConsumableReceipt(signedTransaction, config, expecte
672
673
  const preview = signedTransaction.slice(0, 60);
673
674
  const parts = signedTransaction.split(".").length;
674
675
  const looksLikeJws = parts === 3;
675
- log.warn(
676
- "[onesub/apple] Failed to decode consumable JWS:",
677
- err2?.message ?? err2,
678
- `| receipt preview: "${preview}..." (len=${signedTransaction.length}, parts=${parts}, looksLikeJws=${looksLikeJws})`
679
- );
676
+ log.warn("[onesub/apple] Failed to decode consumable JWS", {
677
+ receiptPreview: preview,
678
+ receiptLength: signedTransaction.length,
679
+ jwsParts: parts,
680
+ looksLikeJws,
681
+ err: err2
682
+ });
680
683
  return null;
681
684
  }
682
685
  if (!tx.bundleId || tx.bundleId !== config.bundleId) {
683
- log.warn("[onesub/apple] Bundle ID mismatch:", tx.bundleId, "!==", config.bundleId);
686
+ log.warn("[onesub/apple] Bundle ID mismatch", { bundleId: tx.bundleId, expected: config.bundleId });
684
687
  return null;
685
688
  }
686
689
  if (tx.type !== "Consumable" && tx.type !== "Non-Consumable") {
687
- log.warn("[onesub/apple] Invalid purchase type for product validation:", tx.type);
690
+ log.warn("[onesub/apple] Invalid purchase type for product validation", { type: tx.type });
688
691
  return null;
689
692
  }
690
693
  if (process.env["NODE_ENV"] === "production" && tx.environment !== "Production" && process.env["ONESUB_ALLOW_SANDBOX"] !== "true") {
691
- log.warn("[onesub/apple] Sandbox receipt rejected in production:", tx.environment);
694
+ log.warn("[onesub/apple] Sandbox receipt rejected in production", { environment: tx.environment });
692
695
  return null;
693
696
  }
694
697
  if (!tx.productId) {
@@ -696,21 +699,28 @@ async function validateAppleConsumableReceipt(signedTransaction, config, expecte
696
699
  return null;
697
700
  }
698
701
  if (expectedProductId && tx.productId !== expectedProductId) {
699
- log.warn("[onesub/apple] Product ID mismatch:", tx.productId, "!==", expectedProductId);
702
+ log.warn("[onesub/apple] Product ID mismatch", { productId: tx.productId, expected: expectedProductId });
700
703
  return null;
701
704
  }
702
705
  if (tx.revocationDate) {
703
- log.warn("[onesub/apple] Purchase was revoked/refunded");
706
+ log.warn("[onesub/apple] Purchase was revoked/refunded", {
707
+ productId: tx.productId,
708
+ transactionId: tx.transactionId
709
+ });
704
710
  return null;
705
711
  }
706
712
  const maxAgeMs = (config.productReceiptMaxAgeHours ?? 72) * 60 * 60 * 1e3;
707
713
  if (tx.purchaseDate && Date.now() - tx.purchaseDate > maxAgeMs) {
708
- log.warn(`[onesub/apple] Consumable receipt too old (>${config.productReceiptMaxAgeHours ?? 72}h)`);
714
+ log.warn("[onesub/apple] Consumable receipt too old", {
715
+ productId: tx.productId,
716
+ maxAgeHours: config.productReceiptMaxAgeHours ?? 72,
717
+ purchaseDate: new Date(tx.purchaseDate).toISOString()
718
+ });
709
719
  return null;
710
720
  }
711
721
  const transactionId = tx.transactionId ?? tx.originalTransactionId;
712
722
  if (!transactionId) {
713
- log.warn("[onesub/apple] No transactionId in consumable transaction");
723
+ log.warn("[onesub/apple] No transactionId in consumable transaction", { productId: tx.productId });
714
724
  return null;
715
725
  }
716
726
  return {
@@ -789,7 +799,7 @@ async function sendAppleConsumptionResponse(transactionId, body, config, options
789
799
  try {
790
800
  jwt = await makeAppleApiJwt(config);
791
801
  } catch (err2) {
792
- log.warn("[onesub/apple] Cannot send consumption response \u2014 JWT mint failed:", err2);
802
+ log.warn("[onesub/apple] Cannot send consumption response \u2014 JWT mint failed", { transactionId, err: err2 });
793
803
  return;
794
804
  }
795
805
  const host = options?.sandbox ? "api.storekit-sandbox.itunes.apple.com" : "api.storekit.itunes.apple.com";
@@ -805,10 +815,14 @@ async function sendAppleConsumptionResponse(transactionId, body, config, options
805
815
  });
806
816
  if (!resp.ok) {
807
817
  const text = await resp.text();
808
- log.warn(`[onesub/apple] Consumption response API error ${resp.status}: ${text}`);
818
+ log.warn("[onesub/apple] Consumption response API error", {
819
+ httpStatus: resp.status,
820
+ transactionId,
821
+ responseBody: text
822
+ });
809
823
  }
810
824
  } catch (err2) {
811
- log.warn("[onesub/apple] Consumption response network error:", err2);
825
+ log.warn("[onesub/apple] Consumption response network error", { transactionId, err: err2 });
812
826
  }
813
827
  }
814
828
  var APPLE_SUBSCRIPTION_STATUS_CODE = {
@@ -839,7 +853,10 @@ async function fetchAppleSubscriptionStatus(originalTransactionId, config, optio
839
853
  try {
840
854
  jwt = await makeAppleApiJwt(config);
841
855
  } catch (err2) {
842
- log.warn("[onesub/apple] Cannot fetch subscription status \u2014 JWT mint failed:", err2);
856
+ log.warn("[onesub/apple] Cannot fetch subscription status \u2014 JWT mint failed", {
857
+ originalTransactionId,
858
+ err: err2
859
+ });
843
860
  return null;
844
861
  }
845
862
  const host = options?.sandbox ? "api.storekit-sandbox.itunes.apple.com" : "api.storekit.itunes.apple.com";
@@ -851,24 +868,31 @@ async function fetchAppleSubscriptionStatus(originalTransactionId, config, optio
851
868
  });
852
869
  if (!resp.ok) {
853
870
  const text = await resp.text();
854
- log.warn(`[onesub/apple] Status API error ${resp.status}: ${text}`);
871
+ log.warn("[onesub/apple] Status API error", {
872
+ httpStatus: resp.status,
873
+ originalTransactionId,
874
+ responseBody: text
875
+ });
855
876
  return null;
856
877
  }
857
878
  body = await resp.json();
858
879
  } catch (err2) {
859
- log.warn("[onesub/apple] Status API network error:", err2);
880
+ log.warn("[onesub/apple] Status API network error", { originalTransactionId, err: err2 });
860
881
  return null;
861
882
  }
862
883
  const entry = body.data?.flatMap((g) => g.lastTransactions ?? []).find((t) => t.originalTransactionId === originalTransactionId);
863
884
  if (!entry || entry.status == null || !entry.signedTransactionInfo) {
864
- log.warn("[onesub/apple] Status API returned no matching transaction for", originalTransactionId);
885
+ log.warn("[onesub/apple] Status API returned no matching transaction", { originalTransactionId });
865
886
  return null;
866
887
  }
867
888
  let tx;
868
889
  try {
869
890
  tx = await decodeJws(entry.signedTransactionInfo, config.skipJwsVerification);
870
891
  } catch (err2) {
871
- log.warn("[onesub/apple] Failed to decode signedTransactionInfo from Status API:", err2);
892
+ log.warn("[onesub/apple] Failed to decode signedTransactionInfo from Status API", {
893
+ originalTransactionId,
894
+ err: err2
895
+ });
872
896
  return null;
873
897
  }
874
898
  let renewal = null;
@@ -879,7 +903,10 @@ async function fetchAppleSubscriptionStatus(originalTransactionId, config, optio
879
903
  }
880
904
  }
881
905
  if (!tx.productId || !tx.expiresDate) {
882
- log.warn("[onesub/apple] Status API transaction missing productId or expiresDate");
906
+ log.warn("[onesub/apple] Status API transaction missing productId or expiresDate", {
907
+ originalTransactionId,
908
+ productId: tx.productId
909
+ });
883
910
  return null;
884
911
  }
885
912
  const status = mapAppleStatusCode(entry.status);
@@ -903,7 +930,10 @@ async function fetchAppleTransactionHistory(originalTransactionId, config, optio
903
930
  try {
904
931
  jwt = await makeAppleApiJwt(config);
905
932
  } catch (err2) {
906
- log.warn("[onesub/apple] Cannot fetch transaction history \u2014 JWT mint failed:", err2);
933
+ log.warn("[onesub/apple] Cannot fetch transaction history \u2014 JWT mint failed", {
934
+ originalTransactionId,
935
+ err: err2
936
+ });
907
937
  return null;
908
938
  }
909
939
  const host = options?.sandbox ? "api.storekit-sandbox.itunes.apple.com" : "api.storekit.itunes.apple.com";
@@ -920,12 +950,16 @@ async function fetchAppleTransactionHistory(originalTransactionId, config, optio
920
950
  });
921
951
  if (!resp.ok) {
922
952
  const text = await resp.text();
923
- log.warn(`[onesub/apple] Transaction History API error ${resp.status}: ${text}`);
953
+ log.warn("[onesub/apple] Transaction History API error", {
954
+ httpStatus: resp.status,
955
+ originalTransactionId,
956
+ responseBody: text
957
+ });
924
958
  return null;
925
959
  }
926
960
  page = await resp.json();
927
961
  } catch (err2) {
928
- log.warn("[onesub/apple] Transaction History API network error:", err2);
962
+ log.warn("[onesub/apple] Transaction History API network error", { originalTransactionId, err: err2 });
929
963
  return null;
930
964
  }
931
965
  for (const signed of page.signedTransactions ?? []) {
@@ -950,14 +984,16 @@ async function fetchAppleTransactionHistory(originalTransactionId, config, optio
950
984
  if (!page.hasMore) break;
951
985
  if (!page.revision || page.revision === revision) {
952
986
  log.warn(
953
- "[onesub/apple] Transaction History API returned hasMore without a new revision cursor \u2014 stopping pagination (partial history returned)"
987
+ "[onesub/apple] Transaction History API returned hasMore without a new revision cursor \u2014 stopping pagination (partial history returned)",
988
+ { originalTransactionId, pageCount }
954
989
  );
955
990
  break;
956
991
  }
957
992
  if (pageCount >= MAX_HISTORY_PAGES) {
958
- log.warn(
959
- `[onesub/apple] Transaction History pagination hit the ${MAX_HISTORY_PAGES}-page cap \u2014 stopping (partial history returned)`
960
- );
993
+ log.warn("[onesub/apple] Transaction History pagination hit the page cap \u2014 stopping (partial history returned)", {
994
+ originalTransactionId,
995
+ maxPages: MAX_HISTORY_PAGES
996
+ });
961
997
  break;
962
998
  }
963
999
  revision = page.revision;
@@ -1107,7 +1143,7 @@ async function acknowledgeGoogleSubscription(purchaseToken, productId, config) {
1107
1143
  try {
1108
1144
  accessToken = await getCachedAccessToken(config.serviceAccountKey);
1109
1145
  } catch (err2) {
1110
- log.warn("[onesub/google] Could not get access token for subscription ack:", err2);
1146
+ log.warn("[onesub/google] Could not get access token for subscription ack", { productId, err: err2 });
1111
1147
  return;
1112
1148
  }
1113
1149
  const url = `https://androidpublisher.googleapis.com/androidpublisher/v3/applications/${encodeURIComponent(config.packageName)}/purchases/subscriptions/${encodeURIComponent(productId)}/tokens/${encodeURIComponent(purchaseToken)}:acknowledge`;
@@ -1119,10 +1155,14 @@ async function acknowledgeGoogleSubscription(purchaseToken, productId, config) {
1119
1155
  });
1120
1156
  if (!resp.ok) {
1121
1157
  const body = await resp.text();
1122
- log.warn(`[onesub/google] Subscription acknowledge API error ${resp.status}: ${body} \u2014 auto-refund risk`);
1158
+ log.warn("[onesub/google] Subscription acknowledge API error \u2014 auto-refund risk", {
1159
+ httpStatus: resp.status,
1160
+ productId,
1161
+ responseBody: body
1162
+ });
1123
1163
  }
1124
1164
  } catch (err2) {
1125
- log.warn("[onesub/google] Subscription acknowledge network error \u2014 auto-refund risk:", err2);
1165
+ log.warn("[onesub/google] Subscription acknowledge network error \u2014 auto-refund risk", { productId, err: err2 });
1126
1166
  }
1127
1167
  }
1128
1168
  async function acknowledgeGoogleProduct(purchaseToken, productId, config) {
@@ -1133,7 +1173,7 @@ async function acknowledgeGoogleProduct(purchaseToken, productId, config) {
1133
1173
  try {
1134
1174
  accessToken = await getCachedAccessToken(config.serviceAccountKey);
1135
1175
  } catch (err2) {
1136
- log.warn("[onesub/google] Could not get access token for product ack:", err2);
1176
+ log.warn("[onesub/google] Could not get access token for product ack", { productId, err: err2 });
1137
1177
  return;
1138
1178
  }
1139
1179
  const url = `https://androidpublisher.googleapis.com/androidpublisher/v3/applications/${encodeURIComponent(config.packageName)}/purchases/products/${encodeURIComponent(productId)}/tokens/${encodeURIComponent(purchaseToken)}:acknowledge`;
@@ -1145,10 +1185,14 @@ async function acknowledgeGoogleProduct(purchaseToken, productId, config) {
1145
1185
  });
1146
1186
  if (!resp.ok) {
1147
1187
  const body = await resp.text();
1148
- log.warn(`[onesub/google] Product acknowledge API error ${resp.status}: ${body} \u2014 auto-refund risk`);
1188
+ log.warn("[onesub/google] Product acknowledge API error \u2014 auto-refund risk", {
1189
+ httpStatus: resp.status,
1190
+ productId,
1191
+ responseBody: body
1192
+ });
1149
1193
  }
1150
1194
  } catch (err2) {
1151
- log.warn("[onesub/google] Product acknowledge network error \u2014 auto-refund risk:", err2);
1195
+ log.warn("[onesub/google] Product acknowledge network error \u2014 auto-refund risk", { productId, err: err2 });
1152
1196
  }
1153
1197
  }
1154
1198
  async function consumeGoogleProductReceipt(purchaseToken, productId, config) {
@@ -1158,7 +1202,7 @@ async function consumeGoogleProductReceipt(purchaseToken, productId, config) {
1158
1202
  try {
1159
1203
  accessToken = await getCachedAccessToken(config.serviceAccountKey);
1160
1204
  } catch (err2) {
1161
- log.warn("[onesub/google] Could not get access token for consume:", err2);
1205
+ log.warn("[onesub/google] Could not get access token for consume", { productId, err: err2 });
1162
1206
  return;
1163
1207
  }
1164
1208
  const url = `https://androidpublisher.googleapis.com/androidpublisher/v3/applications/${encodeURIComponent(config.packageName)}/purchases/products/${encodeURIComponent(productId)}/tokens/${encodeURIComponent(purchaseToken)}:consume`;
@@ -1169,10 +1213,14 @@ async function consumeGoogleProductReceipt(purchaseToken, productId, config) {
1169
1213
  });
1170
1214
  if (!resp.ok) {
1171
1215
  const body = await resp.text();
1172
- log.warn(`[onesub/google] Consume API error ${resp.status}: ${body} \u2014 auto-refund risk`);
1216
+ log.warn("[onesub/google] Consume API error \u2014 auto-refund risk", {
1217
+ httpStatus: resp.status,
1218
+ productId,
1219
+ responseBody: body
1220
+ });
1173
1221
  }
1174
1222
  } catch (err2) {
1175
- log.warn("[onesub/google] Consume network error \u2014 auto-refund risk:", err2);
1223
+ log.warn("[onesub/google] Consume network error \u2014 auto-refund risk", { productId, err: err2 });
1176
1224
  }
1177
1225
  }
1178
1226
  function deriveStatusV2(state) {
@@ -1198,11 +1246,11 @@ function deriveStatusV2(state) {
1198
1246
  async function validateGoogleReceipt(receipt, productId, config) {
1199
1247
  if (config.mockMode) return mockValidateGoogleSubscription(receipt, productId);
1200
1248
  if (!config.serviceAccountKey) {
1201
- log.warn("[onesub/google] No serviceAccountKey provided \u2014 cannot call Play API");
1249
+ log.warn("[onesub/google] No serviceAccountKey provided \u2014 cannot call Play API", { productId });
1202
1250
  return null;
1203
1251
  }
1204
1252
  if (!config.packageName) {
1205
- log.warn("[onesub/google] No packageName provided \u2014 cannot call Play API");
1253
+ log.warn("[onesub/google] No packageName provided \u2014 cannot call Play API", { productId });
1206
1254
  return null;
1207
1255
  }
1208
1256
  let purchase;
@@ -1210,25 +1258,27 @@ async function validateGoogleReceipt(receipt, productId, config) {
1210
1258
  const token = await getCachedAccessToken(config.serviceAccountKey);
1211
1259
  purchase = await fetchSubscriptionPurchaseV2(config.packageName, receipt, token);
1212
1260
  } catch (err2) {
1213
- log.error("[onesub/google] Receipt validation failed:", err2);
1261
+ log.error("[onesub/google] Receipt validation failed", {
1262
+ productId,
1263
+ packageName: config.packageName,
1264
+ err: err2
1265
+ });
1214
1266
  return null;
1215
1267
  }
1216
1268
  const status = deriveStatusV2(purchase.subscriptionState);
1217
1269
  if (!status) {
1218
- log.warn(
1219
- "[onesub/google] Unrecognised or pending subscriptionState \u2014 rejecting:",
1220
- purchase.subscriptionState
1221
- );
1270
+ log.warn("[onesub/google] Unrecognised or pending subscriptionState \u2014 rejecting", {
1271
+ productId,
1272
+ subscriptionState: purchase.subscriptionState
1273
+ });
1222
1274
  return null;
1223
1275
  }
1224
1276
  const lineItem = purchase.lineItems?.find((item) => item.productId === productId);
1225
1277
  if (!lineItem) {
1226
- log.warn(
1227
- "[onesub/google] productId not found in subscription lineItems:",
1278
+ log.warn("[onesub/google] productId not found in subscription lineItems", {
1228
1279
  productId,
1229
- "available:",
1230
- purchase.lineItems?.map((i) => i.productId).join(", ") ?? "(none)"
1231
- );
1280
+ availableProductIds: purchase.lineItems?.map((i) => i.productId) ?? []
1281
+ });
1232
1282
  return null;
1233
1283
  }
1234
1284
  const expiresAt = lineItem.expiryTime ?? (/* @__PURE__ */ new Date()).toISOString();
@@ -1260,11 +1310,11 @@ async function validateGoogleReceipt(receipt, productId, config) {
1260
1310
  async function validateGoogleProductReceipt(purchaseToken, productId, config, type = "non_consumable") {
1261
1311
  if (config.mockMode) return mockValidateGoogleProduct(purchaseToken, productId);
1262
1312
  if (!config.serviceAccountKey) {
1263
- log.warn("[onesub/google] No serviceAccountKey \u2014 cannot validate product receipt");
1313
+ log.warn("[onesub/google] No serviceAccountKey \u2014 cannot validate product receipt", { productId });
1264
1314
  return null;
1265
1315
  }
1266
1316
  if (!config.packageName) {
1267
- log.warn("[onesub/google] No packageName \u2014 cannot validate product receipt");
1317
+ log.warn("[onesub/google] No packageName \u2014 cannot validate product receipt", { productId });
1268
1318
  return null;
1269
1319
  }
1270
1320
  let purchase;
@@ -1272,27 +1322,44 @@ async function validateGoogleProductReceipt(purchaseToken, productId, config, ty
1272
1322
  const token = await getCachedAccessToken(config.serviceAccountKey);
1273
1323
  purchase = await fetchProductPurchase(config.packageName, productId, purchaseToken, token);
1274
1324
  } catch (err2) {
1275
- log.error("[onesub/google] Product receipt validation failed:", err2);
1325
+ log.error("[onesub/google] Product receipt validation failed", {
1326
+ productId,
1327
+ packageName: config.packageName,
1328
+ type,
1329
+ err: err2
1330
+ });
1276
1331
  return null;
1277
1332
  }
1278
1333
  if (purchase.purchaseState !== 0) {
1279
- log.warn("[onesub/google] Purchase not completed, state:", purchase.purchaseState);
1334
+ log.warn("[onesub/google] Purchase not completed", {
1335
+ productId,
1336
+ purchaseState: purchase.purchaseState,
1337
+ orderId: purchase.orderId
1338
+ });
1280
1339
  return null;
1281
1340
  }
1282
1341
  if (type === "consumable" && purchase.consumptionState === 1) {
1283
- log.warn("[onesub/google] Consumable already consumed \u2014 possible replay attack");
1342
+ log.warn("[onesub/google] Consumable already consumed \u2014 possible replay attack", {
1343
+ productId,
1344
+ orderId: purchase.orderId
1345
+ });
1284
1346
  return null;
1285
1347
  }
1286
1348
  if (purchase.purchaseTimeMillis) {
1287
1349
  const purchaseTime = parseInt(purchase.purchaseTimeMillis, 10);
1288
1350
  const maxAgeMs = (config.productReceiptMaxAgeHours ?? 72) * 60 * 60 * 1e3;
1289
1351
  if (Date.now() - purchaseTime > maxAgeMs) {
1290
- log.warn(`[onesub/google] Product receipt too old (>${config.productReceiptMaxAgeHours ?? 72}h)`);
1352
+ log.warn("[onesub/google] Product receipt too old", {
1353
+ productId,
1354
+ orderId: purchase.orderId,
1355
+ maxAgeHours: config.productReceiptMaxAgeHours ?? 72,
1356
+ purchaseDate: new Date(purchaseTime).toISOString()
1357
+ });
1291
1358
  return null;
1292
1359
  }
1293
1360
  }
1294
1361
  if (!purchase.orderId) {
1295
- log.warn("[onesub/google] No orderId in product purchase");
1362
+ log.warn("[onesub/google] No orderId in product purchase", { productId });
1296
1363
  return null;
1297
1364
  }
1298
1365
  return {
@@ -1349,7 +1416,11 @@ function decodeGoogleOneTimeProductNotification(payload) {
1349
1416
  if (!notification.oneTimeProductNotification) return null;
1350
1417
  const { notificationType, purchaseToken, sku } = notification.oneTimeProductNotification;
1351
1418
  if (notificationType !== 1 && notificationType !== 2) {
1352
- log.warn("[onesub/google] Unknown oneTimeProductNotification type:", notificationType);
1419
+ log.warn("[onesub/google] Unknown oneTimeProductNotification type", {
1420
+ notificationType,
1421
+ productId: sku,
1422
+ packageName: notification.packageName
1423
+ });
1353
1424
  return null;
1354
1425
  }
1355
1426
  return { notificationType, purchaseToken, sku, packageName: notification.packageName };
@@ -1423,7 +1494,10 @@ function buildAppRegistry(config) {
1423
1494
  // that would validate one app's receipt against another's credentials.
1424
1495
  (config.apple || config.google || apps.length === 1 ? apps[0] : void 0);
1425
1496
  if (apps.length > 1) {
1426
- 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
+ });
1427
1501
  }
1428
1502
  function match(hint) {
1429
1503
  if (hint.appId) {
@@ -1431,13 +1505,13 @@ function buildAppRegistry(config) {
1431
1505
  (a) => a.id === hint.appId || a.apple?.bundleId === hint.appId || a.google?.packageName === hint.appId
1432
1506
  );
1433
1507
  if (byId) return byId;
1434
- log.warn("[onesub] Unknown appId:", hint.appId);
1508
+ log.warn("[onesub] Unknown appId", { appId: hint.appId });
1435
1509
  return void 0;
1436
1510
  }
1437
1511
  if (hint.bundleId) {
1438
1512
  const byBundle = apps.find((a) => a.apple?.bundleId === hint.bundleId);
1439
1513
  if (byBundle) return byBundle;
1440
- log.warn("[onesub] No app configured for bundleId:", hint.bundleId);
1514
+ log.warn("[onesub] No app configured for bundleId", { bundleId: hint.bundleId });
1441
1515
  return void 0;
1442
1516
  }
1443
1517
  return defaultApp;
@@ -1525,9 +1599,10 @@ function createValidateRouter(config, store) {
1525
1599
  delete sub.boundAccountId;
1526
1600
  const bindingMismatch = platform === "apple" ? boundAccountId && boundAccountId.toLowerCase() !== userId.toLowerCase() : boundAccountId && boundAccountId !== userId;
1527
1601
  if (bindingMismatch) {
1528
- log.warn(
1529
- `[onesub/validate] account binding mismatch for ${sub.originalTransactionId}: receipt token does not match userId ${userId}`
1530
- );
1602
+ log.warn("[onesub/validate] account binding mismatch \u2014 receipt token does not match userId", {
1603
+ originalTransactionId: sub.originalTransactionId,
1604
+ userId
1605
+ });
1531
1606
  sendError(
1532
1607
  res,
1533
1608
  409,
@@ -1540,7 +1615,7 @@ function createValidateRouter(config, store) {
1540
1615
  const isSandbox = sub.sandbox === true;
1541
1616
  delete sub.sandbox;
1542
1617
  if (isSandbox && getTestOverride(userId) === false) {
1543
- 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 });
1544
1619
  sub.status = SUBSCRIPTION_STATUS.EXPIRED;
1545
1620
  sub.willRenew = false;
1546
1621
  }
@@ -1552,7 +1627,7 @@ function createValidateRouter(config, store) {
1552
1627
  const response = { valid: true, subscription: sub };
1553
1628
  res.status(200).json(response);
1554
1629
  } catch (err2) {
1555
- log.error("[onesub/validate] Unexpected error:", err2);
1630
+ log.error("[onesub/validate] Unexpected error", { userId, productId, platform, err: err2 });
1556
1631
  sendError(res, 500, ONESUB_ERROR_CODE.INTERNAL_ERROR, "Internal server error during receipt validation", NO_SUB);
1557
1632
  }
1558
1633
  });
@@ -1584,7 +1659,7 @@ function createStatusRouter(store) {
1584
1659
  const response = { active, subscription: sub };
1585
1660
  res.status(200).json(response);
1586
1661
  } catch (err2) {
1587
- log.error("[onesub/status] Store error:", err2);
1662
+ log.error("[onesub/status] Store error", { userId, err: err2 });
1588
1663
  sendError(res, 500, ONESUB_ERROR_CODE.STORE_ERROR, "Internal server error", NO_SUB2);
1589
1664
  }
1590
1665
  });
@@ -1692,7 +1767,7 @@ async function processAppleNotification(work, config, store, purchaseStore) {
1692
1767
  });
1693
1768
  }
1694
1769
  } catch (err2) {
1695
- log.warn("[onesub/webhook/apple] consumptionInfoProvider failed:", err2);
1770
+ log.warn("[onesub/webhook/apple] consumptionInfoProvider failed", { transactionId, err: err2 });
1696
1771
  }
1697
1772
  })();
1698
1773
  }
@@ -1705,7 +1780,7 @@ async function processAppleNotification(work, config, store, purchaseStore) {
1705
1780
  const lookupId = transactionId ?? originalTransactionId;
1706
1781
  const removed = await purchaseStore.deletePurchaseByTransactionId(lookupId);
1707
1782
  if (!removed) {
1708
- log.warn("[onesub/webhook/apple] IAP refund for unknown transaction:", lookupId);
1783
+ log.warn("[onesub/webhook/apple] IAP refund for unknown transaction", { transactionId: lookupId });
1709
1784
  }
1710
1785
  return;
1711
1786
  }
@@ -1715,7 +1790,10 @@ async function processAppleNotification(work, config, store, purchaseStore) {
1715
1790
  const keepEntitlement = isSubscriptionRefund && config.refundPolicy === "until_expiry";
1716
1791
  const correctedUserId = appAccountToken && existing.userId === originalTransactionId ? appAccountToken : existing.userId;
1717
1792
  if (correctedUserId !== existing.userId) {
1718
- 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
+ });
1719
1797
  }
1720
1798
  const updated = keepEntitlement ? { ...existing, userId: correctedUserId, willRenew: false } : {
1721
1799
  ...existing,
@@ -1733,20 +1811,22 @@ async function processAppleNotification(work, config, store, purchaseStore) {
1733
1811
  fresh.userId = appAccountToken ?? originalTransactionId;
1734
1812
  if (inAppOwnershipType === "FAMILY_SHARED") {
1735
1813
  const source = appAccountToken ? "appAccountToken" : "originalTransactionId (fallback)";
1736
- 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
+ });
1737
1819
  }
1738
1820
  await store.save(fresh);
1739
1821
  } else {
1740
- log.warn(
1741
- "[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", {
1742
1823
  originalTransactionId
1743
- );
1824
+ });
1744
1825
  }
1745
1826
  } else {
1746
- log.warn(
1747
- "[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)", {
1748
1828
  originalTransactionId
1749
- );
1829
+ });
1750
1830
  }
1751
1831
  }
1752
1832
  async function handleAppleWebhook(req, res, config, store, purchaseStore, webhookEventStore, webhookQueue) {
@@ -1762,7 +1842,7 @@ async function handleAppleWebhook(req, res, config, store, purchaseStore, webhoo
1762
1842
  config.apple?.skipJwsVerification
1763
1843
  );
1764
1844
  } catch (err2) {
1765
- log.error("[onesub/webhook/apple] Failed to decode signedPayload:", err2);
1845
+ log.error("[onesub/webhook/apple] Failed to decode signedPayload", { err: err2 });
1766
1846
  sendError(res, 400, ONESUB_ERROR_CODE.INVALID_SIGNED_PAYLOAD, "Invalid signedPayload");
1767
1847
  return;
1768
1848
  }
@@ -1770,7 +1850,9 @@ async function handleAppleWebhook(req, res, config, store, purchaseStore, webhoo
1770
1850
  if (webhookEventStore && typeof payload.notificationUUID === "string") {
1771
1851
  const fresh = await webhookEventStore.markIfNew("apple", payload.notificationUUID);
1772
1852
  if (!fresh) {
1773
- log.info("[onesub/webhook/apple] dedupe: already processed", payload.notificationUUID);
1853
+ log.info("[onesub/webhook/apple] dedupe: already processed", {
1854
+ notificationUUID: payload.notificationUUID
1855
+ });
1774
1856
  res.status(200).json({ received: true, deduped: true });
1775
1857
  return;
1776
1858
  }
@@ -1783,7 +1865,7 @@ async function handleAppleWebhook(req, res, config, store, purchaseStore, webhoo
1783
1865
  }
1784
1866
  const notifiedApp = getAppRegistry(config).configFor({ bundleId: decoded.bundleId });
1785
1867
  if (decoded.bundleId && !notifiedApp.apple) {
1786
- 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 });
1787
1869
  sendError(res, 400, ONESUB_ERROR_CODE.BUNDLE_ID_MISMATCH, "Bundle ID mismatch");
1788
1870
  return;
1789
1871
  }
@@ -1803,7 +1885,10 @@ async function handleAppleWebhook(req, res, config, store, purchaseStore, webhoo
1803
1885
  });
1804
1886
  res.status(200).json({ received: true, queued: true });
1805
1887
  } catch (err2) {
1806
- 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
+ });
1807
1892
  await unmarkWebhookEvent(webhookEventStore, "apple", markedEventId);
1808
1893
  sendError(res, 500, ONESUB_ERROR_CODE.WEBHOOK_PROCESSING_FAILED, "Failed to update subscription");
1809
1894
  }
@@ -1813,7 +1898,10 @@ async function handleAppleWebhook(req, res, config, store, purchaseStore, webhoo
1813
1898
  await processAppleNotification(work, config, store, purchaseStore);
1814
1899
  res.status(200).json({ received: true });
1815
1900
  } catch (err2) {
1816
- 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
+ });
1817
1905
  await unmarkWebhookEvent(webhookEventStore, "apple", markedEventId);
1818
1906
  sendError(res, 500, ONESUB_ERROR_CODE.WEBHOOK_PROCESSING_FAILED, "Failed to update subscription");
1819
1907
  }
@@ -1848,15 +1936,15 @@ async function verifyGooglePushToken(req, expectedAudience, expectedServiceAccou
1848
1936
  }
1849
1937
  var GOOGLE_FAILURE_MESSAGES = {
1850
1938
  voided: {
1851
- logPrefix: "[onesub/webhook/google] voided notification error:",
1939
+ logPrefix: "[onesub/webhook/google] voided notification error",
1852
1940
  message: "Failed to process voided notification"
1853
1941
  },
1854
1942
  oneTimeProduct: {
1855
- logPrefix: "[onesub/webhook/google] oneTimeProduct error:",
1943
+ logPrefix: "[onesub/webhook/google] oneTimeProduct error",
1856
1944
  message: "Failed to process oneTimeProduct notification"
1857
1945
  },
1858
1946
  subscription: {
1859
- logPrefix: "[onesub/webhook/google] Error handling notification:",
1947
+ logPrefix: "[onesub/webhook/google] Error handling notification",
1860
1948
  message: "Failed to process notification"
1861
1949
  }
1862
1950
  };
@@ -1898,12 +1986,14 @@ async function processGoogleNotification(work, config, store, purchaseStore) {
1898
1986
  const updated = config.refundPolicy === "until_expiry" ? { ...existing2, willRenew: false } : { ...existing2, status: SUBSCRIPTION_STATUS.CANCELED };
1899
1987
  await store.save(updated);
1900
1988
  } else {
1901
- 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
+ });
1902
1992
  }
1903
1993
  } else {
1904
1994
  const removed = await purchaseStore.deletePurchaseByTransactionId(voided.orderId);
1905
1995
  if (!removed) {
1906
- 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 });
1907
1997
  }
1908
1998
  }
1909
1999
  return;
@@ -1911,15 +2001,18 @@ async function processGoogleNotification(work, config, store, purchaseStore) {
1911
2001
  if (work.kind === "oneTimeProduct") {
1912
2002
  const { notificationType: notificationType2, purchaseToken: purchaseToken2, sku } = work.oneTimeProduct;
1913
2003
  if (notificationType2 === 1) {
1914
- log.info("[onesub/webhook/google] oneTimeProduct PURCHASED:", sku);
2004
+ log.info("[onesub/webhook/google] oneTimeProduct PURCHASED", { productId: sku });
1915
2005
  const googleCfg = googleFor(work.oneTimeProduct.packageName);
1916
2006
  if (googleCfg?.serviceAccountKey && googleCfg.packageName) {
1917
2007
  void acknowledgeGoogleProduct(purchaseToken2, sku, googleCfg).catch(
1918
- (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
+ })
1919
2012
  );
1920
2013
  }
1921
2014
  } else {
1922
- log.info("[onesub/webhook/google] oneTimeProduct CANCELED (pre-completion):", sku);
2015
+ log.info("[onesub/webhook/google] oneTimeProduct CANCELED (pre-completion)", { productId: sku });
1923
2016
  }
1924
2017
  return;
1925
2018
  }
@@ -1946,7 +2039,7 @@ async function processGoogleNotification(work, config, store, purchaseStore) {
1946
2039
  const subGoogleCfg = googleFor(packageName);
1947
2040
  if (isGooglePriceChangeConfirmedNotification(notificationType) && subGoogleCfg?.onPriceChangeConfirmed) {
1948
2041
  const hook = subGoogleCfg.onPriceChangeConfirmed;
1949
- 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 }));
1950
2043
  }
1951
2044
  if (existing) {
1952
2045
  let updated = { ...existing, status: finalStatus };
@@ -1975,7 +2068,11 @@ async function processGoogleNotification(work, config, store, purchaseStore) {
1975
2068
  const previous = await store.getByTransactionId(fresh.linkedPurchaseToken);
1976
2069
  fresh.userId = previous ? previous.userId : boundAccountId ?? purchaseToken;
1977
2070
  if (previous) {
1978
- 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
+ });
1979
2076
  }
1980
2077
  } else {
1981
2078
  fresh.userId = boundAccountId ?? purchaseToken;
@@ -1983,7 +2080,9 @@ async function processGoogleNotification(work, config, store, purchaseStore) {
1983
2080
  await store.save(fresh);
1984
2081
  }
1985
2082
  } else {
1986
- 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
+ });
1987
2086
  }
1988
2087
  }
1989
2088
  }
@@ -2011,7 +2110,9 @@ async function handleGoogleWebhook(req, res, config, store, purchaseStore, webho
2011
2110
  if (webhookEventStore && typeof body.message.messageId === "string") {
2012
2111
  const fresh = await webhookEventStore.markIfNew("google", body.message.messageId);
2013
2112
  if (!fresh) {
2014
- 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
+ });
2015
2116
  res.status(200).json({ received: true, deduped: true });
2016
2117
  return;
2017
2118
  }
@@ -2022,7 +2123,9 @@ async function handleGoogleWebhook(req, res, config, store, purchaseStore, webho
2022
2123
  const voided = decodeGoogleVoidedNotification(body);
2023
2124
  if (voided) {
2024
2125
  if (!servesPackage(voided.packageName)) {
2025
- 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
+ });
2026
2129
  sendError(res, 400, ONESUB_ERROR_CODE.PACKAGE_NAME_MISMATCH, "Package name mismatch");
2027
2130
  return;
2028
2131
  }
@@ -2031,7 +2134,9 @@ async function handleGoogleWebhook(req, res, config, store, purchaseStore, webho
2031
2134
  const oneTimeProduct = decodeGoogleOneTimeProductNotification(body);
2032
2135
  if (oneTimeProduct) {
2033
2136
  if (!servesPackage(oneTimeProduct.packageName)) {
2034
- 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
+ });
2035
2140
  sendError(res, 400, ONESUB_ERROR_CODE.PACKAGE_NAME_MISMATCH, "Package name mismatch");
2036
2141
  return;
2037
2142
  }
@@ -2043,7 +2148,9 @@ async function handleGoogleWebhook(req, res, config, store, purchaseStore, webho
2043
2148
  return;
2044
2149
  }
2045
2150
  if (!servesPackage(notification.packageName)) {
2046
- 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
+ });
2047
2154
  sendError(res, 400, ONESUB_ERROR_CODE.PACKAGE_NAME_MISMATCH, "Package name mismatch");
2048
2155
  return;
2049
2156
  }
@@ -2061,7 +2168,11 @@ async function handleGoogleWebhook(req, res, config, store, purchaseStore, webho
2061
2168
  });
2062
2169
  res.status(200).json({ received: true, queued: true });
2063
2170
  } catch (err2) {
2064
- 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
+ });
2065
2176
  await unmarkWebhookEvent(webhookEventStore, "google", markedEventId);
2066
2177
  sendError(res, 500, ONESUB_ERROR_CODE.WEBHOOK_PROCESSING_FAILED, GOOGLE_FAILURE_MESSAGES[work.kind].message);
2067
2178
  }
@@ -2072,7 +2183,7 @@ async function handleGoogleWebhook(req, res, config, store, purchaseStore, webho
2072
2183
  res.status(200).json({ received: true });
2073
2184
  } catch (err2) {
2074
2185
  const { logPrefix, message } = GOOGLE_FAILURE_MESSAGES[work.kind];
2075
- log.error(logPrefix, err2);
2186
+ log.error(logPrefix, { kind: work.kind, err: err2 });
2076
2187
  await unmarkWebhookEvent(webhookEventStore, "google", markedEventId);
2077
2188
  sendError(res, 500, ONESUB_ERROR_CODE.WEBHOOK_PROCESSING_FAILED, message);
2078
2189
  }
@@ -2184,9 +2295,10 @@ function createPurchaseRouter(config, purchaseStore) {
2184
2295
  }
2185
2296
  const bindingMismatch = platform === "apple" ? boundAccountId && boundAccountId.toLowerCase() !== userId.toLowerCase() : boundAccountId && boundAccountId !== userId;
2186
2297
  if (bindingMismatch) {
2187
- log.warn(
2188
- `[onesub/purchase] account binding mismatch for transaction ${transactionId}: token does not match userId ${userId}`
2189
- );
2298
+ log.warn("[onesub/purchase] account binding mismatch \u2014 token does not match userId", {
2299
+ transactionId,
2300
+ userId
2301
+ });
2190
2302
  sendError(
2191
2303
  res,
2192
2304
  409,
@@ -2203,9 +2315,11 @@ function createPurchaseRouter(config, purchaseStore) {
2203
2315
  if (existing.userId !== userId) {
2204
2316
  if (type === PURCHASE_TYPE.NON_CONSUMABLE) {
2205
2317
  await purchaseStore.reassignPurchase(transactionId, userId);
2206
- log.info(
2207
- `[onesub/purchase] reassigned transaction ${transactionId} from ${existing.userId} to ${userId}`
2208
- );
2318
+ log.info("[onesub/purchase] reassigned transaction to a new user", {
2319
+ transactionId,
2320
+ fromUserId: existing.userId,
2321
+ userId
2322
+ });
2209
2323
  } else {
2210
2324
  sendError(
2211
2325
  res,
@@ -2249,7 +2363,7 @@ function createPurchaseRouter(config, purchaseStore) {
2249
2363
  };
2250
2364
  res.status(200).json(response);
2251
2365
  } catch (err2) {
2252
- log.error("[onesub/purchase/validate] Unexpected error:", err2);
2366
+ log.error("[onesub/purchase/validate] Unexpected error", { userId, productId, platform, type, err: err2 });
2253
2367
  sendError(res, 500, ONESUB_ERROR_CODE.INTERNAL_ERROR, "Internal server error during purchase validation", NO_PURCHASE);
2254
2368
  }
2255
2369
  });
@@ -2264,7 +2378,7 @@ function createPurchaseRouter(config, purchaseStore) {
2264
2378
  const response = { purchases };
2265
2379
  res.status(200).json(response);
2266
2380
  } catch (err2) {
2267
- log.error("[onesub/purchase/status] Store error:", err2);
2381
+ log.error("[onesub/purchase/status] Store error", { userId, productId, err: err2 });
2268
2382
  sendError(res, 500, ONESUB_ERROR_CODE.STORE_ERROR, "Internal server error", { purchases: [] });
2269
2383
  }
2270
2384
  });
@@ -2329,7 +2443,7 @@ function createEntitlementRouter(config, store, purchaseStore) {
2329
2443
  const response = { id, ...status };
2330
2444
  res.status(200).json(response);
2331
2445
  } catch (err2) {
2332
- log.error("[onesub/entitlement] evaluation error:", err2);
2446
+ log.error("[onesub/entitlement] evaluation error", { userId, entitlement: id, err: err2 });
2333
2447
  sendError(res, 500, ONESUB_ERROR_CODE.STORE_ERROR, "Internal server error");
2334
2448
  }
2335
2449
  });
@@ -2353,7 +2467,7 @@ function createEntitlementRouter(config, store, purchaseStore) {
2353
2467
  };
2354
2468
  res.status(200).json(response);
2355
2469
  } catch (err2) {
2356
- log.error("[onesub/entitlements] evaluation error:", err2);
2470
+ log.error("[onesub/entitlements] evaluation error", { userId, err: err2 });
2357
2471
  sendError(res, 500, ONESUB_ERROR_CODE.STORE_ERROR, "Internal server error", { entitlements: {} });
2358
2472
  }
2359
2473
  });
@@ -2467,7 +2581,7 @@ function createAdminRouter(config, purchaseStore, store, webhookQueue) {
2467
2581
  };
2468
2582
  res.status(200).json(response);
2469
2583
  } catch (err2) {
2470
- log.error("[onesub/admin/subscriptions] list error:", err2);
2584
+ log.error("[onesub/admin/subscriptions] list error", { err: err2 });
2471
2585
  sendError(res, 500, ONESUB_ERROR_CODE.STORE_ERROR, "Internal server error");
2472
2586
  }
2473
2587
  });
@@ -2487,7 +2601,10 @@ function createAdminRouter(config, purchaseStore, store, webhookQueue) {
2487
2601
  }
2488
2602
  res.status(200).json(sub);
2489
2603
  } catch (err2) {
2490
- 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
+ });
2491
2608
  sendError(res, 500, ONESUB_ERROR_CODE.STORE_ERROR, "Internal server error");
2492
2609
  }
2493
2610
  });
@@ -2520,7 +2637,7 @@ function createAdminRouter(config, purchaseStore, store, webhookQueue) {
2520
2637
  };
2521
2638
  res.status(200).json(response);
2522
2639
  } catch (err2) {
2523
- log.error("[onesub/admin/customers/:userId] error:", err2);
2640
+ log.error("[onesub/admin/customers/:userId] error", { userId: params.userId, err: err2 });
2524
2641
  sendError(res, 500, ONESUB_ERROR_CODE.STORE_ERROR, "Internal server error");
2525
2642
  }
2526
2643
  });
@@ -2550,7 +2667,10 @@ function createAdminRouter(config, purchaseStore, store, webhookQueue) {
2550
2667
  await store.save(fresh);
2551
2668
  res.status(200).json({ ok: true, subscription: fresh });
2552
2669
  } catch (err2) {
2553
- log.error("[onesub/admin/sync-apple] error:", err2);
2670
+ log.error("[onesub/admin/sync-apple] error", {
2671
+ originalTransactionId: params.originalTransactionId,
2672
+ err: err2
2673
+ });
2554
2674
  sendError(res, 500, ONESUB_ERROR_CODE.INTERNAL_ERROR, "Internal server error");
2555
2675
  }
2556
2676
  });
@@ -2565,9 +2685,10 @@ function createAdminRouter(config, purchaseStore, store, webhookQueue) {
2565
2685
  const body = parseOrSend(res, overrideBodySchema, req.body);
2566
2686
  if (!body) return;
2567
2687
  setTestOverride(params.userId, body.entitled);
2568
- log.warn(
2569
- `[onesub/admin] sandbox test override set for ${params.userId}: entitled=${body.entitled}`
2570
- );
2688
+ log.warn("[onesub/admin] sandbox test override set", {
2689
+ userId: params.userId,
2690
+ entitled: body.entitled
2691
+ });
2571
2692
  res.json({ ok: true, userId: params.userId, entitled: body.entitled, sandboxOnly: true });
2572
2693
  });
2573
2694
  router.delete(ROUTES.ADMIN_TEST_OVERRIDE, (req, res) => {
@@ -2584,7 +2705,7 @@ function createAdminRouter(config, purchaseStore, store, webhookQueue) {
2584
2705
  const items = await webhookQueue.listDeadLetters();
2585
2706
  res.status(200).json({ items });
2586
2707
  } catch (err2) {
2587
- log.error("[onesub/admin/webhook-deadletters] error:", err2);
2708
+ log.error("[onesub/admin/webhook-deadletters] error", { err: err2 });
2588
2709
  sendError(res, 500, ONESUB_ERROR_CODE.STORE_ERROR, "Internal server error");
2589
2710
  }
2590
2711
  });
@@ -2598,7 +2719,7 @@ function createAdminRouter(config, purchaseStore, store, webhookQueue) {
2598
2719
  await webhookQueue.replayDeadLetter(params.id);
2599
2720
  res.status(200).json({ ok: true });
2600
2721
  } catch (err2) {
2601
- log.error("[onesub/admin/webhook-replay] error:", err2);
2722
+ log.error("[onesub/admin/webhook-replay] error", { deadLetterId: params.id, err: err2 });
2602
2723
  sendError(res, 500, ONESUB_ERROR_CODE.STORE_ERROR, "Internal server error");
2603
2724
  }
2604
2725
  });
@@ -2768,7 +2889,7 @@ function createMetricsRouter(config, store, purchaseStore) {
2768
2889
  );
2769
2890
  res.status(200).json(response);
2770
2891
  } catch (err2) {
2771
- log.error("[onesub/metrics/active] error:", err2);
2892
+ log.error("[onesub/metrics/active] error", { err: err2 });
2772
2893
  sendError(res, 500, ONESUB_ERROR_CODE.STORE_ERROR, "Internal server error");
2773
2894
  }
2774
2895
  });
@@ -2835,7 +2956,7 @@ function createMetricsRouter(config, store, purchaseStore) {
2835
2956
  };
2836
2957
  res.status(200).json(response);
2837
2958
  } catch (err2) {
2838
- log.error(`[onesub/metrics/${name}] error:`, err2);
2959
+ log.error("[onesub/metrics] error", { route: name, err: err2 });
2839
2960
  sendError(res, 500, ONESUB_ERROR_CODE.STORE_ERROR, "Internal server error");
2840
2961
  }
2841
2962
  };
@@ -2907,7 +3028,7 @@ function createAppleOfferRouter(config) {
2907
3028
  );
2908
3029
  res.status(200).json(result);
2909
3030
  } catch (err2) {
2910
- log.error("[onesub/apple/offer] signing error:", err2);
3031
+ log.error("[onesub/apple/offer] signing error", { productId: body.productId, err: err2 });
2911
3032
  sendError(res, 500, ONESUB_ERROR_CODE.INTERNAL_ERROR, "Offer signing failed");
2912
3033
  }
2913
3034
  });
@@ -2979,7 +3100,7 @@ async function createPgPool(connectionString, label) {
2979
3100
  const Pool = pg.default?.Pool ?? pg.Pool;
2980
3101
  const pool = new Pool({ connectionString, max: 10 });
2981
3102
  pool.on("error", (err2) => {
2982
- log.error(`[onesub] ${label} pool error (idle client):`, err2);
3103
+ log.error("[onesub] Postgres pool error (idle client)", { store: label, err: err2 });
2983
3104
  });
2984
3105
  return pool;
2985
3106
  }
@@ -3720,7 +3841,7 @@ var BullMQWebhookQueue = class {
3720
3841
  const { Queue } = await this.getBullMQ();
3721
3842
  const queue = new Queue(this.queueName, { connection: this.connection });
3722
3843
  queue.on?.("error", (err2) => {
3723
- log.error("[onesub] BullMQ queue error:", err2);
3844
+ log.error("[onesub] BullMQ queue error", { err: err2 });
3724
3845
  });
3725
3846
  return queue;
3726
3847
  })();
@@ -3744,13 +3865,13 @@ var BullMQWebhookQueue = class {
3744
3865
  }
3745
3866
  );
3746
3867
  worker.on?.("error", (err2) => {
3747
- log.error("[onesub] BullMQ worker error:", err2);
3868
+ log.error("[onesub] BullMQ worker error", { err: err2 });
3748
3869
  });
3749
3870
  return worker;
3750
3871
  })();
3751
3872
  this.workerPromise.catch((err2) => {
3752
3873
  this.workerStartupError = err2 instanceof Error ? err2 : new Error(String(err2));
3753
- log.error("[onesub] BullMQ worker failed to start:", err2);
3874
+ log.error("[onesub] BullMQ worker failed to start", { err: err2 });
3754
3875
  });
3755
3876
  }
3756
3877
  }