@aglyn/plugins-commerce 1.0.0-beta.218 → 1.0.0-beta.220
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/README.md +6 -0
- package/package.json +14 -14
- package/src/index.d.ts +1 -0
- package/src/index.js +1 -19
- package/src/index.js.map +1 -1
- package/src/lib/components/cart.js +2 -1
- package/src/lib/components/cart.js.map +1 -1
- package/src/lib/components/console/catalog-organization-card.component.js +665 -459
- package/src/lib/components/console/catalog-organization-card.component.js.map +1 -1
- package/src/lib/components/console/host-orders-card.component.js +2 -1
- package/src/lib/components/console/host-orders-card.component.js.map +1 -1
- package/src/lib/components/console/member-posts-card.component.js +1 -1
- package/src/lib/components/console/member-posts-card.component.js.map +1 -1
- package/src/lib/components/console/order-detail-dialog.component.js +8 -7
- package/src/lib/components/console/order-detail-dialog.component.js.map +1 -1
- package/src/lib/components/console/payments-settings-card.component.js +2 -2
- package/src/lib/components/console/payments-settings-card.component.js.map +1 -1
- package/src/lib/components/console/product-editor-dialog.component.js +1221 -889
- package/src/lib/components/console/product-editor-dialog.component.js.map +1 -1
- package/src/lib/components/console/product-zones.d.ts +184 -0
- package/src/lib/components/console/product-zones.js +54 -0
- package/src/lib/components/console/product-zones.js.map +1 -0
- package/src/lib/components/console/products-hub-card.component.js +3 -2
- package/src/lib/components/console/products-hub-card.component.js.map +1 -1
- package/src/lib/components/console/products-hub-zone.component.d.ts +1 -1
- package/src/lib/components/console/products-hub-zone.component.js +2 -1
- package/src/lib/components/console/products-hub-zone.component.js.map +1 -1
- package/src/lib/components/console/site-member-purchases.component.d.ts +24 -0
- package/src/lib/components/console/site-member-purchases.component.js +339 -0
- package/src/lib/components/console/site-member-purchases.component.js.map +1 -0
- package/src/lib/components/console/store-settings-card.component.js +2 -2
- package/src/lib/components/console/store-settings-card.component.js.map +1 -1
- package/src/lib/components/console/storefront-tax-summary-card.component.js +1 -1
- package/src/lib/components/console/storefront-tax-summary-card.component.js.map +1 -1
- package/src/lib/components/member-signup.js +2 -2
- package/src/lib/components/member-signup.js.map +1 -1
- package/src/lib/components/newsletter-signup.js +2 -2
- package/src/lib/components/newsletter-signup.js.map +1 -1
- package/src/lib/components/product-detail.js +2 -1
- package/src/lib/components/product-detail.js.map +1 -1
- package/src/lib/components/product-grid.js +1 -1
- package/src/lib/components/product-grid.js.map +1 -1
- package/src/lib/constants/operator-alerts.js +5 -3
- package/src/lib/constants/operator-alerts.js.map +1 -1
- package/src/lib/constants/storefront-payments.d.ts +33 -0
- package/src/lib/constants/storefront-payments.js +35 -0
- package/src/lib/constants/storefront-payments.js.map +1 -0
- package/src/lib/declarations.server.d.ts +3 -1
- package/src/lib/declarations.server.js +19 -1
- package/src/lib/declarations.server.js.map +1 -1
- package/src/lib/model/commerce-io.js +1 -1
- package/src/lib/model/commerce-io.js.map +1 -1
- package/src/lib/model/commerce-record-lists.d.ts +31 -0
- package/src/lib/model/commerce-record-lists.js +45 -0
- package/src/lib/model/commerce-record-lists.js.map +1 -0
- package/src/lib/model/commerce-record-routes.d.ts +45 -0
- package/src/lib/model/commerce-record-routes.js +72 -0
- package/src/lib/model/commerce-record-routes.js.map +1 -0
- package/src/lib/model/commerce-search-sources.d.ts +21 -0
- package/src/lib/model/commerce-search-sources.js +42 -0
- package/src/lib/model/commerce-search-sources.js.map +1 -0
- package/src/lib/model/commerce-topics.d.ts +20 -0
- package/src/lib/model/commerce-topics.js +33 -0
- package/src/lib/model/commerce-topics.js.map +1 -0
- package/src/lib/model/index.d.ts +1 -0
- package/src/lib/model/index.js +1 -0
- package/src/lib/model/index.js.map +1 -1
- package/src/lib/model/product-record.d.ts +32 -0
- package/src/lib/model/product-record.js +82 -0
- package/src/lib/model/product-record.js.map +1 -0
- package/src/lib/model/site-member-purchases.d.ts +48 -0
- package/src/lib/model/site-member-purchases.js +50 -0
- package/src/lib/model/site-member-purchases.js.map +1 -0
- package/src/lib/model/storefront-tax-summary.d.ts +121 -0
- package/src/lib/model/storefront-tax-summary.js +148 -0
- package/src/lib/model/storefront-tax-summary.js.map +1 -0
- package/src/lib/plan-entitlements.d.ts +35 -0
- package/src/lib/plan-entitlements.js +54 -0
- package/src/lib/plan-entitlements.js.map +1 -0
- package/src/lib/plugin.js +21 -0
- package/src/lib/plugin.js.map +1 -1
- package/src/lib/server/billing-webhook.js +387 -248
- package/src/lib/server/billing-webhook.js.map +1 -1
- package/src/lib/server/cart-checkout.js +3 -3
- package/src/lib/server/cart-checkout.js.map +1 -1
- package/src/lib/server/checkout.js +2 -2
- package/src/lib/server/checkout.js.map +1 -1
- package/src/lib/server/connect.js +5 -5
- package/src/lib/server/connect.js.map +1 -1
- package/src/lib/server/draft-order.js +2 -2
- package/src/lib/server/draft-order.js.map +1 -1
- package/src/lib/server/gift-cards.js +7 -3
- package/src/lib/server/gift-cards.js.map +1 -1
- package/src/lib/server/low-stock.js +3 -1
- package/src/lib/server/low-stock.js.map +1 -1
- package/src/lib/server/member-post.js +2 -2
- package/src/lib/server/member-post.js.map +1 -1
- package/src/lib/server/membership-admin-password.js +4 -1
- package/src/lib/server/membership-admin-password.js.map +1 -1
- package/src/lib/server/membership-content.js +1 -1
- package/src/lib/server/membership-content.js.map +1 -1
- package/src/lib/server/membership-register.js +13 -2
- package/src/lib/server/membership-register.js.map +1 -1
- package/src/lib/server/newsletter.js +32 -19
- package/src/lib/server/newsletter.js.map +1 -1
- package/src/lib/server/pos-order.js +3 -3
- package/src/lib/server/pos-order.js.map +1 -1
- package/src/lib/server/process-abandoned.js +4 -2
- package/src/lib/server/process-abandoned.js.map +1 -1
- package/src/lib/server/process-restock.js +7 -5
- package/src/lib/server/process-restock.js.map +1 -1
- package/src/lib/server/product-record-index.d.ts +19 -0
- package/src/lib/server/product-record-index.js +83 -0
- package/src/lib/server/product-record-index.js.map +1 -0
- package/src/lib/server/reconcile-stock.js +1 -1
- package/src/lib/server/reconcile-stock.js.map +1 -1
- package/src/lib/server/reserve-stock.js +6 -1
- package/src/lib/server/reserve-stock.js.map +1 -1
- package/src/lib/server/reserve.js +2 -2
- package/src/lib/server/reserve.js.map +1 -1
- package/src/lib/server/revenue-source.d.ts +130 -0
- package/src/lib/server/revenue-source.js +286 -0
- package/src/lib/server/revenue-source.js.map +1 -0
- package/src/lib/server/supplier-outbox.js +16 -1
- package/src/lib/server/supplier-outbox.js.map +1 -1
- package/src/lib/server/supplier-update.js +19 -9
- package/src/lib/server/supplier-update.js.map +1 -1
- package/src/lib/server/tax-profile.d.ts +8 -5
- package/src/lib/server/tax-profile.js +22 -5
- package/src/lib/server/tax-profile.js.map +1 -1
- package/src/lib/server/tax-return-source.d.ts +125 -0
- package/src/lib/server/tax-return-source.js +524 -0
- package/src/lib/server/tax-return-source.js.map +1 -0
- package/src/lib/server/tax-summary.d.ts +92 -0
- package/src/lib/server/tax-summary.js +258 -0
- package/src/lib/server/tax-summary.js.map +1 -0
- package/src/lib/server.d.ts +1 -0
- package/src/lib/server.js +23 -0
- package/src/lib/server.js.map +1 -1
- package/src/lib/starter-templates.d.ts +19 -0
- package/src/lib/starter-templates.js +157 -0
- package/src/lib/starter-templates.js.map +1 -0
- package/src/lib/tenant-emails.d.ts +33 -0
- package/src/lib/tenant-emails.js +551 -0
- package/src/lib/tenant-emails.js.map +1 -0
- package/src/lib/testing/stand-in-crm-record-routes.d.ts +25 -0
- package/src/lib/testing/stand-in-crm-record-routes.js +46 -0
- package/src/lib/testing/stand-in-crm-record-routes.js.map +1 -0
|
@@ -15,9 +15,12 @@
|
|
|
15
15
|
* limitations under the License.
|
|
16
16
|
*/ import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
17
17
|
import * as Aglyn from "@aglyn/aglyn/server";
|
|
18
|
-
import { findUserByUidAcrossPools, firebaseAdmin, getOrgForHost, meterHostEmail, notifyHostManagers, notifyRiskEvent, raiseOperatorAlert, renderHostEmailWithTokens,
|
|
18
|
+
import { findUserByUidAcrossPools, firebaseAdmin, getOrgForHost, meterHostEmail, notifyHostManagers, notifyRiskEvent, raiseOperatorAlert, renderHostEmailWithTokens, updateExisting, hostSendingIdentity } from "@aglyn/tenant-data-admin";
|
|
19
|
+
import { paymentProvider } from "@aglyn/tenant-data-admin/server/payment-provider";
|
|
19
20
|
import { captureHostContact } from "@aglyn/tenant-runtime";
|
|
20
21
|
import { formatOperatorAlertAmount } from "@aglyn/aglyn/app-utils/operator-alerts";
|
|
22
|
+
import { resolveSiteTimeZone } from "@aglyn/aglyn/app-utils/collection-entry-date";
|
|
23
|
+
import { resolveHostToken } from "@aglyn/aglyn/app-utils/host-tokens";
|
|
21
24
|
import { COMMERCE_CHARGEBACK_UNROUTABLE, COMMERCE_FEE_REPRICE_REFUSED, COMMERCE_SELLER_SHARE_NOT_REVERSED, COMMERCE_TAX_NOT_REVERSED } from "../constants/operator-alerts.js";
|
|
22
25
|
import { createHmac } from "crypto";
|
|
23
26
|
import { isEmailConfigured, sendEmail } from "@aglyn/shared-util-email";
|
|
@@ -104,11 +107,17 @@ import { enqueueSupplierDelivery } from "./supplier-outbox.js";
|
|
|
104
107
|
}
|
|
105
108
|
if (keys.length) {
|
|
106
109
|
const remaining = await hostRef.collection('licenseKeys').where('productId', '==', productId).where('assignedAtMs', '==', null).limit(6).get();
|
|
107
|
-
|
|
110
|
+
// Once per CROSSING (AGL-3432), as the low-stock alert is: this sale
|
|
111
|
+
// took the pool below five, or took its last key. Every later sale in a
|
|
112
|
+
// low pool stays quiet rather than repeating the same notice.
|
|
113
|
+
const left = remaining.size;
|
|
114
|
+
const before = left + keys.length;
|
|
115
|
+
if (left < 5 && before >= 5 || left === 0) {
|
|
116
|
+
const productName = String(await hostRef.collection('products').doc(productId).get().then((snapshot)=>snapshot.get('name')).catch(()=>'') || 'a product');
|
|
108
117
|
void notifyHostManagers(hostId, {
|
|
109
118
|
type: 'content.lowStock',
|
|
110
|
-
title:
|
|
111
|
-
body:
|
|
119
|
+
title: left === 0 ? `License keys used up — ${productName}` : `License keys running low — ${productName}`,
|
|
120
|
+
body: left === 0 ? `The last license key for ${productName} on {site} was just ` + 'assigned. A buyer who orders it now gets no key; add keys to ' + 'its pool under Products.' : `Only ${left} license key${left === 1 ? ' is' : 's are'} left ` + `for ${productName} on {site}. Every sale of it takes its ` + 'keys from this pool; add more under Products before it runs out.',
|
|
112
121
|
link: `/${hostId}/products`
|
|
113
122
|
});
|
|
114
123
|
}
|
|
@@ -293,6 +302,25 @@ async function findOrderForDispute(paymentIntentId) {
|
|
|
293
302
|
}
|
|
294
303
|
});
|
|
295
304
|
}
|
|
305
|
+
/**
|
|
306
|
+
* A site as an operator alert names it: its name and id, else the id alone
|
|
307
|
+
* (AGL-3432) — staff see faults from every org, and a bare host id has to be
|
|
308
|
+
* looked up before anyone can act. One host read, taken only when a money
|
|
309
|
+
* fault is being reported; a read that fails answers the id.
|
|
310
|
+
*/ async function operatorSiteLabel(hostId) {
|
|
311
|
+
if (!hostId) return 'unknown';
|
|
312
|
+
try {
|
|
313
|
+
var _data;
|
|
314
|
+
const host = (_data = (await firebaseAdmin.app().firestore().collection('hosts').doc(hostId).get()).data()) != null ? _data : {};
|
|
315
|
+
const name = [
|
|
316
|
+
host['displayName'],
|
|
317
|
+
host['subdomain']
|
|
318
|
+
].find((value)=>typeof value === 'string' && value.trim() !== '');
|
|
319
|
+
return name ? `${name.trim()} (${hostId})` : hostId;
|
|
320
|
+
} catch (unused) {
|
|
321
|
+
return hostId;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
296
324
|
/**
|
|
297
325
|
* Tells the operator that a subscription cycle's sales tax stayed with the
|
|
298
326
|
* merchant (AGL-3377): the platform files and remits it, so every one of
|
|
@@ -304,6 +332,7 @@ async function findOrderForDispute(paymentIntentId) {
|
|
|
304
332
|
context: {
|
|
305
333
|
invoiceId: input.invoiceId,
|
|
306
334
|
hostId: input.hostId,
|
|
335
|
+
site: await operatorSiteLabel(input.hostId),
|
|
307
336
|
amount: input.taxCents === undefined ? 'An unknown amount' : formatOperatorAlertAmount(input.taxCents),
|
|
308
337
|
reason: input.reason
|
|
309
338
|
}
|
|
@@ -573,11 +602,11 @@ async function findOrderForDispute(paymentIntentId) {
|
|
|
573
602
|
* fulfilment path are untouched; what changes is that no FURTHER cycle is
|
|
574
603
|
* billed.
|
|
575
604
|
*
|
|
576
|
-
* `cancel_at_period_end` rather than an outright cancel
|
|
577
|
-
*
|
|
578
|
-
*
|
|
579
|
-
*
|
|
580
|
-
*
|
|
605
|
+
* `cancel_at_period_end` rather than an outright cancel: the subscriber keeps
|
|
606
|
+
* the period they just paid for. An immediate cancel would take service away
|
|
607
|
+
* from a shopper who did nothing wrong. The flag is reversible in Stripe, but
|
|
608
|
+
* nothing here clears it when the merchant's plan comes back, so the notice
|
|
609
|
+
* says that restoring the plan does not restart the subscription (AGL-3432).
|
|
581
610
|
*
|
|
582
611
|
* ## Once-only
|
|
583
612
|
*
|
|
@@ -587,7 +616,8 @@ async function findOrderForDispute(paymentIntentId) {
|
|
|
587
616
|
* subscription billing forever with the books saying it had been stopped. The
|
|
588
617
|
* `Idempotency-Key` covers the window where the claim is written and the
|
|
589
618
|
* response never arrives.
|
|
590
|
-
*/ async function stopLapsedStorefrontSubscription(hostId, subscriptionId, subscriptionRef) {
|
|
619
|
+
*/ async function stopLapsedStorefrontSubscription(hostId, subscriptionId, subscriptionRef, /** Who subscribed to what, and the workspace, for the merchant's notice. */ notice) {
|
|
620
|
+
var _ref, _ref1;
|
|
591
621
|
const stripeKey = process.env.STRIPE_SECRET_KEY;
|
|
592
622
|
if (!stripeKey) {
|
|
593
623
|
console.error('Lapsed storefront subscription not stopped: STRIPE_SECRET_KEY is not set (AGL-2071)', {
|
|
@@ -648,10 +678,29 @@ async function findOrderForDispute(paymentIntentId) {
|
|
|
648
678
|
}, {
|
|
649
679
|
merge: true
|
|
650
680
|
}).catch(()=>undefined);
|
|
681
|
+
// When it ends, from Stripe's own answer: `cancel_at` is the period end the
|
|
682
|
+
// flag just scheduled. Told as a date in the site's zone, and left out when
|
|
683
|
+
// Stripe did not say.
|
|
684
|
+
const stopped = await response.json().catch(()=>null);
|
|
685
|
+
const endsAtSeconds = Number((_ref = (_ref1 = stopped == null ? void 0 : stopped.cancel_at) != null ? _ref1 : stopped == null ? void 0 : stopped.current_period_end) != null ? _ref : 0);
|
|
686
|
+
let endsOn = '';
|
|
687
|
+
if (endsAtSeconds > 0) {
|
|
688
|
+
const host = await firebaseAdmin.app().firestore().collection('hosts').doc(hostId).get().then((snapshot)=>snapshot.data()).catch(()=>undefined);
|
|
689
|
+
endsOn = new Date(endsAtSeconds * 1000).toLocaleDateString('en-US', {
|
|
690
|
+
month: 'long',
|
|
691
|
+
day: 'numeric',
|
|
692
|
+
year: 'numeric',
|
|
693
|
+
timeZone: resolveSiteTimeZone(notice.org, host)
|
|
694
|
+
});
|
|
695
|
+
}
|
|
696
|
+
const subscriber = notice.subscriberEmail ? `${notice.subscriberEmail}'s subscription` : 'A subscription';
|
|
651
697
|
void notifyHostManagers(hostId, {
|
|
652
698
|
type: 'content.order',
|
|
653
|
-
title: 'A subscription stopped renewing
|
|
654
|
-
|
|
699
|
+
title: 'A subscription on {site} stopped renewing',
|
|
700
|
+
// Restoring the plan does NOT restart it (AGL-3432): nothing clears
|
|
701
|
+
// Stripe's `cancel_at_period_end` when the plan comes back, so the notice
|
|
702
|
+
// promises no such remedy.
|
|
703
|
+
body: `${subscriber} to ${notice.productName} on {site} will end` + `${endsOn ? ` on ${endsOn}` : ' at the end of its paid period'} ` + 'instead of renewing, because your plan no longer includes ' + 'storefront subscriptions. The subscriber keeps the period they ' + 'already paid for. Restoring your plan does not restart this ' + 'subscription.',
|
|
655
704
|
link: `/${hostId}/products`
|
|
656
705
|
});
|
|
657
706
|
}
|
|
@@ -738,11 +787,13 @@ async function findOrderForDispute(paymentIntentId) {
|
|
|
738
787
|
}
|
|
739
788
|
const refusal = await response.json().catch(()=>null);
|
|
740
789
|
console.error(`Stripe refused the fee re-price for ${subscriptionId} (AGL-2289)`, refusal);
|
|
790
|
+
const repriceHostId = (_ref = (_subscriptionRef_parent_parent = subscriptionRef.parent.parent) == null ? void 0 : _subscriptionRef_parent_parent.id) != null ? _ref : '';
|
|
741
791
|
await raiseOperatorAlert(COMMERCE_FEE_REPRICE_REFUSED, {
|
|
742
792
|
dedupeKey: `${subscriptionId}:${desiredFeePct}`,
|
|
743
793
|
context: {
|
|
744
794
|
subject: `subscription ${subscriptionId}`,
|
|
745
|
-
hostId:
|
|
795
|
+
hostId: repriceHostId,
|
|
796
|
+
site: await operatorSiteLabel(repriceHostId),
|
|
746
797
|
reason: `Stripe refused to set the fee to ${desiredFeePct}% (${(_ref1 = refusal == null ? void 0 : (_refusal_error = refusal.error) == null ? void 0 : _refusal_error.message) != null ? _ref1 : `HTTP ${response.status}`})`
|
|
747
798
|
}
|
|
748
799
|
});
|
|
@@ -764,6 +815,7 @@ async function findOrderForDispute(paymentIntentId) {
|
|
|
764
815
|
context: {
|
|
765
816
|
subject: `invoice ${invoiceId}`,
|
|
766
817
|
hostId,
|
|
818
|
+
site: await operatorSiteLabel(hostId),
|
|
767
819
|
reason
|
|
768
820
|
}
|
|
769
821
|
});
|
|
@@ -1603,14 +1655,15 @@ async function findOrderForDispute(paymentIntentId) {
|
|
|
1603
1655
|
* Tells the operator the platform is out a lost dispute's principal because
|
|
1604
1656
|
* the merchant's share could not be pulled back (AGL-3377). Once per dispute.
|
|
1605
1657
|
*/ async function reportSellerShareNotReversed(orderRef, disputeId, principalCents, reason) {
|
|
1606
|
-
var _ref;
|
|
1607
|
-
var _orderRef_parent_parent;
|
|
1658
|
+
var _ref, _ref1;
|
|
1659
|
+
var _orderRef_parent_parent, _orderRef_parent_parent1;
|
|
1608
1660
|
await raiseOperatorAlert(COMMERCE_SELLER_SHARE_NOT_REVERSED, {
|
|
1609
1661
|
dedupeKey: disputeId,
|
|
1610
1662
|
context: {
|
|
1611
1663
|
disputeId,
|
|
1612
1664
|
orderId: orderRef.id,
|
|
1613
1665
|
hostId: (_ref = (_orderRef_parent_parent = orderRef.parent.parent) == null ? void 0 : _orderRef_parent_parent.id) != null ? _ref : '',
|
|
1666
|
+
site: await operatorSiteLabel((_ref1 = (_orderRef_parent_parent1 = orderRef.parent.parent) == null ? void 0 : _orderRef_parent_parent1.id) != null ? _ref1 : ''),
|
|
1614
1667
|
amount: formatOperatorAlertAmount(principalCents),
|
|
1615
1668
|
reason
|
|
1616
1669
|
}
|
|
@@ -1633,59 +1686,25 @@ async function findOrderForDispute(paymentIntentId) {
|
|
|
1633
1686
|
* `object.metadata.type`, exactly as the inline route sections did.
|
|
1634
1687
|
*/ export const commerceBillingWebhookHandler = async ({ type, object, event, requestHost })=>{
|
|
1635
1688
|
var _object_metadata, _object_metadata1, _object_metadata2, _object_metadata3, _object_metadata4;
|
|
1636
|
-
//
|
|
1689
|
+
// THE MERCHANT'S PAYMENT ACCOUNT CHANGED. Every commerce money route —
|
|
1637
1690
|
// checkout, cart checkout, draft orders, reservations, POS — gates the sale
|
|
1638
|
-
// on the
|
|
1639
|
-
// refreshed it but the merchant reopening that
|
|
1640
|
-
// later restricted kept selling on a stale
|
|
1641
|
-
// failure at payment time.
|
|
1691
|
+
// on the readiness the connect route CACHED on the merchant's profile
|
|
1692
|
+
// (AGL-1997), and nothing refreshed it but the merchant reopening that
|
|
1693
|
+
// route: a merchant the provider later restricted kept selling on a stale
|
|
1694
|
+
// `true`, and the shopper met the failure at payment time. A payout or
|
|
1695
|
+
// transfer that never landed is recorded against the same profile, and a
|
|
1696
|
+
// later one that did retires the warning.
|
|
1642
1697
|
//
|
|
1643
|
-
//
|
|
1644
|
-
//
|
|
1645
|
-
//
|
|
1646
|
-
//
|
|
1647
|
-
//
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
return;
|
|
1654
|
-
}
|
|
1655
|
-
// A PAYOUT OR TRANSFER THAT NEVER LANDED.
|
|
1656
|
-
//
|
|
1657
|
-
// Placed beside `account.updated` because it is the same kind of event —
|
|
1658
|
-
// account-level, nothing to do with the `metadata.type` order sections
|
|
1659
|
-
// below — and returns for the same reason.
|
|
1660
|
-
//
|
|
1661
|
-
// `payout.failed` is the CONNECTED account's balance failing to reach its
|
|
1662
|
-
// bank, so the account id is `event.account`: the Payout object's own
|
|
1663
|
-
// `destination` names the bank, not the Connect account. `transfer.failed`
|
|
1664
|
-
// is the platform's balance failing to reach the connected account, a
|
|
1665
|
-
// platform event whose `destination` IS the account.
|
|
1666
|
-
//
|
|
1667
|
-
// Recorded and surfaced, never retried: Stripe runs its own retry schedule
|
|
1668
|
-
// and a second transfer against an account that just refused one is how a
|
|
1669
|
-
// duplicate lands.
|
|
1670
|
-
if (type === 'payout.failed' || type === 'transfer.failed') {
|
|
1671
|
-
var _ref, _ref1, _ref2;
|
|
1672
|
-
var _object_destination;
|
|
1673
|
-
const failedAccountId = type === 'payout.failed' ? String((_ref = event == null ? void 0 : event.account) != null ? _ref : '') : String((_ref1 = (_ref2 = object == null ? void 0 : (_object_destination = object.destination) == null ? void 0 : _object_destination.id) != null ? _ref2 : object == null ? void 0 : object.destination) != null ? _ref1 : '');
|
|
1674
|
-
await recordConnectPayoutFailure('profiles', {
|
|
1675
|
-
kind: type === 'payout.failed' ? 'payout' : 'transfer',
|
|
1676
|
-
object,
|
|
1677
|
-
accountId: failedAccountId,
|
|
1678
|
-
livemode: event == null ? void 0 : event.livemode
|
|
1679
|
-
});
|
|
1680
|
-
return;
|
|
1681
|
-
}
|
|
1682
|
-
// A later success retires the warning the card shows. The history in
|
|
1683
|
-
// `connectPayoutFailures` is kept — "has this account failed before" is what
|
|
1684
|
-
// that record exists to answer — but a stale warning on a resolved problem
|
|
1685
|
-
// trains people to ignore the surface.
|
|
1686
|
-
if (type === 'payout.paid') {
|
|
1687
|
-
var _ref3;
|
|
1688
|
-
await clearConnectPayoutFailure('profiles', String((_ref3 = event == null ? void 0 : event.account) != null ? _ref3 : ''));
|
|
1698
|
+
// The payment provider reads the event — which ones describe an account,
|
|
1699
|
+
// and where each keeps its id — and mirrors current state, so a redelivery
|
|
1700
|
+
// is harmless. FIRST and with an early return: these events share nothing
|
|
1701
|
+
// with the order sections below, and returning here keeps them out of
|
|
1702
|
+
// every `metadata.type` test.
|
|
1703
|
+
if (await paymentProvider().applyAccountEvent('profiles', {
|
|
1704
|
+
type,
|
|
1705
|
+
object,
|
|
1706
|
+
event
|
|
1707
|
+
})) {
|
|
1689
1708
|
return;
|
|
1690
1709
|
}
|
|
1691
1710
|
// A DEAD SESSION GIVES ITS RESERVATIONS BACK (AGL-2453).
|
|
@@ -1706,9 +1725,9 @@ async function findOrderForDispute(paymentIntentId) {
|
|
|
1706
1725
|
// Same early return as `account.updated`: this event carries no order and
|
|
1707
1726
|
// shares nothing with the `checkout.session.completed` sections below.
|
|
1708
1727
|
if (type === 'checkout.session.expired') {
|
|
1709
|
-
var
|
|
1728
|
+
var _ref, _ref1, _ref2, _ref3, _ref4, _ref5, _ref6, _ref7;
|
|
1710
1729
|
var _object_metadata5, _object_metadata6, _object_metadata7, _object_metadata8, _object_metadata9, _object_metadata10, _object_metadata11, _object_metadata12;
|
|
1711
|
-
const expiredHostId = String((
|
|
1730
|
+
const expiredHostId = String((_ref = object == null ? void 0 : (_object_metadata5 = object.metadata) == null ? void 0 : _object_metadata5.hostId) != null ? _ref : '');
|
|
1712
1731
|
if (!expiredHostId) return;
|
|
1713
1732
|
const expiredHostRef = firebaseAdmin.app().firestore().collection('hosts').doc(expiredHostId);
|
|
1714
1733
|
// THE UNITS COME BACK FIRST (AGL-2356), because they are the reservation a
|
|
@@ -1722,14 +1741,14 @@ async function findOrderForDispute(paymentIntentId) {
|
|
|
1722
1741
|
// fire at all unless the endpoint subscribes to the event. What it buys is
|
|
1723
1742
|
// that the shelf and the storefront agree PROMPTLY, which is the difference
|
|
1724
1743
|
// between a hold a merchant can reason about and one they cannot.
|
|
1725
|
-
await releaseStockHold(expiredHostRef, String((
|
|
1726
|
-
const couponHoldKey = String((
|
|
1727
|
-
const couponCode = String((
|
|
1744
|
+
await releaseStockHold(expiredHostRef, String((_ref1 = object == null ? void 0 : (_object_metadata6 = object.metadata) == null ? void 0 : _object_metadata6.stockHoldKey) != null ? _ref1 : ''));
|
|
1745
|
+
const couponHoldKey = String((_ref2 = object == null ? void 0 : (_object_metadata7 = object.metadata) == null ? void 0 : _object_metadata7.couponHoldKey) != null ? _ref2 : '');
|
|
1746
|
+
const couponCode = String((_ref3 = object == null ? void 0 : (_object_metadata8 = object.metadata) == null ? void 0 : _object_metadata8.couponCode) != null ? _ref3 : '');
|
|
1728
1747
|
if (couponHoldKey && couponCode) {
|
|
1729
1748
|
await releasePromotionHold(expiredHostRef.collection('coupons').doc(couponCode), couponHoldKey);
|
|
1730
1749
|
}
|
|
1731
|
-
const discountHoldKey = String((
|
|
1732
|
-
const discountId = String((
|
|
1750
|
+
const discountHoldKey = String((_ref4 = object == null ? void 0 : (_object_metadata9 = object.metadata) == null ? void 0 : _object_metadata9.discountHoldKey) != null ? _ref4 : '');
|
|
1751
|
+
const discountId = String((_ref5 = object == null ? void 0 : (_object_metadata10 = object.metadata) == null ? void 0 : _object_metadata10.discountId) != null ? _ref5 : '');
|
|
1733
1752
|
if (discountHoldKey && discountId) {
|
|
1734
1753
|
await releasePromotionHold(expiredHostRef.collection('discounts').doc(discountId), discountHoldKey);
|
|
1735
1754
|
}
|
|
@@ -1737,8 +1756,8 @@ async function findOrderForDispute(paymentIntentId) {
|
|
|
1737
1756
|
// event and AGL-2449 left it to the TTL alone. A shopper whose abandoned
|
|
1738
1757
|
// checkout stands their own balance off for a day cannot be told why
|
|
1739
1758
|
// either, and the release is the identical delete sentinel.
|
|
1740
|
-
const giftCardHoldKey = String((
|
|
1741
|
-
const giftCardCode = String((
|
|
1759
|
+
const giftCardHoldKey = String((_ref6 = object == null ? void 0 : (_object_metadata11 = object.metadata) == null ? void 0 : _object_metadata11.giftCardHoldKey) != null ? _ref6 : '');
|
|
1760
|
+
const giftCardCode = String((_ref7 = object == null ? void 0 : (_object_metadata12 = object.metadata) == null ? void 0 : _object_metadata12.giftCardCode) != null ? _ref7 : '');
|
|
1742
1761
|
if (giftCardHoldKey && giftCardCode) {
|
|
1743
1762
|
await expiredHostRef.collection('giftCards').doc(giftCardCode).set({
|
|
1744
1763
|
holds: {
|
|
@@ -1791,9 +1810,9 @@ async function findOrderForDispute(paymentIntentId) {
|
|
|
1791
1810
|
var _object_metadata14;
|
|
1792
1811
|
const subHostId = object == null ? void 0 : (_object_metadata14 = object.metadata) == null ? void 0 : _object_metadata14.hostId;
|
|
1793
1812
|
if (subHostId) {
|
|
1794
|
-
var
|
|
1813
|
+
var _ref8;
|
|
1795
1814
|
await firebaseAdmin.app().firestore().collection('hosts').doc(String(subHostId)).collection('subscriptions').doc(String(object.id)).set({
|
|
1796
|
-
status: type === 'customer.subscription.deleted' ? 'canceled' : String((
|
|
1815
|
+
status: type === 'customer.subscription.deleted' ? 'canceled' : String((_ref8 = object == null ? void 0 : object.status) != null ? _ref8 : 'active'),
|
|
1797
1816
|
currentPeriodEndMs: (object == null ? void 0 : object.current_period_end) ? object.current_period_end * 1000 : null
|
|
1798
1817
|
}, {
|
|
1799
1818
|
merge: true
|
|
@@ -1807,7 +1826,7 @@ async function findOrderForDispute(paymentIntentId) {
|
|
|
1807
1826
|
var _object_metadata15;
|
|
1808
1827
|
const { hostId, productId } = (_object_metadata15 = object.metadata) != null ? _object_metadata15 : {};
|
|
1809
1828
|
if (hostId && productId) {
|
|
1810
|
-
var _productForSnapshot_data,
|
|
1829
|
+
var _productForSnapshot_data, _ref9, _productForSnapshot_get, _subTotals_totalCents, _productForSnapshot_get1, _ref10;
|
|
1811
1830
|
var _object_metadata16, _liftedForSnapshot_subscription, _object_customer_details, _object_customer_details1, _object_customer_details2;
|
|
1812
1831
|
const firestore = firebaseAdmin.app().firestore();
|
|
1813
1832
|
const hostRef = firestore.collection('hosts').doc(String(hostId));
|
|
@@ -1838,7 +1857,7 @@ async function findOrderForDispute(paymentIntentId) {
|
|
|
1838
1857
|
name: 'Product'
|
|
1839
1858
|
});
|
|
1840
1859
|
const soldVariant = ((_object_metadata16 = object.metadata) == null ? void 0 : _object_metadata16.variantId) ? liftedForSnapshot.variants.find((item)=>item.id === String(object.metadata.variantId)) : liftedForSnapshot.variants[0];
|
|
1841
|
-
const variantOptions = Object.values((
|
|
1860
|
+
const variantOptions = Object.values((_ref9 = soldVariant == null ? void 0 : soldVariant.options) != null ? _ref9 : {});
|
|
1842
1861
|
const { lineItems: subLineItems, totals: subTotals } = CommerceModel.computeBuyNowOrder(object, _extends({
|
|
1843
1862
|
name: String((_productForSnapshot_get = productForSnapshot.get('name')) != null ? _productForSnapshot_get : 'Product')
|
|
1844
1863
|
}, variantOptions.length ? {
|
|
@@ -1924,16 +1943,17 @@ async function findOrderForDispute(paymentIntentId) {
|
|
|
1924
1943
|
});
|
|
1925
1944
|
if (!recorded) return;
|
|
1926
1945
|
const subscriptionCents = Number((_subTotals_totalCents = subTotals.totalCents) != null ? _subTotals_totalCents : 0);
|
|
1927
|
-
|
|
1946
|
+
const subscriptionPrice = `$${(subscriptionCents / 100).toFixed(2)}${((_liftedForSnapshot_subscription = liftedForSnapshot.subscription) == null ? void 0 : _liftedForSnapshot_subscription.interval) ? `/${liftedForSnapshot.subscription.interval}` : ''}`;
|
|
1947
|
+
const subscriberEmail = object == null ? void 0 : (_object_customer_details = object.customer_details) == null ? void 0 : _object_customer_details.email;
|
|
1948
|
+
void notifyHostManagers(String(hostId), {
|
|
1928
1949
|
type: 'content.order',
|
|
1929
1950
|
// The amount rides the title exactly as the order notification's
|
|
1930
1951
|
// does (AGL-1732) — "New subscriber" alone never said what for.
|
|
1931
|
-
title: `New subscriber
|
|
1932
|
-
|
|
1933
|
-
body:
|
|
1934
|
-
} : {}, {
|
|
1952
|
+
title: `New subscriber on {site} — ${subscriptionPrice}`,
|
|
1953
|
+
// Who subscribed, to what and where (AGL-3432).
|
|
1954
|
+
body: `${subscriberEmail || 'A shopper'} subscribed to ` + `${String((_productForSnapshot_get1 = productForSnapshot.get('name')) != null ? _productForSnapshot_get1 : 'a product')} on ` + `{site} for ${subscriptionPrice}.`,
|
|
1935
1955
|
link: `/${hostId}/products`
|
|
1936
|
-
})
|
|
1956
|
+
});
|
|
1937
1957
|
// AWAITED SINCE AGL-2473, here and at the five sibling call sites in
|
|
1938
1958
|
// this file. `upsertHostContact` carries `purchaseCents`, so it is what
|
|
1939
1959
|
// feeds `ltvCents` and the RFM ranking — a `void`ed one is a paying
|
|
@@ -1944,7 +1964,7 @@ async function findOrderForDispute(paymentIntentId) {
|
|
|
1944
1964
|
await captureHostContact(_extends({
|
|
1945
1965
|
hostId: String(hostId),
|
|
1946
1966
|
email: object == null ? void 0 : (_object_customer_details1 = object.customer_details) == null ? void 0 : _object_customer_details1.email,
|
|
1947
|
-
name: (
|
|
1967
|
+
name: (_ref10 = object == null ? void 0 : (_object_customer_details2 = object.customer_details) == null ? void 0 : _object_customer_details2.name) != null ? _ref10 : undefined,
|
|
1948
1968
|
source: 'order',
|
|
1949
1969
|
// Every order door names `customer` (AGL-2612): a floor, so the
|
|
1950
1970
|
// stage fills or advances and never moves anybody back.
|
|
@@ -1993,23 +2013,23 @@ async function findOrderForDispute(paymentIntentId) {
|
|
|
1993
2013
|
// is enabled). Handling both means the branch fires whichever is on; the
|
|
1994
2014
|
// invoice-id guard means having both on records the cycle once.
|
|
1995
2015
|
if (type === 'invoice.payment_succeeded' || type === 'invoice.paid') {
|
|
1996
|
-
var
|
|
2016
|
+
var _ref11, _ref12, _ref13, _ref14, _ref15, _ref16;
|
|
1997
2017
|
var _object_subscription_details, _object_parent_subscription_details, _object_parent, _object_parent_subscription_details1, _object_parent1;
|
|
1998
2018
|
// The SUBSCRIPTION's metadata, which is what `checkout.ts` sets
|
|
1999
2019
|
// (`subscription_data[metadata]`) — the invoice's own `metadata` is a
|
|
2000
2020
|
// different, empty bag. `parent.subscription_details` is where newer API
|
|
2001
2021
|
// versions moved it; both are read because the endpoint's version is not
|
|
2002
2022
|
// visible from here.
|
|
2003
|
-
const subscriptionMeta = (
|
|
2004
|
-
const invoiceId = String((
|
|
2005
|
-
const subscriptionId = String((
|
|
2006
|
-
const invoiceHostId = String((
|
|
2023
|
+
const subscriptionMeta = (_ref11 = (_ref12 = object == null ? void 0 : (_object_subscription_details = object.subscription_details) == null ? void 0 : _object_subscription_details.metadata) != null ? _ref12 : object == null ? void 0 : (_object_parent = object.parent) == null ? void 0 : (_object_parent_subscription_details = _object_parent.subscription_details) == null ? void 0 : _object_parent_subscription_details.metadata) != null ? _ref11 : null;
|
|
2024
|
+
const invoiceId = String((_ref13 = object == null ? void 0 : object.id) != null ? _ref13 : '');
|
|
2025
|
+
const subscriptionId = String((_ref14 = (_ref15 = object == null ? void 0 : object.subscription) != null ? _ref15 : object == null ? void 0 : (_object_parent1 = object.parent) == null ? void 0 : (_object_parent_subscription_details1 = _object_parent1.subscription_details) == null ? void 0 : _object_parent_subscription_details1.subscription) != null ? _ref14 : '');
|
|
2026
|
+
const invoiceHostId = String((_ref16 = subscriptionMeta == null ? void 0 : subscriptionMeta.hostId) != null ? _ref16 : '');
|
|
2007
2027
|
// Platform billing — Aglyn charging its own customers — runs through the
|
|
2008
2028
|
// same endpoint and the same fan-out, and its invoices carry `orgId`
|
|
2009
2029
|
// instead. Self-selection is on the same discriminator every other
|
|
2010
2030
|
// section of this file uses.
|
|
2011
2031
|
if ((subscriptionMeta == null ? void 0 : subscriptionMeta.type) === 'commerce-subscription' && invoiceHostId && subscriptionId && invoiceId) {
|
|
2012
|
-
var _soldSnapshot_get,
|
|
2032
|
+
var _soldSnapshot_get, _ref17, _ref18, _ref19, _ref20, _ref21;
|
|
2013
2033
|
var _this, _this1, _object_status_transitions, _object_customer_shipping;
|
|
2014
2034
|
const firestore = firebaseAdmin.app().firestore();
|
|
2015
2035
|
const hostRef = firestore.collection('hosts').doc(invoiceHostId);
|
|
@@ -2060,7 +2080,7 @@ async function findOrderForDispute(paymentIntentId) {
|
|
|
2060
2080
|
// and only for them.
|
|
2061
2081
|
const soldSnapshot = await subscriptionRef.get();
|
|
2062
2082
|
const soldLine = (_this1 = (_soldSnapshot_get = soldSnapshot.get('lineItems')) != null ? _soldSnapshot_get : []) == null ? void 0 : _this1[0];
|
|
2063
|
-
const productId = String((
|
|
2083
|
+
const productId = String((_ref17 = (_ref18 = (_ref19 = soldLine == null ? void 0 : soldLine.productId) != null ? _ref19 : soldSnapshot.get('productId')) != null ? _ref18 : subscriptionMeta == null ? void 0 : subscriptionMeta.productId) != null ? _ref17 : '');
|
|
2064
2084
|
let snapshot;
|
|
2065
2085
|
if (soldLine) {
|
|
2066
2086
|
snapshot = _extends({
|
|
@@ -2077,7 +2097,7 @@ async function findOrderForDispute(paymentIntentId) {
|
|
|
2077
2097
|
productType: soldLine.productType
|
|
2078
2098
|
} : {});
|
|
2079
2099
|
} else {
|
|
2080
|
-
var
|
|
2100
|
+
var _ref22, _soldSnapshot_get1, _ref23, _ref24;
|
|
2081
2101
|
// AGL-1763: no product id, no read. `doc('__missing__')` was a
|
|
2082
2102
|
// deliberate miss that Firestore does not permit — a document id
|
|
2083
2103
|
// matching `__.*__` is RESERVED, so the backend rejects the path with
|
|
@@ -2090,18 +2110,18 @@ async function findOrderForDispute(paymentIntentId) {
|
|
|
2090
2110
|
// exactly the population this branch now serves: a subscription
|
|
2091
2111
|
// Aglyn has no record of, whose metadata nothing of ours wrote.
|
|
2092
2112
|
const productSnapshot = productId ? await hostRef.collection('products').doc(productId).get() : null;
|
|
2093
|
-
const lifted = CommerceModel.liftLegacyProduct((
|
|
2113
|
+
const lifted = CommerceModel.liftLegacyProduct((_ref22 = productSnapshot == null ? void 0 : productSnapshot.data()) != null ? _ref22 : {
|
|
2094
2114
|
name: 'Subscription'
|
|
2095
2115
|
});
|
|
2096
2116
|
const variantId = String((_soldSnapshot_get1 = soldSnapshot.get('variantId')) != null ? _soldSnapshot_get1 : '');
|
|
2097
2117
|
const variant = variantId ? lifted.variants.find((item)=>item.id === variantId) : lifted.variants[0];
|
|
2098
|
-
const variantOptions = Object.values((
|
|
2118
|
+
const variantOptions = Object.values((_ref23 = variant == null ? void 0 : variant.options) != null ? _ref23 : {});
|
|
2099
2119
|
snapshot = _extends({
|
|
2100
2120
|
productId
|
|
2101
2121
|
}, variantId ? {
|
|
2102
2122
|
variantId
|
|
2103
2123
|
} : {}, {
|
|
2104
|
-
name: String((
|
|
2124
|
+
name: String((_ref24 = productSnapshot == null ? void 0 : productSnapshot.get('name')) != null ? _ref24 : 'Subscription')
|
|
2105
2125
|
}, variantOptions.length ? {
|
|
2106
2126
|
variantLabel: variantOptions.join(' / ')
|
|
2107
2127
|
} : {}, (variant == null ? void 0 : variant.sku) ? {
|
|
@@ -2123,8 +2143,8 @@ async function findOrderForDispute(paymentIntentId) {
|
|
|
2123
2143
|
const invoiceTotals = _extends({}, computedInvoiceTotals, {
|
|
2124
2144
|
feeCents: feeCentsInForce
|
|
2125
2145
|
});
|
|
2126
|
-
const paidCents = Math.max(0, Math.round(Number((
|
|
2127
|
-
const billingReason = String((
|
|
2146
|
+
const paidCents = Math.max(0, Math.round(Number((_ref20 = object == null ? void 0 : object.amount_paid) != null ? _ref20 : 0)));
|
|
2147
|
+
const billingReason = String((_ref21 = object == null ? void 0 : object.billing_reason) != null ? _ref21 : '');
|
|
2128
2148
|
// The subscription's FIRST invoice is also a paid invoice, and
|
|
2129
2149
|
// `checkout.session.completed` has already counted that money into the
|
|
2130
2150
|
// contact's lifetime value and already told the managers about it
|
|
@@ -2395,7 +2415,12 @@ async function findOrderForDispute(paymentIntentId) {
|
|
|
2395
2415
|
// `renewalOrgUnreadable` and not `!renewalOrg` (AGL-2258): a null org
|
|
2396
2416
|
// we successfully read still stops, which is AGL-2071's decision.
|
|
2397
2417
|
if (!renewalEntitled && !renewalOrgUnreadable) {
|
|
2398
|
-
|
|
2418
|
+
var _ref25, _ref26;
|
|
2419
|
+
await stopLapsedStorefrontSubscription(invoiceHostId, subscriptionId, subscriptionRef, {
|
|
2420
|
+
subscriberEmail: (_ref25 = (_ref26 = object == null ? void 0 : object.customer_email) != null ? _ref26 : soldSnapshot.get('customerEmail')) != null ? _ref25 : null,
|
|
2421
|
+
productName: snapshot.name || 'a subscription product',
|
|
2422
|
+
org: renewalOrg != null ? renewalOrg : null
|
|
2423
|
+
});
|
|
2399
2424
|
} else if (renewalEntitled) {
|
|
2400
2425
|
// RE-PRICE THE PLATFORM FEE (AGL-2289). `application_fee_percent` was
|
|
2401
2426
|
// set once at the sale and never revisited, and it lives on the
|
|
@@ -2444,8 +2469,8 @@ async function findOrderForDispute(paymentIntentId) {
|
|
|
2444
2469
|
const stockVariantId = (_snapshot_variantId = snapshot.variantId) != null ? _snapshot_variantId : (_stocked_variants_ = stocked.variants[0]) == null ? void 0 : _stocked_variants_.id;
|
|
2445
2470
|
const tracked = stocked.variants.some((variant)=>variant.id === stockVariantId && variant.inventory != null);
|
|
2446
2471
|
if (stockVariantId && tracked) {
|
|
2447
|
-
var
|
|
2448
|
-
const cycleQuantity = (
|
|
2472
|
+
var _ref27;
|
|
2473
|
+
const cycleQuantity = (_ref27 = cycleLine == null ? void 0 : cycleLine.quantity) != null ? _ref27 : 1;
|
|
2449
2474
|
// Atomic since AGL-2320, like every other decrement: a renewal
|
|
2450
2475
|
// batch bills many subscribers at once, so two cycles of the same
|
|
2451
2476
|
// physical box are exactly the concurrent pair that lost a
|
|
@@ -2470,20 +2495,20 @@ async function findOrderForDispute(paymentIntentId) {
|
|
|
2470
2495
|
}
|
|
2471
2496
|
}
|
|
2472
2497
|
if (!isOpeningInvoice && paidCents > 0) {
|
|
2473
|
-
var
|
|
2474
|
-
const renewalEmail = (
|
|
2498
|
+
var _ref28, _ref29;
|
|
2499
|
+
const renewalEmail = (_ref28 = (_ref29 = object == null ? void 0 : object.customer_email) != null ? _ref29 : soldSnapshot.get('customerEmail')) != null ? _ref28 : null;
|
|
2475
2500
|
// A physical cycle now lands in Orders (AGL-1750), but the console
|
|
2476
2501
|
// still has no Subscriptions tab, so for a digital or service
|
|
2477
2502
|
// subscription this notification remains the only place a merchant
|
|
2478
2503
|
// learns the money arrived at all.
|
|
2479
|
-
|
|
2504
|
+
const renewalPrice = `$${(paidCents / 100).toFixed(2)}${interval ? `/${interval}` : ''}`;
|
|
2505
|
+
void notifyHostManagers(invoiceHostId, {
|
|
2480
2506
|
type: 'content.order',
|
|
2481
|
-
title: `Subscription renewed
|
|
2482
|
-
|
|
2483
|
-
body: String(renewalEmail)
|
|
2484
|
-
} : {}, {
|
|
2507
|
+
title: `Subscription renewed on {site} — ${renewalPrice}`,
|
|
2508
|
+
// Whose subscription, to what and where (AGL-3432).
|
|
2509
|
+
body: `${renewalEmail ? `${String(renewalEmail)}'s` : 'A'} ` + `subscription to ${snapshot.name || 'a product'} on {site} ` + `renewed for ${renewalPrice}.`,
|
|
2485
2510
|
link: `/${invoiceHostId}/products`
|
|
2486
|
-
})
|
|
2511
|
+
});
|
|
2487
2512
|
// RFM (AGL-328): a subscriber in month 12 has paid twelve times, and
|
|
2488
2513
|
// counting only the first charge ranks them as a one-purchase
|
|
2489
2514
|
// customer forever. Keyed to the invoice so the guard above is what
|
|
@@ -2510,7 +2535,7 @@ async function findOrderForDispute(paymentIntentId) {
|
|
|
2510
2535
|
var _object_metadata17;
|
|
2511
2536
|
const { hostId, reservationId } = (_object_metadata17 = object.metadata) != null ? _object_metadata17 : {};
|
|
2512
2537
|
if (hostId && reservationId) {
|
|
2513
|
-
var
|
|
2538
|
+
var _ref30, _ref31;
|
|
2514
2539
|
var _object_metadata18;
|
|
2515
2540
|
const firestore = firebaseAdmin.app().firestore();
|
|
2516
2541
|
const reservationRef = firestore.collection('hosts').doc(String(hostId)).collection('reservations').doc(String(reservationId));
|
|
@@ -2523,7 +2548,7 @@ async function findOrderForDispute(paymentIntentId) {
|
|
|
2523
2548
|
// `amount_total` is stay-plus-tax and Stripe's own tax fields read
|
|
2524
2549
|
// zero; the session's metadata is the only witness, exactly as it is
|
|
2525
2550
|
// for a buy-now sale.
|
|
2526
|
-
const taxCents = Math.max(0, Math.round(Number((
|
|
2551
|
+
const taxCents = Math.max(0, Math.round(Number((_ref30 = object == null ? void 0 : (_object_metadata18 = object.metadata) == null ? void 0 : _object_metadata18.taxCents) != null ? _ref30 : 0)));
|
|
2527
2552
|
// What the guest actually handed over, MINUS that tax. This is the
|
|
2528
2553
|
// DEPOSIT when the resource has one (`reserve.ts` charges
|
|
2529
2554
|
// `depositCents || totalCents`), never the stay's `totalCents` — see
|
|
@@ -2537,7 +2562,7 @@ async function findOrderForDispute(paymentIntentId) {
|
|
|
2537
2562
|
// are and under-state what the merchant collects at the register. The
|
|
2538
2563
|
// same figure feeds the guest's lifetime value, which tax is no part
|
|
2539
2564
|
// of either (AGL-1755).
|
|
2540
|
-
const paidCents = Math.max(0, Math.round(Number((
|
|
2565
|
+
const paidCents = Math.max(0, Math.round(Number((_ref31 = object == null ? void 0 : object.amount_total) != null ? _ref31 : 0)) - taxCents);
|
|
2541
2566
|
// Redelivery guard (AGL-1755), the AGL-1748 shape. The `pending` ->
|
|
2542
2567
|
// `confirmed` transition was always the guard, but it was a
|
|
2543
2568
|
// read-then-write with every side effect hanging off it, so two
|
|
@@ -2608,17 +2633,44 @@ async function findOrderForDispute(paymentIntentId) {
|
|
|
2608
2633
|
return true;
|
|
2609
2634
|
});
|
|
2610
2635
|
if (confirmedNow) {
|
|
2611
|
-
var _ref34, _ref35;
|
|
2636
|
+
var _reservation_nights, _reservation_totalCents, _ref32, _ref33, _ref34, _ref35, _ref36;
|
|
2612
2637
|
var _object_customer_details3, _object_customer_details4, _object_customer_details5;
|
|
2613
2638
|
const reservation = reserved != null ? reserved : {};
|
|
2614
|
-
|
|
2639
|
+
// A day key, so its UTC date IS the check-in date.
|
|
2640
|
+
const checkInShort = new Date(Number(reservation['checkInDayMs'])).toUTCString().slice(0, 16);
|
|
2641
|
+
const nights = Number((_reservation_nights = reservation['nights']) != null ? _reservation_nights : 0);
|
|
2642
|
+
const nightsText = `${nights} night${nights === 1 ? '' : 's'}`;
|
|
2643
|
+
/*
|
|
2644
|
+
* WHAT THE GUEST WAS CHARGED TODAY, tax included (AGL-3442).
|
|
2645
|
+
*
|
|
2646
|
+
* `paidCents` is the money applied to the STAY and leaves out the
|
|
2647
|
+
* merchant's lodging tax, so a guest told only that figure is told
|
|
2648
|
+
* less than their card was charged. The two together are Stripe's
|
|
2649
|
+
* session total, and the tax is the figure `reserve.ts` charged as
|
|
2650
|
+
* its own line, named so the guest can match it to their receipt.
|
|
2651
|
+
* The balance below is the stay's alone: the tax is charged on what
|
|
2652
|
+
* is collected at booking, and nothing computes one on what is
|
|
2653
|
+
* collected at the property.
|
|
2654
|
+
*/ const charged = `$${((paidCents + taxCents) / 100).toFixed(2)}`;
|
|
2655
|
+
const taxIncluded = taxCents > 0 ? `, including $${(taxCents / 100).toFixed(2)} lodging tax` : '';
|
|
2656
|
+
const paid = `${charged}${taxIncluded}`;
|
|
2657
|
+
// What the guest still owes for the stay: the console's
|
|
2658
|
+
// reservations card collects `totalCents - paidCents` at the
|
|
2659
|
+
// property, so the guest is told it too (AGL-3432).
|
|
2660
|
+
const balanceCents = Math.max(0, Math.round(Number((_reservation_totalCents = reservation['totalCents']) != null ? _reservation_totalCents : 0)) - paidCents);
|
|
2661
|
+
const balance = `$${(balanceCents / 100).toFixed(2)}`;
|
|
2662
|
+
const reservedBy = (_ref32 = (_ref33 = object == null ? void 0 : (_object_customer_details3 = object.customer_details) == null ? void 0 : _object_customer_details3.email) != null ? _ref33 : reservation['guestEmail']) != null ? _ref32 : 'A guest';
|
|
2663
|
+
// Which room or unit was reserved (AGL-3432): on a property with
|
|
2664
|
+
// several, "a stay" does not say which one to get ready.
|
|
2665
|
+
const resourceName = reservation['resourceId'] ? String((_ref34 = await firestore.collection('hosts').doc(String(hostId)).collection('resources').doc(String(reservation['resourceId'])).get().then((snapshot)=>snapshot.get('name')).catch(()=>'')) != null ? _ref34 : '').trim() : '';
|
|
2666
|
+
void notifyHostManagers(String(hostId), {
|
|
2615
2667
|
type: 'content.booking',
|
|
2616
|
-
title: 'New reservation'
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2668
|
+
title: 'New reservation on {site}',
|
|
2669
|
+
// Who, what, when, what was paid and what is still owed
|
|
2670
|
+
// (AGL-3432).
|
|
2671
|
+
body: `${reservedBy} reserved ${resourceName || 'a stay'} on {site}: check-in ` + `${checkInShort}, ${nightsText}. ${charged} was paid${taxIncluded}` + (balanceCents > 0 ? `; ${balance} is still to collect at the property.` : '.'),
|
|
2620
2672
|
link: `/${hostId}/products`
|
|
2621
|
-
})
|
|
2673
|
+
});
|
|
2622
2674
|
// Contacts ingestion (AGL-1755): this branch stored `paidCents` from
|
|
2623
2675
|
// `amount_total` and then called `upsertHostContact` with no amount,
|
|
2624
2676
|
// so a guest who paid for a stay showed a lifetime value of zero. A
|
|
@@ -2648,14 +2700,14 @@ async function findOrderForDispute(paymentIntentId) {
|
|
|
2648
2700
|
// exactly once. Taking `totalCents` here is what WOULD double-count:
|
|
2649
2701
|
// it would claim money the guest has not paid yet and will pay again
|
|
2650
2702
|
// through the register.
|
|
2651
|
-
const guestContactEmail = (
|
|
2703
|
+
const guestContactEmail = (_ref35 = (_ref36 = object == null ? void 0 : (_object_customer_details4 = object.customer_details) == null ? void 0 : _object_customer_details4.email) != null ? _ref36 : reservation['guestEmail']) != null ? _ref35 : null;
|
|
2652
2704
|
if (guestContactEmail) {
|
|
2653
|
-
var
|
|
2705
|
+
var _ref37, _ref38;
|
|
2654
2706
|
var _object_customer_details6;
|
|
2655
2707
|
await captureHostContact(_extends({
|
|
2656
2708
|
hostId: String(hostId),
|
|
2657
2709
|
email: guestContactEmail,
|
|
2658
|
-
name: (
|
|
2710
|
+
name: (_ref37 = (_ref38 = object == null ? void 0 : (_object_customer_details6 = object.customer_details) == null ? void 0 : _object_customer_details6.name) != null ? _ref38 : reservation['guestName']) != null ? _ref37 : undefined,
|
|
2659
2711
|
source: 'booking',
|
|
2660
2712
|
// A paid stay is a sale (AGL-2612), whatever the source says
|
|
2661
2713
|
// about the kind of thing that was bought.
|
|
@@ -2671,21 +2723,22 @@ async function findOrderForDispute(paymentIntentId) {
|
|
|
2671
2723
|
}
|
|
2672
2724
|
const guestEmail = object == null ? void 0 : (_object_customer_details5 = object.customer_details) == null ? void 0 : _object_customer_details5.email;
|
|
2673
2725
|
if (guestEmail) {
|
|
2674
|
-
var
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
const
|
|
2678
|
-
const fallbackText = `Your stay is confirmed!\n\nCheck-in: ${checkInShort}\n` + `Nights: ${reservation['nights']}\n` + `Paid today: ${paid}\n` + `Reference: ${reservationId}`;
|
|
2726
|
+
var _reservation_nights1, _ref39;
|
|
2727
|
+
// The balance as a whole line, empty when the stay is paid in
|
|
2728
|
+
// full so the built-in copy leaves it out.
|
|
2729
|
+
const balanceNote = balanceCents > 0 ? `Still to pay: ${balance}, at the property. It has not been charged.` : '';
|
|
2730
|
+
const fallbackText = `Your stay is confirmed!\n\nCheck-in: ${checkInShort}\n` + `Nights: ${reservation['nights']}\n` + `Paid today: ${paid}\n` + (balanceNote ? `${balanceNote}\n` : '') + `Reference: ${reservationId}`;
|
|
2679
2731
|
// Site-owner-designed template when published (AGL-771).
|
|
2680
2732
|
const designed = await renderHostEmailWithTokens(firebaseAdmin.app().firestore(), String(hostId), 'reservation-confirmed', {
|
|
2681
2733
|
'reservation.checkIn': checkInShort,
|
|
2682
|
-
'reservation.nights': String((
|
|
2734
|
+
'reservation.nights': String((_reservation_nights1 = reservation['nights']) != null ? _reservation_nights1 : ''),
|
|
2683
2735
|
'reservation.paid': paid,
|
|
2736
|
+
'reservation.balance': balanceNote,
|
|
2684
2737
|
'reservation.ref': String(reservationId)
|
|
2685
2738
|
});
|
|
2686
2739
|
await sendEmail(_extends({
|
|
2687
2740
|
to: guestEmail,
|
|
2688
|
-
subject: (
|
|
2741
|
+
subject: (_ref39 = designed == null ? void 0 : designed.subject) != null ? _ref39 : 'Reservation confirmed',
|
|
2689
2742
|
text: (designed == null ? void 0 : designed.text) || fallbackText
|
|
2690
2743
|
}, (designed == null ? void 0 : designed.html) ? {
|
|
2691
2744
|
html: designed.html
|
|
@@ -2711,7 +2764,7 @@ async function findOrderForDispute(paymentIntentId) {
|
|
|
2711
2764
|
var _object_metadata19;
|
|
2712
2765
|
const { hostId, cartId, feeCents, couponCode } = (_object_metadata19 = object.metadata) != null ? _object_metadata19 : {};
|
|
2713
2766
|
if (hostId && cartId) {
|
|
2714
|
-
var _cartSnapshot_data,
|
|
2767
|
+
var _cartSnapshot_data, _ref40, _ref41, _ref42, _ref43, _ref44, _ref45;
|
|
2715
2768
|
var _object_customer_details7, _object_metadata20, _object_customer_details8, _object_customer_details9, _object_customer_details10, _object_metadata21, _this2, _object_metadata22;
|
|
2716
2769
|
const firestore = firebaseAdmin.app().firestore();
|
|
2717
2770
|
const hostRef = firestore.collection('hosts').doc(String(hostId));
|
|
@@ -2777,7 +2830,11 @@ async function findOrderForDispute(paymentIntentId) {
|
|
|
2777
2830
|
unitAmountCents: Math.round(Number((_ref = variant == null ? void 0 : variant.priceUsd) != null ? _ref : 0) * 100)
|
|
2778
2831
|
});
|
|
2779
2832
|
}).filter(Boolean);
|
|
2780
|
-
const shipping = (
|
|
2833
|
+
const shipping = (_ref40 = object == null ? void 0 : object.shipping_details) != null ? _ref40 : object == null ? void 0 : object.customer_details;
|
|
2834
|
+
// The number the merchant knows this order by, carried out of the
|
|
2835
|
+
// transaction for the notices below (AGL-3432). The document id is
|
|
2836
|
+
// the Stripe session id, which appears nowhere in the console.
|
|
2837
|
+
let cartOrderNumber;
|
|
2781
2838
|
const created = await firestore.runTransaction(async (transaction)=>{
|
|
2782
2839
|
var _counter_get, _ref, _ref1, _ref2, _ref3, _ref4, _shipping_address_line1, _shipping_address_line2, _shipping_address_city, _shipping_address_state, _shipping_address_postal_code, _shipping_address_country, _ref5;
|
|
2783
2840
|
var _object_metadata, _object_customer_details, _object_customer_details1;
|
|
@@ -2787,6 +2844,7 @@ async function findOrderForDispute(paymentIntentId) {
|
|
|
2787
2844
|
]);
|
|
2788
2845
|
if (existing.exists) return false;
|
|
2789
2846
|
const number = Number((_counter_get = counter.get('next')) != null ? _counter_get : 1);
|
|
2847
|
+
cartOrderNumber = number;
|
|
2790
2848
|
transaction.set(counterRef, {
|
|
2791
2849
|
next: number + 1
|
|
2792
2850
|
}, {
|
|
@@ -2861,6 +2919,9 @@ async function findOrderForDispute(paymentIntentId) {
|
|
|
2861
2919
|
// the non-idempotent effects below (inventory / coupon / gift-card
|
|
2862
2920
|
// decrements) that would otherwise double-apply.
|
|
2863
2921
|
if (!created) return;
|
|
2922
|
+
const cartOrderLabel = CommerceModel.formatOrderNumber({
|
|
2923
|
+
number: cartOrderNumber
|
|
2924
|
+
}, String(object.id));
|
|
2864
2925
|
// AGL-2149: loud, not silent. Logged for the platform and pushed to the
|
|
2865
2926
|
// merchant, once, behind the same `created` guard as every other
|
|
2866
2927
|
// non-idempotent effect so a redelivery does not re-nag.
|
|
@@ -2873,7 +2934,7 @@ async function findOrderForDispute(paymentIntentId) {
|
|
|
2873
2934
|
void notifyHostManagers(String(hostId), {
|
|
2874
2935
|
type: 'content.order',
|
|
2875
2936
|
title: 'A paid order is missing items',
|
|
2876
|
-
body: `Order ${
|
|
2937
|
+
body: `Order ${cartOrderLabel} on {site} was charged in full, but ` + `${unresolvedLines.length} of its lines could not be recorded ` + 'because the product was deleted during ' + 'checkout. Refund the difference or fulfill it by hand.',
|
|
2877
2938
|
link: `/${hostId}/products`
|
|
2878
2939
|
});
|
|
2879
2940
|
}
|
|
@@ -2915,10 +2976,10 @@ async function findOrderForDispute(paymentIntentId) {
|
|
|
2915
2976
|
// matters, and the order carries the buyer identity anyway.
|
|
2916
2977
|
const licenseKeysByProduct = {};
|
|
2917
2978
|
for (const line of lineItems){
|
|
2918
|
-
var
|
|
2979
|
+
var _ref46;
|
|
2919
2980
|
var _object_customer_details11;
|
|
2920
2981
|
if (line.productType !== 'digital') continue;
|
|
2921
|
-
const keys = await assignLicenseKeys(firestore, hostRef, String(hostId), line.productId, String(object.id), (
|
|
2982
|
+
const keys = await assignLicenseKeys(firestore, hostRef, String(hostId), line.productId, String(object.id), (_ref46 = object == null ? void 0 : (_object_customer_details11 = object.customer_details) == null ? void 0 : _object_customer_details11.email) != null ? _ref46 : null, line.quantity);
|
|
2922
2983
|
if (keys.length) licenseKeysByProduct[line.productId] = keys;
|
|
2923
2984
|
}
|
|
2924
2985
|
if (Object.keys(licenseKeysByProduct).length) {
|
|
@@ -2931,9 +2992,9 @@ async function findOrderForDispute(paymentIntentId) {
|
|
|
2931
2992
|
// Branded receipt (AGL-296): env-gated like every outbound email.
|
|
2932
2993
|
const buyerEmailForReceipt = object == null ? void 0 : (_object_customer_details7 = object.customer_details) == null ? void 0 : _object_customer_details7.email;
|
|
2933
2994
|
if (isEmailConfigured() && buyerEmailForReceipt) {
|
|
2934
|
-
var
|
|
2995
|
+
var _ref47, _ref48, _ref49, _ref50;
|
|
2935
2996
|
const receiptSettings = await hostRef.collection('settings').doc('store').get().catch(()=>null);
|
|
2936
|
-
const receiptFooter = String((
|
|
2997
|
+
const receiptFooter = String((_ref47 = receiptSettings == null ? void 0 : receiptSettings.get('receiptFooter')) != null ? _ref47 : '');
|
|
2937
2998
|
const linesText = lineItems.map((line)=>`${line.quantity}× ${line.name}${line.variantLabel ? ` (${line.variantLabel})` : ''} — $${(line.unitAmountCents * line.quantity / 100).toFixed(2)}`).join('\n');
|
|
2938
2999
|
// The keys assigned above the gate (AGL-2149); the receipt only
|
|
2939
3000
|
// reports them.
|
|
@@ -2947,9 +3008,9 @@ async function findOrderForDispute(paymentIntentId) {
|
|
|
2947
3008
|
// Digital delivery links (AGL-302); reuse the canonical mint the
|
|
2948
3009
|
// tenant download endpoint verifies so the secret can never drift.
|
|
2949
3010
|
const downloadToken = mintDownloadToken(hostId, String(object.id));
|
|
2950
|
-
const siteOrigin = String((
|
|
3011
|
+
const siteOrigin = String((_ref48 = object == null ? void 0 : object.success_url) != null ? _ref48 : '').replace(/\/\?.*$|\?.*$/, '');
|
|
2951
3012
|
const downloadLines = lineItems.filter((line)=>line.productType === 'digital').map((line)=>`Download ${line.name}: ${siteOrigin}/api/commerce/download` + `?hostId=${hostId}&orderId=${object.id}` + `&productId=${line.productId}&token=${downloadToken}`).join('\n');
|
|
2952
|
-
const orderTotal = `$${(Number((
|
|
3013
|
+
const orderTotal = `$${(Number((_ref49 = object == null ? void 0 : object.amount_total) != null ? _ref49 : 0) / 100).toFixed(2)}`;
|
|
2953
3014
|
const orderSummary = [
|
|
2954
3015
|
linesText,
|
|
2955
3016
|
licenseText,
|
|
@@ -2961,11 +3022,15 @@ async function findOrderForDispute(paymentIntentId) {
|
|
|
2961
3022
|
const designed = await renderHostEmailWithTokens(firebaseAdmin.app().firestore(), String(hostId), 'order-receipt', {
|
|
2962
3023
|
'order.summary': orderSummary,
|
|
2963
3024
|
'order.total': orderTotal,
|
|
2964
|
-
'order.ref': String(object.id)
|
|
3025
|
+
'order.ref': String(object.id),
|
|
3026
|
+
// The store's own Receipt footer (AGL-3432), as a token: the
|
|
3027
|
+
// built-in copy always renders (AGL-3370), so a footer that is
|
|
3028
|
+
// only in the fallback text above never reaches a buyer.
|
|
3029
|
+
'store.receiptFooter': receiptFooter
|
|
2965
3030
|
});
|
|
2966
3031
|
await sendEmail(_extends({
|
|
2967
3032
|
to: buyerEmailForReceipt,
|
|
2968
|
-
subject: (
|
|
3033
|
+
subject: (_ref50 = designed == null ? void 0 : designed.subject) != null ? _ref50 : `Receipt for your order`,
|
|
2969
3034
|
text: (designed == null ? void 0 : designed.text) || fallbackText
|
|
2970
3035
|
}, (designed == null ? void 0 : designed.html) ? {
|
|
2971
3036
|
html: designed.html
|
|
@@ -3042,37 +3107,38 @@ async function findOrderForDispute(paymentIntentId) {
|
|
|
3042
3107
|
// is byte-identical to what it was before this issue for exactly that
|
|
3043
3108
|
// reason. The hold refuses the SECOND shopper; it never gates the
|
|
3044
3109
|
// first one's goods.
|
|
3045
|
-
await releaseStockHold(hostRef, String((
|
|
3046
|
-
|
|
3110
|
+
await releaseStockHold(hostRef, String((_ref41 = (_object_metadata20 = object.metadata) == null ? void 0 : _object_metadata20.stockHoldKey) != null ? _ref41 : ''));
|
|
3111
|
+
const cartOrderTotal = `$${(Number((_ref42 = object == null ? void 0 : object.amount_total) != null ? _ref42 : 0) / 100).toFixed(2)}`;
|
|
3112
|
+
const cartUnits = lineItems.reduce((sum, line)=>sum + line.quantity, 0);
|
|
3113
|
+
void notifyHostManagers(String(hostId), {
|
|
3047
3114
|
type: 'content.order',
|
|
3048
|
-
title: `New order
|
|
3049
|
-
|
|
3050
|
-
body: `
|
|
3051
|
-
} : {}, {
|
|
3115
|
+
title: `New order on {site} — ${cartOrderTotal}`,
|
|
3116
|
+
// What came in, from whom and where (AGL-3432).
|
|
3117
|
+
body: `Order ${cartOrderLabel} came in on {site}: ${cartUnits} ` + `item${cartUnits === 1 ? '' : 's'}, ${cartOrderTotal}` + ((object == null ? void 0 : (_object_customer_details8 = object.customer_details) == null ? void 0 : _object_customer_details8.email) ? `, from ${object.customer_details.email}.` : '.'),
|
|
3052
3118
|
link: `/${hostId}/products`
|
|
3053
|
-
})
|
|
3119
|
+
});
|
|
3054
3120
|
await captureHostContact(_extends({
|
|
3055
3121
|
hostId: String(hostId),
|
|
3056
3122
|
email: object == null ? void 0 : (_object_customer_details9 = object.customer_details) == null ? void 0 : _object_customer_details9.email,
|
|
3057
|
-
name: (
|
|
3123
|
+
name: (_ref43 = object == null ? void 0 : (_object_customer_details10 = object.customer_details) == null ? void 0 : _object_customer_details10.name) != null ? _ref43 : undefined,
|
|
3058
3124
|
source: 'order',
|
|
3059
3125
|
initialLifecycleStage: 'customer'
|
|
3060
3126
|
}, marketingOptIn ? {
|
|
3061
3127
|
marketingConsent: true
|
|
3062
3128
|
} : {}, {
|
|
3063
|
-
purchaseCents: Number((
|
|
3129
|
+
purchaseCents: Number((_ref44 = object == null ? void 0 : object.amount_total) != null ? _ref44 : 0),
|
|
3064
3130
|
interaction: {
|
|
3065
3131
|
refId: String(object.id),
|
|
3066
|
-
summary: `Placed an order ($${(Number((
|
|
3132
|
+
summary: `Placed an order ($${(Number((_ref45 = object == null ? void 0 : object.amount_total) != null ? _ref45 : 0) / 100).toFixed(2)})`
|
|
3067
3133
|
}
|
|
3068
3134
|
}));
|
|
3069
3135
|
if (couponCode) {
|
|
3070
|
-
var
|
|
3136
|
+
var _ref51;
|
|
3071
3137
|
var _object_metadata23;
|
|
3072
3138
|
await settleRedemption({
|
|
3073
3139
|
firestore,
|
|
3074
3140
|
ref: hostRef.collection('coupons').doc(String(couponCode)),
|
|
3075
|
-
holdKey: String((
|
|
3141
|
+
holdKey: String((_ref51 = (_object_metadata23 = object.metadata) == null ? void 0 : _object_metadata23.couponHoldKey) != null ? _ref51 : ''),
|
|
3076
3142
|
orderRef,
|
|
3077
3143
|
label: `coupon ${couponCode}`,
|
|
3078
3144
|
detail: `Coupon ${couponCode} was applied to this order but no longer ` + 'exists, so the redemption is uncounted against its limit.'
|
|
@@ -3080,10 +3146,10 @@ async function findOrderForDispute(paymentIntentId) {
|
|
|
3080
3146
|
}
|
|
3081
3147
|
// Gift card balance settlement (AGL-322, made a settlement by AGL-2449).
|
|
3082
3148
|
if ((_object_metadata21 = object.metadata) == null ? void 0 : _object_metadata21.giftCardCode) {
|
|
3083
|
-
var
|
|
3149
|
+
var _ref52, _ref53;
|
|
3084
3150
|
var _object_metadata24, _object_metadata25;
|
|
3085
|
-
const giftCardCents = Number((
|
|
3086
|
-
const holdKey = String((
|
|
3151
|
+
const giftCardCents = Number((_ref52 = (_object_metadata24 = object.metadata) == null ? void 0 : _object_metadata24.giftCardCents) != null ? _ref52 : 0);
|
|
3152
|
+
const holdKey = String((_ref53 = (_object_metadata25 = object.metadata) == null ? void 0 : _object_metadata25.giftCardHoldKey) != null ? _ref53 : '');
|
|
3087
3153
|
const cardRef = hostRef.collection('giftCards').doc(String(object.metadata.giftCardCode));
|
|
3088
3154
|
const orphanNote = `$${(giftCardCents / 100).toFixed(2)} was applied from gift card ` + `${object.metadata.giftCardCode}, which no longer exists. The ` + 'balance was not deducted from any card.';
|
|
3089
3155
|
if (!holdKey) {
|
|
@@ -3163,7 +3229,7 @@ async function findOrderForDispute(paymentIntentId) {
|
|
|
3163
3229
|
const lineProduct = productsById.get(line.productId);
|
|
3164
3230
|
if (!(lineProduct == null ? void 0 : lineProduct.giftCard)) continue;
|
|
3165
3231
|
for(let unit = 0; unit < line.quantity; unit += 1){
|
|
3166
|
-
var
|
|
3232
|
+
var _ref54, _ref55;
|
|
3167
3233
|
var _object_customer_details12, _object_customer_details13, _object_customer_details14;
|
|
3168
3234
|
const code = `GC-${createHmac('sha256', String(object.id)).update(`${line.productId}:${unit}:${Date.now()}`).digest('hex').slice(0, 12).toUpperCase()}`;
|
|
3169
3235
|
// THE EMAIL IS GATED ON THE WRITE (AGL-2161). This `.set()` used
|
|
@@ -3182,13 +3248,13 @@ async function findOrderForDispute(paymentIntentId) {
|
|
|
3182
3248
|
const issued = await hostRef.collection('giftCards').doc(code).set({
|
|
3183
3249
|
initialCents: line.unitAmountCents,
|
|
3184
3250
|
balanceCents: line.unitAmountCents,
|
|
3185
|
-
recipientEmail: (
|
|
3251
|
+
recipientEmail: (_ref54 = object == null ? void 0 : (_object_customer_details12 = object.customer_details) == null ? void 0 : _object_customer_details12.email) != null ? _ref54 : null,
|
|
3186
3252
|
orderId: String(object.id),
|
|
3187
3253
|
// Which line bought it, so a refund that withdraws that line
|
|
3188
3254
|
// voids this card and no other (AGL-3363).
|
|
3189
3255
|
productId: line.productId,
|
|
3190
3256
|
// What the console's Gift cards search asks (AGL-3321).
|
|
3191
|
-
searchTokens: giftCardSearchTokens(code, (
|
|
3257
|
+
searchTokens: giftCardSearchTokens(code, (_ref55 = object == null ? void 0 : (_object_customer_details13 = object.customer_details) == null ? void 0 : _object_customer_details13.email) != null ? _ref55 : null),
|
|
3192
3258
|
createdAtMs: Date.now()
|
|
3193
3259
|
}).then(()=>true).catch((error)=>{
|
|
3194
3260
|
console.error('Gift card issuance failed', hostId, code, error);
|
|
@@ -3209,24 +3275,27 @@ async function findOrderForDispute(paymentIntentId) {
|
|
|
3209
3275
|
void notifyHostManagers(String(hostId), {
|
|
3210
3276
|
type: 'content.order',
|
|
3211
3277
|
title: 'Gift card paid for but not issued',
|
|
3212
|
-
body: `A $${(line.unitAmountCents / 100).toFixed(2)} gift card
|
|
3278
|
+
body: `A $${(line.unitAmountCents / 100).toFixed(2)} gift card ` + `bought on {site} (order ${cartOrderLabel}) could not be ` + 'written, so no code was emailed to the buyer. Issue one by ' + 'hand from Gift cards.',
|
|
3213
3279
|
link: `/${hostId}/products`
|
|
3214
3280
|
});
|
|
3215
3281
|
continue;
|
|
3216
3282
|
}
|
|
3217
3283
|
const giftTo = object == null ? void 0 : (_object_customer_details14 = object.customer_details) == null ? void 0 : _object_customer_details14.email;
|
|
3218
3284
|
if (giftTo) {
|
|
3219
|
-
var
|
|
3285
|
+
var _ref56;
|
|
3220
3286
|
const giftValue = `$${(line.unitAmountCents / 100).toFixed(2)}`;
|
|
3221
3287
|
const fallbackText = `Gift card code: ${code}\n` + `Value: ${giftValue}\n\n` + 'Enter it at checkout to apply the balance.';
|
|
3222
|
-
// Site-owner-designed template when published (AGL-771).
|
|
3288
|
+
// Site-owner-designed template when published (AGL-771). A
|
|
3289
|
+
// bought card carries no note; empty, so the built-in copy
|
|
3290
|
+
// leaves that line out.
|
|
3223
3291
|
const designed = await renderHostEmailWithTokens(firebaseAdmin.app().firestore(), String(hostId), 'gift-card', {
|
|
3224
3292
|
'giftcard.code': code,
|
|
3225
|
-
'giftcard.value': giftValue
|
|
3293
|
+
'giftcard.value': giftValue,
|
|
3294
|
+
'giftcard.note': ''
|
|
3226
3295
|
});
|
|
3227
3296
|
await sendEmail(_extends({
|
|
3228
3297
|
to: giftTo,
|
|
3229
|
-
subject: (
|
|
3298
|
+
subject: (_ref56 = designed == null ? void 0 : designed.subject) != null ? _ref56 : 'Your gift card',
|
|
3230
3299
|
text: (designed == null ? void 0 : designed.text) || fallbackText
|
|
3231
3300
|
}, (designed == null ? void 0 : designed.html) ? {
|
|
3232
3301
|
html: designed.html
|
|
@@ -3247,12 +3316,12 @@ async function findOrderForDispute(paymentIntentId) {
|
|
|
3247
3316
|
}
|
|
3248
3317
|
// Discounts engine redemptions (AGL-305).
|
|
3249
3318
|
if ((_object_metadata22 = object.metadata) == null ? void 0 : _object_metadata22.discountId) {
|
|
3250
|
-
var
|
|
3319
|
+
var _ref57;
|
|
3251
3320
|
var _object_metadata26;
|
|
3252
3321
|
await settleRedemption({
|
|
3253
3322
|
firestore,
|
|
3254
3323
|
ref: hostRef.collection('discounts').doc(String(object.metadata.discountId)),
|
|
3255
|
-
holdKey: String((
|
|
3324
|
+
holdKey: String((_ref57 = (_object_metadata26 = object.metadata) == null ? void 0 : _object_metadata26.discountHoldKey) != null ? _ref57 : ''),
|
|
3256
3325
|
orderRef,
|
|
3257
3326
|
label: `discount ${object.metadata.discountId}`,
|
|
3258
3327
|
detail: `Discount ${object.metadata.discountId} was applied to this ` + 'order but no longer exists, so the redemption is uncounted ' + 'against its limit.'
|
|
@@ -3266,7 +3335,7 @@ async function findOrderForDispute(paymentIntentId) {
|
|
|
3266
3335
|
var _object_metadata27;
|
|
3267
3336
|
const { hostId, orderId, productId } = (_object_metadata27 = object.metadata) != null ? _object_metadata27 : {};
|
|
3268
3337
|
if (hostId && orderId) {
|
|
3269
|
-
var
|
|
3338
|
+
var _ref58, _ref59, _ref60;
|
|
3270
3339
|
var _object_total_details, _object_total_details1;
|
|
3271
3340
|
const firestore = firebaseAdmin.app().firestore();
|
|
3272
3341
|
const hostRef = firestore.collection('hosts').doc(String(hostId));
|
|
@@ -3305,7 +3374,7 @@ async function findOrderForDispute(paymentIntentId) {
|
|
|
3305
3374
|
//
|
|
3306
3375
|
// The whole map is written rather than one field of it, so a
|
|
3307
3376
|
// `merge`-set's nested-map merge and a plain replace agree.
|
|
3308
|
-
const shippingCents = Math.max(0, Math.round(Number((
|
|
3377
|
+
const shippingCents = Math.max(0, Math.round(Number((_ref58 = object == null ? void 0 : (_object_total_details = object.total_details) == null ? void 0 : _object_total_details.amount_shipping) != null ? _ref58 : 0) || 0));
|
|
3309
3378
|
// Tax joins shipping in the fold (AGL-1953). `draft-order.ts` now
|
|
3310
3379
|
// declares tax — a Stripe Tax Rate for a `manual` store, whose figure
|
|
3311
3380
|
// it already froze into `totals`, or `automatic_tax` for a `stripe`
|
|
@@ -3318,8 +3387,8 @@ async function findOrderForDispute(paymentIntentId) {
|
|
|
3318
3387
|
// `amount_tax` 0 here (its own figure stays on the order, and its
|
|
3319
3388
|
// AGL-1953 witness is metadata, not this field), and a draft composed
|
|
3320
3389
|
// by a store with no tax reports 0 too.
|
|
3321
|
-
const stripeTaxCents = Math.max(0, Math.round(Number((
|
|
3322
|
-
const chargedTotalCents = Number((
|
|
3390
|
+
const stripeTaxCents = Math.max(0, Math.round(Number((_ref59 = object == null ? void 0 : (_object_total_details1 = object.total_details) == null ? void 0 : _object_total_details1.amount_tax) != null ? _ref59 : 0) || 0));
|
|
3391
|
+
const chargedTotalCents = Number((_ref60 = object == null ? void 0 : object.amount_total) != null ? _ref60 : NaN);
|
|
3323
3392
|
// `shipping_details` ONLY, unlike the cart branch's
|
|
3324
3393
|
// `?? customer_details` fallback: that address is the BILLING one, and
|
|
3325
3394
|
// a card sale rung up at a register completes here too — it must not
|
|
@@ -3331,6 +3400,8 @@ async function findOrderForDispute(paymentIntentId) {
|
|
|
3331
3400
|
// transaction below. Carried out so the money can be reported after
|
|
3332
3401
|
// the transaction commits.
|
|
3333
3402
|
let paidAfterCancel = false;
|
|
3403
|
+
// The canceled order's number, for the notice below (AGL-3432).
|
|
3404
|
+
let canceledOrderLabel = '';
|
|
3334
3405
|
const flipped = await firestore.runTransaction(async (transaction)=>{
|
|
3335
3406
|
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;
|
|
3336
3407
|
var _object_customer_details, _object_metadata, _object_customer_details1, _lifted_totals1;
|
|
@@ -3356,6 +3427,7 @@ async function findOrderForDispute(paymentIntentId) {
|
|
|
3356
3427
|
const alreadyNoted = ((_lifted_timeline = lifted.timeline) != null ? _lifted_timeline : []).some((entry)=>(entry == null ? void 0 : entry.event) === 'paid-after-cancel');
|
|
3357
3428
|
if (lifted.status === 'cancelled' && !alreadyNoted) {
|
|
3358
3429
|
paidAfterCancel = true;
|
|
3430
|
+
canceledOrderLabel = CommerceModel.formatOrderNumber(lifted, String(orderId));
|
|
3359
3431
|
// `set(..., { merge: true })`, the same call the paying branch
|
|
3360
3432
|
// below makes on this document. Nothing here is a nested map, so
|
|
3361
3433
|
// merge and replace agree, and the read above has just proven
|
|
@@ -3428,13 +3500,13 @@ async function findOrderForDispute(paymentIntentId) {
|
|
|
3428
3500
|
await notifyHostManagers(String(hostId), {
|
|
3429
3501
|
type: 'content.order',
|
|
3430
3502
|
title: 'A canceled order was paid',
|
|
3431
|
-
body: `Order ${orderId} was canceled, but its payment link was still
|
|
3503
|
+
body: `Order ${canceledOrderLabel || orderId} on {site} was canceled, ` + 'but its payment link was still live and has been paid. The ' + 'money is in Stripe and no sale was recorded — refund it in ' + 'Stripe or reconcile it by hand.',
|
|
3432
3504
|
link: `/${hostId}/orders`
|
|
3433
3505
|
});
|
|
3434
3506
|
}
|
|
3435
3507
|
if (flipped) {
|
|
3436
|
-
var
|
|
3437
|
-
var _object_metadata28, _object_customer_details15;
|
|
3508
|
+
var _ref61, _ref62, _ref63, _ref64, _ref65, _ref66;
|
|
3509
|
+
var _object_metadata28, _object_customer_details15, _order_totals, _object_customer_details16;
|
|
3438
3510
|
const order = paidOrder;
|
|
3439
3511
|
// Discounts engine redemptions (AGL-305), for this branch's BOTH
|
|
3440
3512
|
// tenants: a console draft order and a POS card sale, which carry the
|
|
@@ -3449,20 +3521,25 @@ async function findOrderForDispute(paymentIntentId) {
|
|
|
3449
3521
|
// the hold existed, which Stripe's at-least-once delivery would run
|
|
3450
3522
|
// twice. The transition is what makes it once.
|
|
3451
3523
|
if ((_object_metadata28 = object.metadata) == null ? void 0 : _object_metadata28.discountId) {
|
|
3452
|
-
var
|
|
3524
|
+
var _ref67;
|
|
3453
3525
|
var _object_metadata29;
|
|
3454
3526
|
await settleRedemption({
|
|
3455
3527
|
firestore,
|
|
3456
3528
|
ref: hostRef.collection('discounts').doc(String(object.metadata.discountId)),
|
|
3457
|
-
holdKey: String((
|
|
3529
|
+
holdKey: String((_ref67 = (_object_metadata29 = object.metadata) == null ? void 0 : _object_metadata29.discountHoldKey) != null ? _ref67 : ''),
|
|
3458
3530
|
orderRef,
|
|
3459
3531
|
label: `discount ${object.metadata.discountId}`,
|
|
3460
3532
|
detail: `Discount ${object.metadata.discountId} was applied to this ` + 'order but no longer exists, so the redemption is uncounted ' + 'against its limit.'
|
|
3461
3533
|
});
|
|
3462
3534
|
}
|
|
3535
|
+
const draftLabel = CommerceModel.formatOrderNumber(order, String(orderId));
|
|
3536
|
+
const draftPayer = (_ref61 = (_ref62 = object == null ? void 0 : (_object_customer_details15 = object.customer_details) == null ? void 0 : _object_customer_details15.email) != null ? _ref62 : order.customerEmail) != null ? _ref61 : null;
|
|
3537
|
+
const draftPaidCents = 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);
|
|
3463
3538
|
void notifyHostManagers(String(hostId), {
|
|
3464
3539
|
type: 'content.order',
|
|
3465
|
-
title: `Draft order paid — ${
|
|
3540
|
+
title: `Draft order paid — ${draftLabel}`,
|
|
3541
|
+
// Who paid what, and where (AGL-3432).
|
|
3542
|
+
body: `${draftPayer != null ? draftPayer : 'The buyer'} paid draft order ${draftLabel} ` + `on {site}: $${(draftPaidCents / 100).toFixed(2)}.`,
|
|
3466
3543
|
link: `/${hostId}/products`
|
|
3467
3544
|
});
|
|
3468
3545
|
// Contacts ingestion (AGL-1748): this branch flipped the order,
|
|
@@ -3477,15 +3554,15 @@ async function findOrderForDispute(paymentIntentId) {
|
|
|
3477
3554
|
// figure the draft was priced at, and the two agree by construction,
|
|
3478
3555
|
// but only one of them is the money that moved. Stored totals remain
|
|
3479
3556
|
// the fallback for a session shape that reports no total.
|
|
3480
|
-
const draftEmail = (
|
|
3557
|
+
const draftEmail = (_ref65 = (_ref66 = object == null ? void 0 : (_object_customer_details16 = object.customer_details) == null ? void 0 : _object_customer_details16.email) != null ? _ref66 : order.customerEmail) != null ? _ref65 : null;
|
|
3481
3558
|
if (draftEmail) {
|
|
3482
|
-
var
|
|
3483
|
-
var
|
|
3484
|
-
const chargedCents = Number((
|
|
3559
|
+
var _ref68, _ref69, _ref70;
|
|
3560
|
+
var _order_totals1, _object_customer_details17;
|
|
3561
|
+
const chargedCents = Number((_ref68 = object == null ? void 0 : object.amount_total) != null ? _ref68 : 0) || Number((_ref69 = (_order_totals1 = order.totals) == null ? void 0 : _order_totals1.totalCents) != null ? _ref69 : 0);
|
|
3485
3562
|
await captureHostContact(_extends({
|
|
3486
3563
|
hostId: String(hostId),
|
|
3487
3564
|
email: draftEmail,
|
|
3488
|
-
name: (
|
|
3565
|
+
name: (_ref70 = object == null ? void 0 : (_object_customer_details17 = object.customer_details) == null ? void 0 : _object_customer_details17.name) != null ? _ref70 : undefined,
|
|
3489
3566
|
source: 'order',
|
|
3490
3567
|
initialLifecycleStage: 'customer'
|
|
3491
3568
|
}, chargedCents > 0 ? {
|
|
@@ -3498,15 +3575,15 @@ async function findOrderForDispute(paymentIntentId) {
|
|
|
3498
3575
|
}));
|
|
3499
3576
|
}
|
|
3500
3577
|
if (productId) {
|
|
3501
|
-
var _productSnapshot_data,
|
|
3578
|
+
var _productSnapshot_data, _ref71, _ref72;
|
|
3502
3579
|
var _object_metadata30, _lifted_variants_, _order_lineItems_, _order_lineItems;
|
|
3503
3580
|
const productRef = hostRef.collection('products').doc(String(productId));
|
|
3504
3581
|
const productSnapshot = await productRef.get();
|
|
3505
3582
|
const lifted = CommerceModel.liftLegacyProduct((_productSnapshot_data = productSnapshot.data()) != null ? _productSnapshot_data : {
|
|
3506
3583
|
name: 'Product'
|
|
3507
3584
|
});
|
|
3508
|
-
const soldVariantId = String((
|
|
3509
|
-
const quantity = (
|
|
3585
|
+
const soldVariantId = String((_ref71 = (_object_metadata30 = object.metadata) == null ? void 0 : _object_metadata30.variantId) != null ? _ref71 : '') || ((_lifted_variants_ = lifted.variants[0]) == null ? void 0 : _lifted_variants_.id);
|
|
3586
|
+
const quantity = (_ref72 = (_order_lineItems = order.lineItems) == null ? void 0 : (_order_lineItems_ = _order_lineItems[0]) == null ? void 0 : _order_lineItems_.quantity) != null ? _ref72 : 1;
|
|
3510
3587
|
if (soldVariantId) {
|
|
3511
3588
|
// Atomic since AGL-2320.
|
|
3512
3589
|
const moved = await decrementVariantStock({
|
|
@@ -3558,10 +3635,10 @@ async function findOrderForDispute(paymentIntentId) {
|
|
|
3558
3635
|
// would decrement it twice.
|
|
3559
3636
|
const paidProducts = new Map();
|
|
3560
3637
|
for (const line of (_order_lineItems1 = order.lineItems) != null ? _order_lineItems1 : []){
|
|
3561
|
-
var
|
|
3638
|
+
var _ref73, _ref74, _line_variantId1;
|
|
3562
3639
|
var _lineProduct_variants_;
|
|
3563
|
-
const lineProductId = String((
|
|
3564
|
-
const soldQty = Math.round(Number((
|
|
3640
|
+
const lineProductId = String((_ref73 = line == null ? void 0 : line.productId) != null ? _ref73 : '');
|
|
3641
|
+
const soldQty = Math.round(Number((_ref74 = line == null ? void 0 : line.quantity) != null ? _ref74 : 0));
|
|
3565
3642
|
if (!lineProductId || !(soldQty > 0)) continue;
|
|
3566
3643
|
// Firestore reserves `__…__` ids and `.doc()` throws
|
|
3567
3644
|
// synchronously on one; a corrupt line must not fail the
|
|
@@ -3624,8 +3701,8 @@ async function findOrderForDispute(paymentIntentId) {
|
|
|
3624
3701
|
var _object_metadata31;
|
|
3625
3702
|
const { hostId, productId, feeCents, couponCode } = (_object_metadata31 = object.metadata) != null ? _object_metadata31 : {};
|
|
3626
3703
|
if (hostId && productId) {
|
|
3627
|
-
var
|
|
3628
|
-
var _object_metadata32, _buyNowLineItems_,
|
|
3704
|
+
var _ref75, _productForSnapshot_get2, _productForSnapshot_data1, _ref76, _ref77, _ref78, _ref79, _ref80, _ref81, _ref82;
|
|
3705
|
+
var _object_metadata32, _buyNowLineItems_, _object_customer_details18, _object_customer_details19, _object_customer_details20, _object_metadata33;
|
|
3629
3706
|
const firestore = firebaseAdmin.app().firestore();
|
|
3630
3707
|
const hostRef = firestore.collection('hosts').doc(String(hostId));
|
|
3631
3708
|
// Orders v1 (AGL-283): line-item snapshot + totals + timeline with
|
|
@@ -3635,9 +3712,9 @@ async function findOrderForDispute(paymentIntentId) {
|
|
|
3635
3712
|
// is bounded to Stripe's at-least-once edge).
|
|
3636
3713
|
const orderRef = hostRef.collection('orders').doc(String(object.id));
|
|
3637
3714
|
const counterRef = hostRef.collection('counters').doc('orders');
|
|
3638
|
-
const amountCents = Number((
|
|
3715
|
+
const amountCents = Number((_ref75 = object == null ? void 0 : object.amount_total) != null ? _ref75 : 0);
|
|
3639
3716
|
const productForSnapshot = await hostRef.collection('products').doc(String(productId)).get();
|
|
3640
|
-
const snapshotName = String((
|
|
3717
|
+
const snapshotName = String((_productForSnapshot_get2 = productForSnapshot.get('name')) != null ? _productForSnapshot_get2 : 'Product');
|
|
3641
3718
|
// AGL-1711: the line item and totals used to be fabricated from
|
|
3642
3719
|
// `amount_total` alone — one unit, priced at the whole charge, with tax
|
|
3643
3720
|
// and discount recorded as 0. `computeBuyNowOrder` rebuilds the real
|
|
@@ -3648,7 +3725,7 @@ async function findOrderForDispute(paymentIntentId) {
|
|
|
3648
3725
|
name: snapshotName
|
|
3649
3726
|
});
|
|
3650
3727
|
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];
|
|
3651
|
-
const variantOptions = Object.values((
|
|
3728
|
+
const variantOptions = Object.values((_ref76 = soldVariant == null ? void 0 : soldVariant.options) != null ? _ref76 : {});
|
|
3652
3729
|
const { lineItems: buyNowLineItems, totals: buyNowTotals } = CommerceModel.computeBuyNowOrder(object, _extends({
|
|
3653
3730
|
name: snapshotName
|
|
3654
3731
|
}, variantOptions.length ? {
|
|
@@ -3660,7 +3737,10 @@ async function findOrderForDispute(paymentIntentId) {
|
|
|
3660
3737
|
} : {}, liftedForSnapshot.supplierId ? {
|
|
3661
3738
|
supplierId: liftedForSnapshot.supplierId
|
|
3662
3739
|
} : {}));
|
|
3663
|
-
const soldQuantity = (
|
|
3740
|
+
const soldQuantity = (_ref77 = (_buyNowLineItems_ = buyNowLineItems[0]) == null ? void 0 : _buyNowLineItems_.quantity) != null ? _ref77 : 1;
|
|
3741
|
+
// The number the merchant knows this order by, for the notices below
|
|
3742
|
+
// (AGL-3432).
|
|
3743
|
+
let buyNowOrderNumber;
|
|
3664
3744
|
const created = await firestore.runTransaction(async (transaction)=>{
|
|
3665
3745
|
var _counter_get, _ref, _ref1, _ref2, _ref3;
|
|
3666
3746
|
var _object_metadata, _object_customer_details, _object_customer_details1;
|
|
@@ -3670,6 +3750,7 @@ async function findOrderForDispute(paymentIntentId) {
|
|
|
3670
3750
|
]);
|
|
3671
3751
|
if (existing.exists) return false;
|
|
3672
3752
|
const number = Number((_counter_get = counter.get('next')) != null ? _counter_get : 1);
|
|
3753
|
+
buyNowOrderNumber = number;
|
|
3673
3754
|
transaction.set(counterRef, {
|
|
3674
3755
|
next: number + 1
|
|
3675
3756
|
}, {
|
|
@@ -3716,16 +3797,19 @@ async function findOrderForDispute(paymentIntentId) {
|
|
|
3716
3797
|
// Redelivery guard (AGL-498): skip the notification + fulfilment side
|
|
3717
3798
|
// effects when this order already existed.
|
|
3718
3799
|
if (!created) return;
|
|
3800
|
+
const buyNowOrderLabel = CommerceModel.formatOrderNumber({
|
|
3801
|
+
number: buyNowOrderNumber
|
|
3802
|
+
}, String(object.id));
|
|
3719
3803
|
// In-app order notification (wave v6): host managers see sales
|
|
3720
|
-
// in the bell, not just the owner's email
|
|
3721
|
-
|
|
3804
|
+
// in the bell, not just the owner's email: what came in, from whom
|
|
3805
|
+
// and where (AGL-3432).
|
|
3806
|
+
const buyNowTotal = `$${(Number((_ref78 = object == null ? void 0 : object.amount_total) != null ? _ref78 : 0) / 100).toFixed(2)}`;
|
|
3807
|
+
void notifyHostManagers(String(hostId), {
|
|
3722
3808
|
type: 'content.order',
|
|
3723
|
-
title: `New order
|
|
3724
|
-
|
|
3725
|
-
body: `From ${object.customer_details.email}`
|
|
3726
|
-
} : {}, {
|
|
3809
|
+
title: `New order on {site} — ${buyNowTotal}`,
|
|
3810
|
+
body: `Order ${buyNowOrderLabel} came in on {site}: ${soldQuantity}× ` + `${snapshotName}, ${buyNowTotal}` + ((object == null ? void 0 : (_object_customer_details18 = object.customer_details) == null ? void 0 : _object_customer_details18.email) ? `, from ${object.customer_details.email}.` : '.'),
|
|
3727
3811
|
link: `/${hostId}/products`
|
|
3728
|
-
})
|
|
3812
|
+
});
|
|
3729
3813
|
// Dropship routing (AGL-289): paid lines with a supplier notify
|
|
3730
3814
|
// it (signed webhook and/or email) and stash a callback token so
|
|
3731
3815
|
// the supplier can post tracking back. Plan-gated; failures never
|
|
@@ -3746,8 +3830,8 @@ async function findOrderForDispute(paymentIntentId) {
|
|
|
3746
3830
|
// `supplier-outbox.ts` retries out of band.
|
|
3747
3831
|
await (async ()=>{
|
|
3748
3832
|
try {
|
|
3749
|
-
var _routedProduct_get, _ref, _ref1;
|
|
3750
|
-
var _object_customer_details, _object_customer_details1;
|
|
3833
|
+
var _routedProduct_get, _ref, _ref1, _ref2, _shipTo_address_line1, _shipTo_address_line2, _shipTo_address_city, _shipTo_address_state, _shipTo_address_postal_code, _shipTo_address_country;
|
|
3834
|
+
var _object_shipping_details, _object_customer_details, _object_customer_details1;
|
|
3751
3835
|
const routedOrg = await getOrgForHost(String(hostId));
|
|
3752
3836
|
if (!Aglyn.checkEntitlement(routedOrg == null ? void 0 : routedOrg.org, 'dropshipRouting')) {
|
|
3753
3837
|
return;
|
|
@@ -3770,6 +3854,11 @@ async function findOrderForDispute(paymentIntentId) {
|
|
|
3770
3854
|
}, {
|
|
3771
3855
|
merge: true
|
|
3772
3856
|
});
|
|
3857
|
+
// Where the parcel goes (AGL-3432): the address the shopper gave
|
|
3858
|
+
// at checkout. Buy-now asks for one only when the store ships, so
|
|
3859
|
+
// it can be absent, and a billing address is never offered in its
|
|
3860
|
+
// place.
|
|
3861
|
+
const shipTo = (object == null ? void 0 : (_object_shipping_details = object.shipping_details) == null ? void 0 : _object_shipping_details.address) ? object.shipping_details : null;
|
|
3773
3862
|
const payload = {
|
|
3774
3863
|
hostId: String(hostId),
|
|
3775
3864
|
orderId: String(object.id),
|
|
@@ -3779,7 +3868,15 @@ async function findOrderForDispute(paymentIntentId) {
|
|
|
3779
3868
|
// the buyer paid for.
|
|
3780
3869
|
quantity: soldQuantity,
|
|
3781
3870
|
customerEmail: (_ref = object == null ? void 0 : (_object_customer_details = object.customer_details) == null ? void 0 : _object_customer_details.email) != null ? _ref : null,
|
|
3782
|
-
shippingName: (_ref1 = object == null ? void 0 : (_object_customer_details1 = object.customer_details) == null ? void 0 : _object_customer_details1.name) != null ? _ref1 : null,
|
|
3871
|
+
shippingName: (_ref1 = (_ref2 = shipTo == null ? void 0 : shipTo.name) != null ? _ref2 : object == null ? void 0 : (_object_customer_details1 = object.customer_details) == null ? void 0 : _object_customer_details1.name) != null ? _ref1 : null,
|
|
3872
|
+
shippingAddress: shipTo ? {
|
|
3873
|
+
line1: (_shipTo_address_line1 = shipTo.address.line1) != null ? _shipTo_address_line1 : null,
|
|
3874
|
+
line2: (_shipTo_address_line2 = shipTo.address.line2) != null ? _shipTo_address_line2 : null,
|
|
3875
|
+
city: (_shipTo_address_city = shipTo.address.city) != null ? _shipTo_address_city : null,
|
|
3876
|
+
state: (_shipTo_address_state = shipTo.address.state) != null ? _shipTo_address_state : null,
|
|
3877
|
+
postalCode: (_shipTo_address_postal_code = shipTo.address.postal_code) != null ? _shipTo_address_postal_code : null,
|
|
3878
|
+
country: (_shipTo_address_country = shipTo.address.country) != null ? _shipTo_address_country : null
|
|
3879
|
+
} : null,
|
|
3783
3880
|
updateUrl: `https://${requestHost}/api/commerce/supplier-update` + `?hostId=${hostId}&orderId=${object.id}&token=${supplierToken}`
|
|
3784
3881
|
};
|
|
3785
3882
|
if (supplier.webhookUrl) {
|
|
@@ -3804,11 +3901,31 @@ async function findOrderForDispute(paymentIntentId) {
|
|
|
3804
3901
|
});
|
|
3805
3902
|
}
|
|
3806
3903
|
if (supplier.email) {
|
|
3807
|
-
var
|
|
3904
|
+
var _resolveHostToken, _ref3, _payload_shippingName;
|
|
3905
|
+
// Which store is asking (AGL-3432): a supplier serves several,
|
|
3906
|
+
// and this mail goes out with no site header to say so.
|
|
3907
|
+
const storeName = (_resolveHostToken = resolveHostToken('businessName', await hostRef.get().then((snapshot)=>snapshot.data()).catch(()=>undefined))) != null ? _resolveHostToken : 'A store';
|
|
3908
|
+
const recipient = (_ref3 = (_payload_shippingName = payload.shippingName) != null ? _payload_shippingName : payload.customerEmail) != null ? _ref3 : 'the buyer';
|
|
3909
|
+
const address = payload.shippingAddress ? [
|
|
3910
|
+
payload.shippingAddress.line1,
|
|
3911
|
+
payload.shippingAddress.line2,
|
|
3912
|
+
[
|
|
3913
|
+
payload.shippingAddress.city,
|
|
3914
|
+
[
|
|
3915
|
+
payload.shippingAddress.state,
|
|
3916
|
+
payload.shippingAddress.postalCode
|
|
3917
|
+
].filter(Boolean).join(' ')
|
|
3918
|
+
].filter(Boolean).join(', '),
|
|
3919
|
+
payload.shippingAddress.country
|
|
3920
|
+
].filter(Boolean).join(', ') : '';
|
|
3808
3921
|
await sendEmail({
|
|
3809
3922
|
to: supplier.email,
|
|
3810
|
-
subject: `New order to fulfill: ${payload.productName}`,
|
|
3811
|
-
|
|
3923
|
+
subject: `New order to fulfill for ${storeName}: ${payload.productName}`,
|
|
3924
|
+
// The tracking link opens a form for the carrier and the
|
|
3925
|
+
// number, so it carries no placeholders: a link with
|
|
3926
|
+
// `trackingNumber=TRACKING` in it, opened unedited, records
|
|
3927
|
+
// that word as the tracking.
|
|
3928
|
+
text: `${storeName} has a new order for you to ship: ` + `${payload.quantity}× ${payload.productName} ` + `(order ${buyNowOrderLabel}).\n\n` + (address ? `Ship to: ${recipient}, ${address}\n\n` : `Ship to: ${recipient}. No shipping address was ` + `collected at checkout; ask ${storeName} for it.\n\n`) + 'When it ships, open this link to enter the carrier and ' + `tracking number:\n${payload.updateUrl}`,
|
|
3812
3929
|
fromName: (await brandFor(hostId)).fromName,
|
|
3813
3930
|
sendingIdentity: await hostSendingIdentity(String(hostId)),
|
|
3814
3931
|
audience: 'tenant',
|
|
@@ -3828,14 +3945,14 @@ async function findOrderForDispute(paymentIntentId) {
|
|
|
3828
3945
|
// Contacts ingestion (AGL-197): buyers become contacts.
|
|
3829
3946
|
await captureHostContact({
|
|
3830
3947
|
hostId: String(hostId),
|
|
3831
|
-
email: object == null ? void 0 : (
|
|
3832
|
-
name: (
|
|
3948
|
+
email: object == null ? void 0 : (_object_customer_details19 = object.customer_details) == null ? void 0 : _object_customer_details19.email,
|
|
3949
|
+
name: (_ref79 = object == null ? void 0 : (_object_customer_details20 = object.customer_details) == null ? void 0 : _object_customer_details20.name) != null ? _ref79 : undefined,
|
|
3833
3950
|
source: 'order',
|
|
3834
3951
|
initialLifecycleStage: 'customer',
|
|
3835
|
-
purchaseCents: Number((
|
|
3952
|
+
purchaseCents: Number((_ref80 = object == null ? void 0 : object.amount_total) != null ? _ref80 : 0),
|
|
3836
3953
|
interaction: {
|
|
3837
3954
|
refId: String(object.id),
|
|
3838
|
-
summary: `Placed an order ($${(Number((
|
|
3955
|
+
summary: `Placed an order ($${(Number((_ref81 = object == null ? void 0 : object.amount_total) != null ? _ref81 : 0) / 100).toFixed(2)})`
|
|
3839
3956
|
}
|
|
3840
3957
|
});
|
|
3841
3958
|
const productRef = hostRef.collection('products').doc(String(productId));
|
|
@@ -3853,12 +3970,12 @@ async function findOrderForDispute(paymentIntentId) {
|
|
|
3853
3970
|
// serialize; the snapshot above is still read, for the product NAME on
|
|
3854
3971
|
// the receipt below, and no longer decides any count.
|
|
3855
3972
|
{
|
|
3856
|
-
var _productSnapshot_data2,
|
|
3973
|
+
var _productSnapshot_data2, _ref83;
|
|
3857
3974
|
var _object_metadata34, _lifted_variants_1;
|
|
3858
3975
|
const lifted = CommerceModel.liftLegacyProduct((_productSnapshot_data2 = productSnapshot.data()) != null ? _productSnapshot_data2 : {
|
|
3859
3976
|
name: 'Product'
|
|
3860
3977
|
});
|
|
3861
|
-
const soldVariantId = String((
|
|
3978
|
+
const soldVariantId = String((_ref83 = (_object_metadata34 = object.metadata) == null ? void 0 : _object_metadata34.variantId) != null ? _ref83 : '') || ((_lifted_variants_1 = lifted.variants[0]) == null ? void 0 : _lifted_variants_1.id);
|
|
3862
3979
|
if (soldVariantId) {
|
|
3863
3980
|
// AGL-1711: `-1` regardless of how many units were bought, so a
|
|
3864
3981
|
// 3-unit buy-now sale decremented stock by one and the difference
|
|
@@ -3889,14 +4006,14 @@ async function findOrderForDispute(paymentIntentId) {
|
|
|
3889
4006
|
// Settlement, the same way round and for the same reason as the cart
|
|
3890
4007
|
// branch above (AGL-2356): the decrement lands first, then the
|
|
3891
4008
|
// reservation is dropped.
|
|
3892
|
-
await releaseStockHold(hostRef, String((
|
|
4009
|
+
await releaseStockHold(hostRef, String((_ref82 = (_object_metadata33 = object.metadata) == null ? void 0 : _object_metadata33.stockHoldKey) != null ? _ref82 : ''));
|
|
3893
4010
|
if (couponCode) {
|
|
3894
|
-
var
|
|
4011
|
+
var _ref84;
|
|
3895
4012
|
var _object_metadata35;
|
|
3896
4013
|
await settleRedemption({
|
|
3897
4014
|
firestore,
|
|
3898
4015
|
ref: hostRef.collection('coupons').doc(String(couponCode)),
|
|
3899
|
-
holdKey: String((
|
|
4016
|
+
holdKey: String((_ref84 = (_object_metadata35 = object.metadata) == null ? void 0 : _object_metadata35.couponHoldKey) != null ? _ref84 : ''),
|
|
3900
4017
|
orderRef,
|
|
3901
4018
|
label: `coupon ${couponCode}`,
|
|
3902
4019
|
detail: `Coupon ${couponCode} was applied to this order but no longer ` + 'exists, so the redemption is uncounted against its limit.'
|
|
@@ -3905,24 +4022,31 @@ async function findOrderForDispute(paymentIntentId) {
|
|
|
3905
4022
|
// Receipt + seller notification (AGL-96): env-gated like every
|
|
3906
4023
|
// other outbound email; failures never fail the webhook.
|
|
3907
4024
|
if (isEmailConfigured()) {
|
|
3908
|
-
var _productSnapshot_get,
|
|
3909
|
-
var
|
|
4025
|
+
var _productSnapshot_get, _ref85;
|
|
4026
|
+
var _object_customer_details21, _org, _this3;
|
|
3910
4027
|
const productName = String((_productSnapshot_get = productSnapshot.get('name')) != null ? _productSnapshot_get : 'your purchase');
|
|
3911
|
-
const amount = (Number((
|
|
3912
|
-
const buyerEmail = object == null ? void 0 : (
|
|
4028
|
+
const amount = (Number((_ref85 = object == null ? void 0 : object.amount_total) != null ? _ref85 : 0) / 100).toFixed(2);
|
|
4029
|
+
const buyerEmail = object == null ? void 0 : (_object_customer_details21 = object.customer_details) == null ? void 0 : _object_customer_details21.email;
|
|
3913
4030
|
const orderTotal = `$${amount}`;
|
|
3914
4031
|
if (buyerEmail) {
|
|
3915
|
-
var
|
|
3916
|
-
|
|
4032
|
+
var _ref86, _ref87;
|
|
4033
|
+
// The store's own Receipt footer, as the cart receipt carries it
|
|
4034
|
+
// (AGL-3432).
|
|
4035
|
+
const receiptFooter = String((_ref86 = await hostRef.collection('settings').doc('store').get().then((snapshot)=>snapshot.get('receiptFooter')).catch(()=>'')) != null ? _ref86 : '');
|
|
4036
|
+
// The quantity with the name (AGL-3432), so a three-unit sale
|
|
4037
|
+
// does not read as one item at the whole total.
|
|
4038
|
+
const receiptLine = `${soldQuantity}× ${productName}`;
|
|
4039
|
+
const fallbackText = `Thanks for your purchase!\n\n${receiptLine} — $${amount}` + `\nOrder reference: ${object.id}` + (receiptFooter ? `\n\n${receiptFooter}` : '');
|
|
3917
4040
|
// Site-owner-designed template when published (AGL-771).
|
|
3918
4041
|
const designed = await renderHostEmailWithTokens(firebaseAdmin.app().firestore(), String(hostId), 'order-receipt', {
|
|
3919
|
-
'order.summary':
|
|
4042
|
+
'order.summary': receiptLine,
|
|
3920
4043
|
'order.total': orderTotal,
|
|
3921
|
-
'order.ref': String(object.id)
|
|
4044
|
+
'order.ref': String(object.id),
|
|
4045
|
+
'store.receiptFooter': receiptFooter
|
|
3922
4046
|
});
|
|
3923
4047
|
await sendEmail(_extends({
|
|
3924
4048
|
to: String(buyerEmail),
|
|
3925
|
-
subject: (
|
|
4049
|
+
subject: (_ref87 = designed == null ? void 0 : designed.subject) != null ? _ref87 : `Receipt: ${productName}`,
|
|
3926
4050
|
text: (designed == null ? void 0 : designed.text) || fallbackText
|
|
3927
4051
|
}, (designed == null ? void 0 : designed.html) ? {
|
|
3928
4052
|
html: designed.html
|
|
@@ -3956,7 +4080,7 @@ async function findOrderForDispute(paymentIntentId) {
|
|
|
3956
4080
|
// SSO.
|
|
3957
4081
|
const seller = (_this4 = await findUserByUidAcrossPools(sellerUid).catch(()=>null)) == null ? void 0 : _this4.record;
|
|
3958
4082
|
if (seller == null ? void 0 : seller.email) {
|
|
3959
|
-
var _hostSnapshot_get,
|
|
4083
|
+
var _hostSnapshot_get, _ref88;
|
|
3960
4084
|
const siteName = String((_hostSnapshot_get = hostSnapshot.get('displayName')) != null ? _hostSnapshot_get : hostId);
|
|
3961
4085
|
const fallbackText = `You made a sale on ${siteName}!\n\n${productName} — $${amount}` + (buyerEmail ? `\nBuyer: ${buyerEmail}` : '') + `\nOrder reference: ${object.id}`;
|
|
3962
4086
|
// Site-owner-designed template when published (AGL-771).
|
|
@@ -3969,7 +4093,7 @@ async function findOrderForDispute(paymentIntentId) {
|
|
|
3969
4093
|
});
|
|
3970
4094
|
await sendEmail(_extends({
|
|
3971
4095
|
to: seller.email,
|
|
3972
|
-
subject: (
|
|
4096
|
+
subject: (_ref88 = designed == null ? void 0 : designed.subject) != null ? _ref88 : `New order: ${productName}`,
|
|
3973
4097
|
text: (designed == null ? void 0 : designed.text) || fallbackText
|
|
3974
4098
|
}, (designed == null ? void 0 : designed.html) ? {
|
|
3975
4099
|
html: designed.html
|
|
@@ -3999,15 +4123,15 @@ async function findOrderForDispute(paymentIntentId) {
|
|
|
3999
4123
|
// the refund is written on the order's timeline. Nothing else is moved: a
|
|
4000
4124
|
// Dashboard refund is staff's act, and they chose its transfer reversal.
|
|
4001
4125
|
if (type === 'charge.refunded') {
|
|
4002
|
-
var
|
|
4126
|
+
var _ref89, _ref90;
|
|
4003
4127
|
var _object_payment_intent, _snapshot_ref_parent_parent;
|
|
4004
|
-
const paymentIntentId = typeof (object == null ? void 0 : object.payment_intent) === 'string' ? object.payment_intent : String((
|
|
4128
|
+
const paymentIntentId = typeof (object == null ? void 0 : object.payment_intent) === 'string' ? object.payment_intent : String((_ref89 = object == null ? void 0 : (_object_payment_intent = object.payment_intent) == null ? void 0 : _object_payment_intent.id) != null ? _ref89 : '');
|
|
4005
4129
|
const lookup = paymentIntentId ? await findOrderForDispute(paymentIntentId) : {
|
|
4006
4130
|
kind: 'not-ours'
|
|
4007
4131
|
};
|
|
4008
4132
|
if (lookup.kind !== 'order') return;
|
|
4009
4133
|
const snapshot = lookup.snapshot;
|
|
4010
|
-
const hostId = String((
|
|
4134
|
+
const hostId = String((_ref90 = (_snapshot_ref_parent_parent = snapshot.ref.parent.parent) == null ? void 0 : _snapshot_ref_parent_parent.id) != null ? _ref90 : '');
|
|
4011
4135
|
const external = await recordExternalRefund(snapshot.ref, object);
|
|
4012
4136
|
if (external && hostId && snapshot.ref.parent.parent) {
|
|
4013
4137
|
const outcome = await applyExternalRefundToGiftCards({
|
|
@@ -4019,9 +4143,12 @@ async function findOrderForDispute(paymentIntentId) {
|
|
|
4019
4143
|
fullyRefunded: external.fullyRefunded
|
|
4020
4144
|
});
|
|
4021
4145
|
if (outcome.kind === 'frozen-for-review') {
|
|
4146
|
+
var _snapshot_data;
|
|
4147
|
+
const order = CommerceModel.liftLegacyOrder((_snapshot_data = snapshot.data()) != null ? _snapshot_data : {});
|
|
4022
4148
|
await notifyGiftCardHold({
|
|
4023
4149
|
hostId,
|
|
4024
|
-
|
|
4150
|
+
// The order number the merchant knows it by, never its document id.
|
|
4151
|
+
orderLabel: `order ${CommerceModel.formatOrderNumber(order, snapshot.id)}`,
|
|
4025
4152
|
orderPath: `/${hostId}/products/orders?order=${encodeURIComponent(snapshot.id)}`,
|
|
4026
4153
|
orderId: snapshot.id,
|
|
4027
4154
|
cards: outcome.cards,
|
|
@@ -4045,20 +4172,23 @@ async function findOrderForDispute(paymentIntentId) {
|
|
|
4045
4172
|
// merchant; both report the site so the seller's fraud ledger can name it.
|
|
4046
4173
|
const risk = paymentRiskEventFrom(type, object);
|
|
4047
4174
|
if (risk && risk.signal.kind !== 'dispute') {
|
|
4048
|
-
var
|
|
4049
|
-
var _snapshot_ref_parent_parent1;
|
|
4175
|
+
var _ref91, _snapshot_data1, _ref92, _ref93;
|
|
4176
|
+
var _snapshot_ref_parent_parent1, _order_totals2;
|
|
4050
4177
|
const lookup = risk.paymentIntentId ? await findOrderForDispute(risk.paymentIntentId) : {
|
|
4051
4178
|
kind: 'not-ours'
|
|
4052
4179
|
};
|
|
4053
4180
|
if (lookup.kind !== 'order') return;
|
|
4054
4181
|
const snapshot = lookup.snapshot;
|
|
4055
|
-
const hostId = String((
|
|
4056
|
-
const order = CommerceModel.liftLegacyOrder((
|
|
4182
|
+
const hostId = String((_ref91 = (_snapshot_ref_parent_parent1 = snapshot.ref.parent.parent) == null ? void 0 : _snapshot_ref_parent_parent1.id) != null ? _ref91 : '');
|
|
4183
|
+
const order = CommerceModel.liftLegacyOrder((_snapshot_data1 = snapshot.data()) != null ? _snapshot_data1 : {});
|
|
4184
|
+
const orderCents = Number((_ref92 = (_ref93 = (_order_totals2 = order.totals) == null ? void 0 : _order_totals2.totalCents) != null ? _ref93 : order.amountCents) != null ? _ref92 : 0);
|
|
4057
4185
|
await recordPaymentRiskOnRecord({
|
|
4058
4186
|
ref: snapshot.ref,
|
|
4059
4187
|
signal: risk.signal,
|
|
4060
4188
|
hostId,
|
|
4061
4189
|
subjectLabel: `order ${CommerceModel.formatOrderNumber(order, snapshot.id)}`,
|
|
4190
|
+
// The notice names the sum in question, as the dispute's does.
|
|
4191
|
+
amount: orderCents > 0 ? `$${(orderCents / 100).toFixed(2)}` : null,
|
|
4062
4192
|
// The Orders section of the Products hub, with this order's dialog
|
|
4063
4193
|
// open — where Refund lives (`siteRecordLinks().order`).
|
|
4064
4194
|
link: `/${hostId}/products/orders?order=${encodeURIComponent(snapshot.id)}`,
|
|
@@ -4114,7 +4244,7 @@ async function findOrderForDispute(paymentIntentId) {
|
|
|
4114
4244
|
// finds). AGL-1554 will add its own `closed` branch for the marketplace
|
|
4115
4245
|
// side; the two coexist because each self-selects by finding its own record.
|
|
4116
4246
|
if (type === 'charge.dispute.created' || type === 'charge.dispute.closed') {
|
|
4117
|
-
var _dispute_payment_intent,
|
|
4247
|
+
var _dispute_payment_intent, _ref94;
|
|
4118
4248
|
var _snapshot_ref_parent_parent2;
|
|
4119
4249
|
const dispute = object != null ? object : {};
|
|
4120
4250
|
const paymentIntentId = String((_dispute_payment_intent = dispute.payment_intent) != null ? _dispute_payment_intent : '');
|
|
@@ -4140,14 +4270,16 @@ async function findOrderForDispute(paymentIntentId) {
|
|
|
4140
4270
|
const claimed = lookup.kind === 'order' || lookup.kind === 'unresolved';
|
|
4141
4271
|
const snapshot = lookup.kind === 'order' ? lookup.snapshot : null;
|
|
4142
4272
|
if (snapshot) {
|
|
4143
|
-
var
|
|
4273
|
+
var _ref95;
|
|
4144
4274
|
var _snapshot_ref_parent_parent3;
|
|
4145
|
-
const hostId = String((
|
|
4275
|
+
const hostId = String((_ref95 = (_snapshot_ref_parent_parent3 = snapshot.ref.parent.parent) == null ? void 0 : _snapshot_ref_parent_parent3.id) != null ? _ref95 : '');
|
|
4146
4276
|
if (type === 'charge.dispute.created') {
|
|
4277
|
+
var _snapshot_data2;
|
|
4147
4278
|
// A chargeback's gift cards are frozen while it is open (AGL-3363).
|
|
4279
|
+
const disputed = CommerceModel.liftLegacyOrder((_snapshot_data2 = snapshot.data()) != null ? _snapshot_data2 : {});
|
|
4148
4280
|
await notifyGiftCardHold({
|
|
4149
4281
|
hostId,
|
|
4150
|
-
orderLabel: `order ${snapshot.id}`,
|
|
4282
|
+
orderLabel: `order ${CommerceModel.formatOrderNumber(disputed, snapshot.id)}`,
|
|
4151
4283
|
orderPath: `/${hostId}/products/orders?order=${encodeURIComponent(snapshot.id)}`,
|
|
4152
4284
|
orderId: snapshot.id,
|
|
4153
4285
|
cards: await applyOrderGiftCardRisk(snapshot, {
|
|
@@ -4158,7 +4290,7 @@ async function findOrderForDispute(paymentIntentId) {
|
|
|
4158
4290
|
}, notifyRiskEvent);
|
|
4159
4291
|
const { opened, record } = await recordDisputeOpened(snapshot, dispute);
|
|
4160
4292
|
if (opened && hostId) {
|
|
4161
|
-
var
|
|
4293
|
+
var _snapshot_data3, _record_evidenceDueByMs, _ref96;
|
|
4162
4294
|
// Time-critical — Stripe's evidence window is days, not weeks — so
|
|
4163
4295
|
// this is awaited rather than fired off with `void`: the handler is
|
|
4164
4296
|
// serverless and work left running past the response is work the
|
|
@@ -4166,7 +4298,7 @@ async function findOrderForDispute(paymentIntentId) {
|
|
|
4166
4298
|
// (AGL-3368) tells the site's managers and the workspace's owners
|
|
4167
4299
|
// what a dispute means and when the evidence is due; it never
|
|
4168
4300
|
// throws.
|
|
4169
|
-
const order = CommerceModel.liftLegacyOrder((
|
|
4301
|
+
const order = CommerceModel.liftLegacyOrder((_snapshot_data3 = snapshot.data()) != null ? _snapshot_data3 : {});
|
|
4170
4302
|
await notifyRiskEvent({
|
|
4171
4303
|
kind: 'sale-dispute',
|
|
4172
4304
|
orgId: null,
|
|
@@ -4177,16 +4309,16 @@ async function findOrderForDispute(paymentIntentId) {
|
|
|
4177
4309
|
},
|
|
4178
4310
|
amount: `$${(record.amountCents / 100).toFixed(2)}`,
|
|
4179
4311
|
evidenceDueByMs: (_record_evidenceDueByMs = record.evidenceDueByMs) != null ? _record_evidenceDueByMs : null,
|
|
4180
|
-
dedupeKey: `sale:${String((
|
|
4312
|
+
dedupeKey: `sale:${String((_ref96 = dispute == null ? void 0 : dispute.id) != null ? _ref96 : snapshot.id)}`,
|
|
4181
4313
|
staffEvidence: record.reason ? `Reason: ${record.reason.replace(/_/g, ' ')}.` : null
|
|
4182
4314
|
});
|
|
4183
4315
|
}
|
|
4184
4316
|
} else {
|
|
4185
|
-
var
|
|
4317
|
+
var _ref97, _snapshot_data4, _ref98;
|
|
4186
4318
|
// Lost: the money went back, so the cards it bought are voided. Won
|
|
4187
4319
|
// (or closed on a warning): the dispute's freeze lifts; a freeze a
|
|
4188
4320
|
// fraud warning or review put on stays for the merchant (AGL-3363).
|
|
4189
|
-
await applyOrderGiftCardRisk(snapshot, String((
|
|
4321
|
+
await applyOrderGiftCardRisk(snapshot, String((_ref97 = dispute == null ? void 0 : dispute.status) != null ? _ref97 : '') === 'lost' ? {
|
|
4190
4322
|
kind: 'void',
|
|
4191
4323
|
reason: 'dispute-lost'
|
|
4192
4324
|
} : {
|
|
@@ -4194,11 +4326,18 @@ async function findOrderForDispute(paymentIntentId) {
|
|
|
4194
4326
|
reason: 'dispute'
|
|
4195
4327
|
});
|
|
4196
4328
|
const settled = await recordDisputeClosed(snapshot, dispute);
|
|
4329
|
+
// The number the merchant knows the order by (AGL-3432), for both
|
|
4330
|
+
// notices below; the Stripe session id is nowhere in the console.
|
|
4331
|
+
const disputedOrderLabel = CommerceModel.formatOrderNumber(CommerceModel.liftLegacyOrder((_snapshot_data4 = snapshot.data()) != null ? _snapshot_data4 : {}), snapshot.id);
|
|
4197
4332
|
if (settled.recorded && hostId) {
|
|
4333
|
+
// What the shopper's bank took back left the PLATFORM's balance on
|
|
4334
|
+
// a destination charge, not the merchant's: their own loss is the
|
|
4335
|
+
// seller share in the "Payout adjusted" notice below. Told that way
|
|
4336
|
+
// (AGL-3432), so the two are not read as two losses.
|
|
4198
4337
|
await notifyHostManagers(hostId, {
|
|
4199
4338
|
type: 'content.order',
|
|
4200
|
-
title: settled.lost ? `Chargeback lost —
|
|
4201
|
-
body: `
|
|
4339
|
+
title: settled.lost ? `Chargeback lost — order ${disputedOrderLabel}` : 'Chargeback resolved in your favor',
|
|
4340
|
+
body: settled.lost ? `The chargeback on order ${disputedOrderLabel} on {site} was ` + 'decided in the shopper\'s favor, and their bank took back ' + `$${(settled.reversedCents / 100).toFixed(2)}. If part of the ` + 'sale had been paid out to you, a separate notice says what ' + 'is taken back from your balance.' : `The chargeback on order ${disputedOrderLabel} on {site} ` + 'closed in your favor. Nothing is taken back from you for it.',
|
|
4202
4341
|
link: `/${hostId}/orders`
|
|
4203
4342
|
});
|
|
4204
4343
|
// The customer's side of the ledger (AGL-1754), through the same
|
|
@@ -4250,7 +4389,7 @@ async function findOrderForDispute(paymentIntentId) {
|
|
|
4250
4389
|
// transiently-failed reversal gets its retry — and gated inside on
|
|
4251
4390
|
// the order's own `reversedTransferCents` marker, so it runs at most
|
|
4252
4391
|
// once per dispute however many times Stripe delivers.
|
|
4253
|
-
if (String((
|
|
4392
|
+
if (String((_ref98 = dispute == null ? void 0 : dispute.status) != null ? _ref98 : '') === 'lost') {
|
|
4254
4393
|
const pulledBackCents = await reverseSellerShare(snapshot.ref, dispute);
|
|
4255
4394
|
// The merchant-experience half of the AGL-1794 decision, and not an
|
|
4256
4395
|
// optional extra: the reversal takes money out of the connected
|
|
@@ -4270,7 +4409,7 @@ async function findOrderForDispute(paymentIntentId) {
|
|
|
4270
4409
|
await notifyHostManagers(hostId, {
|
|
4271
4410
|
type: 'content.order',
|
|
4272
4411
|
title: `Payout adjusted — $${(pulledBackCents / 100).toFixed(2)} recovered for a lost chargeback`,
|
|
4273
|
-
body: `
|
|
4412
|
+
body: `Because the chargeback on order ${disputedOrderLabel} on ` + `{site} was lost, the $${(pulledBackCents / 100).toFixed(2)} ` + 'transferred to you for that sale has been taken back. If ' + 'your balance does not cover it, Stripe recovers the rest ' + 'from your future payouts.',
|
|
4274
4413
|
link: `/${hostId}/orders`
|
|
4275
4414
|
});
|
|
4276
4415
|
}
|
|
@@ -4280,7 +4419,7 @@ async function findOrderForDispute(paymentIntentId) {
|
|
|
4280
4419
|
return _extends({
|
|
4281
4420
|
claimed
|
|
4282
4421
|
}, snapshot ? {
|
|
4283
|
-
hostId: String((
|
|
4422
|
+
hostId: String((_ref94 = (_snapshot_ref_parent_parent2 = snapshot.ref.parent.parent) == null ? void 0 : _snapshot_ref_parent_parent2.id) != null ? _ref94 : '')
|
|
4284
4423
|
} : {});
|
|
4285
4424
|
}
|
|
4286
4425
|
};
|