@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.cjs CHANGED
@@ -449,7 +449,7 @@ function outcomePasses(outcome, tag) {
449
449
  throw new Error(`[onesub/mock/${tag}] simulated upstream network error`);
450
450
  }
451
451
  if (outcome.kind === "valid" || outcome.kind === "sandbox") return true;
452
- log.warn(`[onesub/mock/${tag}] receipt rejected`, { outcome: outcome.kind });
452
+ log.warn("[onesub/mock] receipt rejected", { provider: tag, outcome: outcome.kind });
453
453
  return false;
454
454
  }
455
455
  function deterministicTransactionId(prefix, receipt) {
@@ -624,22 +624,23 @@ async function validateAppleReceipt(receipt, config) {
624
624
  } catch (err2) {
625
625
  const preview = receipt.slice(0, 60);
626
626
  const parts = receipt.split(".").length;
627
- log.warn(
628
- "[onesub/apple] Failed to decode receipt as JWS:",
629
- err2?.message ?? err2,
630
- `| preview: "${preview}..." (len=${receipt.length}, parts=${parts})`
631
- );
627
+ log.warn("[onesub/apple] Failed to decode receipt as JWS", {
628
+ receiptPreview: preview,
629
+ receiptLength: receipt.length,
630
+ jwsParts: parts,
631
+ err: err2
632
+ });
632
633
  return null;
633
634
  }
634
635
  if (!tx.originalTransactionId || !tx.productId || !tx.expiresDate) {
635
636
  return null;
636
637
  }
637
638
  if (!tx.bundleId || tx.bundleId !== config.bundleId) {
638
- log.warn("[onesub/apple] Bundle ID mismatch:", tx.bundleId, "!==", config.bundleId);
639
+ log.warn("[onesub/apple] Bundle ID mismatch", { bundleId: tx.bundleId, expected: config.bundleId });
639
640
  return null;
640
641
  }
641
642
  if (process.env["NODE_ENV"] === "production" && tx.environment !== "Production" && process.env["ONESUB_ALLOW_SANDBOX"] !== "true") {
642
- log.warn("[onesub/apple] Sandbox receipt rejected in production:", tx.environment);
643
+ log.warn("[onesub/apple] Sandbox receipt rejected in production", { environment: tx.environment });
643
644
  return null;
644
645
  }
645
646
  const status = deriveStatus(tx, null);
@@ -679,23 +680,25 @@ async function validateAppleConsumableReceipt(signedTransaction, config, expecte
679
680
  const preview = signedTransaction.slice(0, 60);
680
681
  const parts = signedTransaction.split(".").length;
681
682
  const looksLikeJws = parts === 3;
682
- log.warn(
683
- "[onesub/apple] Failed to decode consumable JWS:",
684
- err2?.message ?? err2,
685
- `| receipt preview: "${preview}..." (len=${signedTransaction.length}, parts=${parts}, looksLikeJws=${looksLikeJws})`
686
- );
683
+ log.warn("[onesub/apple] Failed to decode consumable JWS", {
684
+ receiptPreview: preview,
685
+ receiptLength: signedTransaction.length,
686
+ jwsParts: parts,
687
+ looksLikeJws,
688
+ err: err2
689
+ });
687
690
  return null;
688
691
  }
689
692
  if (!tx.bundleId || tx.bundleId !== config.bundleId) {
690
- log.warn("[onesub/apple] Bundle ID mismatch:", tx.bundleId, "!==", config.bundleId);
693
+ log.warn("[onesub/apple] Bundle ID mismatch", { bundleId: tx.bundleId, expected: config.bundleId });
691
694
  return null;
692
695
  }
693
696
  if (tx.type !== "Consumable" && tx.type !== "Non-Consumable") {
694
- log.warn("[onesub/apple] Invalid purchase type for product validation:", tx.type);
697
+ log.warn("[onesub/apple] Invalid purchase type for product validation", { type: tx.type });
695
698
  return null;
696
699
  }
697
700
  if (process.env["NODE_ENV"] === "production" && tx.environment !== "Production" && process.env["ONESUB_ALLOW_SANDBOX"] !== "true") {
698
- log.warn("[onesub/apple] Sandbox receipt rejected in production:", tx.environment);
701
+ log.warn("[onesub/apple] Sandbox receipt rejected in production", { environment: tx.environment });
699
702
  return null;
700
703
  }
701
704
  if (!tx.productId) {
@@ -703,21 +706,28 @@ async function validateAppleConsumableReceipt(signedTransaction, config, expecte
703
706
  return null;
704
707
  }
705
708
  if (expectedProductId && tx.productId !== expectedProductId) {
706
- log.warn("[onesub/apple] Product ID mismatch:", tx.productId, "!==", expectedProductId);
709
+ log.warn("[onesub/apple] Product ID mismatch", { productId: tx.productId, expected: expectedProductId });
707
710
  return null;
708
711
  }
709
712
  if (tx.revocationDate) {
710
- log.warn("[onesub/apple] Purchase was revoked/refunded");
713
+ log.warn("[onesub/apple] Purchase was revoked/refunded", {
714
+ productId: tx.productId,
715
+ transactionId: tx.transactionId
716
+ });
711
717
  return null;
712
718
  }
713
719
  const maxAgeMs = (config.productReceiptMaxAgeHours ?? 72) * 60 * 60 * 1e3;
714
720
  if (tx.purchaseDate && Date.now() - tx.purchaseDate > maxAgeMs) {
715
- log.warn(`[onesub/apple] Consumable receipt too old (>${config.productReceiptMaxAgeHours ?? 72}h)`);
721
+ log.warn("[onesub/apple] Consumable receipt too old", {
722
+ productId: tx.productId,
723
+ maxAgeHours: config.productReceiptMaxAgeHours ?? 72,
724
+ purchaseDate: new Date(tx.purchaseDate).toISOString()
725
+ });
716
726
  return null;
717
727
  }
718
728
  const transactionId = tx.transactionId ?? tx.originalTransactionId;
719
729
  if (!transactionId) {
720
- log.warn("[onesub/apple] No transactionId in consumable transaction");
730
+ log.warn("[onesub/apple] No transactionId in consumable transaction", { productId: tx.productId });
721
731
  return null;
722
732
  }
723
733
  return {
@@ -796,7 +806,7 @@ async function sendAppleConsumptionResponse(transactionId, body, config, options
796
806
  try {
797
807
  jwt = await makeAppleApiJwt(config);
798
808
  } catch (err2) {
799
- log.warn("[onesub/apple] Cannot send consumption response \u2014 JWT mint failed:", err2);
809
+ log.warn("[onesub/apple] Cannot send consumption response \u2014 JWT mint failed", { transactionId, err: err2 });
800
810
  return;
801
811
  }
802
812
  const host = options?.sandbox ? "api.storekit-sandbox.itunes.apple.com" : "api.storekit.itunes.apple.com";
@@ -812,10 +822,14 @@ async function sendAppleConsumptionResponse(transactionId, body, config, options
812
822
  });
813
823
  if (!resp.ok) {
814
824
  const text = await resp.text();
815
- log.warn(`[onesub/apple] Consumption response API error ${resp.status}: ${text}`);
825
+ log.warn("[onesub/apple] Consumption response API error", {
826
+ httpStatus: resp.status,
827
+ transactionId,
828
+ responseBody: text
829
+ });
816
830
  }
817
831
  } catch (err2) {
818
- log.warn("[onesub/apple] Consumption response network error:", err2);
832
+ log.warn("[onesub/apple] Consumption response network error", { transactionId, err: err2 });
819
833
  }
820
834
  }
821
835
  var APPLE_SUBSCRIPTION_STATUS_CODE = {
@@ -846,7 +860,10 @@ async function fetchAppleSubscriptionStatus(originalTransactionId, config, optio
846
860
  try {
847
861
  jwt = await makeAppleApiJwt(config);
848
862
  } catch (err2) {
849
- log.warn("[onesub/apple] Cannot fetch subscription status \u2014 JWT mint failed:", err2);
863
+ log.warn("[onesub/apple] Cannot fetch subscription status \u2014 JWT mint failed", {
864
+ originalTransactionId,
865
+ err: err2
866
+ });
850
867
  return null;
851
868
  }
852
869
  const host = options?.sandbox ? "api.storekit-sandbox.itunes.apple.com" : "api.storekit.itunes.apple.com";
@@ -858,24 +875,31 @@ async function fetchAppleSubscriptionStatus(originalTransactionId, config, optio
858
875
  });
859
876
  if (!resp.ok) {
860
877
  const text = await resp.text();
861
- log.warn(`[onesub/apple] Status API error ${resp.status}: ${text}`);
878
+ log.warn("[onesub/apple] Status API error", {
879
+ httpStatus: resp.status,
880
+ originalTransactionId,
881
+ responseBody: text
882
+ });
862
883
  return null;
863
884
  }
864
885
  body = await resp.json();
865
886
  } catch (err2) {
866
- log.warn("[onesub/apple] Status API network error:", err2);
887
+ log.warn("[onesub/apple] Status API network error", { originalTransactionId, err: err2 });
867
888
  return null;
868
889
  }
869
890
  const entry = body.data?.flatMap((g) => g.lastTransactions ?? []).find((t) => t.originalTransactionId === originalTransactionId);
870
891
  if (!entry || entry.status == null || !entry.signedTransactionInfo) {
871
- log.warn("[onesub/apple] Status API returned no matching transaction for", originalTransactionId);
892
+ log.warn("[onesub/apple] Status API returned no matching transaction", { originalTransactionId });
872
893
  return null;
873
894
  }
874
895
  let tx;
875
896
  try {
876
897
  tx = await decodeJws(entry.signedTransactionInfo, config.skipJwsVerification);
877
898
  } catch (err2) {
878
- log.warn("[onesub/apple] Failed to decode signedTransactionInfo from Status API:", err2);
899
+ log.warn("[onesub/apple] Failed to decode signedTransactionInfo from Status API", {
900
+ originalTransactionId,
901
+ err: err2
902
+ });
879
903
  return null;
880
904
  }
881
905
  let renewal = null;
@@ -886,7 +910,10 @@ async function fetchAppleSubscriptionStatus(originalTransactionId, config, optio
886
910
  }
887
911
  }
