@mj-biz-apps/orders-core-entities-server 5.22.0 → 5.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/AccountingBridge.d.ts +9 -1
- package/dist/AccountingBridge.d.ts.map +1 -1
- package/dist/AccountingBridge.js +5 -3
- package/dist/AccountingBridge.js.map +1 -1
- package/dist/AmendArrangementOperation.d.ts +78 -0
- package/dist/AmendArrangementOperation.d.ts.map +1 -0
- package/dist/AmendArrangementOperation.js +187 -0
- package/dist/AmendArrangementOperation.js.map +1 -0
- package/dist/BasePaymentProvider.d.ts +21 -0
- package/dist/BasePaymentProvider.d.ts.map +1 -1
- package/dist/BasePaymentProvider.js +4 -0
- package/dist/BasePaymentProvider.js.map +1 -1
- package/dist/BundleEngine.d.ts +7 -1
- package/dist/BundleEngine.d.ts.map +1 -1
- package/dist/BundleEngine.js +1 -0
- package/dist/BundleEngine.js.map +1 -1
- package/dist/CheckoutSessionService.d.ts +16 -1
- package/dist/CheckoutSessionService.d.ts.map +1 -1
- package/dist/CheckoutSessionService.js +141 -17
- package/dist/CheckoutSessionService.js.map +1 -1
- package/dist/CheckoutStepLog.d.ts +61 -0
- package/dist/CheckoutStepLog.d.ts.map +1 -0
- package/dist/CheckoutStepLog.js +143 -0
- package/dist/CheckoutStepLog.js.map +1 -0
- package/dist/ConcessionApprovalListener.d.ts +17 -0
- package/dist/ConcessionApprovalListener.d.ts.map +1 -0
- package/dist/ConcessionApprovalListener.js +113 -0
- package/dist/ConcessionApprovalListener.js.map +1 -0
- package/dist/ConcessionApprovalTask.d.ts +139 -0
- package/dist/ConcessionApprovalTask.d.ts.map +1 -0
- package/dist/ConcessionApprovalTask.js +467 -0
- package/dist/ConcessionApprovalTask.js.map +1 -0
- package/dist/ConcessionGate.d.ts +122 -0
- package/dist/ConcessionGate.d.ts.map +1 -0
- package/dist/ConcessionGate.js +317 -0
- package/dist/ConcessionGate.js.map +1 -0
- package/dist/DeliveryBehavior.d.ts +6 -1
- package/dist/DeliveryBehavior.d.ts.map +1 -1
- package/dist/DeliveryBehavior.js +8 -0
- package/dist/DeliveryBehavior.js.map +1 -1
- package/dist/DetectOverlappingSubscriptionsOperation.d.ts +84 -0
- package/dist/DetectOverlappingSubscriptionsOperation.d.ts.map +1 -0
- package/dist/DetectOverlappingSubscriptionsOperation.js +237 -0
- package/dist/DetectOverlappingSubscriptionsOperation.js.map +1 -0
- package/dist/EntitlementBehavior.d.ts +17 -0
- package/dist/EntitlementBehavior.d.ts.map +1 -1
- package/dist/EntitlementBehavior.js +13 -0
- package/dist/EntitlementBehavior.js.map +1 -1
- package/dist/EntitlementEngine.d.ts +7 -1
- package/dist/EntitlementEngine.d.ts.map +1 -1
- package/dist/EntitlementEngine.js +42 -1
- package/dist/EntitlementEngine.js.map +1 -1
- package/dist/EntitlementGrantEntityServer.d.ts +24 -0
- package/dist/EntitlementGrantEntityServer.d.ts.map +1 -0
- package/dist/EntitlementGrantEntityServer.js +114 -0
- package/dist/EntitlementGrantEntityServer.js.map +1 -0
- package/dist/IntentDescription.d.ts +46 -0
- package/dist/IntentDescription.d.ts.map +1 -0
- package/dist/IntentDescription.js +137 -0
- package/dist/IntentDescription.js.map +1 -0
- package/dist/OrderConcessionEntityServer.d.ts +93 -0
- package/dist/OrderConcessionEntityServer.d.ts.map +1 -0
- package/dist/OrderConcessionEntityServer.js +477 -0
- package/dist/OrderConcessionEntityServer.js.map +1 -0
- package/dist/OrderEntityServer.d.ts +21 -0
- package/dist/OrderEntityServer.d.ts.map +1 -1
- package/dist/OrderEntityServer.js +131 -1
- package/dist/OrderEntityServer.js.map +1 -1
- package/dist/OrderLineEntityServer.d.ts +19 -0
- package/dist/OrderLineEntityServer.d.ts.map +1 -1
- package/dist/OrderLineEntityServer.js +29 -0
- package/dist/OrderLineEntityServer.js.map +1 -1
- package/dist/OrdersSettings.d.ts +9 -0
- package/dist/OrdersSettings.d.ts.map +1 -1
- package/dist/OrdersSettings.js +12 -0
- package/dist/OrdersSettings.js.map +1 -1
- package/dist/OutboundEvents.d.ts +116 -0
- package/dist/OutboundEvents.d.ts.map +1 -0
- package/dist/OutboundEvents.js +273 -0
- package/dist/OutboundEvents.js.map +1 -0
- package/dist/PaymentIntentService.d.ts +5 -0
- package/dist/PaymentIntentService.d.ts.map +1 -1
- package/dist/PaymentIntentService.js +3 -0
- package/dist/PaymentIntentService.js.map +1 -1
- package/dist/PaymentTermsChange.d.ts +33 -0
- package/dist/PaymentTermsChange.d.ts.map +1 -0
- package/dist/PaymentTermsChange.js +129 -0
- package/dist/PaymentTermsChange.js.map +1 -0
- package/dist/PaymentTermsSanction.d.ts +5 -0
- package/dist/PaymentTermsSanction.d.ts.map +1 -0
- package/dist/PaymentTermsSanction.js +21 -0
- package/dist/PaymentTermsSanction.js.map +1 -0
- package/dist/PriceBelowEngineExceptions.d.ts +57 -0
- package/dist/PriceBelowEngineExceptions.d.ts.map +1 -0
- package/dist/PriceBelowEngineExceptions.js +85 -0
- package/dist/PriceBelowEngineExceptions.js.map +1 -0
- package/dist/ReplayCheckoutStepOperation.d.ts +28 -0
- package/dist/ReplayCheckoutStepOperation.d.ts.map +1 -0
- package/dist/ReplayCheckoutStepOperation.js +97 -0
- package/dist/ReplayCheckoutStepOperation.js.map +1 -0
- package/dist/SpawnRenewalsOperation.d.ts +2 -0
- package/dist/SpawnRenewalsOperation.d.ts.map +1 -1
- package/dist/SpawnRenewalsOperation.js +24 -0
- package/dist/SpawnRenewalsOperation.js.map +1 -1
- package/dist/StripePaymentProvider.d.ts +2 -1
- package/dist/StripePaymentProvider.d.ts.map +1 -1
- package/dist/StripePaymentProvider.js +16 -0
- package/dist/StripePaymentProvider.js.map +1 -1
- package/dist/SubscriptionTermEntityServer.d.ts +25 -0
- package/dist/SubscriptionTermEntityServer.d.ts.map +1 -0
- package/dist/SubscriptionTermEntityServer.js +46 -0
- package/dist/SubscriptionTermEntityServer.js.map +1 -0
- package/dist/TermExtension.d.ts +52 -0
- package/dist/TermExtension.d.ts.map +1 -0
- package/dist/TermExtension.js +442 -0
- package/dist/TermExtension.js.map +1 -0
- package/dist/TermExtensionPlan.d.ts +101 -0
- package/dist/TermExtensionPlan.d.ts.map +1 -0
- package/dist/TermExtensionPlan.js +109 -0
- package/dist/TermExtensionPlan.js.map +1 -0
- package/dist/entity-names.d.ts +4 -0
- package/dist/entity-names.d.ts.map +1 -1
- package/dist/entity-names.js +4 -0
- package/dist/entity-names.js.map +1 -1
- package/dist/index.d.ts +19 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -1
- package/package.json +18 -23
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CheckoutSessionService.d.ts","sourceRoot":"","sources":["../src/CheckoutSessionService.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAA+E,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAM7H,OAAO,EAcH,KAAK,2BAA2B,EAMhC,KAAK,oBAAoB,
|
|
1
|
+
{"version":3,"file":"CheckoutSessionService.d.ts","sourceRoot":"","sources":["../src/CheckoutSessionService.ts"],"names":[],"mappings":"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"}
|
|
@@ -13,13 +13,15 @@ import { BusinessTimeZoneEngine } from '@mj-biz-apps/common-entities';
|
|
|
13
13
|
// '@memberjunction/core-entities-server' once MJ publishes the engine.
|
|
14
14
|
import { CalendarDayOrToday } from './calendar-day.js';
|
|
15
15
|
import { IdentityClaimEngineServer } from './identityClaimContracts.js';
|
|
16
|
-
import { LoadOrdersEngine, OrderPricingService, OrdersEngine, TodayAsDateValue, CheckAnswersAgainstQuestions, CheckCheckoutAnswers, ReadCheckoutQuestions, NormalizeCheckoutAttribution } from '@mj-biz-apps/orders-entities';
|
|
16
|
+
import { LoadOrdersEngine, OrderPricingService, OrdersEngine, TodayAsDateValue, CheckAnswersAgainstQuestions, CheckCheckoutAnswers, ReadCheckoutQuestions, NormalizeCheckoutAttribution, CheckCheckoutChoices, ChoicesForStorage, ReadCheckoutChoiceGroups, CheckChoicesAgainstGroups } from '@mj-biz-apps/orders-entities';
|
|
17
17
|
import { EscapeText } from './sql-guards.js';
|
|
18
18
|
import { OpenPaymentIntent } from './PaymentIntentService.js';
|
|
19
19
|
import { ResolvePaymentProvider } from './PaymentProviderResolver.js';
|
|
20
20
|
import { CapturePaymentOperation } from './CapturePaymentOperation.js';
|
|
21
21
|
import { raiseCheckoutCaptureTerminalAlert } from './checkoutCaptureAlert.js';
|
|
22
|
+
import { CheckoutStepLog } from './CheckoutStepLog.js';
|
|
22
23
|
import { isCaptureRefusalRetryable, isTerminalCapturePrecheck, } from './checkoutCaptureRetry.js';
|
|
24
|
+
import { DescribeCheckoutSnapshot, SendOrderDescriptionToGateway } from './IntentDescription.js';
|
|
23
25
|
const CHECKOUT_WIDGET_ENTITY = 'MJ_BizApps_Orders: Checkout Widgets';
|
|
24
26
|
const CHECKOUT_DISTRIBUTION_ENTITY = 'MJ_BizApps_Orders: Checkout Widget Distributions';
|
|
25
27
|
const CHECKOUT_SESSION_ENTITY = 'MJ_BizApps_Orders: Checkout Sessions';
|
|
@@ -166,6 +168,7 @@ export class CheckoutSessionService {
|
|
|
166
168
|
'theme',
|
|
167
169
|
'allowCoupons',
|
|
168
170
|
'questions',
|
|
171
|
+
'choiceGroups',
|
|
169
172
|
]); }
|
|
170
173
|
static sanitizeConfigurationForClient(configObj) {
|
|
171
174
|
const sanitized = {};
|
|
@@ -513,6 +516,36 @@ export class CheckoutSessionService {
|
|
|
513
516
|
}
|
|
514
517
|
return CheckCheckoutAnswers(questions, stored);
|
|
515
518
|
}
|
|
519
|
+
/** Whether a line can hold Order Line Choices. An extension-type line (IS-A) does not. */
|
|
520
|
+
static canRecordChoices(line) {
|
|
521
|
+
return !!line.Choices;
|
|
522
|
+
}
|
|
523
|
+
/**
|
|
524
|
+
* The session's stored choices, checked in full against the widget's CURRENT choice groups:
|
|
525
|
+
* every group within its min and max, every option one of the group's. Run where money moves
|
|
526
|
+
* or the order books.
|
|
527
|
+
*/
|
|
528
|
+
static checkSessionChoices(widget, session) {
|
|
529
|
+
let groups;
|
|
530
|
+
if (widget.Configuration) {
|
|
531
|
+
try {
|
|
532
|
+
groups = JSON.parse(widget.Configuration).choiceGroups;
|
|
533
|
+
}
|
|
534
|
+
catch {
|
|
535
|
+
return { Choices: [], Error: 'Invalid widget configuration' };
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
let stored;
|
|
539
|
+
if (session.MetadataJSON) {
|
|
540
|
+
try {
|
|
541
|
+
stored = JSON.parse(session.MetadataJSON).Choices;
|
|
542
|
+
}
|
|
543
|
+
catch {
|
|
544
|
+
stored = undefined;
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
return CheckCheckoutChoices(groups, stored);
|
|
548
|
+
}
|
|
516
549
|
/**
|
|
517
550
|
* Creates an order line entity instance attached to the order's Lines collection.
|
|
518
551
|
*/
|
|
@@ -678,6 +711,20 @@ export class CheckoutSessionService {
|
|
|
678
711
|
if (draftAnswers.Error) {
|
|
679
712
|
return failed(draftAnswers.Error);
|
|
680
713
|
}
|
|
714
|
+
// Choices likewise: a draft may hold fewer than a group's minimum, never more than its
|
|
715
|
+
// maximum or an option it does not offer. The choice is the buyer's for the one item the
|
|
716
|
+
// widget sells, so a widget with choice groups takes a single line.
|
|
717
|
+
const choiceGroups = ReadCheckoutChoiceGroups(widgetConfig.choiceGroups);
|
|
718
|
+
if (choiceGroups.Error) {
|
|
719
|
+
return failed(choiceGroups.Error);
|
|
720
|
+
}
|
|
721
|
+
if (choiceGroups.Groups.length > 0 && Array.isArray(lines) && lines.length > 1) {
|
|
722
|
+
return failed('This checkout sells one item at a time.');
|
|
723
|
+
}
|
|
724
|
+
const draftChoices = CheckChoicesAgainstGroups(choiceGroups.Groups, options?.Choices, { partial: true });
|
|
725
|
+
if (draftChoices.Error) {
|
|
726
|
+
return failed(draftChoices.Error);
|
|
727
|
+
}
|
|
681
728
|
const allowedProductIds = await this.resolveAllowedProductIds(widgetConfig, contextUser);
|
|
682
729
|
const allowAnyProduct = widgetConfig.allowAnyProduct === true;
|
|
683
730
|
if (!allowAnyProduct && allowedProductIds.size === 0) {
|
|
@@ -794,6 +841,12 @@ export class CheckoutSessionService {
|
|
|
794
841
|
}
|
|
795
842
|
}
|
|
796
843
|
}
|
|
844
|
+
// A choice is recorded on the order line itself. A line of an extension type (an event
|
|
845
|
+
// line) has no collection for it, so refuse here, before the buyer pays, rather than at
|
|
846
|
+
// completion.
|
|
847
|
+
if (choiceGroups.Groups.length > 0 && order.Lines.Items.some((l) => !CheckoutSessionService.canRecordChoices(l))) {
|
|
848
|
+
return failed('This checkout is not configured correctly (its choices cannot be recorded on this item).');
|
|
849
|
+
}
|
|
797
850
|
// Price the draft order in memory
|
|
798
851
|
try {
|
|
799
852
|
const pricingService = new OrderPricingService({
|
|
@@ -876,6 +929,7 @@ export class CheckoutSessionService {
|
|
|
876
929
|
TotalGross: order.TotalGross,
|
|
877
930
|
Answers: this.answersForStorage(draftAnswers.Answers),
|
|
878
931
|
...(attribution ? { Attribution: attribution } : {}),
|
|
932
|
+
Choices: ChoicesForStorage(draftChoices.Choices),
|
|
879
933
|
UpdatedAt: new Date().toISOString()
|
|
880
934
|
});
|
|
881
935
|
const sessionSaved = await session.Save();
|
|
@@ -952,6 +1006,10 @@ export class CheckoutSessionService {
|
|
|
952
1006
|
if (answersCheck.Error) {
|
|
953
1007
|
return failed(answersCheck.Error);
|
|
954
1008
|
}
|
|
1009
|
+
const choicesCheck = this.checkSessionChoices(widget, session);
|
|
1010
|
+
if (choicesCheck.Error) {
|
|
1011
|
+
return failed(choicesCheck.Error);
|
|
1012
|
+
}
|
|
955
1013
|
let paymentProviderId;
|
|
956
1014
|
let currencyCode;
|
|
957
1015
|
if (widget.Configuration) {
|
|
@@ -971,6 +1029,8 @@ export class CheckoutSessionService {
|
|
|
971
1029
|
if (!mdProvider) {
|
|
972
1030
|
return failed('No metadata provider is available to open a payment intent.');
|
|
973
1031
|
}
|
|
1032
|
+
// Products only: the order, and so its number, does not exist until completion.
|
|
1033
|
+
const description = await DescribeCheckoutSnapshot(session.MetadataJSON, mdProvider, contextUser);
|
|
974
1034
|
const openResult = await OpenPaymentIntent({
|
|
975
1035
|
PaymentProviderID: paymentProviderId,
|
|
976
1036
|
Amount: snapshotTotal,
|
|
@@ -979,7 +1039,8 @@ export class CheckoutSessionService {
|
|
|
979
1039
|
// Stable per-session idempotency key: reopening for the same session+amount
|
|
980
1040
|
// returns the SAME gateway intent instead of minting a fresh one per retry.
|
|
981
1041
|
IdempotencyKey: `checkout-${sessionID}-${Math.round(snapshotTotal * 100)}`,
|
|
982
|
-
Metadata: { CheckoutSessionID: sessionID }
|
|
1042
|
+
Metadata: { CheckoutSessionID: sessionID },
|
|
1043
|
+
Description: description
|
|
983
1044
|
}, mdProvider, contextUser);
|
|
984
1045
|
if (!openResult.Success || !openResult.PaymentIntentID) {
|
|
985
1046
|
return failed(openResult.Reason ?? 'The payment provider refused to open an intent.');
|
|
@@ -1034,7 +1095,7 @@ export class CheckoutSessionService {
|
|
|
1034
1095
|
// `payment_intent.succeeded` is the other — BookSettledCheckoutPaymentIfNeeded, including
|
|
1035
1096
|
// on AlreadyApplied webhook deliveries so a 500 after applyEvent still converges.
|
|
1036
1097
|
if (orderLoaded) {
|
|
1037
|
-
await this.applySettledPaymentToOrder(session, existingOrder, contextUser);
|
|
1098
|
+
await this.applySettledPaymentToOrder(session, existingOrder, contextUser, { Source: 'Checkout' });
|
|
1038
1099
|
}
|
|
1039
1100
|
return {
|
|
1040
1101
|
Success: true,
|
|
@@ -1074,6 +1135,8 @@ export class CheckoutSessionService {
|
|
|
1074
1135
|
session.Status = 'Processing';
|
|
1075
1136
|
// Set the moment order.Confirm() commits; controls the catch's no-revert posture.
|
|
1076
1137
|
let confirmedOrderID = null;
|
|
1138
|
+
// The Confirm step's record, begun once payment has checked out (#326).
|
|
1139
|
+
let confirmStep = null;
|
|
1077
1140
|
try {
|
|
1078
1141
|
const widget = await md.GetEntityObject(CHECKOUT_WIDGET_ENTITY, contextUser);
|
|
1079
1142
|
await widget.Load(session.CheckoutWidgetID);
|
|
@@ -1091,12 +1154,14 @@ export class CheckoutSessionService {
|
|
|
1091
1154
|
}
|
|
1092
1155
|
// Refused before anything is written, so an unanswered checkout creates no Person.
|
|
1093
1156
|
const answersCheck = this.checkSessionAnswers(widget, session);
|
|
1094
|
-
|
|
1157
|
+
const choicesCheck = this.checkSessionChoices(widget, session);
|
|
1158
|
+
const unanswered = answersCheck.Error ?? choicesCheck.Error;
|
|
1159
|
+
if (unanswered) {
|
|
1095
1160
|
await CheckoutSessionService.revertSessionOpenAtomic(sessionID, md, contextUser);
|
|
1096
1161
|
session.Status = 'Open';
|
|
1097
1162
|
return {
|
|
1098
1163
|
Success: false,
|
|
1099
|
-
ErrorMessage:
|
|
1164
|
+
ErrorMessage: unanswered,
|
|
1100
1165
|
SessionID: sessionID,
|
|
1101
1166
|
Status: 'Open'
|
|
1102
1167
|
};
|
|
@@ -1242,6 +1307,21 @@ export class CheckoutSessionService {
|
|
|
1242
1307
|
row.Answer = answer.Answer;
|
|
1243
1308
|
row.OtherText = answer.OtherText;
|
|
1244
1309
|
}
|
|
1310
|
+
// The choices ride on each line the checkout sold (every seat, in per-unit mode), in the
|
|
1311
|
+
// same graph: the entitlement walk reads them from the line to decide which conditional
|
|
1312
|
+
// entitlements to grant, and a refused confirm leaves none behind.
|
|
1313
|
+
for (const line of order.Lines.Items) {
|
|
1314
|
+
if (choicesCheck.Choices.length > 0 && !CheckoutSessionService.canRecordChoices(line)) {
|
|
1315
|
+
throw new Error('This checkout is not configured correctly (its choices cannot be recorded on this item).');
|
|
1316
|
+
}
|
|
1317
|
+
for (const choice of choicesCheck.Choices) {
|
|
1318
|
+
const row = await line.Choices.Create();
|
|
1319
|
+
row.GroupKey = choice.GroupKey;
|
|
1320
|
+
row.GroupLabel = choice.GroupLabel;
|
|
1321
|
+
row.OptionValue = choice.OptionValue;
|
|
1322
|
+
row.OptionLabel = choice.OptionLabel;
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1245
1325
|
// Price lines before confirmation
|
|
1246
1326
|
const pricingService = new OrderPricingService({
|
|
1247
1327
|
Provider: (order.ProviderToUse ?? md),
|
|
@@ -1290,10 +1370,16 @@ export class CheckoutSessionService {
|
|
|
1290
1370
|
Status: 'Open'
|
|
1291
1371
|
};
|
|
1292
1372
|
}
|
|
1373
|
+
// Payment has checked out, so from here a failure leaves a paid checkout without
|
|
1374
|
+
// an order. Record the attempt; Confirm rolls back on failure, the record does not.
|
|
1375
|
+
confirmStep = await CheckoutStepLog.Begin(sessionID, 'Confirm', 'Checkout', contextUser);
|
|
1293
1376
|
}
|
|
1294
1377
|
// Confirm order via BaseEntity lifecycle (executes GL booking, entitlement issuance, status latching)
|
|
1295
1378
|
await order.Confirm();
|
|
1296
1379
|
confirmedOrderID = order.ID;
|
|
1380
|
+
if (confirmStep) {
|
|
1381
|
+
await CheckoutStepLog.Succeed(confirmStep);
|
|
1382
|
+
}
|
|
1297
1383
|
// The order is now COMMITTED — from this point nothing may revert the session to
|
|
1298
1384
|
// Open, or a retry would book a second order for the same purchase. Session
|
|
1299
1385
|
// stamping failures are logged and repaired atomically, never surfaced as a
|
|
@@ -1307,7 +1393,7 @@ export class CheckoutSessionService {
|
|
|
1307
1393
|
}
|
|
1308
1394
|
// Book the PaymentHeader + allocation now that the order exists. retrieve-on-complete
|
|
1309
1395
|
// only stamps PaymentIntent.Status = Succeeded; AmountPaid stays 0 until CapturePayment.
|
|
1310
|
-
await this.applySettledPaymentToOrder(session, order, contextUser);
|
|
1396
|
+
await this.applySettledPaymentToOrder(session, order, contextUser, { Source: 'Checkout' });
|
|
1311
1397
|
// Mint the GuestOrder identity claim for the buyer's email so a later account
|
|
1312
1398
|
// (MJ core's IdentityClaimEngineServer) can attach the order + its entitlement
|
|
1313
1399
|
// grants on redemption or claim-on-login. Best-effort: a claim failure must never
|
|
@@ -1354,6 +1440,9 @@ export class CheckoutSessionService {
|
|
|
1354
1440
|
Status: 'Confirmed'
|
|
1355
1441
|
};
|
|
1356
1442
|
}
|
|
1443
|
+
if (confirmStep) {
|
|
1444
|
+
await CheckoutStepLog.Fail(confirmStep, msg);
|
|
1445
|
+
}
|
|
1357
1446
|
await CheckoutSessionService.revertSessionOpenAtomic(sessionID, md, contextUser);
|
|
1358
1447
|
session.Status = 'Open';
|
|
1359
1448
|
LogError(`[CheckoutSessionService] CompleteCheckout failed for session ${sessionID}: ${msg}`);
|
|
@@ -1432,7 +1521,27 @@ export class CheckoutSessionService {
|
|
|
1432
1521
|
if (!(await order.Load(session.DraftOrderID))) {
|
|
1433
1522
|
return { Attempted: false, Booked: false };
|
|
1434
1523
|
}
|
|
1435
|
-
return this.applySettledPaymentToOrder(session, order, contextUser);
|
|
1524
|
+
return this.applySettledPaymentToOrder(session, order, contextUser, { Source: 'Webhook' });
|
|
1525
|
+
}
|
|
1526
|
+
/**
|
|
1527
|
+
* Operator replay of the Capture step (`Orders.ReplayCheckoutStep`, #326). The same idempotent
|
|
1528
|
+
* CapturePayment as the complete call and the webhook, recorded as a Replay by the operator.
|
|
1529
|
+
* Returns a refusal message instead when the session has no confirmed order to capture against.
|
|
1530
|
+
*/
|
|
1531
|
+
static async ReplayCapture(sessionID, contextUser, replayedByUserID) {
|
|
1532
|
+
const md = new Metadata();
|
|
1533
|
+
const session = await md.GetEntityObject(CHECKOUT_SESSION_ENTITY, contextUser);
|
|
1534
|
+
if (!(await session.Load(sessionID))) {
|
|
1535
|
+
return `Checkout session ${sessionID} not found.`;
|
|
1536
|
+
}
|
|
1537
|
+
if (session.Status !== 'Confirmed' || !session.DraftOrderID) {
|
|
1538
|
+
return `Checkout session ${sessionID} has no confirmed order (status ${session.Status}); there is nothing to capture against.`;
|
|
1539
|
+
}
|
|
1540
|
+
const order = await md.GetEntityObject(ORDER_HEADER_ENTITY, contextUser);
|
|
1541
|
+
if (!(await order.Load(session.DraftOrderID))) {
|
|
1542
|
+
return `The order on checkout session ${sessionID} could not be read.`;
|
|
1543
|
+
}
|
|
1544
|
+
return this.applySettledPaymentToOrder(session, order, contextUser, { Source: 'Replay', ReplayedByUserID: replayedByUserID });
|
|
1436
1545
|
}
|
|
1437
1546
|
/**
|
|
1438
1547
|
* After the order is committed, book `Orders.CapturePayment` against the session's
|
|
@@ -1442,7 +1551,7 @@ export class CheckoutSessionService {
|
|
|
1442
1551
|
* Retries are CompleteCheckout replay and `BookSettledCheckoutPaymentIfNeeded`
|
|
1443
1552
|
* from the payment_intent.succeeded webhook (including AlreadyApplied).
|
|
1444
1553
|
*/
|
|
1445
|
-
static async applySettledPaymentToOrder(session, order, contextUser) {
|
|
1554
|
+
static async applySettledPaymentToOrder(session, order, contextUser, trigger) {
|
|
1446
1555
|
if (!session.PaymentIntentID) {
|
|
1447
1556
|
return { Attempted: false, Booked: false };
|
|
1448
1557
|
}
|
|
@@ -1451,27 +1560,36 @@ export class CheckoutSessionService {
|
|
|
1451
1560
|
return { Attempted: false, Booked: false };
|
|
1452
1561
|
}
|
|
1453
1562
|
if (Number(order.AmountPaid ?? 0) + 0.005 >= due) {
|
|
1563
|
+
// Paid, possibly by another path after an earlier capture attempt failed. A record
|
|
1564
|
+
// left Failed or Running would keep the session in the review queue.
|
|
1565
|
+
await CheckoutStepLog.CloseIfOpen(session.ID, 'Capture', contextUser);
|
|
1454
1566
|
return { Attempted: false, Booked: true };
|
|
1455
1567
|
}
|
|
1456
1568
|
if (!contextUser) {
|
|
1457
|
-
return this.captureFailed(order.ID, 'no context user', undefined, undefined, session.ID);
|
|
1569
|
+
return this.captureFailed(order.ID, 'no context user', undefined, undefined, session.ID, null);
|
|
1458
1570
|
}
|
|
1571
|
+
const step = await CheckoutStepLog.Begin(session.ID, 'Capture', trigger.Source, contextUser, trigger.ReplayedByUserID);
|
|
1459
1572
|
if (!order.BillToPersonID && !order.BillToOrganizationID) {
|
|
1460
|
-
return this.captureFailed(order.ID, 'no bill-to party', undefined, contextUser, session.ID);
|
|
1573
|
+
return this.captureFailed(order.ID, 'no bill-to party', undefined, contextUser, session.ID, step);
|
|
1461
1574
|
}
|
|
1462
1575
|
const md = new Metadata();
|
|
1463
1576
|
const intent = await md.GetEntityObject(PAYMENT_INTENT_ENTITY, contextUser);
|
|
1464
1577
|
if (!(await intent.Load(session.PaymentIntentID))) {
|
|
1465
|
-
return this.captureFailed(order.ID, 'payment intent not found', undefined, contextUser, session.ID);
|
|
1578
|
+
return this.captureFailed(order.ID, 'payment intent not found', undefined, contextUser, session.ID, step);
|
|
1466
1579
|
}
|
|
1467
1580
|
if (!SETTLED_INTENT_STATUSES.includes(intent.Status)) {
|
|
1468
1581
|
await this.refreshIntentFromGateway(intent, contextUser);
|
|
1469
1582
|
}
|
|
1470
1583
|
if (!SETTLED_INTENT_STATUSES.includes(intent.Status)) {
|
|
1471
|
-
return this.captureFailed(order.ID, `intent status is ${intent.Status}`, undefined, contextUser, session.ID);
|
|
1584
|
+
return this.captureFailed(order.ID, `intent status is ${intent.Status}`, undefined, contextUser, session.ID, step);
|
|
1472
1585
|
}
|
|
1473
1586
|
if (!intent.OrderHeaderID) {
|
|
1474
1587
|
intent.OrderHeaderID = order.ID;
|
|
1588
|
+
// Second step of the gateway description: the intent was opened before the order existed.
|
|
1589
|
+
const mdForGateway = Metadata.Provider;
|
|
1590
|
+
if (mdForGateway) {
|
|
1591
|
+
await SendOrderDescriptionToGateway(intent, order.ID, mdForGateway, contextUser);
|
|
1592
|
+
}
|
|
1475
1593
|
}
|
|
1476
1594
|
if (!intent.BillToPersonID && order.BillToPersonID) {
|
|
1477
1595
|
intent.BillToPersonID = order.BillToPersonID;
|
|
@@ -1497,7 +1615,7 @@ export class CheckoutSessionService {
|
|
|
1497
1615
|
}
|
|
1498
1616
|
const mdProvider = Metadata.Provider;
|
|
1499
1617
|
if (!mdProvider) {
|
|
1500
|
-
return this.captureFailed(order.ID, 'no metadata provider', undefined, contextUser, session.ID);
|
|
1618
|
+
return this.captureFailed(order.ID, 'no metadata provider', undefined, contextUser, session.ID, step);
|
|
1501
1619
|
}
|
|
1502
1620
|
try {
|
|
1503
1621
|
const op = new CapturePaymentOperation();
|
|
@@ -1525,21 +1643,27 @@ export class CheckoutSessionService {
|
|
|
1525
1643
|
?? output?.Blockers?.map((b) => b.Message).join('; ')
|
|
1526
1644
|
?? 'unknown error';
|
|
1527
1645
|
const codes = (output?.Blockers ?? []).map((b) => b.Code).filter((c) => Boolean(c));
|
|
1528
|
-
return this.captureFailed(order.ID, detail, isCaptureRefusalRetryable(codes), contextUser, session.ID);
|
|
1646
|
+
return this.captureFailed(order.ID, detail, isCaptureRefusalRetryable(codes), contextUser, session.ID, step);
|
|
1529
1647
|
}
|
|
1648
|
+
await CheckoutStepLog.Succeed(step);
|
|
1530
1649
|
return { Attempted: true, Booked: true };
|
|
1531
1650
|
}
|
|
1532
1651
|
catch (err) {
|
|
1533
1652
|
const msg = err instanceof Error ? err.message : String(err);
|
|
1534
|
-
return this.captureFailed(order.ID, msg, true, contextUser, session.ID);
|
|
1653
|
+
return this.captureFailed(order.ID, msg, true, contextUser, session.ID, step);
|
|
1535
1654
|
}
|
|
1536
1655
|
}
|
|
1537
|
-
static captureFailed(orderID, message, retryable, contextUser, sessionID) {
|
|
1656
|
+
static async captureFailed(orderID, message, retryable, contextUser, sessionID, step) {
|
|
1538
1657
|
const Retryable = retryable ?? !isTerminalCapturePrecheck(message);
|
|
1539
1658
|
LogError(`[CheckoutSessionService] CapturePayment failed for order ${orderID}: ${message}`);
|
|
1540
|
-
|
|
1659
|
+
// One Task per terminal failure, not one per replay of it: the step record says whether
|
|
1660
|
+
// the last attempt already ended the same way.
|
|
1661
|
+
if (!Retryable && (!step || CheckoutStepLog.IsNewTerminalFailure(step))) {
|
|
1541
1662
|
void raiseCheckoutCaptureTerminalAlert(orderID, sessionID, message, contextUser);
|
|
1542
1663
|
}
|
|
1664
|
+
if (step) {
|
|
1665
|
+
await CheckoutStepLog.Fail(step, message, Retryable);
|
|
1666
|
+
}
|
|
1543
1667
|
return { Attempted: true, Booked: false, ErrorMessage: message, Retryable };
|
|
1544
1668
|
}
|
|
1545
1669
|
/**
|