@aglyn/plugins-commerce 1.0.0-beta.199 → 1.0.0-beta.201

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.
Files changed (111) hide show
  1. package/package.json +13 -13
  2. package/src/lib/components/console/catalog-organization-card.component.js +48 -3
  3. package/src/lib/components/console/catalog-organization-card.component.js.map +1 -1
  4. package/src/lib/components/console/gift-cards-card.component.d.ts +9 -0
  5. package/src/lib/components/console/gift-cards-card.component.js +53 -28
  6. package/src/lib/components/console/gift-cards-card.component.js.map +1 -1
  7. package/src/lib/components/console/host-orders-card.component.d.ts +26 -0
  8. package/src/lib/components/console/host-orders-card.component.js +264 -194
  9. package/src/lib/components/console/host-orders-card.component.js.map +1 -1
  10. package/src/lib/components/console/order-detail-dialog.component.js +12 -0
  11. package/src/lib/components/console/order-detail-dialog.component.js.map +1 -1
  12. package/src/lib/components/console/pos-page.component.d.ts +14 -0
  13. package/src/lib/components/console/pos-page.component.js +86 -45
  14. package/src/lib/components/console/pos-page.component.js.map +1 -1
  15. package/src/lib/components/console/product-editor-dialog.component.js +30 -6
  16. package/src/lib/components/console/product-editor-dialog.component.js.map +1 -1
  17. package/src/lib/components/console/product-slugs.d.ts +43 -0
  18. package/src/lib/components/console/product-slugs.js +60 -0
  19. package/src/lib/components/console/product-slugs.js.map +1 -0
  20. package/src/lib/components/console/products-hub-card.component.d.ts +2 -2
  21. package/src/lib/components/console/products-hub-card.component.js +229 -282
  22. package/src/lib/components/console/products-hub-card.component.js.map +1 -1
  23. package/src/lib/components/console/products-hub-zone.component.d.ts +10 -5
  24. package/src/lib/components/console/products-hub-zone.component.js +32 -9
  25. package/src/lib/components/console/products-hub-zone.component.js.map +1 -1
  26. package/src/lib/components/console/recovery-queue-card.component.d.ts +37 -5
  27. package/src/lib/components/console/recovery-queue-card.component.js +107 -86
  28. package/src/lib/components/console/recovery-queue-card.component.js.map +1 -1
  29. package/src/lib/components/console/reservations-card.component.js +15 -3
  30. package/src/lib/components/console/reservations-card.component.js.map +1 -1
  31. package/src/lib/components/console/smart-collections.d.ts +32 -0
  32. package/src/lib/components/console/smart-collections.js +57 -0
  33. package/src/lib/components/console/smart-collections.js.map +1 -0
  34. package/src/lib/components/console/stock-movements-card.component.d.ts +13 -4
  35. package/src/lib/components/console/stock-movements-card.component.js +142 -180
  36. package/src/lib/components/console/stock-movements-card.component.js.map +1 -1
  37. package/src/lib/components/product-grid.d.ts +5 -0
  38. package/src/lib/components/product-grid.js +95 -34
  39. package/src/lib/components/product-grid.js.map +1 -1
  40. package/src/lib/constants/orders-list-query.d.ts +40 -0
  41. package/src/lib/constants/orders-list-query.js +205 -0
  42. package/src/lib/constants/orders-list-query.js.map +1 -0
  43. package/src/lib/constants/product-list-query.d.ts +42 -0
  44. package/src/lib/constants/product-list-query.js +217 -0
  45. package/src/lib/constants/product-list-query.js.map +1 -0
  46. package/src/lib/constants/stock-movement-query.d.ts +27 -0
  47. package/src/lib/constants/stock-movement-query.js +110 -0
  48. package/src/lib/constants/stock-movement-query.js.map +1 -0
  49. package/src/lib/constants/storefront-catalog-query.d.ts +103 -0
  50. package/src/lib/constants/storefront-catalog-query.js +312 -0
  51. package/src/lib/constants/storefront-catalog-query.js.map +1 -0
  52. package/src/lib/model/checkout-recovery.d.ts +37 -0
  53. package/src/lib/model/checkout-recovery.js +44 -0
  54. package/src/lib/model/checkout-recovery.js.map +1 -0
  55. package/src/lib/model/commerce-orders.d.ts +7 -0
  56. package/src/lib/model/commerce-orders.js.map +1 -1
  57. package/src/lib/model/commerce.d.ts +80 -0
  58. package/src/lib/model/commerce.js +67 -6
  59. package/src/lib/model/commerce.js.map +1 -1
  60. package/src/lib/model/gift-card-search.d.ts +24 -0
  61. package/src/lib/model/gift-card-search.js +48 -0
  62. package/src/lib/model/gift-card-search.js.map +1 -0
  63. package/src/lib/model/index.d.ts +1 -0
  64. package/src/lib/model/index.js +1 -0
  65. package/src/lib/model/index.js.map +1 -1
  66. package/src/lib/model/order-list-fields.d.ts +80 -0
  67. package/src/lib/model/order-list-fields.js +112 -0
  68. package/src/lib/model/order-list-fields.js.map +1 -0
  69. package/src/lib/server/billing-webhook.js +180 -99
  70. package/src/lib/server/billing-webhook.js.map +1 -1
  71. package/src/lib/server/cancel-order.js +6 -6
  72. package/src/lib/server/cancel-order.js.map +1 -1
  73. package/src/lib/server/cart-checkout.js +9 -2
  74. package/src/lib/server/cart-checkout.js.map +1 -1
  75. package/src/lib/server/catalog.d.ts +46 -30
  76. package/src/lib/server/catalog.js +507 -170
  77. package/src/lib/server/catalog.js.map +1 -1
  78. package/src/lib/server/checkout.js +3 -1
  79. package/src/lib/server/checkout.js.map +1 -1
  80. package/src/lib/server/collection-membership.d.ts +35 -0
  81. package/src/lib/server/collection-membership.js +172 -0
  82. package/src/lib/server/collection-membership.js.map +1 -0
  83. package/src/lib/server/draft-order.js +5 -3
  84. package/src/lib/server/draft-order.js.map +1 -1
  85. package/src/lib/server/gift-cards.js +7 -1
  86. package/src/lib/server/gift-cards.js.map +1 -1
  87. package/src/lib/server/member-name-search.d.ts +17 -0
  88. package/src/lib/server/member-name-search.js +23 -1
  89. package/src/lib/server/member-name-search.js.map +1 -1
  90. package/src/lib/server/membership-account.js +7 -3
  91. package/src/lib/server/membership-account.js.map +1 -1
  92. package/src/lib/server/membership-admin-password.js +8 -2
  93. package/src/lib/server/membership-admin-password.js.map +1 -1
  94. package/src/lib/server/membership-recover.js +4 -1
  95. package/src/lib/server/membership-recover.js.map +1 -1
  96. package/src/lib/server/membership-register.js +3 -1
  97. package/src/lib/server/membership-register.js.map +1 -1
  98. package/src/lib/server/pos-order.js +9 -5
  99. package/src/lib/server/pos-order.js.map +1 -1
  100. package/src/lib/server/process-abandoned.js +20 -7
  101. package/src/lib/server/process-abandoned.js.map +1 -1
  102. package/src/lib/server/process-restock.js +6 -2
  103. package/src/lib/server/process-restock.js.map +1 -1
  104. package/src/lib/server/reserve-stock.js +3 -4
  105. package/src/lib/server/reserve-stock.js.map +1 -1
  106. package/src/lib/server/reserve.js +3 -1
  107. package/src/lib/server/reserve.js.map +1 -1
  108. package/src/lib/server/site-page-enricher.js +3 -3
  109. package/src/lib/server/site-page-enricher.js.map +1 -1
  110. package/src/lib/server.js +4 -0
  111. package/src/lib/server.js.map +1 -1