888
912
  if (!tx.productId || !tx.expiresDate) {
889
- log.warn("[onesub/apple] Status API transaction missing productId or expiresDate");
913
+ log.warn("[onesub/apple] Status API transaction missing productId or expiresDate", {
914
+ originalTransactionId,
915
+ productId: tx.productId
916
+ });
890
917
  return null;
891
918
  }
892
919
  const status = mapAppleStatusCode(entry.status);
@@ -910,7 +937,10 @@ async function fetchAppleTransactionHistory(originalTransactionId, config, optio
910
937
  try {
911
938
  jwt = await makeAppleApiJwt(config);
912
939
  } catch (err2) {
913
- log.warn("[onesub/apple] Cannot fetch transaction history \u2014 JWT mint failed:", err2);
940
+ log.warn("[onesub/apple] Cannot fetch transaction history \u2014 JWT mint failed", {
941
+ originalTransactionId,
942
+ err: err2
943
+ });
914
944
  return null;
915
945
  }
916
946
  const host = options?.sandbox ? "api.storekit-sandbox.itunes.apple.com" : "api.storekit.itunes.apple.com";
@@ -927,12 +957,16 @@ async function fetchAppleTransactionHistory(originalTransactionId, config, optio
927
957
  });
928
958
  if (!resp.ok) {
929
959
  const text = await resp.text();
930
- log.warn(`[onesub/apple] Transaction History API error ${resp.status}: ${text}`);
960
+ log.warn("[onesub/apple] Transaction History API error", {
961
+ httpStatus: resp.status,
962
+ originalTransactionId,
963
+ responseBody: text
964
+ });
931
965
  return null;
932
966
  }
933
967
  page = await resp.json();
934
968
  } catch (err2) {
935
- log.warn("[onesub/apple] Transaction History API network error:", err2);
969
+ log.warn("[onesub/apple] Transaction History API network error", { originalTransactionId, err: err2 });
936
970
  return null;
937
971
  }
938
972
  for (const signed of page.signedTransactions ?? []) {
@@ -957,14 +991,16 @@ async function fetchAppleTransactionHistory(originalTransactionId, config, optio
957
991
  if (!page.hasMore) break;
958
992
  if (!page.revision || page.revision === revision) {
959
993
  log.warn(
960
- "[onesub/apple] Transaction History API returned hasMore without a new revision cursor \u2014 stopping pagination (partial history returned)"
994
+ "[onesub/apple] Transaction History API returned hasMore without a new revision cursor \u2014 stopping pagination (partial history returned)",
995
+ { originalTransactionId, pageCount }
961
996
  );
962
997
  break;
963
998
  }
964
999
  if (pageCount >= MAX_HISTORY_PAGES) {
965
- log.warn(
966
- `[onesub/apple] Transaction History pagination hit the ${MAX_HISTORY_PAGES}-page cap \u2014 stopping (partial history returned)`
967
- );
1000
+ log.warn("[onesub/apple] Transaction History pagination hit the page cap \u2014 stopping (partial history returned)", {
1001
+ originalTransactionId,
1002
+ maxPages: MAX_HISTORY_PAGES
1003
+ });
968
1004
  break;
969
1005
  }
970
1006
  revision = page.revision;
@@ -1114,7 +1150,7 @@ async function acknowledgeGoogleSubscription(purchaseToken, productId, config) {
1114
1150
  try {
1115
1151
  accessToken = await getCachedAccessToken(config.serviceAccountKey);
1116
1152
  } catch (err2) {
1117
- log.warn("[onesub/google] Could not get access token for subscription ack:", err2);
1153
+ log.warn("[onesub/google] Could not get access token for subscription ack", { productId, err: err2 });
1118
1154
  return;
1119
1155
  }
1120
1156
  const url = `https://androidpublisher.googleapis.com/androidpublisher/v3/applications/${encodeURIComponent(config.packageName)}/purchases/subscriptions/${encodeURIComponent(productId)}/tokens/${encodeURIComponent(purchaseToken)}:acknowledge`;
@@ -1126,10 +1162,14 @@ async function acknowledgeGoogleSubscription(purchaseToken, productId, config) {
1126
1162
  });
1127
1163
  if (!resp.ok) {
1128
1164
  const body = await resp.text();
1129
- log.warn(`[onesub/google] Subscription acknowledge API error ${resp.status}: ${body} \u2014 auto-refund risk`);
1165
+ log.warn("[onesub/google] Subscription acknowledge API error \u2014 auto-refund risk", {
1166
+ httpStatus: resp.status,
1167
+ productId,
1168
+ responseBody: body
1169
+ });
1130
1170
  }
1131
1171
  } catch (err2) {
1132
- log.warn("[onesub/google] Subscription acknowledge network error \u2014 auto-refund risk:", err2);
1172
+ log.warn("[onesub/google] Subscription acknowledge network error \u2014 auto-refund risk", { productId, err: err2 });
1133
1173
  }
1134
1174
  }
