@onesub/server 0.13.1 → 0.14.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
@@ -255,10 +255,12 @@ function mockValidateAppleProduct(receipt, expectedProductId) {
255
255
  const outcome = classifyMockReceipt(receipt);
256
256
  if (!outcomePasses(outcome, "apple")) return null;
257
257
  const productId = expectedProductId ?? "mock_product";
258
+ const tokenMatch = receipt.match(/#token=([^#]+)/);
258
259
  return {
259
260
  transactionId: deterministicTransactionId(`mock_apple_${productId}`, receipt),
260
261
  productId,
261
- purchasedAt: (/* @__PURE__ */ new Date()).toISOString()
262
+ purchasedAt: (/* @__PURE__ */ new Date()).toISOString(),
263
+ ...tokenMatch ? { appAccountToken: tokenMatch[1] } : {}
262
264
  };
263
265
  }
264
266
  function mockValidateGoogleSubscription(receipt, productId) {
@@ -280,9 +282,11 @@ function mockValidateGoogleSubscription(receipt, productId) {
280
282
  function mockValidateGoogleProduct(receipt, productId) {
281
283
  const outcome = classifyMockReceipt(receipt);
282
284
  if (!outcomePasses(outcome, "google")) return null;
285
+ const tokenMatch = receipt.match(/#token=([^#]+)/);
283
286
  return {
284
287
  transactionId: deterministicTransactionId(`mock_google_${productId}`, receipt),
285
- purchasedAt: (/* @__PURE__ */ new Date()).toISOString()
288
+ purchasedAt: (/* @__PURE__ */ new Date()).toISOString(),
289
+ ...tokenMatch ? { obfuscatedExternalAccountId: tokenMatch[1] } : {}
286
290
  };
287
291
  }
288
292
 
@@ -446,7 +450,8 @@ async function validateAppleConsumableReceipt(signedTransaction, config, expecte
446
450
  return {
447
451
  transactionId,
448
452
  productId: tx.productId,
449
- purchasedAt: tx.purchaseDate ? new Date(tx.purchaseDate).toISOString() : (/* @__PURE__ */ new Date()).toISOString()
453
+ purchasedAt: tx.purchaseDate ? new Date(tx.purchaseDate).toISOString() : (/* @__PURE__ */ new Date()).toISOString(),
454
+ appAccountToken: tx.appAccountToken ?? void 0
450
455
  };
451
456
  }
452
457
  async function decodeAppleNotification(payload, skipJwsVerification = false) {
@@ -1005,7 +1010,8 @@ async function validateGoogleProductReceipt(purchaseToken, productId, config, ty
1005
1010
  }
1006
1011
  return {
1007
1012
  transactionId: purchase.orderId,
1008
- purchasedAt: purchase.purchaseTimeMillis ? new Date(parseInt(purchase.purchaseTimeMillis, 10)).toISOString() : (/* @__PURE__ */ new Date()).toISOString()
1013
+ purchasedAt: purchase.purchaseTimeMillis ? new Date(parseInt(purchase.purchaseTimeMillis, 10)).toISOString() : (/* @__PURE__ */ new Date()).toISOString(),
1014
+ obfuscatedExternalAccountId: purchase.obfuscatedExternalAccountId
1009
1015
  };
1010
1016
  }
1011
1017
  function decodeGoogleNotification(payload) {
@@ -1551,6 +1557,7 @@ function createPurchaseRouter(config, purchaseStore) {
1551
1557
  }
1552
1558
  let transactionId = null;
1553
1559
  let purchasedAt = (/* @__PURE__ */ new Date()).toISOString();
1560
+ let boundAccountId = null;
1554
1561
  if (platform === "apple") {
1555
1562
  if (!config.apple) {
1556
1563
  sendError(res, 500, shared.ONESUB_ERROR_CODE.APPLE_CONFIG_MISSING, "Apple configuration not provided", NO_PURCHASE);
@@ -1560,6 +1567,7 @@ function createPurchaseRouter(config, purchaseStore) {
1560
1567
  if (result) {
1561
1568
  transactionId = result.transactionId;
1562
1569
  purchasedAt = result.purchasedAt;
1570
+ boundAccountId = result.appAccountToken ?? null;
1563
1571
  }
1564
1572
  } else {
1565
1573
  if (!config.google) {
@@ -1575,12 +1583,26 @@ function createPurchaseRouter(config, purchaseStore) {
1575
1583
  if (result) {
1576
1584
  transactionId = result.transactionId;
1577
1585
  purchasedAt = result.purchasedAt;
1586
+ boundAccountId = result.obfuscatedExternalAccountId ?? null;
1578
1587
  }
1579
1588
  }
1580
1589
  if (!transactionId) {
1581
1590
  sendError(res, 422, shared.ONESUB_ERROR_CODE.RECEIPT_VALIDATION_FAILED, "Receipt validation failed", NO_PURCHASE);
1582
1591
  return;
1583
1592
  }
1593
+ if (boundAccountId && boundAccountId.toLowerCase() !== userId.toLowerCase()) {
1594
+ log.warn(
1595
+ `[onesub/purchase] account binding mismatch for transaction ${transactionId}: token does not match userId ${userId}`
1596
+ );
1597
+ sendError(
1598
+ res,
1599
+ 409,
1600
+ shared.ONESUB_ERROR_CODE.TRANSACTION_BELONGS_TO_OTHER_USER,
1601
+ "TRANSACTION_BELONGS_TO_OTHER_USER",
1602
+ NO_PURCHASE
1603
+ );
1604
+ return;
1605
+ }
1584
1606
  const existing = await purchaseStore.getPurchaseByTransactionId(transactionId);
1585
1607
  let action = "new";
1586
1608
  if (existing) {