@aglyn/plugins-commerce 1.0.0-beta.200 → 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.
@@ -25,6 +25,8 @@ 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";
@@ -2591,7 +2593,10 @@ async function findOrderForDispute(paymentIntentId) {
2591
2593
  fromName: (await brandFor(hostId)).fromName,
2592
2594
  sendingIdentity: await hostSendingIdentity(String(hostId)),
2593
2595
  audience: 'tenant',
2594
- 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'
2595
2600
  }));
2596
2601
  // Cost meter (AGL-1438). Transactional: the guest has paid, and a
2597
2602
  // confirmation a quota refused reads as a failed reservation.
@@ -2868,7 +2873,10 @@ async function findOrderForDispute(paymentIntentId) {
2868
2873
  fromName: (await brandFor(hostId)).fromName,
2869
2874
  sendingIdentity: await hostSendingIdentity(String(hostId)),
2870
2875
  audience: 'tenant',
2871
- 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'
2872
2880
  }));
2873
2881
  // Cost meter (AGL-1438). Transactional: a dropped receipt looks to
2874
2882
  // the buyer like an order that did not go through.
@@ -3123,7 +3131,10 @@ async function findOrderForDispute(paymentIntentId) {
3123
3131
  fromName: (await brandFor(hostId)).fromName,
3124
3132
  sendingIdentity: await hostSendingIdentity(String(hostId)),
3125
3133
  audience: 'tenant',
3126
- 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'
3127
3138
  }));
3128
3139
  // Cost meter (AGL-1438). Transactional: this email IS the
3129
3140
  // purchased goods.
@@ -3698,7 +3709,10 @@ async function findOrderForDispute(paymentIntentId) {
3698
3709
  fromName: (await brandFor(hostId)).fromName,
3699
3710
  sendingIdentity: await hostSendingIdentity(String(hostId)),
3700
3711
  audience: 'tenant',
3701
- 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'
3702
3716
  });
3703
3717
  // Cost meter (AGL-1438). Transactional: without it the order is
3704
3718
  // never fulfilled.
@@ -3813,7 +3827,10 @@ async function findOrderForDispute(paymentIntentId) {
3813
3827
  fromName: (await brandFor(hostId)).fromName,
3814
3828
  sendingIdentity: await hostSendingIdentity(String(hostId)),
3815
3829
  audience: 'tenant',
3816
- 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'
3817
3834
  }));
3818
3835
  // Cost meter (AGL-1438). Transactional, as the cart receipt above.
3819
3836
  await meterHostEmail(String(hostId));
@@ -3857,7 +3874,10 @@ async function findOrderForDispute(paymentIntentId) {
3857
3874
  fromName: (await brandFor(hostId)).fromName,
3858
3875
  sendingIdentity: await hostSendingIdentity(String(hostId)),
3859
3876
  audience: 'tenant',
3860
- 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'
3861
3881
  }));
3862
3882
  // Cost meter (AGL-1438). Transactional: the seller learns about
3863
3883
  // the order here.
@@ -3867,6 +3887,45 @@ async function findOrderForDispute(paymentIntentId) {
3867
3887
  }
3868
3888
  }
3869
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
+ }
3870
3929
  // Card disputes against a merchant's store (AGL-1787).
3871
3930
  //
3872
3931
  // Commerce subscribed no `charge.dispute.*` event at all, so a shopper
@@ -3892,7 +3951,8 @@ async function findOrderForDispute(paymentIntentId) {
3892
3951
  // finds). AGL-1554 will add its own `closed` branch for the marketplace
3893
3952
  // side; the two coexist because each self-selects by finding its own record.
3894
3953
  if (type === 'charge.dispute.created' || type === 'charge.dispute.closed') {
3895
- var _dispute_payment_intent;
3954
+ var _dispute_payment_intent, _ref84;
3955
+ var _snapshot_ref_parent_parent1;
3896
3956
  const dispute = object != null ? object : {};
3897
3957
  const paymentIntentId = String((_dispute_payment_intent = dispute.payment_intent) != null ? _dispute_payment_intent : '');
3898
3958
  const lookup = paymentIntentId ? await findOrderForDispute(paymentIntentId) : {
@@ -3917,9 +3977,9 @@ async function findOrderForDispute(paymentIntentId) {
3917
3977
  const claimed = lookup.kind === 'order' || lookup.kind === 'unresolved';
3918
3978
  const snapshot = lookup.kind === 'order' ? lookup.snapshot : null;
3919
3979
  if (snapshot) {
3920
- var _ref83;
3921
- var _snapshot_ref_parent_parent;
3922
- const hostId = String((_ref83 = (_snapshot_ref_parent_parent = snapshot.ref.parent.parent) == null ? void 0 : _snapshot_ref_parent_parent.id) != null ? _ref83 : '');
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 : '');
3923
3983
  if (type === 'charge.dispute.created') {
3924
3984
  const { opened, record } = await recordDisputeOpened(snapshot, dispute);
3925
3985
  if (opened && hostId) {
@@ -3936,7 +3996,7 @@ async function findOrderForDispute(paymentIntentId) {
3936
3996
  });
3937
3997
  }
3938
3998
  } else {
3939
- var _ref84;
3999
+ var _ref86;
3940
4000
  const settled = await recordDisputeClosed(snapshot, dispute);
3941
4001
  if (settled.recorded && hostId) {
3942
4002
  await notifyHostManagers(hostId, {
@@ -3994,7 +4054,7 @@ async function findOrderForDispute(paymentIntentId) {
3994
4054
  // transiently-failed reversal gets its retry — and gated inside on
3995
4055
  // the order's own `reversedTransferCents` marker, so it runs at most
3996
4056
  // once per dispute however many times Stripe delivers.
3997
- if (String((_ref84 = dispute == null ? void 0 : dispute.status) != null ? _ref84 : '') === 'lost') {
4057
+ if (String((_ref86 = dispute == null ? void 0 : dispute.status) != null ? _ref86 : '') === 'lost') {
3998
4058
  const pulledBackCents = await reverseSellerShare(snapshot.ref, dispute);
3999
4059
  // The merchant-experience half of the AGL-1794 decision, and not an
4000
4060
  // optional extra: the reversal takes money out of the connected
@@ -4021,9 +4081,11 @@ async function findOrderForDispute(paymentIntentId) {
4021
4081
  }
4022
4082
  }
4023
4083
  }
4024
- return {
4084
+ return _extends({
4025
4085
  claimed
4026
- };
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
+ } : {});
4027
4089
  }
4028
4090
  };
4029
4091