1135
1175
  async function acknowledgeGoogleProduct(purchaseToken, productId, config) {
@@ -1140,7 +1180,7 @@ async function acknowledgeGoogleProduct(purchaseToken, productId, config) {
1140
1180
  try {
1141
1181
  accessToken = await getCachedAccessToken(config.serviceAccountKey);
1142
1182
  } catch (err2) {
1143
- log.warn("[onesub/google] Could not get access token for product ack:", err2);
1183
+ log.warn("[onesub/google] Could not get access token for product ack", { productId, err: err2 });
1144
1184
  return;
1145
1185
  }
1146
1186
  const url = `https://androidpublisher.googleapis.com/androidpublisher/v3/applications/${encodeURIComponent(config.packageName)}/purchases/products/${encodeURIComponent(productId)}/tokens/${encodeURIComponent(purchaseToken)}:acknowledge`;
@@ -1152,10 +1192,14 @@ async function acknowledgeGoogleProduct(purchaseToken, productId, config) {
1152
1192
  });
1153
1193
  if (!resp.ok) {
1154
1194
  const body = await resp.text();
1155
- log.warn(`[onesub/google] Product acknowledge API error ${resp.status}: ${body} \u2014 auto-refund risk`);
1195
+ log.warn("[onesub/google] Product acknowledge API error \u2014 auto-refund risk", {
1196
+ httpStatus: resp.status,
1197
+ productId,
1198
+ responseBody: body
1199
+ });
1156
1200
  }
1157
1201
  } catch (err2) {
1158
- log.warn("[onesub/google] Product acknowledge network error \u2014 auto-refund risk:", err2);
1202
+ log.warn("[onesub/google] Product acknowledge network error \u2014 auto-refund risk", { productId, err: err2 });
1159
1203
  }
1160
1204
  }
1161
1205
  async function consumeGoogleProductReceipt(purchaseToken, productId, config) {
@@ -1165,7 +1209,7 @@ async function consumeGoogleProductReceipt(purchaseToken, productId, config) {
1165
1209
  try {
1166
1210
  accessToken = await getCachedAccessToken(config.serviceAccountKey);
1167
1211
  } catch (err2) {
1168
- log.warn("[onesub/google] Could not get access token for consume:", err2);
1212
+ log.warn("[onesub/google] Could not get access token for consume", { productId, err: err2 });
1169
1213
  return;
1170
1214
  }
1171
1215
  const url = `https://androidpublisher.googleapis.com/androidpublisher/v3/applications/${encodeURIComponent(config.packageName)}/purchases/products/${encodeURIComponent(productId)}/tokens/${encodeURIComponent(purchaseToken)}:consume`;
@@ -1176,10 +1220,14 @@ async function consumeGoogleProductReceipt(purchaseToken, productId, config) {
1176
1220
  });
1177
1221
  if (!resp.ok) {
1178
1222
  const body = await resp.text();
1179
- log.warn(`[onesub/google] Consume API error ${resp.status}: ${body} \u2014 auto-refund risk`);
1223
+ log.warn("[onesub/google] Consume API error \u2014 auto-refund risk", {
1224
+ httpStatus: resp.status,
1225
+ productId,
1226
+ responseBody: body
1227
+ });
1180
1228
  }
1181
1229
  } catch (err2) {
1182
- log.warn("[onesub/google] Consume network error \u2014 auto-refund risk:", err2);
1230
+ log.warn("[onesub/google] Consume network error \u2014 auto-refund risk", { productId, err: err2 });
1183
1231
  }
1184
1232
  }
