@onesub/server 0.24.0 → 0.25.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
@@ -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 };