@@ -25,9 +25,12 @@ import { recordContactRefund } from "./contact-refund.js";
25
25
  // specs in this library mock `@aglyn/tenant-data-admin` wholesale, and a
26
26
  // permissive stub would turn a reversal that never happened green.
27
27
  import { reverseEmailAttributedRevenue } from "@aglyn/tenant-data-admin/server/email-revenue-attribution";
28
+ import { paymentRiskEventFrom } from "@aglyn/aglyn/app-utils/payment-risk";
29
+ import { recordPaymentRiskOnRecord } from "@aglyn/tenant-data-admin/server/payment-risk-record";
28
30
  import { mintDownloadToken, tokenSigningSecret } from "./download.js";
29
31
  import { alertLowStockCrossing } from "./low-stock.js";
30
32
  import { decrementVariantStock } from "./reserve-stock.js";
33
+ import { giftCardSearchTokens } from "../model/gift-card-search.js";
31
34
  import { releaseStockHold } from "./stock-hold.js";
32
35
  import { releasePromotionHold, settlePromotionSlot } from "./promotion-hold.js";
33
36
  import { flagOrderRestock } from "./restock-flag.js";
@@ -326,7 +329,7 @@ async function findOrderForDispute(paymentIntentId) {
326
329
  */ async function recordDisputeOpened(snapshot, dispute) {
327
330
  const record = disputeFromEvent(dispute);
328
331
  const opened = await firebaseAdmin.app().firestore().runTransaction(async (transaction)=>{
329
- var _fresh_data;
332
+ var _fresh_data, _fresh_data1;
330
333
  var _order_dispute;
331
334
  const fresh = await transaction.get(snapshot.ref);
332
335
  if (!fresh.exists) return false;
@@ -347,10 +350,12 @@ async function findOrderForDispute(paymentIntentId) {
347
350
  // the same charge and does replace it.
348
351
  if (((_order_dispute = order.dispute) == null ? void 0 : _order_dispute.id) === record.id) return false;
349
352
  const amount = `$${(record.amountCents / 100).toFixed(2)}`;
350
- transaction.update(snapshot.ref, {
353
+ transaction.update(snapshot.ref, _extends({}, CommerceModel.orderListFields(_extends({}, (_fresh_data1 = fresh.data()) != null ? _fresh_data1 : {}, {
354
+ dispute: record
355
+ }), snapshot.id), {
351
356
  dispute: record,
352
357
  timeline: CommerceModel.appendOrderEvent(order, 'dispute', `Chargeback opened for ${amount}` + (record.reason ? ` (${record.reason.replace(/_/g, ' ')})` : '') + (record.evidenceDueByMs ? ` — evidence due ${new Date(record.evidenceDueByMs).toISOString().slice(0, 10)}` : ''), record.openedAtMs)
353
- });
358
+ }));
354
359
  return true;
355
360
  });
356
361
  return {
@@ -407,7 +412,7 @@ async function findOrderForDispute(paymentIntentId) {
407
412
  const outcome = String((_ref = dispute == null ? void 0 : dispute.status) != null ? _ref : '');
408
413
  const lost = outcome === 'lost';
409
414
  return firebaseAdmin.app().firestore().runTransaction(async (transaction)=>{
410
- var _fresh_data, _ref, _order_amountCents, _order_refundedCents;
415
+ var _fresh_data, _ref, _order_amountCents, _order_refundedCents, _fresh_data1;
411
416
  var _order_totals;
412
417
  const idle = {
413
418
  recorded: false,
@@ -430,19 +435,24 @@ async function findOrderForDispute(paymentIntentId) {
430
435
  const closedTheOrder = fullyReversed && CommerceModel.canTransitionOrder(order.status, 'refunded');
431
436
  const closedAtMs = Date.now();
432
437
  const amount = `$${(reversedCents / 100).toFixed(2)}`;
433
- transaction.update(snapshot.ref, _extends({}, reversedCents > 0 ? {
438
+ const settled = _extends({}, event, (stored == null ? void 0 : stored.id) === event.id && stored.openedAtMs ? {
439
+ openedAtMs: stored.openedAtMs
440
+ } : {}, {
441
+ status: outcome,
442
+ outcome,
443
+ closedAtMs,
444
+ reversedCents
445
+ });
446
+ transaction.update(snapshot.ref, _extends({}, CommerceModel.orderListFields(_extends({}, (_fresh_data1 = fresh.data()) != null ? _fresh_data1 : {}, closedTheOrder ? {
447
+ status: 'refunded'
448
+ } : {}, {
449
+ dispute: settled
450
+ }), snapshot.id), reversedCents > 0 ? {
434
451
  refundedCents: reversedTotal
435
452
  } : {}, closedTheOrder ? {
436
453
  status: 'refunded'
437
454
  } : {}, {
438
- dispute: _extends({}, event, (stored == null ? void 0 : stored.id) === event.id && stored.openedAtMs ? {
439
- openedAtMs: stored.openedAtMs
440
- } : {}, {
441
- status: outcome,
442
- outcome,
443
- closedAtMs,
444
- reversedCents
445
- }),
455
+ dispute: settled,
446
456
  timeline: CommerceModel.appendOrderEvent(order, 'dispute', lost ? `${amount} charged back (dispute lost)` + (closedTheOrder ? ' (full)' : '') : `Dispute ${outcome.replace(/_/g, ' ')} — no money reversed`, closedAtMs)
447
457
  }));
448
458
  return {
@@ -1394,7 +1404,7 @@ async function findOrderForDispute(paymentIntentId) {
1394
1404
  * this far. A notification is not idempotent by any doc key of its own.
1395
1405
  */ const settle = async (reversedTransferCents, transferReversalId, note)=>{
1396
1406
  return firebaseAdmin.app().firestore().runTransaction(async (transaction)=>{
1397
- var _fresh_data;
1407
+ var _fresh_data, _fresh_data1;
1398
1408
  const fresh = await transaction.get(orderRef);
1399
1409
  if (!fresh.exists) return false;
1400
1410
  const current = CommerceModel.liftLegacyOrder((_fresh_data = fresh.data()) != null ? _fresh_data : {});
@@ -1402,15 +1412,18 @@ async function findOrderForDispute(paymentIntentId) {
1402
1412
  if ((record == null ? void 0 : record.id) !== disputeId || record.reversedTransferCents != null) {
1403
1413
  return false;
1404
1414
  }
1405
- transaction.update(orderRef, {
1406
- // Written whole, the field's own rule — see `OrderDispute`.
1407
- dispute: _extends({}, record, {
1408
- reversedTransferCents
1409
- }, transferReversalId ? {
1410
- transferReversalId
1411
- } : {}),
1415
+ // Written whole, the field's own rule — see `OrderDispute`.
1416
+ const dispute = _extends({}, record, {
1417
+ reversedTransferCents
1418
+ }, transferReversalId ? {
1419
+ transferReversalId
1420
+ } : {});
1421
+ transaction.update(orderRef, _extends({}, CommerceModel.orderListFields(_extends({}, (_fresh_data1 = fresh.data()) != null ? _fresh_data1 : {}, {
1422
+ dispute
1423
+ }), orderRef.id), {
1424
+ dispute,
1412
1425
  timeline: CommerceModel.appendOrderEvent(current, 'dispute', note)
1413
- });
1426
+ }));
1414
1427
  return true;
1415
1428
  });
1416
1429
  };
@@ -2215,7 +2228,8 @@ async function findOrderForDispute(paymentIntentId) {
2215
2228
  }, {
2216
2229
  merge: true
2217
2230
  });
2218
- transaction.set(orderRef, _extends({
2231
+ // With the fields the orders list queries by (AGL-3321).
2232
+ transaction.set(orderRef, CommerceModel.withOrderListFields(orderRef.id, _extends({
2219
2233
  number: orderNumber,
2220
2234
  status: 'paid',
2221
2235
  channel: 'subscription',
@@ -2256,7 +2270,7 @@ async function findOrderForDispute(paymentIntentId) {
2256
2270
  feeCents: Number((_invoiceTotals_feeCents = invoiceTotals.feeCents) != null ? _invoiceTotals_feeCents : 0),
2257
2271
  createdAtMs: paidAtMs,
2258
2272
  createdAt: firebaseAdmin.firestore.FieldValue.serverTimestamp()
2259
- }));
2273
+ })));
2260
2274
  }
2261
2275
  return true;
2262
2276
  });
@@ -2579,7 +2593,10 @@ async function findOrderForDispute(paymentIntentId) {
2579
2593
  fromName: (await brandFor(hostId)).fromName,
2580
2594
  sendingIdentity: await hostSendingIdentity(String(hostId)),
2581
2595
  audience: 'tenant',
2582
- context: 'reservation confirmation'
2596
+ context: 'reservation confirmation',
2597
+ // Owed to the recipient by their own booking: the phishing
2598
+ // screen's soft rules never hold it (AGL-3356).
2599
+ owedFor: 'booking'
2583
2600
  }));
2584
2601
  // Cost meter (AGL-1438). Transactional: the guest has paid, and a
2585
2602
  // confirmation a quota refused reads as a failed reservation.
@@ -2681,7 +2698,8 @@ async function findOrderForDispute(paymentIntentId) {
2681
2698
  const totals = CommerceModel.computeCheckoutSessionTotals(lineItems, object, {
2682
2699
  feeCents: Number(feeCents != null ? feeCents : 0)
2683
2700
  });
2684
- transaction.set(orderRef, _extends({
2701
+ // With the fields the orders list queries by (AGL-3321).
2702
+ transaction.set(orderRef, CommerceModel.withOrderListFields(orderRef.id, _extends({
2685
2703
  number,
2686
2704
  status: 'paid',
2687
2705
  channel: 'online',
@@ -2735,7 +2753,7 @@ async function findOrderForDispute(paymentIntentId) {
2735
2753
  feeCents: Number(feeCents != null ? feeCents : 0),
2736
2754
  createdAtMs: Date.now(),
2737
2755
  createdAt: firebaseAdmin.firestore.FieldValue.serverTimestamp()
2738
- }));
2756
+ })));
2739
2757
  return true;
2740
2758
  });
2741
2759
  // Redelivery/replay guard (AGL-498): only fulfil when the order was
@@ -2855,7 +2873,10 @@ async function findOrderForDispute(paymentIntentId) {
2855
2873
  fromName: (await brandFor(hostId)).fromName,
2856
2874
  sendingIdentity: await hostSendingIdentity(String(hostId)),
2857
2875
  audience: 'tenant',
2858
- context: 'cart receipt'
2876
+ context: 'cart receipt',
2877
+ // Owed to the recipient by their own order: the phishing
2878
+ // screen's soft rules never hold it (AGL-3356).
2879
+ owedFor: 'order'
2859
2880
  }));
2860
2881
  // Cost meter (AGL-1438). Transactional: a dropped receipt looks to
2861
2882
  // the buyer like an order that did not go through.
@@ -3042,8 +3063,8 @@ async function findOrderForDispute(paymentIntentId) {
3042
3063
  const lineProduct = productsById.get(line.productId);
3043
3064
  if (!(lineProduct == null ? void 0 : lineProduct.giftCard)) continue;
3044
3065
  for(let unit = 0; unit < line.quantity; unit += 1){
3045
- var _ref53;
3046
- var _object_customer_details12, _object_customer_details13;
3066
+ var _ref53, _ref54;
3067
+ var _object_customer_details12, _object_customer_details13, _object_customer_details14;
3047
3068
  const code = `GC-${createHmac('sha256', String(object.id)).update(`${line.productId}:${unit}:${Date.now()}`).digest('hex').slice(0, 12).toUpperCase()}`;
3048
3069
  // THE EMAIL IS GATED ON THE WRITE (AGL-2161). This `.set()` used
3049
3070
  // to `.catch(() => undefined)` and fall straight through to the
@@ -3063,6 +3084,8 @@ async function findOrderForDispute(paymentIntentId) {
3063
3084
  balanceCents: line.unitAmountCents,
3064
3085
  recipientEmail: (_ref53 = object == null ? void 0 : (_object_customer_details12 = object.customer_details) == null ? void 0 : _object_customer_details12.email) != null ? _ref53 : null,
3065
3086
  orderId: String(object.id),
3087
+ // What the console's Gift cards search asks (AGL-3321).
3088
+ searchTokens: giftCardSearchTokens(code, (_ref54 = object == null ? void 0 : (_object_customer_details13 = object.customer_details) == null ? void 0 : _object_customer_details13.email) != null ? _ref54 : null),
3066
3089
  createdAtMs: Date.now()
3067
3090
  }).then(()=>true).catch((error)=>{
3068
3091
  console.error('Gift card issuance failed', hostId, code, error);
@@ -3088,9 +3111,9 @@ async function findOrderForDispute(paymentIntentId) {
3088
3111
  });
3089
3112
  continue;
3090
3113
  }
3091
- const giftTo = object == null ? void 0 : (_object_customer_details13 = object.customer_details) == null ? void 0 : _object_customer_details13.email;
3114
+ const giftTo = object == null ? void 0 : (_object_customer_details14 = object.customer_details) == null ? void 0 : _object_customer_details14.email;
3092
3115
  if (giftTo) {
3093
- var _ref54;
3116
+ var _ref55;
3094
3117
  const giftValue = `$${(line.unitAmountCents / 100).toFixed(2)}`;
3095
3118
  const fallbackText = `Gift card code: ${code}\n` + `Value: ${giftValue}\n\n` + 'Enter it at checkout to apply the balance.';
3096
3119
  // Site-owner-designed template when published (AGL-771).
@@ -3100,7 +3123,7 @@ async function findOrderForDispute(paymentIntentId) {
3100
3123
  });
3101
3124
  await sendEmail(_extends({
3102
3125
  to: giftTo,
3103
- subject: (_ref54 = designed == null ? void 0 : designed.subject) != null ? _ref54 : 'Your gift card',
3126
+ subject: (_ref55 = designed == null ? void 0 : designed.subject) != null ? _ref55 : 'Your gift card',
3104
3127
  text: (designed == null ? void 0 : designed.text) || fallbackText
3105
3128
  }, (designed == null ? void 0 : designed.html) ? {
3106
3129
  html: designed.html
@@ -3108,7 +3131,10 @@ async function findOrderForDispute(paymentIntentId) {
3108
3131
  fromName: (await brandFor(hostId)).fromName,
3109
3132
  sendingIdentity: await hostSendingIdentity(String(hostId)),
3110
3133
  audience: 'tenant',
3111
- context: 'gift card'
3134
+ context: 'gift card',
3135
+ // Owed to the recipient by their own order: the phishing
3136
+ // screen's soft rules never hold it (AGL-3356).
3137
+ owedFor: 'order'
3112
3138
  }));
3113
3139
  // Cost meter (AGL-1438). Transactional: this email IS the
3114
3140
  // purchased goods.
@@ -3118,12 +3144,12 @@ async function findOrderForDispute(paymentIntentId) {
3118
3144
  }
3119
3145
  // Discounts engine redemptions (AGL-305).
3120
3146
  if ((_object_metadata22 = object.metadata) == null ? void 0 : _object_metadata22.discountId) {
3121
- var _ref55;
3147
+ var _ref56;
3122
3148
  var _object_metadata26;
3123
3149
  await settleRedemption({
3124
3150
  firestore,
3125
3151
  ref: hostRef.collection('discounts').doc(String(object.metadata.discountId)),
3126
- holdKey: String((_ref55 = (_object_metadata26 = object.metadata) == null ? void 0 : _object_metadata26.discountHoldKey) != null ? _ref55 : ''),
3152
+ holdKey: String((_ref56 = (_object_metadata26 = object.metadata) == null ? void 0 : _object_metadata26.discountHoldKey) != null ? _ref56 : ''),
3127
3153
  orderRef,
3128
3154
  label: `discount ${object.metadata.discountId}`,
3129
3155
  detail: `Discount ${object.metadata.discountId} was applied to this ` + 'order but no longer exists, so the redemption is uncounted ' + 'against its limit.'
@@ -3137,7 +3163,7 @@ async function findOrderForDispute(paymentIntentId) {
3137
3163
  var _object_metadata27;
3138
3164
  const { hostId, orderId, productId } = (_object_metadata27 = object.metadata) != null ? _object_metadata27 : {};
3139
3165
  if (hostId && orderId) {
3140
- var _ref56, _ref57, _ref58;
3166
+ var _ref57, _ref58, _ref59;
3141
3167
  var _object_total_details, _object_total_details1;
3142
3168
  const firestore = firebaseAdmin.app().firestore();
3143
3169
  const hostRef = firestore.collection('hosts').doc(String(hostId));
@@ -3176,7 +3202,7 @@ async function findOrderForDispute(paymentIntentId) {
3176
3202
  //
3177
3203
  // The whole map is written rather than one field of it, so a
3178
3204
  // `merge`-set's nested-map merge and a plain replace agree.
3179
- const shippingCents = Math.max(0, Math.round(Number((_ref56 = object == null ? void 0 : (_object_total_details = object.total_details) == null ? void 0 : _object_total_details.amount_shipping) != null ? _ref56 : 0) || 0));
3205
+ const shippingCents = Math.max(0, Math.round(Number((_ref57 = object == null ? void 0 : (_object_total_details = object.total_details) == null ? void 0 : _object_total_details.amount_shipping) != null ? _ref57 : 0) || 0));
3180
3206
  // Tax joins shipping in the fold (AGL-1953). `draft-order.ts` now
3181
3207
  // declares tax — a Stripe Tax Rate for a `manual` store, whose figure
3182
3208
  // it already froze into `totals`, or `automatic_tax` for a `stripe`
@@ -3189,8 +3215,8 @@ async function findOrderForDispute(paymentIntentId) {
3189
3215
  // `amount_tax` 0 here (its own figure stays on the order, and its
3190
3216
  // AGL-1953 witness is metadata, not this field), and a draft composed
3191
3217
  // by a store with no tax reports 0 too.
3192
- const stripeTaxCents = Math.max(0, Math.round(Number((_ref57 = object == null ? void 0 : (_object_total_details1 = object.total_details) == null ? void 0 : _object_total_details1.amount_tax) != null ? _ref57 : 0) || 0));
3193
- const chargedTotalCents = Number((_ref58 = object == null ? void 0 : object.amount_total) != null ? _ref58 : NaN);
3218
+ const stripeTaxCents = Math.max(0, Math.round(Number((_ref58 = object == null ? void 0 : (_object_total_details1 = object.total_details) == null ? void 0 : _object_total_details1.amount_tax) != null ? _ref58 : 0) || 0));
3219
+ const chargedTotalCents = Number((_ref59 = object == null ? void 0 : object.amount_total) != null ? _ref59 : NaN);
3194
3220
  // `shipping_details` ONLY, unlike the cart branch's
3195
3221
  // `?? customer_details` fallback: that address is the BILLING one, and
3196
3222
  // a card sale rung up at a register completes here too — it must not
@@ -3203,8 +3229,8 @@ async function findOrderForDispute(paymentIntentId) {
3203
3229
  // the transaction commits.
3204
3230
  let paidAfterCancel = false;
3205
3231
  const flipped = await firestore.runTransaction(async (transaction)=>{
3206
- var _snapshot_data, _ref, _ref1, _ref2, _ref3, _lifted_totals, _ref4, _shipTo_name, _shipTo_address_line1, _shipTo_address_line2, _shipTo_address_city, _shipTo_address_state, _shipTo_address_postal_code, _shipTo_address_country;
3207
- var _object_metadata, _object_customer_details, _lifted_totals1;
3232
+ var _snapshot_data, _snapshot_data1, _ref, _ref1, _ref2, _ref3, _ref4, _ref5, _lifted_totals, _ref6, _shipTo_name, _shipTo_address_line1, _shipTo_address_line2, _shipTo_address_city, _shipTo_address_state, _shipTo_address_postal_code, _shipTo_address_country;
3233
+ var _object_customer_details, _object_metadata, _object_customer_details1, _lifted_totals1;
3208
3234
  const snapshot = await transaction.get(orderRef);
3209
3235
  if (!snapshot.exists) return false;
3210
3236
  const lifted = CommerceModel.liftLegacyOrder((_snapshot_data = snapshot.data()) != null ? _snapshot_data : {});
@@ -3241,7 +3267,10 @@ async function findOrderForDispute(paymentIntentId) {
3241
3267
  return false;
3242
3268
  }
3243
3269
  paidOrder = lifted;
3244
- transaction.set(orderRef, _extends({
3270
+ transaction.set(orderRef, _extends({}, CommerceModel.orderListFields(_extends({}, (_snapshot_data1 = snapshot.data()) != null ? _snapshot_data1 : {}, {
3271
+ status: 'paid',
3272
+ customerEmail: (_ref = (_ref1 = object == null ? void 0 : (_object_customer_details = object.customer_details) == null ? void 0 : _object_customer_details.email) != null ? _ref1 : lifted.customerEmail) != null ? _ref : null
3273
+ }), orderRef.id), {
3245
3274
  status: 'paid',
3246
3275
  // WHICH TAX THIS ORDER CARRIED (AGL-2451), resolved from the
3247
3276
  // session that actually charged it rather than from the draft's
@@ -3258,9 +3287,9 @@ async function findOrderForDispute(paymentIntentId) {
3258
3287
  // record that any of the charge was tax (AGL-1953). Without it
3259
3288
  // every register card sale would stamp `none` beside an order
3260
3289
  // plainly carrying tax.
3261
- taxMode: storefrontTaxModeOf(object, Number((_ref = object == null ? void 0 : (_object_metadata = object.metadata) == null ? void 0 : _object_metadata.taxCents) != null ? _ref : 0)),
3262
- paymentIntentId: String((_ref1 = object == null ? void 0 : object.payment_intent) != null ? _ref1 : '') || null,
3263
- customerEmail: (_ref2 = (_ref3 = object == null ? void 0 : (_object_customer_details = object.customer_details) == null ? void 0 : _object_customer_details.email) != null ? _ref3 : lifted.customerEmail) != null ? _ref2 : null,
3290
+ taxMode: storefrontTaxModeOf(object, Number((_ref2 = object == null ? void 0 : (_object_metadata = object.metadata) == null ? void 0 : _object_metadata.taxCents) != null ? _ref2 : 0)),
3291
+ paymentIntentId: String((_ref3 = object == null ? void 0 : object.payment_intent) != null ? _ref3 : '') || null,
3292
+ customerEmail: (_ref4 = (_ref5 = object == null ? void 0 : (_object_customer_details1 = object.customer_details) == null ? void 0 : _object_customer_details1.email) != null ? _ref5 : lifted.customerEmail) != null ? _ref4 : null,
3264
3293
  timeline: CommerceModel.appendOrderEvent(lifted, 'paid')
3265
3294
  }, shippingCents > 0 || stripeTaxCents > 0 ? {
3266
3295
  totals: _extends({}, (_lifted_totals = lifted.totals) != null ? _lifted_totals : {}, shippingCents > 0 ? {
@@ -3271,7 +3300,7 @@ async function findOrderForDispute(paymentIntentId) {
3271
3300
  // Stripe's own figure, for the AGL-1698 reason: the
3272
3301
  // frozen total is what the draft was priced at, and only
3273
3302
  // `amount_total` is the money that moved.
3274
- totalCents: Number.isFinite(chargedTotalCents) ? chargedTotalCents : Number((_ref4 = (_lifted_totals1 = lifted.totals) == null ? void 0 : _lifted_totals1.totalCents) != null ? _ref4 : 0) + shippingCents
3303
+ totalCents: Number.isFinite(chargedTotalCents) ? chargedTotalCents : Number((_ref6 = (_lifted_totals1 = lifted.totals) == null ? void 0 : _lifted_totals1.totalCents) != null ? _ref6 : 0) + shippingCents
3275
3304
  })
3276
3305
  } : {}, (shipTo == null ? void 0 : shipTo.address) ? {
3277
3306
  shippingAddress: {
@@ -3301,8 +3330,8 @@ async function findOrderForDispute(paymentIntentId) {
3301
3330
  });
3302
3331
  }
3303
3332
  if (flipped) {
3304
- var _ref59, _ref60;
3305
- var _object_metadata28, _object_customer_details14;
3333
+ var _ref60, _ref61;
3334
+ var _object_metadata28, _object_customer_details15;
3306
3335
  const order = paidOrder;
3307
3336
  // Discounts engine redemptions (AGL-305), for this branch's BOTH
3308
3337
  // tenants: a console draft order and a POS card sale, which carry the
@@ -3317,12 +3346,12 @@ async function findOrderForDispute(paymentIntentId) {
3317
3346
  // the hold existed, which Stripe's at-least-once delivery would run
3318
3347
  // twice. The transition is what makes it once.
3319
3348
  if ((_object_metadata28 = object.metadata) == null ? void 0 : _object_metadata28.discountId) {
3320
- var _ref61;
3349
+ var _ref62;
3321
3350
  var _object_metadata29;
3322
3351
  await settleRedemption({
3323
3352
  firestore,
3324
3353
  ref: hostRef.collection('discounts').doc(String(object.metadata.discountId)),
3325
- holdKey: String((_ref61 = (_object_metadata29 = object.metadata) == null ? void 0 : _object_metadata29.discountHoldKey) != null ? _ref61 : ''),
3354
+ holdKey: String((_ref62 = (_object_metadata29 = object.metadata) == null ? void 0 : _object_metadata29.discountHoldKey) != null ? _ref62 : ''),
3326
3355
  orderRef,
3327
3356
  label: `discount ${object.metadata.discountId}`,
3328
3357
  detail: `Discount ${object.metadata.discountId} was applied to this ` + 'order but no longer exists, so the redemption is uncounted ' + 'against its limit.'
@@ -3345,15 +3374,15 @@ async function findOrderForDispute(paymentIntentId) {
3345
3374
  // figure the draft was priced at, and the two agree by construction,
3346
3375
  // but only one of them is the money that moved. Stored totals remain
3347
3376
  // the fallback for a session shape that reports no total.
3348
- const draftEmail = (_ref59 = (_ref60 = object == null ? void 0 : (_object_customer_details14 = object.customer_details) == null ? void 0 : _object_customer_details14.email) != null ? _ref60 : order.customerEmail) != null ? _ref59 : null;
3377
+ const draftEmail = (_ref60 = (_ref61 = object == null ? void 0 : (_object_customer_details15 = object.customer_details) == null ? void 0 : _object_customer_details15.email) != null ? _ref61 : order.customerEmail) != null ? _ref60 : null;
3349
3378
  if (draftEmail) {
3350
- var _ref62, _ref63, _ref64;
3351
- var _order_totals, _object_customer_details15;
3352
- const chargedCents = Number((_ref62 = object == null ? void 0 : object.amount_total) != null ? _ref62 : 0) || Number((_ref63 = (_order_totals = order.totals) == null ? void 0 : _order_totals.totalCents) != null ? _ref63 : 0);
3379
+ var _ref63, _ref64, _ref65;
3380
+ var _order_totals, _object_customer_details16;
3381
+ const chargedCents = Number((_ref63 = object == null ? void 0 : object.amount_total) != null ? _ref63 : 0) || Number((_ref64 = (_order_totals = order.totals) == null ? void 0 : _order_totals.totalCents) != null ? _ref64 : 0);
3353
3382
  await captureHostContact(_extends({
3354
3383
  hostId: String(hostId),
3355
3384
  email: draftEmail,
3356
- name: (_ref64 = object == null ? void 0 : (_object_customer_details15 = object.customer_details) == null ? void 0 : _object_customer_details15.name) != null ? _ref64 : undefined,
3385
+ name: (_ref65 = object == null ? void 0 : (_object_customer_details16 = object.customer_details) == null ? void 0 : _object_customer_details16.name) != null ? _ref65 : undefined,
3357
3386
  source: 'order',
3358
3387
  initialLifecycleStage: 'customer'
3359
3388
  }, chargedCents > 0 ? {
@@ -3366,15 +3395,15 @@ async function findOrderForDispute(paymentIntentId) {
3366
3395
  }));
3367
3396
  }
3368
3397
  if (productId) {
3369
- var _productSnapshot_data, _ref65, _ref66;
3398
+ var _productSnapshot_data, _ref66, _ref67;
3370
3399
  var _object_metadata30, _lifted_variants_, _order_lineItems_, _order_lineItems;
3371
3400
  const productRef = hostRef.collection('products').doc(String(productId));
3372
3401
  const productSnapshot = await productRef.get();
3373
3402
  const lifted = CommerceModel.liftLegacyProduct((_productSnapshot_data = productSnapshot.data()) != null ? _productSnapshot_data : {
3374
3403
  name: 'Product'
3375
3404
  });
3376
- const soldVariantId = String((_ref65 = (_object_metadata30 = object.metadata) == null ? void 0 : _object_metadata30.variantId) != null ? _ref65 : '') || ((_lifted_variants_ = lifted.variants[0]) == null ? void 0 : _lifted_variants_.id);
3377
- const quantity = (_ref66 = (_order_lineItems = order.lineItems) == null ? void 0 : (_order_lineItems_ = _order_lineItems[0]) == null ? void 0 : _order_lineItems_.quantity) != null ? _ref66 : 1;
3405
+ const soldVariantId = String((_ref66 = (_object_metadata30 = object.metadata) == null ? void 0 : _object_metadata30.variantId) != null ? _ref66 : '') || ((_lifted_variants_ = lifted.variants[0]) == null ? void 0 : _lifted_variants_.id);
3406
+ const quantity = (_ref67 = (_order_lineItems = order.lineItems) == null ? void 0 : (_order_lineItems_ = _order_lineItems[0]) == null ? void 0 : _order_lineItems_.quantity) != null ? _ref67 : 1;
3378
3407
  if (soldVariantId) {
3379
3408
  // Atomic since AGL-2320.
3380
3409
  const moved = await decrementVariantStock({
@@ -3426,10 +3455,10 @@ async function findOrderForDispute(paymentIntentId) {
3426
3455
  // would decrement it twice.
3427
3456
  const paidProducts = new Map();
3428
3457
  for (const line of (_order_lineItems1 = order.lineItems) != null ? _order_lineItems1 : []){
3429
- var _ref67, _ref68, _line_variantId1;
3458
+ var _ref68, _ref69, _line_variantId1;
3430
3459
  var _lineProduct_variants_;
3431
- const lineProductId = String((_ref67 = line == null ? void 0 : line.productId) != null ? _ref67 : '');
3432
- const soldQty = Math.round(Number((_ref68 = line == null ? void 0 : line.quantity) != null ? _ref68 : 0));
3460
+ const lineProductId = String((_ref68 = line == null ? void 0 : line.productId) != null ? _ref68 : '');
3461
+ const soldQty = Math.round(Number((_ref69 = line == null ? void 0 : line.quantity) != null ? _ref69 : 0));
3433
3462
  if (!lineProductId || !(soldQty > 0)) continue;
3434
3463
  // Firestore reserves `__…__` ids and `.doc()` throws
3435
3464
  // synchronously on one; a corrupt line must not fail the
@@ -3492,8 +3521,8 @@ async function findOrderForDispute(paymentIntentId) {
3492
3521
  var _object_metadata31;
3493
3522
  const { hostId, productId, feeCents, couponCode } = (_object_metadata31 = object.metadata) != null ? _object_metadata31 : {};
3494
3523
  if (hostId && productId) {
3495
- var _ref69, _productForSnapshot_get1, _productForSnapshot_data1, _ref70, _ref71, _ref72, _ref73, _ref74, _ref75, _ref76;
3496
- var _object_metadata32, _buyNowLineItems_, _object_customer_details16, _object_customer_details17, _object_customer_details18, _object_metadata33;
3524
+ var _ref70, _productForSnapshot_get1, _productForSnapshot_data1, _ref71, _ref72, _ref73, _ref74, _ref75, _ref76, _ref77;
3525
+ var _object_metadata32, _buyNowLineItems_, _object_customer_details17, _object_customer_details18, _object_customer_details19, _object_metadata33;
3497
3526
  const firestore = firebaseAdmin.app().firestore();
3498
3527
  const hostRef = firestore.collection('hosts').doc(String(hostId));
3499
3528
  // Orders v1 (AGL-283): line-item snapshot + totals + timeline with
@@ -3503,7 +3532,7 @@ async function findOrderForDispute(paymentIntentId) {
3503
3532
  // is bounded to Stripe's at-least-once edge).
3504
3533
  const orderRef = hostRef.collection('orders').doc(String(object.id));
3505
3534
  const counterRef = hostRef.collection('counters').doc('orders');
3506
- const amountCents = Number((_ref69 = object == null ? void 0 : object.amount_total) != null ? _ref69 : 0);
3535
+ const amountCents = Number((_ref70 = object == null ? void 0 : object.amount_total) != null ? _ref70 : 0);
3507
3536
  const productForSnapshot = await hostRef.collection('products').doc(String(productId)).get();
3508
3537
  const snapshotName = String((_productForSnapshot_get1 = productForSnapshot.get('name')) != null ? _productForSnapshot_get1 : 'Product');
3509
3538
  // AGL-1711: the line item and totals used to be fabricated from
@@ -3516,7 +3545,7 @@ async function findOrderForDispute(paymentIntentId) {
3516
3545
  name: snapshotName
3517
3546
  });
3518
3547
  const soldVariant = ((_object_metadata32 = object.metadata) == null ? void 0 : _object_metadata32.variantId) ? liftedForSnapshot.variants.find((item)=>item.id === String(object.metadata.variantId)) : liftedForSnapshot.variants[0];
3519
- const variantOptions = Object.values((_ref70 = soldVariant == null ? void 0 : soldVariant.options) != null ? _ref70 : {});
3548
+ const variantOptions = Object.values((_ref71 = soldVariant == null ? void 0 : soldVariant.options) != null ? _ref71 : {});
3520
3549
  const { lineItems: buyNowLineItems, totals: buyNowTotals } = CommerceModel.computeBuyNowOrder(object, _extends({
3521
3550
  name: snapshotName
3522
3551
  }, variantOptions.length ? {
@@ -3528,7 +3557,7 @@ async function findOrderForDispute(paymentIntentId) {
3528
3557
  } : {}, liftedForSnapshot.supplierId ? {
3529
3558
  supplierId: liftedForSnapshot.supplierId
3530
3559
  } : {}));
3531
- const soldQuantity = (_ref71 = (_buyNowLineItems_ = buyNowLineItems[0]) == null ? void 0 : _buyNowLineItems_.quantity) != null ? _ref71 : 1;
3560
+ const soldQuantity = (_ref72 = (_buyNowLineItems_ = buyNowLineItems[0]) == null ? void 0 : _buyNowLineItems_.quantity) != null ? _ref72 : 1;
3532
3561
  const created = await firestore.runTransaction(async (transaction)=>{
3533
3562
  var _counter_get, _ref, _ref1, _ref2, _ref3;
3534
3563
  var _object_metadata, _object_customer_details, _object_customer_details1;
@@ -3543,7 +3572,8 @@ async function findOrderForDispute(paymentIntentId) {
3543
3572
  }, {
3544
3573
  merge: true
3545
3574
  });
3546
- transaction.set(orderRef, _extends({
3575
+ // With the fields the orders list queries by (AGL-3321).
3576
+ transaction.set(orderRef, CommerceModel.withOrderListFields(orderRef.id, _extends({
3547
3577
  number,
3548
3578
  status: 'paid',
3549
3579
  channel: 'online',
@@ -3577,7 +3607,7 @@ async function findOrderForDispute(paymentIntentId) {
3577
3607
  couponCode
3578
3608
  } : {}, {
3579
3609
  createdAt: firebaseAdmin.firestore.FieldValue.serverTimestamp()
3580
- }));
3610
+ })));
3581
3611
  return true;
3582
3612
  });
3583
3613
  // Redelivery guard (AGL-498): skip the notification + fulfilment side
@@ -3587,8 +3617,8 @@ async function findOrderForDispute(paymentIntentId) {
3587
3617
  // in the bell, not just the owner's email.
3588
3618
  void notifyHostManagers(String(hostId), _extends({
3589
3619
  type: 'content.order',
3590
- title: `New order — $${(Number((_ref72 = object == null ? void 0 : object.amount_total) != null ? _ref72 : 0) / 100).toFixed(2)}`
3591
- }, (object == null ? void 0 : (_object_customer_details16 = object.customer_details) == null ? void 0 : _object_customer_details16.email) ? {
3620
+ title: `New order — $${(Number((_ref73 = object == null ? void 0 : object.amount_total) != null ? _ref73 : 0) / 100).toFixed(2)}`
3621
+ }, (object == null ? void 0 : (_object_customer_details17 = object.customer_details) == null ? void 0 : _object_customer_details17.email) ? {
3592
3622
  body: `From ${object.customer_details.email}`
3593
3623
  } : {}, {
3594
3624
  link: `/${hostId}/products`
@@ -3679,7 +3709,10 @@ async function findOrderForDispute(paymentIntentId) {
3679
3709
  fromName: (await brandFor(hostId)).fromName,
3680
3710
  sendingIdentity: await hostSendingIdentity(String(hostId)),
3681
3711
  audience: 'tenant',
3682
- context: 'dropship supplier notice'
3712
+ context: 'dropship supplier notice',
3713
+ // Owed to the recipient by their own order: the phishing
3714
+ // screen's soft rules never hold it (AGL-3356).
3715
+ owedFor: 'order'
3683
3716
  });
3684
3717
  // Cost meter (AGL-1438). Transactional: without it the order is
3685
3718
  // never fulfilled.
@@ -3692,14 +3725,14 @@ async function findOrderForDispute(paymentIntentId) {
3692
3725
  // Contacts ingestion (AGL-197): buyers become contacts.
3693
3726
  await captureHostContact({
3694
3727
  hostId: String(hostId),
3695
- email: object == null ? void 0 : (_object_customer_details17 = object.customer_details) == null ? void 0 : _object_customer_details17.email,
3696
- name: (_ref73 = object == null ? void 0 : (_object_customer_details18 = object.customer_details) == null ? void 0 : _object_customer_details18.name) != null ? _ref73 : undefined,
3728
+ email: object == null ? void 0 : (_object_customer_details18 = object.customer_details) == null ? void 0 : _object_customer_details18.email,
3729
+ name: (_ref74 = object == null ? void 0 : (_object_customer_details19 = object.customer_details) == null ? void 0 : _object_customer_details19.name) != null ? _ref74 : undefined,
3697
3730
  source: 'order',
3698
3731
  initialLifecycleStage: 'customer',
3699
- purchaseCents: Number((_ref74 = object == null ? void 0 : object.amount_total) != null ? _ref74 : 0),
3732
+ purchaseCents: Number((_ref75 = object == null ? void 0 : object.amount_total) != null ? _ref75 : 0),
3700
3733
  interaction: {
3701
3734
  refId: String(object.id),
3702
- summary: `Placed an order ($${(Number((_ref75 = object == null ? void 0 : object.amount_total) != null ? _ref75 : 0) / 100).toFixed(2)})`
3735
+ summary: `Placed an order ($${(Number((_ref76 = object == null ? void 0 : object.amount_total) != null ? _ref76 : 0) / 100).toFixed(2)})`
3703
3736
  }
3704
3737
  });
3705
3738
  const productRef = hostRef.collection('products').doc(String(productId));
@@ -3717,12 +3750,12 @@ async function findOrderForDispute(paymentIntentId) {
3717
3750
  // serialize; the snapshot above is still read, for the product NAME on
3718
3751
  // the receipt below, and no longer decides any count.
3719
3752
  {
3720
- var _productSnapshot_data2, _ref77;
3753
+ var _productSnapshot_data2, _ref78;
3721
3754
  var _object_metadata34, _lifted_variants_1;
3722
3755
  const lifted = CommerceModel.liftLegacyProduct((_productSnapshot_data2 = productSnapshot.data()) != null ? _productSnapshot_data2 : {
3723
3756
  name: 'Product'
3724
3757
  });
3725
- const soldVariantId = String((_ref77 = (_object_metadata34 = object.metadata) == null ? void 0 : _object_metadata34.variantId) != null ? _ref77 : '') || ((_lifted_variants_1 = lifted.variants[0]) == null ? void 0 : _lifted_variants_1.id);
3758
+ const soldVariantId = String((_ref78 = (_object_metadata34 = object.metadata) == null ? void 0 : _object_metadata34.variantId) != null ? _ref78 : '') || ((_lifted_variants_1 = lifted.variants[0]) == null ? void 0 : _lifted_variants_1.id);
3726
3759
  if (soldVariantId) {
3727
3760
  // AGL-1711: `-1` regardless of how many units were bought, so a
3728
3761
  // 3-unit buy-now sale decremented stock by one and the difference
@@ -3753,14 +3786,14 @@ async function findOrderForDispute(paymentIntentId) {
3753
3786
  // Settlement, the same way round and for the same reason as the cart
3754
3787
  // branch above (AGL-2356): the decrement lands first, then the
3755
3788
  // reservation is dropped.
3756
- await releaseStockHold(hostRef, String((_ref76 = (_object_metadata33 = object.metadata) == null ? void 0 : _object_metadata33.stockHoldKey) != null ? _ref76 : ''));
3789
+ await releaseStockHold(hostRef, String((_ref77 = (_object_metadata33 = object.metadata) == null ? void 0 : _object_metadata33.stockHoldKey) != null ? _ref77 : ''));
3757
3790
  if (couponCode) {
3758
- var _ref78;
3791
+ var _ref79;
3759
3792
  var _object_metadata35;
3760
3793
  await settleRedemption({
3761
3794
  firestore,
3762
3795
  ref: hostRef.collection('coupons').doc(String(couponCode)),
3763
- holdKey: String((_ref78 = (_object_metadata35 = object.metadata) == null ? void 0 : _object_metadata35.couponHoldKey) != null ? _ref78 : ''),
3796
+ holdKey: String((_ref79 = (_object_metadata35 = object.metadata) == null ? void 0 : _object_metadata35.couponHoldKey) != null ? _ref79 : ''),
3764
3797
  orderRef,
3765
3798
  label: `coupon ${couponCode}`,
3766
3799
  detail: `Coupon ${couponCode} was applied to this order but no longer ` + 'exists, so the redemption is uncounted against its limit.'
@@ -3769,14 +3802,14 @@ async function findOrderForDispute(paymentIntentId) {
3769
3802
  // Receipt + seller notification (AGL-96): env-gated like every
3770
3803
  // other outbound email; failures never fail the webhook.
3771
3804
  if (isEmailConfigured()) {
3772
- var _productSnapshot_get, _ref79;
3773
- var _object_customer_details19, _org, _this3;
3805
+ var _productSnapshot_get, _ref80;
3806
+ var _object_customer_details20, _org, _this3;
3774
3807
  const productName = String((_productSnapshot_get = productSnapshot.get('name')) != null ? _productSnapshot_get : 'your purchase');
3775
- const amount = (Number((_ref79 = object == null ? void 0 : object.amount_total) != null ? _ref79 : 0) / 100).toFixed(2);
3776
- const buyerEmail = object == null ? void 0 : (_object_customer_details19 = object.customer_details) == null ? void 0 : _object_customer_details19.email;
3808
+ const amount = (Number((_ref80 = object == null ? void 0 : object.amount_total) != null ? _ref80 : 0) / 100).toFixed(2);
3809
+ const buyerEmail = object == null ? void 0 : (_object_customer_details20 = object.customer_details) == null ? void 0 : _object_customer_details20.email;
3777
3810
  const orderTotal = `$${amount}`;
3778
3811
  if (buyerEmail) {
3779
- var _ref80;
3812
+ var _ref81;
3780
3813
  const fallbackText = `Thanks for your purchase!\n\n${productName} — $${amount}` + `\nOrder reference: ${object.id}`;
3781
3814
  // Site-owner-designed template when published (AGL-771).
3782
3815
  const designed = await renderHostEmailWithTokens(firebaseAdmin.app().firestore(), String(hostId), 'order-receipt', {
@@ -3786,7 +3819,7 @@ async function findOrderForDispute(paymentIntentId) {
3786
3819
  });
3787
3820
  await sendEmail(_extends({
3788
3821
  to: String(buyerEmail),
3789
- subject: (_ref80 = designed == null ? void 0 : designed.subject) != null ? _ref80 : `Receipt: ${productName}`,
3822
+ subject: (_ref81 = designed == null ? void 0 : designed.subject) != null ? _ref81 : `Receipt: ${productName}`,
3790
3823
  text: (designed == null ? void 0 : designed.text) || fallbackText
3791
3824
  }, (designed == null ? void 0 : designed.html) ? {
3792
3825
  html: designed.html
@@ -3794,7 +3827,10 @@ async function findOrderForDispute(paymentIntentId) {
3794
3827
  fromName: (await brandFor(hostId)).fromName,
3795
3828
  sendingIdentity: await hostSendingIdentity(String(hostId)),
3796
3829
  audience: 'tenant',
3797
- context: 'receipt'
3830
+ context: 'receipt',
3831
+ // Owed to the recipient by their own order: the phishing
3832
+ // screen's soft rules never hold it (AGL-3356).
3833
+ owedFor: 'order'
3798
3834
  }));
3799
3835
  // Cost meter (AGL-1438). Transactional, as the cart receipt above.
3800
3836
  await meterHostEmail(String(hostId));
@@ -3817,7 +3853,7 @@ async function findOrderForDispute(paymentIntentId) {
3817
3853
  // SSO.
3818
3854
  const seller = (_this4 = await findUserByUidAcrossPools(sellerUid).catch(()=>null)) == null ? void 0 : _this4.record;
3819
3855
  if (seller == null ? void 0 : seller.email) {
3820
- var _hostSnapshot_get, _ref81;
3856
+ var _hostSnapshot_get, _ref82;
3821
3857
  const siteName = String((_hostSnapshot_get = hostSnapshot.get('displayName')) != null ? _hostSnapshot_get : hostId);
3822
3858
  const fallbackText = `You made a sale on ${siteName}!\n\n${productName} — $${amount}` + (buyerEmail ? `\nBuyer: ${buyerEmail}` : '') + `\nOrder reference: ${object.id}`;
3823
3859
  // Site-owner-designed template when published (AGL-771).
@@ -3830,7 +3866,7 @@ async function findOrderForDispute(paymentIntentId) {
3830
3866
  });
3831
3867
  await sendEmail(_extends({
3832
3868
  to: seller.email,
3833
- subject: (_ref81 = designed == null ? void 0 : designed.subject) != null ? _ref81 : `New order: ${productName}`,
3869
+ subject: (_ref82 = designed == null ? void 0 : designed.subject) != null ? _ref82 : `New order: ${productName}`,
3834
3870
  text: (designed == null ? void 0 : designed.text) || fallbackText
3835
3871
  }, (designed == null ? void 0 : designed.html) ? {
3836
3872
  html: designed.html
@@ -3838,7 +3874,10 @@ async function findOrderForDispute(paymentIntentId) {
3838
3874
  fromName: (await brandFor(hostId)).fromName,
3839
3875
  sendingIdentity: await hostSendingIdentity(String(hostId)),
3840
3876
  audience: 'tenant',
3841
- context: 'seller order notice'
3877
+ context: 'seller order notice',
3878
+ // Owed to the recipient by their own order: the phishing
3879
+ // screen's soft rules never hold it (AGL-3356).
3880
+ owedFor: 'order'
3842
3881
  }));
3843
3882
  // Cost meter (AGL-1438). Transactional: the seller learns about
3844
3883
  // the order here.
@@ -3848,6 +3887,45 @@ async function findOrderForDispute(paymentIntentId) {
3848
3887
  }
3849
3888
  }
3850
3889
  }
3890
+ // Fraud signals on a store's sale (AGL-3360): an issuer's early fraud
3891
+ // warning and a Radar review. The merchant decides what to do for their
3892
+ // own customer, so the signal lands on the ORDER — a timeline line and the
3893
+ // shared `paymentRisk` chip — and the site's managers are told once.
3894
+ // Nothing is refunded or canceled. Found by the same payment-intent join a
3895
+ // dispute uses; a signal on anything else is not commerce's and stays
3896
+ // silent here (the platform route and the other plugins answer it).
3897
+ // Disputes keep their own branch below, which has always told the
3898
+ // merchant; both report the site so the seller's fraud ledger can name it.
3899
+ const risk = paymentRiskEventFrom(type, object);
3900
+ if (risk && risk.signal.kind !== 'dispute') {
3901
+ var _ref83, _snapshot_data;
3902
+ var _snapshot_ref_parent_parent;
3903
+ const lookup = risk.paymentIntentId ? await findOrderForDispute(risk.paymentIntentId) : {
3904
+ kind: 'not-ours'
3905
+ };
3906
+ if (lookup.kind !== 'order') return;
3907
+ const snapshot = lookup.snapshot;
3908
+ const hostId = String((_ref83 = (_snapshot_ref_parent_parent = snapshot.ref.parent.parent) == null ? void 0 : _snapshot_ref_parent_parent.id) != null ? _ref83 : '');
3909
+ const order = CommerceModel.liftLegacyOrder((_snapshot_data = snapshot.data()) != null ? _snapshot_data : {});
3910
+ await recordPaymentRiskOnRecord({
3911
+ ref: snapshot.ref,
3912
+ signal: risk.signal,
3913
+ hostId,
3914
+ subjectLabel: `Order ${CommerceModel.formatOrderNumber(order, snapshot.id)}`,
3915
+ link: `/${hostId}/orders`,
3916
+ notificationType: 'content.order',
3917
+ extraUpdate: (data)=>({
3918
+ timeline: CommerceModel.appendOrderEvent(CommerceModel.liftLegacyOrder(data), risk.signal.kind === 'early-fraud-warning' ? 'fraud-warning' : 'payment-review', risk.signal.kind === 'early-fraud-warning' ? 'The card issuer reported this payment as possibly fraudulent' + (risk.signal.detail ? ` (${risk.signal.detail.replace(/_/g, ' ')})` : '') : 'Stripe Radar is holding this payment for review', risk.signal.atMs)
3919
+ })
3920
+ }, {
3921
+ firestore: firebaseAdmin.app().firestore(),
3922
+ notify: notifyHostManagers
3923
+ });
3924
+ return {
3925
+ claimed: true,
3926
+ hostId
3927
+ };
3928
+ }
3851
3929
  // Card disputes against a merchant's store (AGL-1787).
3852
3930
  //
3853
3931
  // Commerce subscribed no `charge.dispute.*` event at all, so a shopper
@@ -3873,7 +3951,8 @@ async function findOrderForDispute(paymentIntentId) {
3873
3951
  // finds). AGL-1554 will add its own `closed` branch for the marketplace
3874
3952
  // side; the two coexist because each self-selects by finding its own record.
3875
3953
  if (type === 'charge.dispute.created' || type === 'charge.dispute.closed') {
3876
- var _dispute_payment_intent;
3954
+ var _dispute_payment_intent, _ref84;
3955
+ var _snapshot_ref_parent_parent1;
3877
3956
  const dispute = object != null ? object : {};
3878
3957
  const paymentIntentId = String((_dispute_payment_intent = dispute.payment_intent) != null ? _dispute_payment_intent : '');
3879
3958
  const lookup = paymentIntentId ? await findOrderForDispute(paymentIntentId) : {
@@ -3898,9 +3977,9 @@ async function findOrderForDispute(paymentIntentId) {
3898
3977
  const claimed = lookup.kind === 'order' || lookup.kind === 'unresolved';
3899
3978
  const snapshot = lookup.kind === 'order' ? lookup.snapshot : null;
3900
3979
  if (snapshot) {
3901
- var _ref82;
3902
- var _snapshot_ref_parent_parent;
3903
- const hostId = String((_ref82 = (_snapshot_ref_parent_parent = snapshot.ref.parent.parent) == null ? void 0 : _snapshot_ref_parent_parent.id) != null ? _ref82 : '');
3980
+ var _ref85;
3981
+ var _snapshot_ref_parent_parent2;
3982
+ const hostId = String((_ref85 = (_snapshot_ref_parent_parent2 = snapshot.ref.parent.parent) == null ? void 0 : _snapshot_ref_parent_parent2.id) != null ? _ref85 : '');
3904
3983
  if (type === 'charge.dispute.created') {
3905
3984
  const { opened, record } = await recordDisputeOpened(snapshot, dispute);
3906
3985
  if (opened && hostId) {
@@ -3917,7 +3996,7 @@ async function findOrderForDispute(paymentIntentId) {
3917
3996
  });
3918
3997
  }
3919
3998
  } else {
3920
- var _ref83;
3999
+ var _ref86;
3921
4000
  const settled = await recordDisputeClosed(snapshot, dispute);
3922
4001
  if (settled.recorded && hostId) {
3923
4002
  await notifyHostManagers(hostId, {
@@ -3975,7 +4054,7 @@ async function findOrderForDispute(paymentIntentId) {
3975
4054
  // transiently-failed reversal gets its retry — and gated inside on
3976
4055
  // the order's own `reversedTransferCents` marker, so it runs at most
3977
4056
  // once per dispute however many times Stripe delivers.
3978
- if (String((_ref83 = dispute == null ? void 0 : dispute.status) != null ? _ref83 : '') === 'lost') {
4057
+ if (String((_ref86 = dispute == null ? void 0 : dispute.status) != null ? _ref86 : '') === 'lost') {
3979
4058
  const pulledBackCents = await reverseSellerShare(snapshot.ref, dispute);
3980
4059
  // The merchant-experience half of the AGL-1794 decision, and not an
3981
4060
  // optional extra: the reversal takes money out of the connected
@@ -4002,9 +4081,11 @@ async function findOrderForDispute(paymentIntentId) {
4002
4081
  }
4003
4082
  }
4004
4083
  }
4005
- return {
4084
+ return _extends({
4006
4085
  claimed
4007
- };
4086
+ }, snapshot ? {
4087
+ hostId: String((_ref84 = (_snapshot_ref_parent_parent1 = snapshot.ref.parent.parent) == null ? void 0 : _snapshot_ref_parent_parent1.id) != null ? _ref84 : '')
4088
+ } : {});
4008
4089
  }
4009
4090
  };
4010
4091