1185
1233
  function deriveStatusV2(state) {
@@ -1205,11 +1253,11 @@ function deriveStatusV2(state) {
1205
1253
  async function validateGoogleReceipt(receipt, productId, config) {
1206
1254
  if (config.mockMode) return mockValidateGoogleSubscription(receipt, productId);
1207
1255
  if (!config.serviceAccountKey) {
1208
- log.warn("[onesub/google] No serviceAccountKey provided \u2014 cannot call Play API");
1256
+ log.warn("[onesub/google] No serviceAccountKey provided \u2014 cannot call Play API", { productId });
1209
1257
  return null;
1210
1258
  }
1211
1259
  if (!config.packageName) {
1212
- log.warn("[onesub/google] No packageName provided \u2014 cannot call Play API");
1260
+ log.warn("[onesub/google] No packageName provided \u2014 cannot call Play API", { productId });
1213
1261
  return null;
1214
1262
  }
1215
1263
  let purchase;
@@ -1217,25 +1265,27 @@ async function validateGoogleReceipt(receipt, productId, config) {
1217
1265
  const token = await getCachedAccessToken(config.serviceAccountKey);
1218
1266
  purchase = await fetchSubscriptionPurchaseV2(config.packageName, receipt, token);
1219
1267
  } catch (err2) {
1220
- log.error("[onesub/google] Receipt validation failed:", err2);
1268
+ log.error("[onesub/google] Receipt validation failed", {
1269
+ productId,
1270
+ packageName: config.packageName,
1271
+ err: err2
1272
+ });
1221
1273
  return null;
1222
1274
  }
1223
1275
  const status = deriveStatusV2(purchase.subscriptionState);
1224
1276
  if (!status) {
1225
- log.warn(
1226
- "[onesub/google] Unrecognised or pending subscriptionState \u2014 rejecting:",
1227
- purchase.subscriptionState
1228
- );
1277
+ log.warn("[onesub/google] Unrecognised or pending subscriptionState \u2014 rejecting", {
1278
+ productId,
1279
+ subscriptionState: purchase.subscriptionState
1280
+ });
1229
1281
  return null;
1230
1282
  }
1231
1283
  const lineItem = purchase.lineItems?.find((item) => item.productId === productId);
1232
1284
  if (!lineItem) {
1233
- log.warn(
1234
- "[onesub/google] productId not found in subscription lineItems:",
1285
+ log.warn("[onesub/google] productId not found in subscription lineItems", {
1235
1286
  productId,
1236
- "available:",
1237
- purchase.lineItems?.map((i) => i.productId).join(", ") ?? "(none)"
1238
- );
1287
+ availableProductIds: purchase.lineItems?.map((i) => i.productId) ?? []
1288
+ });
1239
1289
  return null;
1240
1290
  }
1241
1291
  const expiresAt = lineItem.expiryTime ?? (/* @__PURE__ */ new Date()).toISOString();
@@ -1267,11 +1317,11 @@ async function validateGoogleReceipt(receipt, productId, config) {
1267
1317
  async function validateGoogleProductReceipt(purchaseToken, productId, config, type = "non_consumable") {
1268
1318
  if (config.mockMode) return mockValidateGoogleProduct(purchaseToken, productId);
1269
1319
  if (!config.serviceAccountKey) {
1270
- log.warn("[onesub/google] No serviceAccountKey \u2014 cannot validate product receipt");
1320
+ log.warn("[onesub/google] No serviceAccountKey \u2014 cannot validate product receipt", { productId });
1271
1321
  return null;
1272
1322
  }
1273
1323
  if (!config.packageName) {
1274
- log.warn("[onesub/google] No packageName \u2014 cannot validate product receipt");
1324
+ log.warn("[onesub/google] No packageName \u2014 cannot validate product receipt", { productId });
1275
1325
  return null;
1276
1326
  }
1277
1327
  let purchase;
@@ -1279,27 +1329,44 @@ async function validateGoogleProductReceipt(purchaseToken, productId, config, ty
1279
1329
  const token = await getCachedAccessToken(config.serviceAccountKey);
1280
1330
  purchase = await fetchProductPurchase(config.packageName, productId, purchaseToken, token);
1281
1331
  } catch (err2) {
1282
- log.error("[onesub/google] Product receipt validation failed:", err2);
1332
+ log.error("[onesub/google] Product receipt validation failed", {
1333
+ productId,
1334
+ packageName: config.packageName,
1335
+ type,
1336
+ err: err2
1337
+ });
1283
1338
  return null;
1284
1339
  }
1285
1340
  if (purchase.purchaseState !== 0) {
1286
- log.warn("[onesub/google] Purchase not completed, state:", purchase.purchaseState);
1341
+ log.warn("[onesub/google] Purchase not completed", {
1342
+ productId,
1343
+ purchaseState: purchase.purchaseState,
1344
+ orderId: purchase.orderId
1345
+ });
1287
1346
  return null;
1288
1347
  }
1289
1348
  if (type === "consumable" && purchase.consumptionState === 1) {
1290
- log.warn("[onesub/google] Consumable already consumed \u2014 possible replay attack");
1349
+ log.warn("[onesub/google] Consumable already consumed \u2014 possible replay attack", {
1350
+ productId,
1351
+ orderId: purchase.orderId
1352
+ });
1291
1353
  return null;
1292
1354
  }
1293
1355
  if (purchase.purchaseTimeMillis) {
1294
1356
  const purchaseTime = parseInt(purchase.purchaseTimeMillis, 10);
1295
1357
  const maxAgeMs = (config.productReceiptMaxAgeHours ?? 72) * 60 * 60 * 1e3;
1296
1358
  if (Date.now() - purchaseTime > maxAgeMs) {
1297
- log.warn(`[onesub/google] Product receipt too old (>${config.productReceiptMaxAgeHours ?? 72}h)`);
1359
+ log.warn("[onesub/google] Product receipt too old", {
1360
+ productId,
1361
+ orderId: purchase.orderId,
1362
+ maxAgeHours: config.productReceiptMaxAgeHours ?? 72,
1363
+ purchaseDate: new Date(purchaseTime).toISOString()
1364
+ });
1298
1365
  return null;
1299
1366
  }
1300
1367
  }
1301
1368
  if (!purchase.orderId) {
1302
- log.warn("[onesub/google] No orderId in product purchase");
1369
+ log.warn("[onesub/google] No orderId in product purchase", { productId });
1303
1370
  return null;
1304
1371
  }
1305
1372
  return {
@@ -1356,7 +1423,11 @@ function decodeGoogleOneTimeProductNotification(payload) {
1356
1423
  if (!notification.oneTimeProductNotification) return null;
1357
1424
  const { notificationType, purchaseToken, sku } = notification.oneTimeProductNotification;
1358
1425
  if (notificationType !== 1 && notificationType !== 2) {
1359
- log.warn("[onesub/google] Unknown oneTimeProductNotification type:", notificationType);
1426
+ log.warn("[onesub/google] Unknown oneTimeProductNotification type", {
1427
+ notificationType,
1428
+ productId: sku,
1429
+ packageName: notification.packageName
1430
+ });
1360
1431
  return null;
1361
1432
  }
1362
1433
  return { notificationType, purchaseToken, sku, packageName: notification.packageName };
@@ -1430,7 +1501,10 @@ function buildAppRegistry(config) {
1430
1501
  // that would validate one app's receipt against another's credentials.
1431
1502
  (config.apple || config.google || apps.length === 1 ? apps[0] : void 0);
1432
1503
  if (apps.length > 1) {
1433
- log.info("[onesub] Multi-app mode:", apps.map(appName).join(", "), "| default:", defaultApp ? appName(defaultApp) : "(none)");
1504
+ log.info("[onesub] Multi-app mode", {
1505
+ appIds: apps.map(appName),
1506
+ defaultAppId: defaultApp ? appName(defaultApp) : null
1507
+ });
1434
1508
  }
1435
1509
  function match(hint) {
1436
1510
  if (hint.appId) {
@@ -1438,13 +1512,13 @@ function buildAppRegistry(config) {
1438
1512
  (a) => a.id === hint.appId || a.apple?.bundleId === hint.appId || a.google?.packageName === hint.appId
1439
1513
  );
1440
1514
  if (byId) return byId;
1441
- log.warn("[onesub] Unknown appId:", hint.appId);
1515
+ log.warn("[onesub] Unknown appId", { appId: hint.appId });
1442
1516
  return void 0;
1443
1517
  }
1444
1518
  if (hint.bundleId) {
1445
1519
  const byBundle = apps.find((a) => a.apple?.bundleId === hint.bundleId);
1446
1520
  if (byBundle) return byBundle;
1447
- log.warn("[onesub] No app configured for bundleId:", hint.bundleId);
1521
+ log.warn("[onesub] No app configured for bundleId", { bundleId: hint.bundleId });
1448
1522
  return void 0;
1449
1523
  }
1450
1524
  return defaultApp;
@@ -1532,9 +1606,10 @@ function createValidateRouter(config, store) {
1532
1606
  delete sub.boundAccountId;
1533
1607
  const bindingMismatch = platform === "apple" ? boundAccountId && boundAccountId.toLowerCase() !== userId.toLowerCase() : boundAccountId && boundAccountId !== userId;
1534
1608
  if (bindingMismatch) {
1535
- log.warn(
1536
- `[onesub/validate] account binding mismatch for ${sub.originalTransactionId}: receipt token does not match userId ${userId}`
1537
- );
1609
+ log.warn("[onesub/validate] account binding mismatch \u2014 receipt token does not match userId", {
1610
+ originalTransactionId: sub.originalTransactionId,
1611
+ userId
1612
+ });
1538
1613
  sendError(
1539
1614
  res,
1540
1615
  409,
@@ -1547,7 +1622,7 @@ function createValidateRouter(config, store) {
1547
1622
  const isSandbox = sub.sandbox === true;
1548
1623
  delete sub.sandbox;
1549
1624
  if (isSandbox && getTestOverride(userId) === false) {
1550
- log.warn(`[onesub/validate] sandbox test override active for ${userId}: forcing not-entitled`);
1625
+ log.warn("[onesub/validate] sandbox test override active \u2014 forcing not-entitled", { userId });
1551
1626
  sub.status = shared.SUBSCRIPTION_STATUS.EXPIRED;
1552
1627
  sub.willRenew = false;
1553
1628
  }
@@ -1559,7 +1634,7 @@ function createValidateRouter(config, store) {
1559
1634
  const response = { valid: true, subscription: sub };
1560
1635
  res.status(200).json(response);
1561
1636
  } catch (err2) {
1562
- log.error("[onesub/validate] Unexpected error:", err2);
1637
+ log.error("[onesub/validate] Unexpected error", { userId, productId, platform, err: err2 });
1563
1638
  sendError(res, 500, shared.ONESUB_ERROR_CODE.INTERNAL_ERROR, "Internal server error during receipt validation", NO_SUB);
1564
1639
  }
1565
1640
  });
@@ -1591,7 +1666,7 @@ function createStatusRouter(store) {
1591
1666
  const response = { active, subscription: sub };
1592
1667
  res.status(200).json(response);
1593
1668
  } catch (err2) {
1594
- log.error("[onesub/status] Store error:", err2);
1669
+ log.error("[onesub/status] Store error", { userId, err: err2 });
1595
1670
  sendError(res, 500, shared.ONESUB_ERROR_CODE.STORE_ERROR, "Internal server error", NO_SUB2);
1596
1671
  }
1597
1672
  });
@@ -1699,7 +1774,7 @@ async function processAppleNotification(work, config, store, purchaseStore) {
1699
1774
  });
1700
1775
  }
1701
1776
  } catch (err2) {
1702
- log.warn("[onesub/webhook/apple] consumptionInfoProvider failed:", err2);
1777
+ log.warn("[onesub/webhook/apple] consumptionInfoProvider failed", { transactionId, err: err2 });
1703
1778
  }
1704
1779
  })();
1705
1780
  }
@@ -1712,7 +1787,7 @@ async function processAppleNotification(work, config, store, purchaseStore) {
1712
1787
  const lookupId = transactionId ?? originalTransactionId;
1713
1788
  const removed = await purchaseStore.deletePurchaseByTransactionId(lookupId);
1714
1789
  if (!removed) {
1715
- log.warn("[onesub/webhook/apple] IAP refund for unknown transaction:", lookupId);
1790
+ log.warn("[onesub/webhook/apple] IAP refund for unknown transaction", { transactionId: lookupId });
1716
1791
  }
1717
1792
  return;
1718
1793
  }
@@ -1722,7 +1797,10 @@ async function processAppleNotification(work, config, store, purchaseStore) {
1722
1797
  const keepEntitlement = isSubscriptionRefund && config.refundPolicy === "until_expiry";
1723
1798
  const correctedUserId = appAccountToken && existing.userId === originalTransactionId ? appAccountToken : existing.userId;
1724
1799
  if (correctedUserId !== existing.userId) {
1725
- log.info("[onesub/webhook/apple] correcting userId from originalTransactionId to appAccountToken:", correctedUserId);
1800
+ log.info("[onesub/webhook/apple] correcting userId from originalTransactionId to appAccountToken", {
1801
+ originalTransactionId,
1802
+ userId: correctedUserId
1803
+ });
1726
1804
  }
1727
1805
  const updated = keepEntitlement ? { ...existing, userId: correctedUserId, willRenew: false } : {
1728
1806
  ...existing,
@@ -1740,20 +1818,22 @@ async function processAppleNotification(work, config, store, purchaseStore) {
1740
1818
  fresh.userId = appAccountToken ?? originalTransactionId;
1741
1819
  if (inAppOwnershipType === "FAMILY_SHARED") {
1742
1820
  const source = appAccountToken ? "appAccountToken" : "originalTransactionId (fallback)";
1743
- log.info(`[onesub/webhook/apple] FAMILY_SHARED \u2014 userId: ${fresh.userId} (source: ${source})`);
1821
+ log.info("[onesub/webhook/apple] FAMILY_SHARED", {
1822
+ originalTransactionId,
1823
+ userId: fresh.userId,
1824
+ userIdSource: source
1825
+ });
1744
1826
  }
1745
1827
  await store.save(fresh);
1746
1828
  } else {
1747
- log.warn(
1748
- "[onesub/webhook/apple] Unknown transaction and Status API returned no record:",
1829
+ log.warn("[onesub/webhook/apple] Unknown transaction and Status API returned no record", {
1749
1830
  originalTransactionId
1750
- );
1831
+ });
1751
1832
  }
1752
1833
  } else {
1753
- log.warn(
1754
- "[onesub/webhook/apple] Received notification for unknown transaction (no API creds to recover):",
1834
+ log.warn("[onesub/webhook/apple] Received notification for unknown transaction (no API creds to recover)", {
1755
1835
  originalTransactionId
1756
- );
1836
+ });
1757
1837
  }
1758
1838
  }
1759
1839
  async function handleAppleWebhook(req, res, config, store, purchaseStore, webhookEventStore, webhookQueue) {
@@ -1769,7 +1849,7 @@ async function handleAppleWebhook(req, res, config, store, purchaseStore, webhoo
1769
1849
  config.apple?.skipJwsVerification
1770
1850
  );
1771
1851
  } catch (err2) {
1772
- log.error("[onesub/webhook/apple] Failed to decode signedPayload:", err2);
1852
+ log.error("[onesub/webhook/apple] Failed to decode signedPayload", { err: err2 });
1773
1853
  sendError(res, 400, shared.ONESUB_ERROR_CODE.INVALID_SIGNED_PAYLOAD, "Invalid signedPayload");
1774
1854
  return;
1775
1855
  }
@@ -1777,7 +1857,9 @@ async function handleAppleWebhook(req, res, config, store, purchaseStore, webhoo
1777
1857
  if (webhookEventStore && typeof payload.notificationUUID === "string") {
1778
1858
  const fresh = await webhookEventStore.markIfNew("apple", payload.notificationUUID);
1779
1859
  if (!fresh) {
1780
- log.info("[onesub/webhook/apple] dedupe: already processed", payload.notificationUUID);
1860
+ log.info("[onesub/webhook/apple] dedupe: already processed", {
1861
+ notificationUUID: payload.notificationUUID
1862
+ });
1781
1863
  res.status(200).json({ received: true, deduped: true });
1782
1864
  return;
1783
1865
  }
@@ -1790,7 +1872,7 @@ async function handleAppleWebhook(req, res, config, store, purchaseStore, webhoo
1790
1872
  }
1791
1873
  const notifiedApp = getAppRegistry(config).configFor({ bundleId: decoded.bundleId });
1792
1874
  if (decoded.bundleId && !notifiedApp.apple) {
1793
- log.warn("[onesub/webhook/apple] No app configured for bundleId:", decoded.bundleId);
1875
+ log.warn("[onesub/webhook/apple] No app configured for bundleId", { bundleId: decoded.bundleId });
1794
1876
  sendError(res, 400, shared.ONESUB_ERROR_CODE.BUNDLE_ID_MISMATCH, "Bundle ID mismatch");
1795
1877
  return;
1796
1878
  }
@@ -1810,7 +1892,10 @@ async function handleAppleWebhook(req, res, config, store, purchaseStore, webhoo
1810
1892
  });
1811
1893
  res.status(200).json({ received: true, queued: true });
1812
1894
  } catch (err2) {
1813
- log.error("[onesub/webhook/apple] Failed to enqueue webhook job:", err2);
1895
+ log.error("[onesub/webhook/apple] Failed to enqueue webhook job", {
1896
+ notificationUUID: payload.notificationUUID,
1897
+ err: err2
1898
+ });
1814
1899
  await unmarkWebhookEvent(webhookEventStore, "apple", markedEventId);
1815
1900
  sendError(res, 500, shared.ONESUB_ERROR_CODE.WEBHOOK_PROCESSING_FAILED, "Failed to update subscription");
1816
1901
  }
@@ -1820,7 +1905,10 @@ async function handleAppleWebhook(req, res, config, store, purchaseStore, webhoo
1820
1905
  await processAppleNotification(work, config, store, purchaseStore);
1821
1906
  res.status(200).json({ received: true });
1822
1907
  } catch (err2) {
1823
- log.error("[onesub/webhook/apple] Store update error:", err2);
1908
+ log.error("[onesub/webhook/apple] Store update error", {
1909
+ notificationUUID: payload.notificationUUID,
1910
+ err: err2
1911
+ });
1824
1912
  await unmarkWebhookEvent(webhookEventStore, "apple", markedEventId);
1825
1913
  sendError(res, 500, shared.ONESUB_ERROR_CODE.WEBHOOK_PROCESSING_FAILED, "Failed to update subscription");
1826
1914
  }
@@ -1855,15 +1943,15 @@ async function verifyGooglePushToken(req, expectedAudience, expectedServiceAccou
1855
1943
  }
1856
1944
  var GOOGLE_FAILURE_MESSAGES = {
1857
1945
  voided: {
1858
- logPrefix: "[onesub/webhook/google] voided notification error:",
1946
+ logPrefix: "[onesub/webhook/google] voided notification error",
1859
1947
  message: "Failed to process voided notification"
1860
1948
  },
1861
1949
  oneTimeProduct: {
1862
- logPrefix: "[onesub/webhook/google] oneTimeProduct error:",
1950
+ logPrefix: "[onesub/webhook/google] oneTimeProduct error",
1863
1951
  message: "Failed to process oneTimeProduct notification"
1864
1952
  },
1865
1953
  subscription: {
1866
- logPrefix: "[onesub/webhook/google] Error handling notification:",
1954
+ logPrefix: "[onesub/webhook/google] Error handling notification",
1867
1955
  message: "Failed to process notification"
1868
1956
  }
1869
1957
  };
@@ -1905,12 +1993,14 @@ async function processGoogleNotification(work, config, store, purchaseStore) {
1905
1993
  const updated = config.refundPolicy === "until_expiry" ? { ...existing2, willRenew: false } : { ...existing2, status: shared.SUBSCRIPTION_STATUS.CANCELED };
1906
1994
  await store.save(updated);
1907
1995
  } else {
1908
- log.warn("[onesub/webhook/google] voided subscription for unknown purchaseToken:", voided.purchaseToken);
1996
+ log.warn("[onesub/webhook/google] voided subscription for unknown purchaseToken", {
1997
+ purchaseToken: voided.purchaseToken
1998
+ });
1909
1999
  }
1910
2000
  } else {
1911
2001
  const removed = await purchaseStore.deletePurchaseByTransactionId(voided.orderId);
1912
2002
  if (!removed) {
1913
- log.warn("[onesub/webhook/google] voided IAP for unknown orderId:", voided.orderId);
2003
+ log.warn("[onesub/webhook/google] voided IAP for unknown orderId", { orderId: voided.orderId });
1914
2004
  }
1915
2005
  }
1916
2006
  return;
@@ -1918,15 +2008,18 @@ async function processGoogleNotification(work, config, store, purchaseStore) {
1918
2008
  if (work.kind === "oneTimeProduct") {
1919
2009
  const { notificationType: notificationType2, purchaseToken: purchaseToken2, sku } = work.oneTimeProduct;
1920
2010
  if (notificationType2 === 1) {
1921
- log.info("[onesub/webhook/google] oneTimeProduct PURCHASED:", sku);
2011
+ log.info("[onesub/webhook/google] oneTimeProduct PURCHASED", { productId: sku });
1922
2012
  const googleCfg = googleFor(work.oneTimeProduct.packageName);
1923
2013
  if (googleCfg?.serviceAccountKey && googleCfg.packageName) {
1924
2014
  void acknowledgeGoogleProduct(purchaseToken2, sku, googleCfg).catch(
1925
- (err2) => log.warn(`[onesub/webhook/google] oneTimeProduct ack failed for SKU ${sku} \u2014 3-day auto-refund risk:`, err2)
2015
+ (err2) => log.warn("[onesub/webhook/google] oneTimeProduct ack failed \u2014 3-day auto-refund risk", {
2016
+ productId: sku,
2017
+ err: err2
2018
+ })
1926
2019
  );
1927
2020
  }
1928
2021
  } else {
1929
- log.info("[onesub/webhook/google] oneTimeProduct CANCELED (pre-completion):", sku);
2022
+ log.info("[onesub/webhook/google] oneTimeProduct CANCELED (pre-completion)", { productId: sku });
1930
2023
  }
1931
2024
  return;
1932
2025
  }
@@ -1953,7 +2046,7 @@ async function processGoogleNotification(work, config, store, purchaseStore) {
1953
2046
  const subGoogleCfg = googleFor(packageName);
1954
2047
  if (isGooglePriceChangeConfirmedNotification(notificationType) && subGoogleCfg?.onPriceChangeConfirmed) {
1955
2048
  const hook = subGoogleCfg.onPriceChangeConfirmed;
1956
- void Promise.resolve().then(() => hook({ purchaseToken, subscriptionId, packageName })).catch((err2) => log.warn("[onesub/webhook/google] onPriceChangeConfirmed hook failed:", err2));
2049
+ void Promise.resolve().then(() => hook({ purchaseToken, subscriptionId, packageName })).catch((err2) => log.warn("[onesub/webhook/google] onPriceChangeConfirmed hook failed", { err: err2 }));
1957
2050
  }
1958
2051
  if (existing) {
1959
2052
  let updated = { ...existing, status: finalStatus };
@@ -1982,7 +2075,11 @@ async function processGoogleNotification(work, config, store, purchaseStore) {
1982
2075
  const previous = await store.getByTransactionId(fresh.linkedPurchaseToken);
1983
2076
  fresh.userId = previous ? previous.userId : boundAccountId ?? purchaseToken;
1984
2077
  if (previous) {
1985
- log.info(`[onesub/webhook/google] inherited userId ${previous.userId} from linkedPurchaseToken ${fresh.linkedPurchaseToken} \u2192 new token ${purchaseToken}`);
2078
+ log.info("[onesub/webhook/google] inherited userId from linkedPurchaseToken", {
2079
+ userId: previous.userId,
2080
+ linkedPurchaseToken: fresh.linkedPurchaseToken,
2081
+ purchaseToken
2082
+ });
1986
2083
  }
1987
2084
  } else {
1988
2085
  fresh.userId = boundAccountId ?? purchaseToken;
@@ -1990,7 +2087,9 @@ async function processGoogleNotification(work, config, store, purchaseStore) {
1990
2087
  await store.save(fresh);
1991
2088
  }
1992
2089
  } else {
1993
- log.warn("[onesub/webhook/google] Unknown purchase token and no serviceAccountKey to re-fetch:", purchaseToken);
2090
+ log.warn("[onesub/webhook/google] Unknown purchase token and no serviceAccountKey to re-fetch", {
2091
+ purchaseToken
2092
+ });
1994
2093
  }
1995
2094
  }
1996
2095
  }
@@ -2018,7 +2117,9 @@ async function handleGoogleWebhook(req, res, config, store, purchaseStore, webho
2018
2117
  if (webhookEventStore && typeof body.message.messageId === "string") {
2019
2118
  const fresh = await webhookEventStore.markIfNew("google", body.message.messageId);
2020
2119
  if (!fresh) {
2021
- log.info("[onesub/webhook/google] dedupe: already processed", body.message.messageId);
2120
+ log.info("[onesub/webhook/google] dedupe: already processed", {
2121
+ messageId: body.message.messageId
2122
+ });
2022
2123
  res.status(200).json({ received: true, deduped: true });
2023
2124
  return;
2024
2125
  }
@@ -2029,7 +2130,9 @@ async function handleGoogleWebhook(req, res, config, store, purchaseStore, webho
2029
2130
  const voided = decodeGoogleVoidedNotification(body);
2030
2131
  if (voided) {
2031
2132
  if (!servesPackage(voided.packageName)) {
2032
- log.warn("[onesub/webhook/google] voided package name not served:", voided.packageName);
2133
+ log.warn("[onesub/webhook/google] voided package name not served", {
2134
+ packageName: voided.packageName
2135
+ });
2033
2136
  sendError(res, 400, shared.ONESUB_ERROR_CODE.PACKAGE_NAME_MISMATCH, "Package name mismatch");
2034
2137
  return;
2035
2138
  }
@@ -2038,7 +2141,9 @@ async function handleGoogleWebhook(req, res, config, store, purchaseStore, webho
2038
2141
  const oneTimeProduct = decodeGoogleOneTimeProductNotification(body);
2039
2142
  if (oneTimeProduct) {
2040
2143
  if (!servesPackage(oneTimeProduct.packageName)) {
2041
- log.warn("[onesub/webhook/google] oneTimeProduct package name not served:", oneTimeProduct.packageName);
2144
+ log.warn("[onesub/webhook/google] oneTimeProduct package name not served", {
2145
+ packageName: oneTimeProduct.packageName
2146
+ });
2042
2147
  sendError(res, 400, shared.ONESUB_ERROR_CODE.PACKAGE_NAME_MISMATCH, "Package name mismatch");
2043
2148
  return;
2044
2149
  }
@@ -2050,7 +2155,9 @@ async function handleGoogleWebhook(req, res, config, store, purchaseStore, webho
2050
2155
  return;
2051
2156
  }
2052
2157
  if (!servesPackage(notification.packageName)) {
2053
- log.warn("[onesub/webhook/google] Package name not served:", notification.packageName);
2158
+ log.warn("[onesub/webhook/google] Package name not served", {
2159
+ packageName: notification.packageName
2160
+ });
2054
2161
  sendError(res, 400, shared.ONESUB_ERROR_CODE.PACKAGE_NAME_MISMATCH, "Package name mismatch");
2055
2162
  return;
2056
2163
  }
@@ -2068,7 +2175,11 @@ async function handleGoogleWebhook(req, res, config, store, purchaseStore, webho
2068
2175
  });
2069
2176
  res.status(200).json({ received: true, queued: true });
2070
2177
  } catch (err2) {
2071
- log.error("[onesub/webhook/google] Failed to enqueue webhook job:", err2);
2178
+ log.error("[onesub/webhook/google] Failed to enqueue webhook job", {
2179
+ kind: work.kind,
2180
+ messageId: body.message.messageId,
2181
+ err: err2
2182
+ });
2072
2183
  await unmarkWebhookEvent(webhookEventStore, "google", markedEventId);
2073
2184
  sendError(res, 500, shared.ONESUB_ERROR_CODE.WEBHOOK_PROCESSING_FAILED, GOOGLE_FAILURE_MESSAGES[work.kind].message);
2074
2185
  }
@@ -2079,7 +2190,7 @@ async function handleGoogleWebhook(req, res, config, store, purchaseStore, webho
2079
2190
  res.status(200).json({ received: true });
2080
2191
  } catch (err2) {
2081
2192
  const { logPrefix, message } = GOOGLE_FAILURE_MESSAGES[work.kind];
2082
- log.error(logPrefix, err2);
2193
+ log.error(logPrefix, { kind: work.kind, err: err2 });
2083
2194
  await unmarkWebhookEvent(webhookEventStore, "google", markedEventId);
2084
2195
  sendError(res, 500, shared.ONESUB_ERROR_CODE.WEBHOOK_PROCESSING_FAILED, message);
2085
2196
  }
@@ -2191,9 +2302,10 @@ function createPurchaseRouter(config, purchaseStore) {
2191
2302
  }
2192
2303
  const bindingMismatch = platform === "apple" ? boundAccountId && boundAccountId.toLowerCase() !== userId.toLowerCase() : boundAccountId && boundAccountId !== userId;
2193
2304
  if (bindingMismatch) {
2194
- log.warn(
2195
- `[onesub/purchase] account binding mismatch for transaction ${transactionId}: token does not match userId ${userId}`
2196
- );
2305
+ log.warn("[onesub/purchase] account binding mismatch \u2014 token does not match userId", {
2306
+ transactionId,
2307
+ userId
2308
+ });
2197
2309
  sendError(
2198
2310
  res,
2199
2311
  409,
@@ -2210,9 +2322,11 @@ function createPurchaseRouter(config, purchaseStore) {
2210
2322
  if (existing.userId !== userId) {
2211
2323
  if (type === shared.PURCHASE_TYPE.NON_CONSUMABLE) {
2212
2324
  await purchaseStore.reassignPurchase(transactionId, userId);
2213
- log.info(
2214
- `[onesub/purchase] reassigned transaction ${transactionId} from ${existing.userId} to ${userId}`
2215
- );
2325
+ log.info("[onesub/purchase] reassigned transaction to a new user", {
2326
+ transactionId,
2327
+ fromUserId: existing.userId,
2328
+ userId
2329
+ });
2216
2330
  } else {
2217
2331
  sendError(
2218
2332
  res,
@@ -2256,7 +2370,7 @@ function createPurchaseRouter(config, purchaseStore) {
2256
2370
  };
2257
2371
  res.status(200).json(response);
2258
2372
  } catch (err2) {
2259
- log.error("[onesub/purchase/validate] Unexpected error:", err2);
2373
+ log.error("[onesub/purchase/validate] Unexpected error", { userId, productId, platform, type, err: err2 });
2260
2374
  sendError(res, 500, shared.ONESUB_ERROR_CODE.INTERNAL_ERROR, "Internal server error during purchase validation", NO_PURCHASE);
2261
2375
  }
2262
2376
  });
@@ -2271,7 +2385,7 @@ function createPurchaseRouter(config, purchaseStore) {
2271
2385
  const response = { purchases };
2272
2386
  res.status(200).json(response);
2273
2387
  } catch (err2) {
2274
- log.error("[onesub/purchase/status] Store error:", err2);
2388
+ log.error("[onesub/purchase/status] Store error", { userId, productId, err: err2 });
2275
2389
  sendError(res, 500, shared.ONESUB_ERROR_CODE.STORE_ERROR, "Internal server error", { purchases: [] });
2276
2390
  }
2277
2391
  });
@@ -2336,7 +2450,7 @@ function createEntitlementRouter(config, store, purchaseStore) {
2336
2450
  const response = { id, ...status };
2337
2451
  res.status(200).json(response);
2338
2452
  } catch (err2) {
2339
- log.error("[onesub/entitlement] evaluation error:", err2);
2453
+ log.error("[onesub/entitlement] evaluation error", { userId, entitlement: id, err: err2 });
2340
2454
  sendError(res, 500, shared.ONESUB_ERROR_CODE.STORE_ERROR, "Internal server error");
2341
2455
  }
2342
2456
  });
@@ -2360,7 +2474,7 @@ function createEntitlementRouter(config, store, purchaseStore) {
2360
2474
  };
2361
2475
  res.status(200).json(response);
2362
2476
  } catch (err2) {
2363
- log.error("[onesub/entitlements] evaluation error:", err2);
2477
+ log.error("[onesub/entitlements] evaluation error", { userId, err: err2 });
2364
2478
  sendError(res, 500, shared.ONESUB_ERROR_CODE.STORE_ERROR, "Internal server error", { entitlements: {} });
2365
2479
  }
2366
2480
  });
@@ -2474,7 +2588,7 @@ function createAdminRouter(config, purchaseStore, store, webhookQueue) {
2474
2588
  };
2475
2589
  res.status(200).json(response);
2476
2590
  } catch (err2) {
2477
- log.error("[onesub/admin/subscriptions] list error:", err2);
2591
+ log.error("[onesub/admin/subscriptions] list error", { err: err2 });
2478
2592
  sendError(res, 500, shared.ONESUB_ERROR_CODE.STORE_ERROR, "Internal server error");
2479
2593
  }
2480
2594
  });
