@mj-biz-apps/orders-core-entities-server 0.0.1 → 5.2.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/LICENSE +183 -0
- package/README.md +141 -2
- package/dist/AccountingBridge.d.ts +65 -0
- package/dist/AccountingBridge.d.ts.map +1 -0
- package/dist/AccountingBridge.js +102 -0
- package/dist/AccountingBridge.js.map +1 -0
- package/dist/AdvanceOrderStateOperation.d.ts +80 -0
- package/dist/AdvanceOrderStateOperation.d.ts.map +1 -0
- package/dist/AdvanceOrderStateOperation.js +267 -0
- package/dist/AdvanceOrderStateOperation.js.map +1 -0
- package/dist/ApplyAccountCreditOperation.d.ts +97 -0
- package/dist/ApplyAccountCreditOperation.d.ts.map +1 -0
- package/dist/ApplyAccountCreditOperation.js +275 -0
- package/dist/ApplyAccountCreditOperation.js.map +1 -0
- package/dist/BaseDeliveryChannel.d.ts +106 -0
- package/dist/BaseDeliveryChannel.d.ts.map +1 -0
- package/dist/BaseDeliveryChannel.js +36 -0
- package/dist/BaseDeliveryChannel.js.map +1 -0
- package/dist/BasePaymentProvider.d.ts +239 -0
- package/dist/BasePaymentProvider.d.ts.map +1 -0
- package/dist/BasePaymentProvider.js +91 -0
- package/dist/BasePaymentProvider.js.map +1 -0
- package/dist/BundleBehavior.d.ts +139 -0
- package/dist/BundleBehavior.d.ts.map +1 -0
- package/dist/BundleBehavior.js +173 -0
- package/dist/BundleBehavior.js.map +1 -0
- package/dist/BundleEngine.d.ts +60 -0
- package/dist/BundleEngine.d.ts.map +1 -0
- package/dist/BundleEngine.js +197 -0
- package/dist/BundleEngine.js.map +1 -0
- package/dist/CancelSubscriptionOperation.d.ts +98 -0
- package/dist/CancelSubscriptionOperation.d.ts.map +1 -0
- package/dist/CancelSubscriptionOperation.js +317 -0
- package/dist/CancelSubscriptionOperation.js.map +1 -0
- package/dist/CapturePaymentOperation.d.ts +62 -0
- package/dist/CapturePaymentOperation.d.ts.map +1 -0
- package/dist/CapturePaymentOperation.js +463 -0
- package/dist/CapturePaymentOperation.js.map +1 -0
- package/dist/CheckEntitlementOperation.d.ts +27 -0
- package/dist/CheckEntitlementOperation.d.ts.map +1 -0
- package/dist/CheckEntitlementOperation.js +45 -0
- package/dist/CheckEntitlementOperation.js.map +1 -0
- package/dist/CheckoutSessionService.d.ts +258 -0
- package/dist/CheckoutSessionService.d.ts.map +1 -0
- package/dist/CheckoutSessionService.js +1557 -0
- package/dist/CheckoutSessionService.js.map +1 -0
- package/dist/DeliveryBehavior.d.ts +121 -0
- package/dist/DeliveryBehavior.d.ts.map +1 -0
- package/dist/DeliveryBehavior.js +145 -0
- package/dist/DeliveryBehavior.js.map +1 -0
- package/dist/DeliveryRecipientResolver.d.ts +38 -0
- package/dist/DeliveryRecipientResolver.d.ts.map +1 -0
- package/dist/DeliveryRecipientResolver.js +93 -0
- package/dist/DeliveryRecipientResolver.js.map +1 -0
- package/dist/DeliveryResolver.d.ts +14 -0
- package/dist/DeliveryResolver.d.ts.map +1 -0
- package/dist/DeliveryResolver.js +45 -0
- package/dist/DeliveryResolver.js.map +1 -0
- package/dist/EmailDeliveryChannel.d.ts +31 -0
- package/dist/EmailDeliveryChannel.d.ts.map +1 -0
- package/dist/EmailDeliveryChannel.js +158 -0
- package/dist/EmailDeliveryChannel.js.map +1 -0
- package/dist/EntitlementBehavior.d.ts +235 -0
- package/dist/EntitlementBehavior.d.ts.map +1 -0
- package/dist/EntitlementBehavior.js +330 -0
- package/dist/EntitlementBehavior.js.map +1 -0
- package/dist/EntitlementEngine.d.ts +97 -0
- package/dist/EntitlementEngine.d.ts.map +1 -0
- package/dist/EntitlementEngine.js +338 -0
- package/dist/EntitlementEngine.js.map +1 -0
- package/dist/EntitlementGrantClaimDriver.d.ts +42 -0
- package/dist/EntitlementGrantClaimDriver.d.ts.map +1 -0
- package/dist/EntitlementGrantClaimDriver.js +158 -0
- package/dist/EntitlementGrantClaimDriver.js.map +1 -0
- package/dist/EntitlementRead.d.ts +82 -0
- package/dist/EntitlementRead.d.ts.map +1 -0
- package/dist/EntitlementRead.js +368 -0
- package/dist/EntitlementRead.js.map +1 -0
- package/dist/FulfillOrderLinesOperation.d.ts +34 -0
- package/dist/FulfillOrderLinesOperation.d.ts.map +1 -0
- package/dist/FulfillOrderLinesOperation.js +208 -0
- package/dist/FulfillOrderLinesOperation.js.map +1 -0
- package/dist/FulfillmentBehavior.d.ts +101 -0
- package/dist/FulfillmentBehavior.d.ts.map +1 -0
- package/dist/FulfillmentBehavior.js +145 -0
- package/dist/FulfillmentBehavior.js.map +1 -0
- package/dist/GLAccountResolver.d.ts +123 -0
- package/dist/GLAccountResolver.d.ts.map +1 -0
- package/dist/GLAccountResolver.js +175 -0
- package/dist/GLAccountResolver.js.map +1 -0
- package/dist/GetFulfillmentQueueOperation.d.ts +35 -0
- package/dist/GetFulfillmentQueueOperation.d.ts.map +1 -0
- package/dist/GetFulfillmentQueueOperation.js +221 -0
- package/dist/GetFulfillmentQueueOperation.js.map +1 -0
- package/dist/GetOverdueWorklistOperation.d.ts +41 -0
- package/dist/GetOverdueWorklistOperation.d.ts.map +1 -0
- package/dist/GetOverdueWorklistOperation.js +210 -0
- package/dist/GetOverdueWorklistOperation.js.map +1 -0
- package/dist/GiftCardBehavior.d.ts +97 -0
- package/dist/GiftCardBehavior.d.ts.map +1 -0
- package/dist/GiftCardBehavior.js +121 -0
- package/dist/GiftCardBehavior.js.map +1 -0
- package/dist/GiftCardEngine.d.ts +59 -0
- package/dist/GiftCardEngine.d.ts.map +1 -0
- package/dist/GiftCardEngine.js +197 -0
- package/dist/GiftCardEngine.js.map +1 -0
- package/dist/GuestOrderClaimDriver.d.ts +36 -0
- package/dist/GuestOrderClaimDriver.d.ts.map +1 -0
- package/dist/GuestOrderClaimDriver.js +161 -0
- package/dist/GuestOrderClaimDriver.js.map +1 -0
- package/dist/InvoiceBehavior.d.ts +394 -0
- package/dist/InvoiceBehavior.d.ts.map +1 -0
- package/dist/InvoiceBehavior.js +496 -0
- package/dist/InvoiceBehavior.js.map +1 -0
- package/dist/InvoiceBuilder.d.ts +48 -0
- package/dist/InvoiceBuilder.d.ts.map +1 -0
- package/dist/InvoiceBuilder.js +352 -0
- package/dist/InvoiceBuilder.js.map +1 -0
- package/dist/InvoiceDisplay.d.ts +96 -0
- package/dist/InvoiceDisplay.d.ts.map +1 -0
- package/dist/InvoiceDisplay.js +121 -0
- package/dist/InvoiceDisplay.js.map +1 -0
- package/dist/ListEntitlementsOperation.d.ts +21 -0
- package/dist/ListEntitlementsOperation.d.ts.map +1 -0
- package/dist/ListEntitlementsOperation.js +39 -0
- package/dist/ListEntitlementsOperation.js.map +1 -0
- package/dist/ManualPaymentProvider.d.ts +19 -0
- package/dist/ManualPaymentProvider.d.ts.map +1 -0
- package/dist/ManualPaymentProvider.js +92 -0
- package/dist/ManualPaymentProvider.js.map +1 -0
- package/dist/OrderEntityServer.d.ts +542 -0
- package/dist/OrderEntityServer.d.ts.map +1 -0
- package/dist/OrderEntityServer.js +2100 -0
- package/dist/OrderEntityServer.js.map +1 -0
- package/dist/OrderJournalEntryFactory.d.ts +140 -0
- package/dist/OrderJournalEntryFactory.d.ts.map +1 -0
- package/dist/OrderJournalEntryFactory.js +466 -0
- package/dist/OrderJournalEntryFactory.js.map +1 -0
- package/dist/OrderLineEntityServer.d.ts +90 -0
- package/dist/OrderLineEntityServer.d.ts.map +1 -0
- package/dist/OrderLineEntityServer.js +260 -0
- package/dist/OrderLineEntityServer.js.map +1 -0
- package/dist/OrdersSettings.d.ts +36 -0
- package/dist/OrdersSettings.d.ts.map +1 -0
- package/dist/OrdersSettings.js +147 -0
- package/dist/OrdersSettings.js.map +1 -0
- package/dist/PaymentAllocationFactory.d.ts +128 -0
- package/dist/PaymentAllocationFactory.d.ts.map +1 -0
- package/dist/PaymentAllocationFactory.js +235 -0
- package/dist/PaymentAllocationFactory.js.map +1 -0
- package/dist/PaymentHeaderEntityServer.d.ts +180 -0
- package/dist/PaymentHeaderEntityServer.d.ts.map +1 -0
- package/dist/PaymentHeaderEntityServer.js +656 -0
- package/dist/PaymentHeaderEntityServer.js.map +1 -0
- package/dist/PaymentIntentService.d.ts +101 -0
- package/dist/PaymentIntentService.d.ts.map +1 -0
- package/dist/PaymentIntentService.js +150 -0
- package/dist/PaymentIntentService.js.map +1 -0
- package/dist/PaymentJournalEntryFactory.d.ts +99 -0
- package/dist/PaymentJournalEntryFactory.d.ts.map +1 -0
- package/dist/PaymentJournalEntryFactory.js +127 -0
- package/dist/PaymentJournalEntryFactory.js.map +1 -0
- package/dist/PaymentLineEntityServer.d.ts +73 -0
- package/dist/PaymentLineEntityServer.d.ts.map +1 -0
- package/dist/PaymentLineEntityServer.js +303 -0
- package/dist/PaymentLineEntityServer.js.map +1 -0
- package/dist/PaymentProviderBehavior.d.ts +222 -0
- package/dist/PaymentProviderBehavior.d.ts.map +1 -0
- package/dist/PaymentProviderBehavior.js +368 -0
- package/dist/PaymentProviderBehavior.js.map +1 -0
- package/dist/PaymentProviderResolver.d.ts +84 -0
- package/dist/PaymentProviderResolver.d.ts.map +1 -0
- package/dist/PaymentProviderResolver.js +220 -0
- package/dist/PaymentProviderResolver.js.map +1 -0
- package/dist/PaymentReversalFactory.d.ts +107 -0
- package/dist/PaymentReversalFactory.d.ts.map +1 -0
- package/dist/PaymentReversalFactory.js +174 -0
- package/dist/PaymentReversalFactory.js.map +1 -0
- package/dist/PaymentSettlement.d.ts +56 -0
- package/dist/PaymentSettlement.d.ts.map +1 -0
- package/dist/PaymentSettlement.js +243 -0
- package/dist/PaymentSettlement.js.map +1 -0
- package/dist/PaymentTermsBehavior.d.ts +109 -0
- package/dist/PaymentTermsBehavior.d.ts.map +1 -0
- package/dist/PaymentTermsBehavior.js +172 -0
- package/dist/PaymentTermsBehavior.js.map +1 -0
- package/dist/PaymentWebhookHandler.d.ts +103 -0
- package/dist/PaymentWebhookHandler.d.ts.map +1 -0
- package/dist/PaymentWebhookHandler.js +246 -0
- package/dist/PaymentWebhookHandler.js.map +1 -0
- package/dist/PreviewPriceOperation.d.ts +62 -0
- package/dist/PreviewPriceOperation.d.ts.map +1 -0
- package/dist/PreviewPriceOperation.js +161 -0
- package/dist/PreviewPriceOperation.js.map +1 -0
- package/dist/PriceOrderOperation.d.ts +93 -0
- package/dist/PriceOrderOperation.d.ts.map +1 -0
- package/dist/PriceOrderOperation.js +146 -0
- package/dist/PriceOrderOperation.js.map +1 -0
- package/dist/ProductPriceEntityServer.d.ts +34 -0
- package/dist/ProductPriceEntityServer.d.ts.map +1 -0
- package/dist/ProductPriceEntityServer.js +97 -0
- package/dist/ProductPriceEntityServer.js.map +1 -0
- package/dist/RefundPaymentOperation.d.ts +61 -0
- package/dist/RefundPaymentOperation.d.ts.map +1 -0
- package/dist/RefundPaymentOperation.js +177 -0
- package/dist/RefundPaymentOperation.js.map +1 -0
- package/dist/RevenueRecognition.d.ts +76 -0
- package/dist/RevenueRecognition.d.ts.map +1 -0
- package/dist/RevenueRecognition.js +133 -0
- package/dist/RevenueRecognition.js.map +1 -0
- package/dist/ReversalBehavior.d.ts +82 -0
- package/dist/ReversalBehavior.d.ts.map +1 -0
- package/dist/ReversalBehavior.js +100 -0
- package/dist/ReversalBehavior.js.map +1 -0
- package/dist/ReversalResolver.d.ts +37 -0
- package/dist/ReversalResolver.d.ts.map +1 -0
- package/dist/ReversalResolver.js +96 -0
- package/dist/ReversalResolver.js.map +1 -0
- package/dist/SpawnRenewalsOperation.d.ts +109 -0
- package/dist/SpawnRenewalsOperation.d.ts.map +1 -0
- package/dist/SpawnRenewalsOperation.js +295 -0
- package/dist/SpawnRenewalsOperation.js.map +1 -0
- package/dist/StoredValuePaymentProvider.d.ts +52 -0
- package/dist/StoredValuePaymentProvider.d.ts.map +1 -0
- package/dist/StoredValuePaymentProvider.js +205 -0
- package/dist/StoredValuePaymentProvider.js.map +1 -0
- package/dist/StripeACHPaymentProvider.d.ts +50 -0
- package/dist/StripeACHPaymentProvider.d.ts.map +1 -0
- package/dist/StripeACHPaymentProvider.js +211 -0
- package/dist/StripeACHPaymentProvider.js.map +1 -0
- package/dist/StripePaymentProvider.d.ts +83 -0
- package/dist/StripePaymentProvider.d.ts.map +1 -0
- package/dist/StripePaymentProvider.js +442 -0
- package/dist/StripePaymentProvider.js.map +1 -0
- package/dist/SubscriptionBehavior.d.ts +197 -0
- package/dist/SubscriptionBehavior.d.ts.map +1 -0
- package/dist/SubscriptionBehavior.js +415 -0
- package/dist/SubscriptionBehavior.js.map +1 -0
- package/dist/checkoutCaptureAlert.d.ts +16 -0
- package/dist/checkoutCaptureAlert.d.ts.map +1 -0
- package/dist/checkoutCaptureAlert.js +55 -0
- package/dist/checkoutCaptureAlert.js.map +1 -0
- package/dist/checkoutCaptureRetry.d.ts +27 -0
- package/dist/checkoutCaptureRetry.d.ts.map +1 -0
- package/dist/checkoutCaptureRetry.js +50 -0
- package/dist/checkoutCaptureRetry.js.map +1 -0
- package/dist/claimDriverHelpers.d.ts +15 -0
- package/dist/claimDriverHelpers.d.ts.map +1 -0
- package/dist/claimDriverHelpers.js +34 -0
- package/dist/claimDriverHelpers.js.map +1 -0
- package/dist/entity-names.d.ts +15 -0
- package/dist/entity-names.d.ts.map +1 -0
- package/dist/entity-names.js +15 -0
- package/dist/entity-names.js.map +1 -0
- package/dist/identityClaimContracts.d.ts +118 -0
- package/dist/identityClaimContracts.d.ts.map +1 -0
- package/dist/identityClaimContracts.js +58 -0
- package/dist/identityClaimContracts.js.map +1 -0
- package/dist/index.d.ts +126 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +115 -0
- package/dist/index.js.map +1 -0
- package/dist/sql-guards.d.ts +78 -0
- package/dist/sql-guards.d.ts.map +1 -0
- package/dist/sql-guards.js +115 -0
- package/dist/sql-guards.js.map +1 -0
- package/package.json +50 -5
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gift-card issuance decisions, with no database in sight.
|
|
3
|
+
*
|
|
4
|
+
* WHAT SELLING A GIFT CARD ACTUALLY IS. It is not a sale. Nothing has been delivered and no revenue
|
|
5
|
+
* has been earned — the company has taken money and now OWES goods. So the line books a LIABILITY,
|
|
6
|
+
* and revenue appears later, on whatever order the card is eventually spent on. Getting this wrong
|
|
7
|
+
* is the classic gift-card error: recognise at issue and you book revenue twice, once when the card
|
|
8
|
+
* is sold and again when it is redeemed.
|
|
9
|
+
*
|
|
10
|
+
* THE ONE THAT LOOKS RIGHT AND IS NOT. Face value is `UnitPrice`, deliberately NOT the discounted
|
|
11
|
+
* line amount. Sell a $50 card in a 10%-off promotion and the customer pays $45 — but the card is
|
|
12
|
+
* still worth $50 and the company still owes $50 of goods. Deriving the liability from what was
|
|
13
|
+
* PAID understates it by exactly the discount, and every downstream number still reconciles, because
|
|
14
|
+
* the order, the payment and the journal entry all agree with each other. Only the balance sheet is
|
|
15
|
+
* wrong, and only until somebody redeems.
|
|
16
|
+
*
|
|
17
|
+
* ONE CARD PER UNIT. Quantity 3 at $50 is three $50 cards, not one $150 card. That is how a person
|
|
18
|
+
* buying three gift cards expects it to work, and it is why quantity must be a whole number here —
|
|
19
|
+
* half a gift card is not a thing, and silently flooring it would hand the customer less than they
|
|
20
|
+
* bought.
|
|
21
|
+
*
|
|
22
|
+
* CONNECTS TO:
|
|
23
|
+
* CODE: GiftCardEngine (the rows) · OrderEntityServer.issueGiftCards (the lifecycle point)
|
|
24
|
+
* DOC: plans/archive/bizapps-orders-master.md D4 (GiftCard product type), D27 (beneficiary), D44
|
|
25
|
+
*/
|
|
26
|
+
/** The ProductType.Code that marks a product as a gift card. Seeded per D4. */
|
|
27
|
+
export const GIFT_CARD_PRODUCT_TYPE_CODE = 'GiftCard';
|
|
28
|
+
const EMPTY = (reason) => ({ Cards: [], Refusal: reason });
|
|
29
|
+
/** Round to cents the way the rest of the engine does. */
|
|
30
|
+
const Money = (n) => Math.round((n + Number.EPSILON) * 100) / 100;
|
|
31
|
+
/** Is this line for a gift-card product at all? */
|
|
32
|
+
export function IsGiftCardLine(line) {
|
|
33
|
+
return (line.ProductTypeCode ?? '').trim().toLowerCase() === GIFT_CARD_PRODUCT_TYPE_CODE.toLowerCase();
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Decide what a line issues.
|
|
37
|
+
*
|
|
38
|
+
* Returns an empty plan WITH a reason rather than throwing, because most lines are not gift cards
|
|
39
|
+
* and that is not an error. The caller distinguishes "nothing to do" from "something was wrong" by
|
|
40
|
+
* looking at `Refusal`, which is why the two cases are never both empty and silent.
|
|
41
|
+
*/
|
|
42
|
+
export function PlanGiftCardIssuance(line, order) {
|
|
43
|
+
if (!IsGiftCardLine(line))
|
|
44
|
+
return EMPTY('NotAGiftCardProduct');
|
|
45
|
+
// A reversal line carries a negative quantity and points at the line it undoes. It must not mint
|
|
46
|
+
// more cards — the return path voids the origin's cards instead (see PlanGiftCardVoid).
|
|
47
|
+
if (line.ReversesOrderLineID)
|
|
48
|
+
return EMPTY('ReversalLine');
|
|
49
|
+
const qty = Number(line.Quantity ?? 0);
|
|
50
|
+
if (!(qty > 0))
|
|
51
|
+
return EMPTY('NonPositiveQuantity');
|
|
52
|
+
if (!Number.isInteger(qty))
|
|
53
|
+
return EMPTY('FractionalQuantity');
|
|
54
|
+
const faceValue = Money(Number(line.UnitPrice ?? 0));
|
|
55
|
+
if (!(faceValue > 0))
|
|
56
|
+
return EMPTY('NonPositiveFaceValue');
|
|
57
|
+
// WHO THE CARD IS FOR (D27). A gift card is the archetypal case of a line bought by one person
|
|
58
|
+
// for another, so the line's ship-to wins when stated. The order's bill-to is the fallback for
|
|
59
|
+
// the ordinary case of buying a card for yourself — NOT a default that overrides an explicit
|
|
60
|
+
// recipient, which is why each side falls back independently.
|
|
61
|
+
const beneficiaryPersonID = line.ShipToPersonID ?? order.BillToPersonID ?? null;
|
|
62
|
+
const beneficiaryOrganizationID = line.ShipToOrganizationID ?? order.BillToOrganizationID ?? null;
|
|
63
|
+
const cards = [];
|
|
64
|
+
for (let i = 1; i <= qty; i++) {
|
|
65
|
+
cards.push({
|
|
66
|
+
OrderLineID: line.ID,
|
|
67
|
+
Sequence: i,
|
|
68
|
+
FaceValue: faceValue,
|
|
69
|
+
BeneficiaryPersonID: beneficiaryPersonID,
|
|
70
|
+
BeneficiaryOrganizationID: beneficiaryOrganizationID,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
return { Cards: cards, Refusal: null };
|
|
74
|
+
}
|
|
75
|
+
/** How many of a line's cards a return of `reversalQuantity` units should void. */
|
|
76
|
+
export function PlanGiftCardVoid(reversalQuantity, issuedCount) {
|
|
77
|
+
// The reversal quantity is negative by convention (D10); its magnitude is what was sent back.
|
|
78
|
+
const returned = Math.abs(Number(reversalQuantity ?? 0));
|
|
79
|
+
if (!(returned > 0) || !(issuedCount > 0))
|
|
80
|
+
return 0;
|
|
81
|
+
// Never void more than were issued. A partial return of a three-card line voids that many, and
|
|
82
|
+
// returning the whole line voids the lot.
|
|
83
|
+
return Math.min(Math.floor(returned), issuedCount);
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* A card code: `GC-` + 16 unambiguous characters in groups of four.
|
|
87
|
+
*
|
|
88
|
+
* The alphabet omits I, L, O, 0, 1, U — the pairs people mistype when reading a card aloud or off a
|
|
89
|
+
* printed insert, and U because it turns short random runs into words nobody wants printed on a gift
|
|
90
|
+
* card. 30 symbols over 16 positions is ~78 bits, so collisions are not a practical concern; the
|
|
91
|
+
* UNIQUE constraint on `StoredValueAccount.Code` is the actual guarantee and this only has to make
|
|
92
|
+
* hitting it vanishingly unlikely.
|
|
93
|
+
*
|
|
94
|
+
* `randomChar` is injected so tests can make this deterministic without stubbing global crypto.
|
|
95
|
+
*/
|
|
96
|
+
export const GIFT_CARD_ALPHABET = '23456789ABCDEFGHJKMNPQRSTVWXYZ';
|
|
97
|
+
export function FormatGiftCardCode(randomChar, groups = 4, groupSize = 4) {
|
|
98
|
+
const parts = [];
|
|
99
|
+
for (let g = 0; g < groups; g++) {
|
|
100
|
+
let part = '';
|
|
101
|
+
for (let i = 0; i < groupSize; i++)
|
|
102
|
+
part += randomChar();
|
|
103
|
+
parts.push(part);
|
|
104
|
+
}
|
|
105
|
+
return `GC-${parts.join('-')}`;
|
|
106
|
+
}
|
|
107
|
+
/** True if a string looks like a code this module would have produced. */
|
|
108
|
+
export function IsGiftCardCode(code) {
|
|
109
|
+
return /^GC-([23456789ABCDEFGHJKMNPQRSTVWXYZ]{4}-){3}[23456789ABCDEFGHJKMNPQRSTVWXYZ]{4}$/.test(code);
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* The total liability a plan creates.
|
|
113
|
+
*
|
|
114
|
+
* Deliberately separate from the line's own money. `LineTotalNet` is what the CUSTOMER PAID, after
|
|
115
|
+
* discounts and promotions; this is what the COMPANY OWES. On an undiscounted line they are equal,
|
|
116
|
+
* which is exactly why a bug here would go unnoticed until the first discounted card.
|
|
117
|
+
*/
|
|
118
|
+
export function GiftCardLiability(plan) {
|
|
119
|
+
return Money(plan.Cards.reduce((sum, c) => sum + c.FaceValue, 0));
|
|
120
|
+
}
|
|
121
|
+
//# sourceMappingURL=GiftCardBehavior.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GiftCardBehavior.js","sourceRoot":"","sources":["../src/GiftCardBehavior.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turn gift-card lines into StoredValueAccount rows and their opening ledger entries.
|
|
3
|
+
*
|
|
4
|
+
* The DECISIONS live in `GiftCardBehavior` and are unit-tested without a database. What lives here
|
|
5
|
+
* is the part that needs one: finding which lines are gift cards, minting codes that survive the
|
|
6
|
+
* UNIQUE constraint, and writing the account plus its `Issue` transaction as one act.
|
|
7
|
+
*
|
|
8
|
+
* IDEMPOTENCY IS THE WHOLE GAME. `OrderEntityServer.Save` re-runs on every save of an already
|
|
9
|
+
* confirmed order, and issuing a second set of cards would hand the customer free money that
|
|
10
|
+
* reconciles perfectly — the accounts exist, the ledger balances, the order is unchanged. So
|
|
11
|
+
* issuance keys on `IssuedFromOrderLineID`: a line that has already issued issues nothing. That is
|
|
12
|
+
* checked against the DATABASE rather than a flag in memory, because the second save is a different
|
|
13
|
+
* object than the first.
|
|
14
|
+
*
|
|
15
|
+
* CONNECTS TO:
|
|
16
|
+
* PURE: GiftCardBehavior (+ its unit tests)
|
|
17
|
+
* CODE: OrderEntityServer.issueGiftCards (lifecycle) · StoredValuePaymentProvider (redemption)
|
|
18
|
+
* DOC: plans/archive/bizapps-orders-master.md D4, D27, D44
|
|
19
|
+
*/
|
|
20
|
+
import { EntitySaveOptions, IMetadataProvider, UserInfo } from '@memberjunction/core';
|
|
21
|
+
import { type GiftCardOrderFacts } from './GiftCardBehavior.js';
|
|
22
|
+
/** What the caller hands us about the order. */
|
|
23
|
+
export interface GiftCardOrder extends GiftCardOrderFacts {
|
|
24
|
+
ID: string;
|
|
25
|
+
IssuingCompanyID: string;
|
|
26
|
+
}
|
|
27
|
+
/** What the caller hands us about each line. `ProductTypeCode` is resolved here, not by the caller. */
|
|
28
|
+
export interface GiftCardOrderLine {
|
|
29
|
+
ID: string;
|
|
30
|
+
ProductID: string;
|
|
31
|
+
Quantity: number;
|
|
32
|
+
UnitPrice: number;
|
|
33
|
+
ReversesOrderLineID: string | null;
|
|
34
|
+
ShipToPersonID: string | null;
|
|
35
|
+
ShipToOrganizationID: string | null;
|
|
36
|
+
}
|
|
37
|
+
export interface GiftCardOutcome {
|
|
38
|
+
/** Accounts created by this call. Empty on a re-save, which is correct rather than a failure. */
|
|
39
|
+
Issued: Array<{
|
|
40
|
+
ID: string;
|
|
41
|
+
Code: string;
|
|
42
|
+
FaceValue: number;
|
|
43
|
+
OrderLineID: string;
|
|
44
|
+
}>;
|
|
45
|
+
/** Total face value minted. What the company now OWES — not what the customer paid. */
|
|
46
|
+
Liability: number;
|
|
47
|
+
/** Accounts voided because a reversal line sent cards back. */
|
|
48
|
+
Voided: string[];
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Issue every gift card this order's lines confer.
|
|
52
|
+
*
|
|
53
|
+
* Returns an outcome rather than throwing when there is nothing to do — most orders sell no gift
|
|
54
|
+
* cards, and that is not an error. A genuine failure (a save the database refused) does throw, so
|
|
55
|
+
* the enclosing confirm transaction rolls back: an order that took the money and failed to mint the
|
|
56
|
+
* card has sold nothing, and that must not be committed.
|
|
57
|
+
*/
|
|
58
|
+
export declare function IssueGiftCards(order: GiftCardOrder, lines: GiftCardOrderLine[], provider: IMetadataProvider, user: UserInfo, options?: EntitySaveOptions): Promise<GiftCardOutcome>;
|
|
59
|
+
//# sourceMappingURL=GiftCardEngine.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GiftCardEngine.d.ts","sourceRoot":"","sources":["../src/GiftCardEngine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,EAEH,iBAAiB,EACjB,iBAAiB,EAGjB,QAAQ,EACX,MAAM,sBAAsB,CAAC;AAK9B,OAAO,EAQH,KAAK,kBAAkB,EAC1B,MAAM,uBAAuB,CAAC;AAU/B,gDAAgD;AAChD,MAAM,WAAW,aAAc,SAAQ,kBAAkB;IACrD,EAAE,EAAE,MAAM,CAAC;IACX,gBAAgB,EAAE,MAAM,CAAC;CAC5B;AAED,uGAAuG;AACvG,MAAM,WAAW,iBAAiB;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,mBAAmB,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,oBAAoB,EAAE,MAAM,GAAG,IAAI,CAAC;CACvC;AAED,MAAM,WAAW,eAAe;IAC5B,iGAAiG;IACjG,MAAM,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACpF,uFAAuF;IACvF,SAAS,EAAE,MAAM,CAAC;IAClB,+DAA+D;IAC/D,MAAM,EAAE,MAAM,EAAE,CAAC;CACpB;AAgBD;;;;;;;GAOG;AACH,wBAAsB,cAAc,CAChC,KAAK,EAAE,aAAa,EACpB,KAAK,EAAE,iBAAiB,EAAE,EAC1B,QAAQ,EAAE,iBAAiB,EAC3B,IAAI,EAAE,QAAQ,EACd,OAAO,CAAC,EAAE,iBAAiB,GAC5B,OAAO,CAAC,eAAe,CAAC,CAkL1B"}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turn gift-card lines into StoredValueAccount rows and their opening ledger entries.
|
|
3
|
+
*
|
|
4
|
+
* The DECISIONS live in `GiftCardBehavior` and are unit-tested without a database. What lives here
|
|
5
|
+
* is the part that needs one: finding which lines are gift cards, minting codes that survive the
|
|
6
|
+
* UNIQUE constraint, and writing the account plus its `Issue` transaction as one act.
|
|
7
|
+
*
|
|
8
|
+
* IDEMPOTENCY IS THE WHOLE GAME. `OrderEntityServer.Save` re-runs on every save of an already
|
|
9
|
+
* confirmed order, and issuing a second set of cards would hand the customer free money that
|
|
10
|
+
* reconciles perfectly — the accounts exist, the ledger balances, the order is unchanged. So
|
|
11
|
+
* issuance keys on `IssuedFromOrderLineID`: a line that has already issued issues nothing. That is
|
|
12
|
+
* checked against the DATABASE rather than a flag in memory, because the second save is a different
|
|
13
|
+
* object than the first.
|
|
14
|
+
*
|
|
15
|
+
* CONNECTS TO:
|
|
16
|
+
* PURE: GiftCardBehavior (+ its unit tests)
|
|
17
|
+
* CODE: OrderEntityServer.issueGiftCards (lifecycle) · StoredValuePaymentProvider (redemption)
|
|
18
|
+
* DOC: plans/archive/bizapps-orders-master.md D4, D27, D44
|
|
19
|
+
*/
|
|
20
|
+
import { RunView, } from '@memberjunction/core';
|
|
21
|
+
import { FormatGiftCardCode, GIFT_CARD_ALPHABET, GiftCardLiability, IsGiftCardLine, PlanGiftCardIssuance, PlanGiftCardVoid, } from './GiftCardBehavior.js';
|
|
22
|
+
const STORED_VALUE_ACCOUNT_ENTITY = 'MJ_BizApps_Orders: Stored Value Accounts';
|
|
23
|
+
const STORED_VALUE_TRANSACTION_ENTITY = 'MJ_BizApps_Orders: Stored Value Transactions';
|
|
24
|
+
const PRODUCT_ENTITY = 'MJ_BizApps_Orders: Products';
|
|
25
|
+
const PRODUCT_TYPE_ENTITY = 'MJ_BizApps_Orders: Product Types';
|
|
26
|
+
const key = (id) => (id ?? '').toLowerCase();
|
|
27
|
+
const quote = (ids) => [...new Set(ids.map((i) => `'${i}'`))].join(',');
|
|
28
|
+
/** A crypto-quality character picker over the unambiguous alphabet. */
|
|
29
|
+
function randomChar() {
|
|
30
|
+
// Web Crypto, not node:crypto — the shared tsconfig sets "types": [], so the node typings are
|
|
31
|
+
// not visible here. Same reason PaymentProviderBehavior uses crypto.subtle.
|
|
32
|
+
const buf = new Uint8Array(1);
|
|
33
|
+
// Rejection-sample so the alphabet stays uniform: 256 is not a multiple of 30, and taking the
|
|
34
|
+
// modulo without rejecting the tail would make the first 16 characters slightly likelier.
|
|
35
|
+
const limit = 256 - (256 % GIFT_CARD_ALPHABET.length);
|
|
36
|
+
for (;;) {
|
|
37
|
+
crypto.getRandomValues(buf);
|
|
38
|
+
if (buf[0] < limit)
|
|
39
|
+
return GIFT_CARD_ALPHABET[buf[0] % GIFT_CARD_ALPHABET.length];
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Issue every gift card this order's lines confer.
|
|
44
|
+
*
|
|
45
|
+
* Returns an outcome rather than throwing when there is nothing to do — most orders sell no gift
|
|
46
|
+
* cards, and that is not an error. A genuine failure (a save the database refused) does throw, so
|
|
47
|
+
* the enclosing confirm transaction rolls back: an order that took the money and failed to mint the
|
|
48
|
+
* card has sold nothing, and that must not be committed.
|
|
49
|
+
*/
|
|
50
|
+
export async function IssueGiftCards(order, lines, provider, user, options) {
|
|
51
|
+
const out = { Issued: [], Liability: 0, Voided: [] };
|
|
52
|
+
if (!lines.length)
|
|
53
|
+
return out;
|
|
54
|
+
const rv = new RunView(provider);
|
|
55
|
+
// Which of these products are gift cards? One query for the product→type join rather than one
|
|
56
|
+
// per line: an order of sixty lines should not cost sixty round trips.
|
|
57
|
+
const productIDs = [...new Set(lines.map((l) => l.ProductID))];
|
|
58
|
+
const products = await rv.RunView({
|
|
59
|
+
EntityName: PRODUCT_ENTITY,
|
|
60
|
+
ExtraFilter: `ID IN (${quote(productIDs)})`,
|
|
61
|
+
ResultType: 'simple',
|
|
62
|
+
}, user);
|
|
63
|
+
const typeIDs = [...new Set((products.Results ?? []).map((p) => p.ProductTypeID))].filter(Boolean);
|
|
64
|
+
if (!typeIDs.length)
|
|
65
|
+
return out;
|
|
66
|
+
const types = await rv.RunView({
|
|
67
|
+
EntityName: PRODUCT_TYPE_ENTITY,
|
|
68
|
+
ExtraFilter: `ID IN (${quote(typeIDs)})`,
|
|
69
|
+
ResultType: 'simple',
|
|
70
|
+
}, user);
|
|
71
|
+
const codeByTypeID = new Map((types.Results ?? []).map((t) => [key(t.ID), t.Code]));
|
|
72
|
+
const typeCodeByProductID = new Map((products.Results ?? []).map((p) => [key(p.ID), codeByTypeID.get(key(p.ProductTypeID)) ?? null]));
|
|
73
|
+
const facts = (line) => ({
|
|
74
|
+
ID: line.ID,
|
|
75
|
+
ProductID: line.ProductID,
|
|
76
|
+
ProductTypeCode: typeCodeByProductID.get(key(line.ProductID)) ?? null,
|
|
77
|
+
Quantity: Number(line.Quantity ?? 0),
|
|
78
|
+
UnitPrice: Number(line.UnitPrice ?? 0),
|
|
79
|
+
ReversesOrderLineID: line.ReversesOrderLineID,
|
|
80
|
+
ShipToPersonID: line.ShipToPersonID,
|
|
81
|
+
ShipToOrganizationID: line.ShipToOrganizationID,
|
|
82
|
+
});
|
|
83
|
+
const giftCardLines = lines.filter((l) => IsGiftCardLine(facts(l)));
|
|
84
|
+
if (!giftCardLines.length)
|
|
85
|
+
return out;
|
|
86
|
+
// WHAT HAS ALREADY BEEN ISSUED. One query across every gift-card line on the order, so a re-save
|
|
87
|
+
// costs one round trip and mints nothing. Reversal lines are included because voiding needs to
|
|
88
|
+
// find the ORIGIN line's cards.
|
|
89
|
+
const relevantLineIDs = [
|
|
90
|
+
...giftCardLines.map((l) => l.ID),
|
|
91
|
+
...giftCardLines.map((l) => l.ReversesOrderLineID).filter((x) => !!x),
|
|
92
|
+
];
|
|
93
|
+
const existing = await rv.RunView({
|
|
94
|
+
EntityName: STORED_VALUE_ACCOUNT_ENTITY,
|
|
95
|
+
ExtraFilter: `IssuedFromOrderLineID IN (${quote(relevantLineIDs)})`,
|
|
96
|
+
ResultType: 'simple',
|
|
97
|
+
}, user);
|
|
98
|
+
const issuedByLine = new Map();
|
|
99
|
+
for (const row of existing.Results ?? []) {
|
|
100
|
+
const k = key(row.IssuedFromOrderLineID);
|
|
101
|
+
if (!issuedByLine.has(k))
|
|
102
|
+
issuedByLine.set(k, []);
|
|
103
|
+
issuedByLine.get(k).push({ ID: row.ID, Status: row.Status });
|
|
104
|
+
}
|
|
105
|
+
for (const line of giftCardLines) {
|
|
106
|
+
// ── A REVERSAL: void, never mint ──────────────────────────────────────────────────────────
|
|
107
|
+
if (line.ReversesOrderLineID) {
|
|
108
|
+
const originCards = (issuedByLine.get(key(line.ReversesOrderLineID)) ?? []).filter((c) => c.Status === 'Active');
|
|
109
|
+
const toVoid = PlanGiftCardVoid(Number(line.Quantity ?? 0), originCards.length);
|
|
110
|
+
for (let i = 0; i < toVoid; i++) {
|
|
111
|
+
const account = await provider.GetEntityObject(STORED_VALUE_ACCOUNT_ENTITY, user);
|
|
112
|
+
if (!(await account.Load(originCards[i].ID))) {
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
// Voided, not Depleted: the card was never spent, it was un-sold. Keeping the two
|
|
116
|
+
// apart is what lets breakage reporting tell a card the customer used from one the
|
|
117
|
+
// company took back.
|
|
118
|
+
account.Status = 'Voided';
|
|
119
|
+
const balanceBefore = Number(account.CurrentBalance ?? 0);
|
|
120
|
+
account.CurrentBalance = 0;
|
|
121
|
+
if (!(await account.Save(options))) {
|
|
122
|
+
throw new Error(`Could not void gift card ${originCards[i].ID} for returned line ${line.ID}: ` +
|
|
123
|
+
`${account.LatestResult?.CompleteMessage ?? 'no reason given'}`);
|
|
124
|
+
}
|
|
125
|
+
// The ledger has to show the money leaving, or the account's balance and its
|
|
126
|
+
// transaction history stop agreeing.
|
|
127
|
+
if (balanceBefore > 0) {
|
|
128
|
+
await writeTransaction(provider, user, originCards[i].ID, 'Refund', -balanceBefore, 0, order.ID, options);
|
|
129
|
+
}
|
|
130
|
+
out.Voided.push(originCards[i].ID);
|
|
131
|
+
}
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
// ── An ordinary gift-card line ────────────────────────────────────────────────────────────
|
|
135
|
+
// Already issued? Then this is a re-save and there is nothing to do. Checked against the
|
|
136
|
+
// database, not memory: the second save is a different object than the first.
|
|
137
|
+
if ((issuedByLine.get(key(line.ID)) ?? []).length > 0)
|
|
138
|
+
continue;
|
|
139
|
+
const plan = PlanGiftCardIssuance(facts(line), order);
|
|
140
|
+
if (!plan.Cards.length) {
|
|
141
|
+
// A gift-card product that issues nothing is worth failing over rather than shrugging
|
|
142
|
+
// at — a zero-value or fractional-quantity card line means somebody sold something that
|
|
143
|
+
// cannot exist, and letting it through books revenue against no instrument.
|
|
144
|
+
throw new Error(`Order line ${line.ID} is a gift-card product but issues no card (${plan.Refusal}). ` +
|
|
145
|
+
`A gift card needs a whole positive quantity and a face value above zero.`);
|
|
146
|
+
}
|
|
147
|
+
for (const card of plan.Cards) {
|
|
148
|
+
const account = await provider.GetEntityObject(STORED_VALUE_ACCOUNT_ENTITY, user);
|
|
149
|
+
account.NewRecord();
|
|
150
|
+
account.Code = FormatGiftCardCode(randomChar);
|
|
151
|
+
account.IssuingCompanyID = order.IssuingCompanyID;
|
|
152
|
+
account.InitialAmount = card.FaceValue;
|
|
153
|
+
account.CurrentBalance = card.FaceValue;
|
|
154
|
+
account.Status = 'Active';
|
|
155
|
+
account.IssuedFromOrderLineID = card.OrderLineID;
|
|
156
|
+
if (card.BeneficiaryPersonID)
|
|
157
|
+
account.BeneficiaryPersonID = card.BeneficiaryPersonID;
|
|
158
|
+
if (card.BeneficiaryOrganizationID) {
|
|
159
|
+
account.BeneficiaryOrganizationID = card.BeneficiaryOrganizationID;
|
|
160
|
+
}
|
|
161
|
+
// ExpiresAt stays null. Breakage and expiry are deferred (§21), and a card with an
|
|
162
|
+
// expiry nothing enforces would be worse than one with none.
|
|
163
|
+
if (!(await account.Save(options))) {
|
|
164
|
+
throw new Error(`Could not issue gift card ${card.Sequence} of ${plan.Cards.length} for line ` +
|
|
165
|
+
`${card.OrderLineID}: ${account.LatestResult?.CompleteMessage ?? 'no reason given'}`);
|
|
166
|
+
}
|
|
167
|
+
const accountID = account.ID;
|
|
168
|
+
await writeTransaction(provider, user, accountID, 'Issue', card.FaceValue, card.FaceValue, order.ID, options);
|
|
169
|
+
out.Issued.push({
|
|
170
|
+
ID: accountID,
|
|
171
|
+
Code: account.Code,
|
|
172
|
+
FaceValue: card.FaceValue,
|
|
173
|
+
OrderLineID: card.OrderLineID,
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
out.Liability += GiftCardLiability(plan);
|
|
177
|
+
}
|
|
178
|
+
out.Liability = Math.round((out.Liability + Number.EPSILON) * 100) / 100;
|
|
179
|
+
return out;
|
|
180
|
+
}
|
|
181
|
+
/** One signed movement on a card's ledger. */
|
|
182
|
+
async function writeTransaction(provider, user, storedValueAccountID, type, amount, balanceAfter, relatedOrderHeaderID, options) {
|
|
183
|
+
const txn = await provider.GetEntityObject(STORED_VALUE_TRANSACTION_ENTITY, user);
|
|
184
|
+
txn.NewRecord();
|
|
185
|
+
txn.StoredValueAccountID = storedValueAccountID;
|
|
186
|
+
txn.TransactionType = type;
|
|
187
|
+
txn.Amount = amount;
|
|
188
|
+
txn.BalanceAfter = balanceAfter;
|
|
189
|
+
if (relatedOrderHeaderID)
|
|
190
|
+
txn.RelatedOrderHeaderID = relatedOrderHeaderID;
|
|
191
|
+
txn.OccurredAt = new Date();
|
|
192
|
+
if (!(await txn.Save(options))) {
|
|
193
|
+
throw new Error(`Could not write the ${type} transaction for stored-value account ${storedValueAccountID}: ` +
|
|
194
|
+
`${txn.LatestResult?.CompleteMessage ?? 'no reason given'}`);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
//# sourceMappingURL=GiftCardEngine.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GiftCardEngine.js","sourceRoot":"","sources":["../src/GiftCardEngine.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Guest Order Claim Driver
|
|
3
|
+
*
|
|
4
|
+
* Implements the BaseIdentityClaimDriver interface for BizApps Orders guest orders.
|
|
5
|
+
* When a guest redeems an order claim token, this driver links the OrderHeader (and any
|
|
6
|
+
* associated EntitlementGrants) to the authenticated user's Person record.
|
|
7
|
+
*
|
|
8
|
+
* @module @mj-biz-apps/orders-core-entities-server/GuestOrderClaimDriver
|
|
9
|
+
*/
|
|
10
|
+
import { BaseIdentityClaimDriver, type ClaimContext, type ClaimRedeemContext, type ClaimResult } from './identityClaimContracts.js';
|
|
11
|
+
/**
|
|
12
|
+
* Pluggable driver for GuestOrder identity claims.
|
|
13
|
+
*/
|
|
14
|
+
export declare class GuestOrderClaimDriver extends BaseIdentityClaimDriver {
|
|
15
|
+
/**
|
|
16
|
+
* Hook called after a guest order claim is created.
|
|
17
|
+
*/
|
|
18
|
+
OnCreate(context: ClaimContext): Promise<void>;
|
|
19
|
+
/**
|
|
20
|
+
* Hook called when a guest order claim is redeemed by an authenticated user.
|
|
21
|
+
*/
|
|
22
|
+
OnClaim(context: ClaimRedeemContext): Promise<ClaimResult>;
|
|
23
|
+
/**
|
|
24
|
+
* Hook called when a guest order claim is revoked.
|
|
25
|
+
*/
|
|
26
|
+
OnRevoke(context: ClaimContext): Promise<void>;
|
|
27
|
+
/**
|
|
28
|
+
* Hook called when a guest order claim expires.
|
|
29
|
+
*/
|
|
30
|
+
OnExpire(context: ClaimContext): Promise<void>;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Registration helper to ensure class factory decorator executes.
|
|
34
|
+
*/
|
|
35
|
+
export declare function LoadGuestOrderClaimDriver(): void;
|
|
36
|
+
//# sourceMappingURL=GuestOrderClaimDriver.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GuestOrderClaimDriver.d.ts","sourceRoot":"","sources":["../src/GuestOrderClaimDriver.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAQH,OAAO,EACH,uBAAuB,EACvB,KAAK,YAAY,EACjB,KAAK,kBAAkB,EACvB,KAAK,WAAW,EACnB,MAAM,6BAA6B,CAAC;AAUrC;;GAEG;AACH,qBACa,qBAAsB,SAAQ,uBAAuB;IAC9D;;OAEG;IACU,QAAQ,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAI3D;;OAEG;IACU,OAAO,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,WAAW,CAAC;IAiHvE;;OAEG;IACU,QAAQ,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAI3D;;OAEG;IACU,QAAQ,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;CAG9D;AAED;;GAEG;AACH,wBAAgB,yBAAyB,IAAI,IAAI,CAEhD"}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Guest Order Claim Driver
|
|
3
|
+
*
|
|
4
|
+
* Implements the BaseIdentityClaimDriver interface for BizApps Orders guest orders.
|
|
5
|
+
* When a guest redeems an order claim token, this driver links the OrderHeader (and any
|
|
6
|
+
* associated EntitlementGrants) to the authenticated user's Person record.
|
|
7
|
+
*
|
|
8
|
+
* @module @mj-biz-apps/orders-core-entities-server/GuestOrderClaimDriver
|
|
9
|
+
*/
|
|
10
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
11
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
12
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
13
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
14
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
15
|
+
};
|
|
16
|
+
import { RegisterClass } from '@memberjunction/global';
|
|
17
|
+
// Local fallback — see sql-guards.ts. Restore to '@memberjunction/global' once published.
|
|
18
|
+
import { EscapeSQLString } from './sql-guards.js';
|
|
19
|
+
import { Metadata, RunView } from '@memberjunction/core';
|
|
20
|
+
// Local fallback contracts — see identityClaimContracts.ts. Restore this import to
|
|
21
|
+
// '@memberjunction/core-entities' once MJ publishes these exports.
|
|
22
|
+
import { BaseIdentityClaimDriver, } from './identityClaimContracts.js';
|
|
23
|
+
import { resolvePersonID } from './claimDriverHelpers.js';
|
|
24
|
+
const ORDER_HEADER_ENTITY = 'MJ_BizApps_Orders: Order Headers';
|
|
25
|
+
const ENTITLEMENT_GRANT_ENTITY = 'MJ_BizApps_Orders: Entitlement Grants';
|
|
26
|
+
/**
|
|
27
|
+
* Pluggable driver for GuestOrder identity claims.
|
|
28
|
+
*/
|
|
29
|
+
let GuestOrderClaimDriver = class GuestOrderClaimDriver extends BaseIdentityClaimDriver {
|
|
30
|
+
/**
|
|
31
|
+
* Hook called after a guest order claim is created.
|
|
32
|
+
*/
|
|
33
|
+
async OnCreate(context) {
|
|
34
|
+
// Claim registered in pending state
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Hook called when a guest order claim is redeemed by an authenticated user.
|
|
38
|
+
*/
|
|
39
|
+
async OnClaim(context) {
|
|
40
|
+
const { Claim, User } = context;
|
|
41
|
+
const provider = Claim.ProviderToUse;
|
|
42
|
+
let orderID = Claim.RecordID;
|
|
43
|
+
if (!orderID && Claim.PayloadJSON) {
|
|
44
|
+
try {
|
|
45
|
+
const payload = JSON.parse(Claim.PayloadJSON);
|
|
46
|
+
if (typeof payload.OrderID === 'string') {
|
|
47
|
+
orderID = payload.OrderID;
|
|
48
|
+
}
|
|
49
|
+
else if (typeof payload.orderId === 'string') {
|
|
50
|
+
orderID = payload.orderId;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
// Ignore payload parse errors
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if (!orderID) {
|
|
58
|
+
return {
|
|
59
|
+
Success: false,
|
|
60
|
+
ErrorMessage: 'Claim does not reference an OrderHeader RecordID'
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
const md = new Metadata();
|
|
64
|
+
const order = await md.GetEntityObject(ORDER_HEADER_ENTITY, User);
|
|
65
|
+
const loaded = await order.Load(orderID);
|
|
66
|
+
if (!loaded) {
|
|
67
|
+
return {
|
|
68
|
+
Success: false,
|
|
69
|
+
ErrorMessage: `Order with ID ${orderID} not found`
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
const personID = await resolvePersonID(User, provider);
|
|
73
|
+
if (!personID) {
|
|
74
|
+
return {
|
|
75
|
+
Success: false,
|
|
76
|
+
ErrorMessage: `Could not resolve Person record for redeeming user ${User?.Email || 'unknown'}`
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
// Link Order Header parties to the redeeming person if unset or different
|
|
80
|
+
let orderModified = false;
|
|
81
|
+
if (!order.BillToPersonID || order.BillToPersonID !== personID) {
|
|
82
|
+
order.BillToPersonID = personID;
|
|
83
|
+
orderModified = true;
|
|
84
|
+
}
|
|
85
|
+
if (!order.ShipToPersonID || order.ShipToPersonID !== personID) {
|
|
86
|
+
order.ShipToPersonID = personID;
|
|
87
|
+
orderModified = true;
|
|
88
|
+
}
|
|
89
|
+
if (orderModified) {
|
|
90
|
+
const saved = await order.Save();
|
|
91
|
+
if (!saved) {
|
|
92
|
+
return {
|
|
93
|
+
Success: false,
|
|
94
|
+
ErrorMessage: `Failed to update Order with redeeming PersonID: ${order.LatestResult?.Message ?? 'Unknown save error'}`
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
// Cascade to any EntitlementGrants hanging off this order's lines
|
|
99
|
+
const updatedGrants = [];
|
|
100
|
+
try {
|
|
101
|
+
const runViewProvider = (provider && 'RunView' in provider) ? provider : null;
|
|
102
|
+
const rv = new RunView(runViewProvider);
|
|
103
|
+
const escapedOrderID = EscapeSQLString(orderID);
|
|
104
|
+
const grantResult = await rv.RunView({
|
|
105
|
+
EntityName: ENTITLEMENT_GRANT_ENTITY,
|
|
106
|
+
ExtraFilter: `OrderLineID IN (SELECT ID FROM vwOrderLines WHERE OrderID = '${escapedOrderID}')`,
|
|
107
|
+
ResultType: 'simple'
|
|
108
|
+
}, User);
|
|
109
|
+
if (grantResult.Success && grantResult.Results && grantResult.Results.length > 0) {
|
|
110
|
+
for (const row of grantResult.Results) {
|
|
111
|
+
const grant = await md.GetEntityObject(ENTITLEMENT_GRANT_ENTITY, User);
|
|
112
|
+
if (await grant.Load(row.ID)) {
|
|
113
|
+
grant.BeneficiaryPersonID = personID;
|
|
114
|
+
if (grant.Status !== 'Active') {
|
|
115
|
+
grant.Status = 'Active';
|
|
116
|
+
grant.ProvisionedAt = new Date();
|
|
117
|
+
}
|
|
118
|
+
if (await grant.Save()) {
|
|
119
|
+
updatedGrants.push(grant.ID);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
// Entitlement grant cascade failure should not block overall order link
|
|
127
|
+
}
|
|
128
|
+
return {
|
|
129
|
+
Success: true,
|
|
130
|
+
Data: {
|
|
131
|
+
OrderID: order.ID,
|
|
132
|
+
OrderNumber: order.OrderNumber,
|
|
133
|
+
PersonID: personID,
|
|
134
|
+
CascadedGrants: updatedGrants
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Hook called when a guest order claim is revoked.
|
|
140
|
+
*/
|
|
141
|
+
async OnRevoke(context) {
|
|
142
|
+
// Safe teardown / no-op
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Hook called when a guest order claim expires.
|
|
146
|
+
*/
|
|
147
|
+
async OnExpire(context) {
|
|
148
|
+
// Safe teardown / no-op
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
GuestOrderClaimDriver = __decorate([
|
|
152
|
+
RegisterClass(BaseIdentityClaimDriver, 'GuestOrderClaimDriver')
|
|
153
|
+
], GuestOrderClaimDriver);
|
|
154
|
+
export { GuestOrderClaimDriver };
|
|
155
|
+
/**
|
|
156
|
+
* Registration helper to ensure class factory decorator executes.
|
|
157
|
+
*/
|
|
158
|
+
export function LoadGuestOrderClaimDriver() {
|
|
159
|
+
// Explicit trigger for bundler tree-shaking preservation
|
|
160
|
+
}
|
|
161
|
+
//# sourceMappingURL=GuestOrderClaimDriver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GuestOrderClaimDriver.js","sourceRoot":"","sources":["../src/GuestOrderClaimDriver.ts"],"names":[],"mappings":"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"}
|