@@ -2494,7 +2608,10 @@ function createAdminRouter(config, purchaseStore, store, webhookQueue) {
2494
2608
  }
2495
2609
  res.status(200).json(sub);
2496
2610
  } catch (err2) {
2497
- log.error("[onesub/admin/subscriptions/:transactionId] detail error:", err2);
2611
+ log.error("[onesub/admin/subscriptions/:transactionId] detail error", {
2612
+ transactionId: params.transactionId,
2613
+ err: err2
2614
+ });
2498
2615
  sendError(res, 500, shared.ONESUB_ERROR_CODE.STORE_ERROR, "Internal server error");
2499
2616
  }
2500
2617
  });
@@ -2527,7 +2644,7 @@ function createAdminRouter(config, purchaseStore, store, webhookQueue) {
2527
2644
  };
2528
2645
  res.status(200).json(response);
2529
2646
  } catch (err2) {
2530
- log.error("[onesub/admin/customers/:userId] error:", err2);
2647
+ log.error("[onesub/admin/customers/:userId] error", { userId: params.userId, err: err2 });
2531
2648
  sendError(res, 500, shared.ONESUB_ERROR_CODE.STORE_ERROR, "Internal server error");
2532
2649
  }
2533
2650
  });
@@ -2557,7 +2674,10 @@ function createAdminRouter(config, purchaseStore, store, webhookQueue) {
2557
2674
  await store.save(fresh);
2558
2675
  res.status(200).json({ ok: true, subscription: fresh });
2559
2676
  } catch (err2) {
2560
- log.error("[onesub/admin/sync-apple] error:", err2);
2677
+ log.error("[onesub/admin/sync-apple] error", {
2678
+ originalTransactionId: params.originalTransactionId,
2679
+ err: err2
2680
+ });
2561
2681
  sendError(res, 500, shared.ONESUB_ERROR_CODE.INTERNAL_ERROR, "Internal server error");
2562
2682
  }
2563
2683
  });
@@ -2572,9 +2692,10 @@ function createAdminRouter(config, purchaseStore, store, webhookQueue) {
2572
2692
  const body = parseOrSend(res, overrideBodySchema, req.body);
2573
2693
  if (!body) return;
2574
2694
  setTestOverride(params.userId, body.entitled);
2575
- log.warn(
2576
- `[onesub/admin] sandbox test override set for ${params.userId}: entitled=${body.entitled}`
2577
- );
2695
+ log.warn("[onesub/admin] sandbox test override set", {
2696
+ userId: params.userId,
2697
+ entitled: body.entitled
2698
+ });
2578
2699
  res.json({ ok: true, userId: params.userId, entitled: body.entitled, sandboxOnly: true });
2579
2700
  });
2580
2701
  router.delete(shared.ROUTES.ADMIN_TEST_OVERRIDE, (req, res) => {
@@ -2591,7 +2712,7 @@ function createAdminRouter(config, purchaseStore, store, webhookQueue) {
2591
2712
  const items = await webhookQueue.listDeadLetters();
2592
2713
  res.status(200).json({ items });
2593
2714
  } catch (err2) {
2594
- log.error("[onesub/admin/webhook-deadletters] error:", err2);
2715
+ log.error("[onesub/admin/webhook-deadletters] error", { err: err2 });
2595
2716
  sendError(res, 500, shared.ONESUB_ERROR_CODE.STORE_ERROR, "Internal server error");
2596
2717
  }
2597
2718
  });
@@ -2605,7 +2726,7 @@ function createAdminRouter(config, purchaseStore, store, webhookQueue) {
2605
2726
  await webhookQueue.replayDeadLetter(params.id);
2606
2727
  res.status(200).json({ ok: true });
2607
2728
  } catch (err2) {
2608
- log.error("[onesub/admin/webhook-replay] error:", err2);
2729
+ log.error("[onesub/admin/webhook-replay] error", { deadLetterId: params.id, err: err2 });
2609
2730
  sendError(res, 500, shared.ONESUB_ERROR_CODE.STORE_ERROR, "Internal server error");
2610
2731
  }
2611
2732
  });
@@ -2775,7 +2896,7 @@ function createMetricsRouter(config, store, purchaseStore) {
2775
2896
  );
2776
2897
  res.status(200).json(response);
2777
2898
  } catch (err2) {
2778
- log.error("[onesub/metrics/active] error:", err2);
2899
+ log.error("[onesub/metrics/active] error", { err: err2 });
2779
2900
  sendError(res, 500, shared.ONESUB_ERROR_CODE.STORE_ERROR, "Internal server error");
2780
2901
  }
2781
2902
  });
@@ -2842,7 +2963,7 @@ function createMetricsRouter(config, store, purchaseStore) {
2842
2963
  };
2843
2964
  res.status(200).json(response);
2844
2965
  } catch (err2) {
2845
- log.error(`[onesub/metrics/${name}] error:`, err2);
2966
+ log.error("[onesub/metrics] error", { route: name, err: err2 });
2846
2967
  sendError(res, 500, shared.ONESUB_ERROR_CODE.STORE_ERROR, "Internal server error");
2847
2968
  }
2848
2969
  };
@@ -2914,7 +3035,7 @@ function createAppleOfferRouter(config) {
2914
3035
  );
2915
3036
  res.status(200).json(result);
2916
3037
  } catch (err2) {
2917
- log.error("[onesub/apple/offer] signing error:", err2);
3038
+ log.error("[onesub/apple/offer] signing error", { productId: body.productId, err: err2 });
2918
3039
  sendError(res, 500, shared.ONESUB_ERROR_CODE.INTERNAL_ERROR, "Offer signing failed");
2919
3040
  }
2920
3041
  });
@@ -2986,7 +3107,7 @@ async function createPgPool(connectionString, label) {
2986
3107
  const Pool = pg.default?.Pool ?? pg.Pool;
2987
3108
  const pool = new Pool({ connectionString, max: 10 });
2988
3109
  pool.on("error", (err2) => {
2989
- log.error(`[onesub] ${label} pool error (idle client):`, err2);
3110
+ log.error("[onesub] Postgres pool error (idle client)", { store: label, err: err2 });
2990
3111
  });
2991
3112
  return pool;
2992
3113
  }
@@ -3727,7 +3848,7 @@ var BullMQWebhookQueue = class {
3727
3848
  const { Queue } = await this.getBullMQ();
3728
3849
  const queue = new Queue(this.queueName, { connection: this.connection });
3729
3850
  queue.on?.("error", (err2) => {
3730
- log.error("[onesub] BullMQ queue error:", err2);
3851
+ log.error("[onesub] BullMQ queue error", { err: err2 });
3731
3852
  });
3732
3853
  return queue;
3733
3854
  })();
@@ -3751,13 +3872,13 @@ var BullMQWebhookQueue = class {
3751
3872
  }
3752
3873
  );
3753
3874
  worker.on?.("error", (err2) => {
3754
- log.error("[onesub] BullMQ worker error:", err2);
3875
+ log.error("[onesub] BullMQ worker error", { err: err2 });
3755
3876
  });
3756
3877
  return worker;
3757
3878
  })();
3758
3879
  this.workerPromise.catch((err2) => {
3759
3880
  this.workerStartupError = err2 instanceof Error ? err2 : new Error(String(err2));
3760
- log.error("[onesub] BullMQ worker failed to start:", err2);
3881
+ log.error("[onesub] BullMQ worker failed to start", { err: err2 });
3761
3882
  });
3762
3883
  }
3763
3884
  }