@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,442 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
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;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* StripePaymentProvider — the real PaymentIntent lifecycle, and a stub that stands in for it.
|
|
9
|
+
*
|
|
10
|
+
* STUB-FIRST, per D19. The committed default in a non-live provider is a deterministic stub: it opens
|
|
11
|
+
* an intent, succeeds on capture, and reports a plausible fee, all without a network call. That is
|
|
12
|
+
* what makes the integration suite able to exercise the payment path at all — 245 checks cannot each
|
|
13
|
+
* wait on a live gateway, and a suite that mocks at the HTTP layer instead would be asserting against
|
|
14
|
+
* its own mock.
|
|
15
|
+
*
|
|
16
|
+
* The stub is chosen by `PaymentProvider.IsLiveMode = 0`, not by an environment variable. The
|
|
17
|
+
* distinction belongs to the CONFIGURED ACCOUNT: a deployment can hold a live account and a test
|
|
18
|
+
* account side by side, and the row says which is which. An env flag would make that a property of the
|
|
19
|
+
* process, so one misconfigured server would either fake real payments or really charge test ones.
|
|
20
|
+
*
|
|
21
|
+
* NO SDK DEPENDENCY. Stripe's REST API over `fetch` is stable, and the surface used here is small —
|
|
22
|
+
* three endpoints. Adding the SDK would pull a large dependency into a package that is imported by
|
|
23
|
+
* every server process for the sake of URL construction. The trade is deliberate: we write the form
|
|
24
|
+
* encoding ourselves (`ToFormBody` below), which Stripe requires and which is the one fiddly part.
|
|
25
|
+
*
|
|
26
|
+
* WHAT IS DELIBERATELY NOT HERE. Reconciliation, dispute handling, and the forensics of a partially
|
|
27
|
+
* captured intent. D19 defers those, and the seams for them are the webhook event kinds this driver
|
|
28
|
+
* already declines to handle rather than pretends to.
|
|
29
|
+
*
|
|
30
|
+
* CONNECTS TO:
|
|
31
|
+
* PURE: ./PaymentProviderBehavior.ts — minor units, signature, status mapping
|
|
32
|
+
* BASE: ./BasePaymentProvider.ts
|
|
33
|
+
* DOC: plans/archive/bizapps-orders-master.md D19
|
|
34
|
+
*/
|
|
35
|
+
import { RegisterClass } from '@memberjunction/global';
|
|
36
|
+
import { LogError } from '@memberjunction/core';
|
|
37
|
+
import { BasePaymentProvider, } from './BasePaymentProvider.js';
|
|
38
|
+
import { FromMinorUnits, MapStripeIntentStatus, ToMinorUnits, VerifyWebhookSignature, } from './PaymentProviderBehavior.js';
|
|
39
|
+
const STRIPE_API = 'https://api.stripe.com/v1';
|
|
40
|
+
/** Stripe's own event names, kept as strings because they are Stripe's vocabulary and not ours. */
|
|
41
|
+
const HANDLED = [
|
|
42
|
+
'payment_intent.succeeded',
|
|
43
|
+
'payment_intent.payment_failed',
|
|
44
|
+
'payment_intent.canceled',
|
|
45
|
+
'charge.refunded',
|
|
46
|
+
];
|
|
47
|
+
let StripePaymentProvider = class StripePaymentProvider extends BasePaymentProvider {
|
|
48
|
+
get HandledEventKinds() {
|
|
49
|
+
return HANDLED;
|
|
50
|
+
}
|
|
51
|
+
/** The stub stands in whenever the configured account is not live. */
|
|
52
|
+
get useStub() {
|
|
53
|
+
return !this.Config?.IsLiveMode;
|
|
54
|
+
}
|
|
55
|
+
// ─── Extension points ──────────────────────────────────────────────────────
|
|
56
|
+
//
|
|
57
|
+
// Three small seams, added so a SIBLING Stripe rail (bank debits, and whatever comes after) can
|
|
58
|
+
// change the parts that differ without reimplementing the parts that do not. The alternative was a
|
|
59
|
+
// second class copying `CreateIntent`, `call`, `ToFormBody` and the error handling — four places to
|
|
60
|
+
// fix the next time Stripe changes a response shape, three of which nobody would remember to look
|
|
61
|
+
// at. Everything genuinely shared stays here; a subclass overrides only what its rail changes.
|
|
62
|
+
/**
|
|
63
|
+
* The intent status the STUB reports on creation.
|
|
64
|
+
*
|
|
65
|
+
* A card intent opens waiting for an instrument. A bank debit opens already submitted, and a stub
|
|
66
|
+
* that claimed otherwise would exercise a state the real rail never passes through.
|
|
67
|
+
*/
|
|
68
|
+
get StubIntentStatus() {
|
|
69
|
+
return 'RequiresPayment';
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* The fee the STUB reports on capture — 2.9% + 30c, Stripe's standard US card rate.
|
|
73
|
+
*
|
|
74
|
+
* See the note at the call site for why a stub must not report zero.
|
|
75
|
+
*/
|
|
76
|
+
StubFeeFor(amount, _currencyCode) {
|
|
77
|
+
return amount > 0 ? Math.round((amount * 0.029 + 0.3) * 100) / 100 : 0;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Last chance to add rail-specific fields to a `POST /payment_intents` body.
|
|
81
|
+
*
|
|
82
|
+
* Mutates rather than returns, so a subclass that forgets to call `super` cannot silently discard
|
|
83
|
+
* everything the base assembled — there is nothing to discard.
|
|
84
|
+
*/
|
|
85
|
+
DecorateIntentBody(_body, _request) {
|
|
86
|
+
// Cards need nothing beyond what CreateIntent already builds.
|
|
87
|
+
}
|
|
88
|
+
// ─── Intent ────────────────────────────────────────────────────────────────
|
|
89
|
+
async CreateIntent(request) {
|
|
90
|
+
if (request.Amount <= 0) {
|
|
91
|
+
return { Success: false, Reason: `Stripe cannot open an intent for ${request.Amount}.` };
|
|
92
|
+
}
|
|
93
|
+
if (this.useStub) {
|
|
94
|
+
// Deterministic from the order, so re-running a check produces the same id and a duplicate
|
|
95
|
+
// is visible as a duplicate rather than as two unrelated intents.
|
|
96
|
+
const seed = request.OrderHeaderID ?? request.IdempotencyKey ?? 'no-order';
|
|
97
|
+
return {
|
|
98
|
+
Success: true,
|
|
99
|
+
ProviderIntentID: `pi_stub_${seed.replace(/-/g, '').slice(0, 24)}`,
|
|
100
|
+
Status: this.StubIntentStatus,
|
|
101
|
+
ClientSecret: `pi_stub_${seed.slice(0, 8)}_secret_stub`,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
const body = {
|
|
105
|
+
amount: String(ToMinorUnits(request.Amount, request.CurrencyCode)),
|
|
106
|
+
currency: request.CurrencyCode.toLowerCase(),
|
|
107
|
+
// Off-session by default: this is a back-office confirm, not a customer at a checkout.
|
|
108
|
+
// A driver charging a saved instrument must say so or Stripe expects an interactive
|
|
109
|
+
// confirmation that will never come.
|
|
110
|
+
confirm: request.ProviderInstrumentRef ? 'true' : 'false',
|
|
111
|
+
};
|
|
112
|
+
if (request.ProviderCustomerRef)
|
|
113
|
+
body.customer = request.ProviderCustomerRef;
|
|
114
|
+
if (request.ProviderInstrumentRef) {
|
|
115
|
+
body.payment_method = request.ProviderInstrumentRef;
|
|
116
|
+
body.off_session = 'true';
|
|
117
|
+
}
|
|
118
|
+
for (const [k, v] of Object.entries(request.Metadata ?? {}))
|
|
119
|
+
body[`metadata[${k}]`] = v;
|
|
120
|
+
if (request.OrderHeaderID)
|
|
121
|
+
body['metadata[OrderHeaderID]'] = request.OrderHeaderID;
|
|
122
|
+
this.DecorateIntentBody(body, request);
|
|
123
|
+
const result = await this.call('POST', '/payment_intents', body, request.IdempotencyKey);
|
|
124
|
+
if (!result.Ok)
|
|
125
|
+
return { Success: false, Reason: result.Reason };
|
|
126
|
+
return {
|
|
127
|
+
Success: true,
|
|
128
|
+
ProviderIntentID: String(result.Body.id),
|
|
129
|
+
Status: MapStripeIntentStatus(result.Body.status),
|
|
130
|
+
ClientSecret: result.Body.client_secret,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
async RetrieveIntent(request) {
|
|
134
|
+
if (!request.ProviderIntentID) {
|
|
135
|
+
return { Success: false, Reason: 'A Stripe retrieve needs a provider intent id.' };
|
|
136
|
+
}
|
|
137
|
+
if (this.useStub) {
|
|
138
|
+
// The stub never sees a browser confirmCardPayment; it stays RequiresPayment
|
|
139
|
+
// until Capture, matching the card rail's opening state.
|
|
140
|
+
return { Success: true, Status: this.StubIntentStatus };
|
|
141
|
+
}
|
|
142
|
+
const result = await this.call('GET', `/payment_intents/${encodeURIComponent(request.ProviderIntentID)}`);
|
|
143
|
+
if (!result.Ok) {
|
|
144
|
+
return { Success: false, Reason: result.Reason };
|
|
145
|
+
}
|
|
146
|
+
const currency = (result.Body.currency ?? 'usd').toUpperCase();
|
|
147
|
+
const minor = Number(result.Body.amount_received ?? result.Body.amount ?? 0);
|
|
148
|
+
return {
|
|
149
|
+
Success: true,
|
|
150
|
+
Status: MapStripeIntentStatus(result.Body.status),
|
|
151
|
+
Amount: Number.isFinite(minor) && minor > 0 ? FromMinorUnits(minor, currency) : undefined,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
// ─── Capture ───────────────────────────────────────────────────────────────
|
|
155
|
+
async Capture(request) {
|
|
156
|
+
if (this.useStub) {
|
|
157
|
+
const amount = request.Amount ?? 0;
|
|
158
|
+
return {
|
|
159
|
+
Success: true,
|
|
160
|
+
Amount: amount,
|
|
161
|
+
// 2.9% + 30c, Stripe's standard US card rate. A stub that reported ZERO fee would make
|
|
162
|
+
// the fee leg of the capture entry (D18) unreachable in every test, so the one thing
|
|
163
|
+
// that must be exercised would be the one thing never exercised.
|
|
164
|
+
FeeAmount: this.StubFeeFor(amount, request.CurrencyCode),
|
|
165
|
+
ProviderChargeID: `ch_stub_${request.ProviderIntentID.slice(-12)}`,
|
|
166
|
+
Status: 'Succeeded',
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
const body = {};
|
|
170
|
+
if (request.Amount != null) {
|
|
171
|
+
body.amount_to_capture = String(ToMinorUnits(request.Amount, request.CurrencyCode));
|
|
172
|
+
}
|
|
173
|
+
const captured = await this.call('POST', `/payment_intents/${encodeURIComponent(request.ProviderIntentID)}/capture`, body);
|
|
174
|
+
if (!captured.Ok) {
|
|
175
|
+
// Browser confirmCardPayment with automatic capture already captured the intent.
|
|
176
|
+
// POST /capture then returns payment_intent_unexpected_state. Treat that as
|
|
177
|
+
// success by reading the Succeeded intent — same money, not a second capture.
|
|
178
|
+
if (stripeCaptureAlreadyCollected(captured.Reason, captured.Body)) {
|
|
179
|
+
const retrieved = await this.call('GET', `/payment_intents/${encodeURIComponent(request.ProviderIntentID)}`);
|
|
180
|
+
if (retrieved.Ok) {
|
|
181
|
+
const retrievedStatus = MapStripeIntentStatus(retrieved.Body.status);
|
|
182
|
+
if (retrievedStatus === 'Succeeded') {
|
|
183
|
+
return this.captureResultFromIntent(retrieved.Body, request);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return { Success: false, Reason: captured.Reason };
|
|
188
|
+
}
|
|
189
|
+
const status = MapStripeIntentStatus(captured.Body.status);
|
|
190
|
+
if (status !== 'Succeeded') {
|
|
191
|
+
// A capture that did not succeed is a REFUSAL, not a fault: the card was declined, or the
|
|
192
|
+
// intent needed action. Reporting success here would book cash that never arrived.
|
|
193
|
+
return { Success: false, Reason: `Stripe reported the intent as '${captured.Body.status}' after capture.`, Status: status };
|
|
194
|
+
}
|
|
195
|
+
return this.captureResultFromIntent(captured.Body, request);
|
|
196
|
+
}
|
|
197
|
+
// ─── Refund ────────────────────────────────────────────────────────────────
|
|
198
|
+
async Refund(request) {
|
|
199
|
+
const target = request.ProviderChargeID ?? request.ProviderIntentID;
|
|
200
|
+
if (!target) {
|
|
201
|
+
return { Success: false, Reason: 'A Stripe refund needs either a charge id or an intent id.' };
|
|
202
|
+
}
|
|
203
|
+
if (this.useStub) {
|
|
204
|
+
return {
|
|
205
|
+
Success: true,
|
|
206
|
+
Amount: request.Amount ?? 0,
|
|
207
|
+
ProviderRefundID: `re_stub_${target.slice(-12)}`,
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
const body = {};
|
|
211
|
+
if (request.ProviderChargeID)
|
|
212
|
+
body.charge = request.ProviderChargeID;
|
|
213
|
+
else
|
|
214
|
+
body.payment_intent = request.ProviderIntentID;
|
|
215
|
+
if (request.Amount != null)
|
|
216
|
+
body.amount = String(ToMinorUnits(request.Amount, request.CurrencyCode));
|
|
217
|
+
if (request.Reason)
|
|
218
|
+
body.reason = 'requested_by_customer';
|
|
219
|
+
const result = await this.call('POST', '/refunds', body, request.IdempotencyKey);
|
|
220
|
+
if (!result.Ok)
|
|
221
|
+
return { Success: false, Reason: result.Reason };
|
|
222
|
+
const currency = result.Body.currency ?? request.CurrencyCode;
|
|
223
|
+
return {
|
|
224
|
+
Success: true,
|
|
225
|
+
Amount: FromMinorUnits(Number(result.Body.amount ?? 0), currency),
|
|
226
|
+
ProviderRefundID: String(result.Body.id),
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
// ─── Webhooks ──────────────────────────────────────────────────────────────
|
|
230
|
+
async VerifyWebhook(rawBody, headers) {
|
|
231
|
+
// Header names arrive in whatever case the proxy chose, so both are read.
|
|
232
|
+
const signature = headers['stripe-signature'] ?? headers['Stripe-Signature'];
|
|
233
|
+
return VerifyWebhookSignature(rawBody, signature, this.Credentials?.WebhookSecret);
|
|
234
|
+
}
|
|
235
|
+
ParseWebhookEvent(rawBody) {
|
|
236
|
+
let parsed;
|
|
237
|
+
try {
|
|
238
|
+
parsed = JSON.parse(rawBody);
|
|
239
|
+
}
|
|
240
|
+
catch {
|
|
241
|
+
return null;
|
|
242
|
+
}
|
|
243
|
+
// VALID JSON IS NOT NECESSARILY AN OBJECT. `JSON.parse` happily returns `null` for the body
|
|
244
|
+
// `null`, and a number for `42` — both of which then threw a TypeError on the first property
|
|
245
|
+
// read, from a method whose contract is "null when it is not an event we can read". The throw
|
|
246
|
+
// escaped `HandlePaymentWebhook`'s parse step, which is not wrapped, so a body that verified
|
|
247
|
+
// and was not an object crashed the route instead of answering 400.
|
|
248
|
+
//
|
|
249
|
+
// Reachable only behind signature verification, so not an open door — but the contract is the
|
|
250
|
+
// contract, and "returns null OR throws" is not a thing a caller can branch on.
|
|
251
|
+
if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed))
|
|
252
|
+
return null;
|
|
253
|
+
const payload = parsed;
|
|
254
|
+
const kind = payload.type;
|
|
255
|
+
const object = (payload.data?.object ?? {});
|
|
256
|
+
if (!payload.id || !kind)
|
|
257
|
+
return null;
|
|
258
|
+
const currency = (object.currency ?? 'usd').toUpperCase();
|
|
259
|
+
const event = { EventID: String(payload.id), Kind: kind, CurrencyCode: currency };
|
|
260
|
+
const created = Number(payload.created);
|
|
261
|
+
if (Number.isFinite(created) && created > 0) {
|
|
262
|
+
event.OccurredAt = new Date(created * 1000);
|
|
263
|
+
}
|
|
264
|
+
if (kind.startsWith('payment_intent.')) {
|
|
265
|
+
event.ProviderIntentID = object.id;
|
|
266
|
+
event.ProviderChargeID = this.latestCharge(object) ?? undefined;
|
|
267
|
+
const minor = Number(object.amount_received ?? object.amount ?? 0);
|
|
268
|
+
if (Number.isFinite(minor) && minor > 0)
|
|
269
|
+
event.Amount = FromMinorUnits(minor, currency);
|
|
270
|
+
event.Status =
|
|
271
|
+
kind === 'payment_intent.payment_failed'
|
|
272
|
+
? 'Failed'
|
|
273
|
+
: MapStripeIntentStatus(object.status);
|
|
274
|
+
const failure = object.last_payment_error;
|
|
275
|
+
if (failure?.message)
|
|
276
|
+
event.FailureReason = String(failure.message);
|
|
277
|
+
}
|
|
278
|
+
else if (kind === 'charge.refunded') {
|
|
279
|
+
event.ProviderChargeID = object.id;
|
|
280
|
+
event.ProviderIntentID = object.payment_intent;
|
|
281
|
+
const refunded = Number(object.amount_refunded ?? 0);
|
|
282
|
+
if (Number.isFinite(refunded) && refunded > 0)
|
|
283
|
+
event.Amount = FromMinorUnits(refunded, currency);
|
|
284
|
+
}
|
|
285
|
+
return event;
|
|
286
|
+
}
|
|
287
|
+
// ─── HTTP ──────────────────────────────────────────────────────────────────
|
|
288
|
+
/**
|
|
289
|
+
* One call, with Stripe's own error shape read out of the body.
|
|
290
|
+
*
|
|
291
|
+
* A 4xx from Stripe is usually a REFUSAL — a declined card, an intent in the wrong state — and its
|
|
292
|
+
* `error.message` is written for a human. Surfacing that rather than "HTTP 402" is the difference
|
|
293
|
+
* between a support ticket somebody can answer and one they cannot.
|
|
294
|
+
*
|
|
295
|
+
* Only transport failures and 5xx throw. Those are faults; the rest are answers.
|
|
296
|
+
*/
|
|
297
|
+
async call(method, path, body, idempotencyKey) {
|
|
298
|
+
const apiKey = this.Credentials?.ApiKey;
|
|
299
|
+
if (!apiKey) {
|
|
300
|
+
return {
|
|
301
|
+
Ok: false,
|
|
302
|
+
Body: {},
|
|
303
|
+
Reason: `No Stripe API key is available for provider '${this.Config?.Name ?? this.Config?.ID}'. ` +
|
|
304
|
+
`Its CredentialsRef must resolve to a key — see PaymentProviderResolver.`,
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
const headers = {
|
|
308
|
+
Authorization: `Bearer ${apiKey}`,
|
|
309
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
310
|
+
// Pinned deliberately. Stripe changes response shapes between versions, and inheriting
|
|
311
|
+
// whatever the account defaults to means this driver's behaviour can change without a
|
|
312
|
+
// deploy.
|
|
313
|
+
'Stripe-Version': '2024-06-20',
|
|
314
|
+
};
|
|
315
|
+
if (idempotencyKey)
|
|
316
|
+
headers['Idempotency-Key'] = idempotencyKey;
|
|
317
|
+
let response;
|
|
318
|
+
try {
|
|
319
|
+
response = await fetch(`${STRIPE_API}${path}`, {
|
|
320
|
+
method,
|
|
321
|
+
headers,
|
|
322
|
+
body: body ? ToFormBody(body) : undefined,
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
catch (err) {
|
|
326
|
+
// A fault, not an answer. The caller's payment is in an UNKNOWN state, which is worse than
|
|
327
|
+
// a decline, so it must not be reported as one.
|
|
328
|
+
throw new Error(`Stripe was unreachable calling ${method} ${path}: ${err.message}`);
|
|
329
|
+
}
|
|
330
|
+
const text = await response.text();
|
|
331
|
+
let parsed = {};
|
|
332
|
+
try {
|
|
333
|
+
parsed = text ? JSON.parse(text) : {};
|
|
334
|
+
}
|
|
335
|
+
catch {
|
|
336
|
+
parsed = {};
|
|
337
|
+
}
|
|
338
|
+
if (response.ok)
|
|
339
|
+
return { Ok: true, Body: parsed };
|
|
340
|
+
if (response.status >= 500) {
|
|
341
|
+
throw new Error(`Stripe returned ${response.status} calling ${method} ${path}. The payment state is unknown.`);
|
|
342
|
+
}
|
|
343
|
+
const error = (parsed.error ?? {});
|
|
344
|
+
const reason = error.message ?? `Stripe returned ${response.status} with no message.`;
|
|
345
|
+
LogError(`Stripe ${method} ${path} refused: ${reason}`);
|
|
346
|
+
return { Ok: false, Body: parsed, Reason: reason };
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* Map a Succeeded Stripe PaymentIntent body onto our CaptureResult. Shared by a real
|
|
350
|
+
* POST /capture and by the already-captured retrieve fallback so both paths stamp the
|
|
351
|
+
* same charge id and fee.
|
|
352
|
+
*/
|
|
353
|
+
async captureResultFromIntent(body, request) {
|
|
354
|
+
const currency = body.currency ?? request.CurrencyCode;
|
|
355
|
+
const amountMinor = Number(body.amount_received ?? body.amount ?? 0);
|
|
356
|
+
const charge = this.latestCharge(body);
|
|
357
|
+
return {
|
|
358
|
+
Success: true,
|
|
359
|
+
Amount: FromMinorUnits(amountMinor, currency),
|
|
360
|
+
// UNDEFINED rather than zero when the fee is not expanded in the response. Stripe reports
|
|
361
|
+
// it on the balance transaction, which needs a separate fetch, and "we do not know the fee"
|
|
362
|
+
// must not read as "there was no fee" — the first leaves the fee leg unbooked, the second
|
|
363
|
+
// books a wrong one.
|
|
364
|
+
FeeAmount: await this.feeFor(charge, currency),
|
|
365
|
+
ProviderChargeID: charge ?? undefined,
|
|
366
|
+
Status: 'Succeeded',
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
/** `latest_charge` is a string on modern API versions and an object on older expanded responses. */
|
|
370
|
+
latestCharge(object) {
|
|
371
|
+
const latest = object.latest_charge;
|
|
372
|
+
if (typeof latest === 'string')
|
|
373
|
+
return latest;
|
|
374
|
+
if (latest && typeof latest === 'object')
|
|
375
|
+
return latest.id ?? null;
|
|
376
|
+
const charges = object.charges;
|
|
377
|
+
const first = charges?.data?.[0];
|
|
378
|
+
return first?.id ?? null;
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* The gateway's cut, from the charge's balance transaction.
|
|
382
|
+
*
|
|
383
|
+
* Returns UNDEFINED when it cannot be determined, never 0 — see the note at the call site. A failed
|
|
384
|
+
* lookup here must not silently suppress the fee leg of the capture entry, and it must not fail the
|
|
385
|
+
* capture either: the money HAS moved by this point, and refusing now would leave the ledger
|
|
386
|
+
* disagreeing with the gateway.
|
|
387
|
+
*/
|
|
388
|
+
async feeFor(chargeID, currency) {
|
|
389
|
+
if (!chargeID)
|
|
390
|
+
return undefined;
|
|
391
|
+
try {
|
|
392
|
+
const charge = await this.call('GET', `/charges/${encodeURIComponent(chargeID)}?expand[]=balance_transaction`);
|
|
393
|
+
if (!charge.Ok)
|
|
394
|
+
return undefined;
|
|
395
|
+
const bt = charge.Body.balance_transaction;
|
|
396
|
+
if (!bt || typeof bt === 'string')
|
|
397
|
+
return undefined;
|
|
398
|
+
const fee = Number(bt.fee ?? NaN);
|
|
399
|
+
return Number.isFinite(fee) ? FromMinorUnits(fee, currency) : undefined;
|
|
400
|
+
}
|
|
401
|
+
catch (err) {
|
|
402
|
+
LogError(`Could not read the Stripe fee for charge ${chargeID}: ${err.message}`);
|
|
403
|
+
return undefined;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
};
|
|
407
|
+
StripePaymentProvider = __decorate([
|
|
408
|
+
RegisterClass(BasePaymentProvider, 'Stripe')
|
|
409
|
+
], StripePaymentProvider);
|
|
410
|
+
export { StripePaymentProvider };
|
|
411
|
+
/**
|
|
412
|
+
* Stripe wants `application/x-www-form-urlencoded`, including for nested keys like `metadata[Foo]`.
|
|
413
|
+
*
|
|
414
|
+
* `URLSearchParams` encodes the brackets as `%5B%5D`, which Stripe accepts — so this is a thin wrapper
|
|
415
|
+
* rather than hand-rolled escaping. It exists as a named function because the temptation to build the
|
|
416
|
+
* body with template strings is exactly how an unescaped value ends up changing the request.
|
|
417
|
+
*/
|
|
418
|
+
export function ToFormBody(fields) {
|
|
419
|
+
const params = new URLSearchParams();
|
|
420
|
+
for (const [key, value] of Object.entries(fields)) {
|
|
421
|
+
if (value != null)
|
|
422
|
+
params.append(key, String(value));
|
|
423
|
+
}
|
|
424
|
+
return params.toString();
|
|
425
|
+
}
|
|
426
|
+
/**
|
|
427
|
+
* True when Stripe refused POST /capture because the intent was already captured
|
|
428
|
+
* (automatic-capture confirmCardPayment, or a retried Capture).
|
|
429
|
+
*/
|
|
430
|
+
export function stripeCaptureAlreadyCollected(reason, body) {
|
|
431
|
+
const error = (body?.error ?? {});
|
|
432
|
+
const code = String(error.code ?? '');
|
|
433
|
+
const msg = `${reason ?? ''} ${String(error.message ?? '')}`.toLowerCase();
|
|
434
|
+
return (code === 'payment_intent_unexpected_state' ||
|
|
435
|
+
msg.includes('already been captured') ||
|
|
436
|
+
msg.includes('already succeeded'));
|
|
437
|
+
}
|
|
438
|
+
/** Tree-shaking anchor — call from the server bootstrap so @RegisterClass is retained. */
|
|
439
|
+
export function LoadStripePaymentProvider() {
|
|
440
|
+
// intentionally empty
|
|
441
|
+
}
|
|
442
|
+
//# sourceMappingURL=StripePaymentProvider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"StripePaymentProvider.js","sourceRoot":"","sources":["../src/StripePaymentProvider.ts"],"names":[],"mappings":";;;;;;AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AACvD,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAChD,OAAO,EACH,mBAAmB,GAUtB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACH,cAAc,EACd,qBAAqB,EACrB,YAAY,EACZ,sBAAsB,GAEzB,MAAM,8BAA8B,CAAC;AAEtC,MAAM,UAAU,GAAG,2BAA2B,CAAC;AAE/C,mGAAmG;AACnG,MAAM,OAAO,GAAG;IACZ,0BAA0B;IAC1B,+BAA+B;IAC/B,yBAAyB;IACzB,iBAAiB;CACX,CAAC;AAGJ,IAAM,qBAAqB,GAA3B,MAAM,qBAAsB,SAAQ,mBAAmB;IAC1D,IAAoB,iBAAiB;QACjC,OAAO,OAAO,CAAC;IACnB,CAAC;IAED,sEAAsE;IACtE,IAAc,OAAO;QACjB,OAAO,CAAC,IAAI,CAAC,MAAM,EAAE,UAAU,CAAC;IACpC,CAAC;IAED,8EAA8E;IAC9E,EAAE;IACF,gGAAgG;IAChG,mGAAmG;IACnG,oGAAoG;IACpG,kGAAkG;IAClG,+FAA+F;IAE/F;;;;;OAKG;IACH,IAAc,gBAAgB;QAC1B,OAAO,iBAAiB,CAAC;IAC7B,CAAC;IAED;;;;OAIG;IACO,UAAU,CAAC,MAAc,EAAE,aAAqB;QACtD,OAAO,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,GAAG,KAAK,GAAG,GAAG,CAAC,GAAG,GAAG,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IAC3E,CAAC;IAED;;;;;OAKG;IACO,kBAAkB,CAAC,KAA6B,EAAE,QAA6B;QACrF,8DAA8D;IAClE,CAAC;IAED,8EAA8E;IAE9D,KAAK,CAAC,YAAY,CAAC,OAA4B;QAC3D,IAAI,OAAO,CAAC,MAAM,IAAI,CAAC,EAAE,CAAC;YACtB,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,oCAAoC,OAAO,CAAC,MAAM,GAAG,EAAE,CAAC;QAC7F,CAAC;QAED,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACf,2FAA2F;YAC3F,kEAAkE;YAClE,MAAM,IAAI,GAAG,OAAO,CAAC,aAAa,IAAI,OAAO,CAAC,cAAc,IAAI,UAAU,CAAC;YAC3E,OAAO;gBACH,OAAO,EAAE,IAAI;gBACb,gBAAgB,EAAE,WAAW,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE;gBAClE,MAAM,EAAE,IAAI,CAAC,gBAAgB;gBAC7B,YAAY,EAAE,WAAW,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,cAAc;aAC1D,CAAC;QACN,CAAC;QAED,MAAM,IAAI,GAA2B;YACjC,MAAM,EAAE,MAAM,CAAC,YAAY,CAAC,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC;YAClE,QAAQ,EAAE,OAAO,CAAC,YAAY,CAAC,WAAW,EAAE;YAC5C,uFAAuF;YACvF,oFAAoF;YACpF,qCAAqC;YACrC,OAAO,EAAE,OAAO,CAAC,qBAAqB,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO;SAC5D,CAAC;QACF,IAAI,OAAO,CAAC,mBAAmB;YAAE,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC,mBAAmB,CAAC;QAC7E,IAAI,OAAO,CAAC,qBAAqB,EAAE,CAAC;YAChC,IAAI,CAAC,cAAc,GAAG,OAAO,CAAC,qBAAqB,CAAC;YACpD,IAAI,CAAC,WAAW,GAAG,MAAM,CAAC;QAC9B,CAAC;QACD,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,QAAQ,IAAI,EAAE,CAAC;YAAE,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACxF,IAAI,OAAO,CAAC,aAAa;YAAE,IAAI,CAAC,yBAAyB,CAAC,GAAG,OAAO,CAAC,aAAa,CAAC;QACnF,IAAI,CAAC,kBAAkB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QAEvC,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,kBAAkB,EAAE,IAAI,EAAE,OAAO,CAAC,cAAc,CAAC,CAAC;QACzF,IAAI,CAAC,MAAM,CAAC,EAAE;YAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC;QAEjE,OAAO;YACH,OAAO,EAAE,IAAI;YACb,gBAAgB,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;YACxC,MAAM,EAAE,qBAAqB,CAAC,MAAM,CAAC,IAAI,CAAC,MAAgB,CAAC;YAC3D,YAAY,EAAE,MAAM,CAAC,IAAI,CAAC,aAAmC;SAChE,CAAC;IACN,CAAC;IAEe,KAAK,CAAC,cAAc,CAAC,OAA8B;QAC/D,IAAI,CAAC,OAAO,CAAC,gBAAgB,EAAE,CAAC;YAC5B,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,+CAA+C,EAAE,CAAC;QACvF,CAAC;QACD,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACf,6EAA6E;YAC7E,yDAAyD;YACzD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,gBAAgB,EAAE,CAAC;QAC5D,CAAC;QACD,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,IAAI,CAC1B,KAAK,EACL,oBAAoB,kBAAkB,CAAC,OAAO,CAAC,gBAAgB,CAAC,EAAE,CACrE,CAAC;QACF,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;YACb,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC;QACrD,CAAC;QACD,MAAM,QAAQ,GAAG,CAAE,MAAM,CAAC,IAAI,CAAC,QAAmB,IAAI,KAAK,CAAC,CAAC,WAAW,EAAE,CAAC;QAC3E,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,eAAe,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC;QAC7E,OAAO;YACH,OAAO,EAAE,IAAI;YACb,MAAM,EAAE,qBAAqB,CAAC,MAAM,CAAC,IAAI,CAAC,MAAgB,CAAC;YAC3D,MAAM,EAAE,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,SAAS;SAC5F,CAAC;IACN,CAAC;IAED,8EAA8E;IAE9D,KAAK,CAAC,OAAO,CAAC,OAAuB;QACjD,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACf,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,CAAC,CAAC;YACnC,OAAO;gBACH,OAAO,EAAE,IAAI;gBACb,MAAM,EAAE,MAAM;gBACd,uFAAuF;gBACvF,qFAAqF;gBACrF,iEAAiE;gBACjE,SAAS,EAAE,IAAI,CAAC,UAAU,CAAC,MAAM,EAAE,OAAO,CAAC,YAAY,CAAC;gBACxD,gBAAgB,EAAE,WAAW,OAAO,CAAC,gBAAgB,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,EAAE;gBAClE,MAAM,EAAE,WAAW;aACtB,CAAC;QACN,CAAC;QAED,MAAM,IAAI,GAA2B,EAAE,CAAC;QACxC,IAAI,OAAO,CAAC,MAAM,IAAI,IAAI,EAAE,CAAC;YACzB,IAAI,CAAC,iBAAiB,GAAG,MAAM,CAAC,YAAY,CAAC,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC;QACxF,CAAC;QACD,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,IAAI,CAC5B,MAAM,EACN,oBAAoB,kBAAkB,CAAC,OAAO,CAAC,gBAAgB,CAAC,UAAU,EAC1E,IAAI,CACP,CAAC;QACF,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACf,iFAAiF;YACjF,4EAA4E;YAC5E,8EAA8E;YAC9E,IAAI,6BAA6B,CAAC,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;gBAChE,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,IAAI,CAC7B,KAAK,EACL,oBAAoB,kBAAkB,CAAC,OAAO,CAAC,gBAAgB,CAAC,EAAE,CACrE,CAAC;gBACF,IAAI,SAAS,CAAC,EAAE,EAAE,CAAC;oBACf,MAAM,eAAe,GAAG,qBAAqB,CAAC,SAAS,CAAC,IAAI,CAAC,MAAgB,CAAC,CAAC;oBAC/E,IAAI,eAAe,KAAK,WAAW,EAAE,CAAC;wBAClC,OAAO,IAAI,CAAC,uBAAuB,CAAC,SAAS,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;oBACjE,CAAC;gBACL,CAAC;YACL,CAAC;YACD,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,EAAE,CAAC;QACvD,CAAC;QAED,MAAM,MAAM,GAAG,qBAAqB,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAgB,CAAC,CAAC;QACrE,IAAI,MAAM,KAAK,WAAW,EAAE,CAAC;YACzB,0FAA0F;YAC1F,mFAAmF;YACnF,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,kCAAkC,QAAQ,CAAC,IAAI,CAAC,MAAM,kBAAkB,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;QAChI,CAAC;QAED,OAAO,IAAI,CAAC,uBAAuB,CAAC,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IAChE,CAAC;IAED,8EAA8E;IAE9D,KAAK,CAAC,MAAM,CAAC,OAAsB;QAC/C,MAAM,MAAM,GAAG,OAAO,CAAC,gBAAgB,IAAI,OAAO,CAAC,gBAAgB,CAAC;QACpE,IAAI,CAAC,MAAM,EAAE,CAAC;YACV,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,2DAA2D,EAAE,CAAC;QACnG,CAAC;QAED,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACf,OAAO;gBACH,OAAO,EAAE,IAAI;gBACb,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,CAAC;gBAC3B,gBAAgB,EAAE,WAAW,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,EAAE;aACnD,CAAC;QACN,CAAC;QAED,MAAM,IAAI,GAA2B,EAAE,CAAC;QACxC,IAAI,OAAO,CAAC,gBAAgB;YAAE,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;;YAChE,IAAI,CAAC,cAAc,GAAG,OAAO,CAAC,gBAAiB,CAAC;QACrD,IAAI,OAAO,CAAC,MAAM,IAAI,IAAI;YAAE,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC,YAAY,CAAC,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC;QACrG,IAAI,OAAO,CAAC,MAAM;YAAE,IAAI,CAAC,MAAM,GAAG,uBAAuB,CAAC;QAE1D,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,OAAO,CAAC,cAAc,CAAC,CAAC;QACjF,IAAI,CAAC,MAAM,CAAC,EAAE;YAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC;QAEjE,MAAM,QAAQ,GAAI,MAAM,CAAC,IAAI,CAAC,QAAmB,IAAI,OAAO,CAAC,YAAY,CAAC;QAC1E,OAAO;YACH,OAAO,EAAE,IAAI;YACb,MAAM,EAAE,cAAc,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,IAAI,CAAC,CAAC,EAAE,QAAQ,CAAC;YACjE,gBAAgB,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;SAC3C,CAAC;IACN,CAAC;IAED,8EAA8E;IAE9D,KAAK,CAAC,aAAa,CAC/B,OAAe,EACf,OAA2C;QAE3C,0EAA0E;QAC1E,MAAM,SAAS,GAAG,OAAO,CAAC,kBAAkB,CAAC,IAAI,OAAO,CAAC,kBAAkB,CAAC,CAAC;QAC7E,OAAO,sBAAsB,CAAC,OAAO,EAAE,SAAS,EAAE,IAAI,CAAC,WAAW,EAAE,aAAa,CAAC,CAAC;IACvF,CAAC;IAEe,iBAAiB,CAAC,OAAe;QAC7C,IAAI,MAAe,CAAC;QACpB,IAAI,CAAC;YACD,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QACjC,CAAC;QAAC,MAAM,CAAC;YACL,OAAO,IAAI,CAAC;QAChB,CAAC;QAED,4FAA4F;QAC5F,6FAA6F;QAC7F,8FAA8F;QAC9F,6FAA6F;QAC7F,oEAAoE;QACpE,EAAE;QACF,8FAA8F;QAC9F,gFAAgF;QAChF,IAAI,MAAM,KAAK,IAAI,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;YAAE,OAAO,IAAI,CAAC;QACxF,MAAM,OAAO,GAAG,MAAiC,CAAC;QAElD,MAAM,IAAI,GAAG,OAAO,CAAC,IAA0B,CAAC;QAChD,MAAM,MAAM,GAAG,CAAE,OAAO,CAAC,IAAgC,EAAE,MAAM,IAAI,EAAE,CAA4B,CAAC;QACpG,IAAI,CAAC,OAAO,CAAC,EAAE,IAAI,CAAC,IAAI;YAAE,OAAO,IAAI,CAAC;QAEtC,MAAM,QAAQ,GAAG,CAAE,MAAM,CAAC,QAAmB,IAAI,KAAK,CAAC,CAAC,WAAW,EAAE,CAAC;QACtE,MAAM,KAAK,GAAiB,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,YAAY,EAAE,QAAQ,EAAE,CAAC;QAChG,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;QACxC,IAAI,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,OAAO,GAAG,CAAC,EAAE,CAAC;YAC1C,KAAK,CAAC,UAAU,GAAG,IAAI,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC;QAChD,CAAC;QAED,IAAI,IAAI,CAAC,UAAU,CAAC,iBAAiB,CAAC,EAAE,CAAC;YACrC,KAAK,CAAC,gBAAgB,GAAG,MAAM,CAAC,EAAwB,CAAC;YACzD,KAAK,CAAC,gBAAgB,GAAG,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,IAAI,SAAS,CAAC;YAChE,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC,eAAe,IAAI,MAAM,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC;YACnE,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC;gBAAE,KAAK,CAAC,MAAM,GAAG,cAAc,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;YACxF,KAAK,CAAC,MAAM;gBACR,IAAI,KAAK,+BAA+B;oBACpC,CAAC,CAAC,QAAQ;oBACV,CAAC,CAAC,qBAAqB,CAAC,MAAM,CAAC,MAAgB,CAAC,CAAC;YACzD,MAAM,OAAO,GAAG,MAAM,CAAC,kBAAyD,CAAC;YACjF,IAAI,OAAO,EAAE,OAAO;gBAAE,KAAK,CAAC,aAAa,GAAG,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;QACxE,CAAC;aAAM,IAAI,IAAI,KAAK,iBAAiB,EAAE,CAAC;YACpC,KAAK,CAAC,gBAAgB,GAAG,MAAM,CAAC,EAAwB,CAAC;YACzD,KAAK,CAAC,gBAAgB,GAAG,MAAM,CAAC,cAAoC,CAAC;YACrE,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,CAAC,eAAe,IAAI,CAAC,CAAC,CAAC;YACrD,IAAI,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,QAAQ,GAAG,CAAC;gBAAE,KAAK,CAAC,MAAM,GAAG,cAAc,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;QACrG,CAAC;QAED,OAAO,KAAK,CAAC;IACjB,CAAC;IAED,8EAA8E;IAE9E;;;;;;;;OAQG;IACO,KAAK,CAAC,IAAI,CAChB,MAAsB,EACtB,IAAY,EACZ,IAA6B,EAC7B,cAAuB;QAEvB,MAAM,MAAM,GAAG,IAAI,CAAC,WAAW,EAAE,MAAM,CAAC;QACxC,IAAI,CAAC,MAAM,EAAE,CAAC;YACV,OAAO;gBACH,EAAE,EAAE,KAAK;gBACT,IAAI,EAAE,EAAE;gBACR,MAAM,EACF,gDAAgD,IAAI,CAAC,MAAM,EAAE,IAAI,IAAI,IAAI,CAAC,MAAM,EAAE,EAAE,KAAK;oBACzF,yEAAyE;aAChF,CAAC;QACN,CAAC;QAED,MAAM,OAAO,GAA2B;YACpC,aAAa,EAAE,UAAU,MAAM,EAAE;YACjC,cAAc,EAAE,mCAAmC;YACnD,uFAAuF;YACvF,sFAAsF;YACtF,UAAU;YACV,gBAAgB,EAAE,YAAY;SACjC,CAAC;QACF,IAAI,cAAc;YAAE,OAAO,CAAC,iBAAiB,CAAC,GAAG,cAAc,CAAC;QAEhE,IAAI,QAAkB,CAAC;QACvB,IAAI,CAAC;YACD,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,UAAU,GAAG,IAAI,EAAE,EAAE;gBAC3C,MAAM;gBACN,OAAO;gBACP,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS;aAC5C,CAAC,CAAC;QACP,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACX,2FAA2F;YAC3F,gDAAgD;YAChD,MAAM,IAAI,KAAK,CAAC,kCAAkC,MAAM,IAAI,IAAI,KAAM,GAAa,CAAC,OAAO,EAAE,CAAC,CAAC;QACnG,CAAC;QAED,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;QACnC,IAAI,MAAM,GAA4B,EAAE,CAAC;QACzC,IAAI,CAAC;YACD,MAAM,GAAG,IAAI,CAAC,CAAC,CAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAA6B,CAAC,CAAC,CAAC,EAAE,CAAC;QACvE,CAAC;QAAC,MAAM,CAAC;YACL,MAAM,GAAG,EAAE,CAAC;QAChB,CAAC;QAED,IAAI,QAAQ,CAAC,EAAE;YAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;QAEnD,IAAI,QAAQ,CAAC,MAAM,IAAI,GAAG,EAAE,CAAC;YACzB,MAAM,IAAI,KAAK,CAAC,mBAAmB,QAAQ,CAAC,MAAM,YAAY,MAAM,IAAI,IAAI,iCAAiC,CAAC,CAAC;QACnH,CAAC;QAED,MAAM,KAAK,GAAG,CAAC,MAAM,CAAC,KAAK,IAAI,EAAE,CAA4B,CAAC;QAC9D,MAAM,MAAM,GAAI,KAAK,CAAC,OAAkB,IAAI,mBAAmB,QAAQ,CAAC,MAAM,mBAAmB,CAAC;QAClG,QAAQ,CAAC,UAAU,MAAM,IAAI,IAAI,aAAa,MAAM,EAAE,CAAC,CAAC;QACxD,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;IACvD,CAAC;IAED;;;;OAIG;IACK,KAAK,CAAC,uBAAuB,CACjC,IAA6B,EAC7B,OAAuB;QAEvB,MAAM,QAAQ,GAAI,IAAI,CAAC,QAAmB,IAAI,OAAO,CAAC,YAAY,CAAC;QACnE,MAAM,WAAW,GAAG,MAAM,CAAC,IAAI,CAAC,eAAe,IAAI,IAAI,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC;QACrE,MAAM,MAAM,GAAG,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;QACvC,OAAO;YACH,OAAO,EAAE,IAAI;YACb,MAAM,EAAE,cAAc,CAAC,WAAW,EAAE,QAAQ,CAAC;YAC7C,0FAA0F;YAC1F,4FAA4F;YAC5F,0FAA0F;YAC1F,qBAAqB;YACrB,SAAS,EAAE,MAAM,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC;YAC9C,gBAAgB,EAAE,MAAM,IAAI,SAAS;YACrC,MAAM,EAAE,WAAW;SACtB,CAAC;IACN,CAAC;IAED,oGAAoG;IAC1F,YAAY,CAAC,MAA+B;QAClD,MAAM,MAAM,GAAG,MAAM,CAAC,aAAa,CAAC;QACpC,IAAI,OAAO,MAAM,KAAK,QAAQ;YAAE,OAAO,MAAM,CAAC;QAC9C,IAAI,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ;YAAE,OAAS,MAAkC,CAAC,EAAa,IAAI,IAAI,CAAC;QAC5G,MAAM,OAAO,GAAG,MAAM,CAAC,OAA8C,CAAC;QACtE,MAAM,KAAK,GAAI,OAAO,EAAE,IAAmD,EAAE,CAAC,CAAC,CAAC,CAAC;QACjF,OAAQ,KAAK,EAAE,EAAa,IAAI,IAAI,CAAC;IACzC,CAAC;IAED;;;;;;;OAOG;IACO,KAAK,CAAC,MAAM,CAAC,QAAuB,EAAE,QAAgB;QAC5D,IAAI,CAAC,QAAQ;YAAE,OAAO,SAAS,CAAC;QAChC,IAAI,CAAC;YACD,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,YAAY,kBAAkB,CAAC,QAAQ,CAAC,+BAA+B,CAAC,CAAC;YAC/G,IAAI,CAAC,MAAM,CAAC,EAAE;gBAAE,OAAO,SAAS,CAAC;YACjC,MAAM,EAAE,GAAG,MAAM,CAAC,IAAI,CAAC,mBAAmE,CAAC;YAC3F,IAAI,CAAC,EAAE,IAAI,OAAO,EAAE,KAAK,QAAQ;gBAAE,OAAO,SAAS,CAAC;YACpD,MAAM,GAAG,GAAG,MAAM,CAAE,EAA8B,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC;YAC/D,OAAO,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QAC5E,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACX,QAAQ,CAAC,4CAA4C,QAAQ,KAAM,GAAa,CAAC,OAAO,EAAE,CAAC,CAAC;YAC5F,OAAO,SAAS,CAAC;QACrB,CAAC;IACL,CAAC;CACJ,CAAA;AA7YY,qBAAqB;IADjC,aAAa,CAAC,mBAAmB,EAAE,QAAQ,CAAC;GAChC,qBAAqB,CA6YjC;;AAED;;;;;;GAMG;AACH,MAAM,UAAU,UAAU,CAAC,MAA8B;IACrD,MAAM,MAAM,GAAG,IAAI,eAAe,EAAE,CAAC;IACrC,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QAChD,IAAI,KAAK,IAAI,IAAI;YAAE,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;IACzD,CAAC;IACD,OAAO,MAAM,CAAC,QAAQ,EAAE,CAAC;AAC7B,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,6BAA6B,CACzC,MAA0B,EAC1B,IAA6B;IAE7B,MAAM,KAAK,GAAG,CAAC,IAAI,EAAE,KAAK,IAAI,EAAE,CAA4B,CAAC;IAC7D,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC;IACtC,MAAM,GAAG,GAAG,GAAG,MAAM,IAAI,EAAE,IAAI,MAAM,CAAC,KAAK,CAAC,OAAO,IAAI,EAAE,CAAC,EAAE,CAAC,WAAW,EAAE,CAAC;IAC3E,OAAO,CACH,IAAI,KAAK,iCAAiC;QAC1C,GAAG,CAAC,QAAQ,CAAC,uBAAuB,CAAC;QACrC,GAAG,CAAC,QAAQ,CAAC,mBAAmB,CAAC,CACpC,CAAC;AACN,CAAC;AAED,0FAA0F;AAC1F,MAAM,UAAU,yBAAyB;IACrC,sBAAsB;AAC1B,CAAC"}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/** The rules row, as plain data. */
|
|
2
|
+
export interface SubscriptionTypeRules {
|
|
3
|
+
ID: string;
|
|
4
|
+
Code: string;
|
|
5
|
+
DriverClass?: string | null;
|
|
6
|
+
SubscriberScope: 'Organization' | 'Person' | 'Either';
|
|
7
|
+
/**
|
|
8
|
+
* WHO derives the benefit — a different question from who HOLDS it, which `SubscriberScope`
|
|
9
|
+
* answers.
|
|
10
|
+
*
|
|
11
|
+
* `Holder` is NOT redundant with `Individual`, though it looks it: it means the benefit follows
|
|
12
|
+
* whoever holds the subscription, which is what a `SubscriberScope='Either'` type needs. Forcing
|
|
13
|
+
* those to `Individual` makes them demand a named person and breaks org purchases outright.
|
|
14
|
+
*/
|
|
15
|
+
BenefitModel: 'Holder' | 'Individual' | 'Organization';
|
|
16
|
+
StartMode: 'Immediate' | 'Deferred' | 'CalendarAnchored';
|
|
17
|
+
DeferredStartDays?: number | null;
|
|
18
|
+
AnchorMonth?: number | null;
|
|
19
|
+
AnchorDay?: number | null;
|
|
20
|
+
PartialPeriodMode?: 'Prorate' | 'ChargeFull' | 'ExtendToNextAnchor' | null;
|
|
21
|
+
DefaultTermMonths?: number | null;
|
|
22
|
+
BillingCadence: 'Monthly' | 'Quarterly' | 'Annual' | 'Custom';
|
|
23
|
+
RecognitionCadence: 'Monthly' | 'Quarterly' | 'Annual' | 'MatchBilling';
|
|
24
|
+
CustomCycleDays?: number | null;
|
|
25
|
+
TrialDays: number;
|
|
26
|
+
ConcurrencyMode: 'AllowMultiple' | 'ExtendExisting' | 'RejectDuplicate';
|
|
27
|
+
ReactivationMode: 'ReactivateExisting' | 'AlwaysCreateNew' | 'ReactivateWithinWindow';
|
|
28
|
+
ReactivationWindowDays?: number | null;
|
|
29
|
+
AutoRenewDefault: boolean;
|
|
30
|
+
RenewalLeadDays: number;
|
|
31
|
+
CancellationMode: 'Immediate' | 'EndOfTerm' | 'EndOfBillingPeriod';
|
|
32
|
+
CancellationRefundMode: 'NoRefund' | 'ProrateUnused' | 'FullRefundWithinWindow';
|
|
33
|
+
CancellationWindowDays?: number | null;
|
|
34
|
+
GracePeriodDays: number;
|
|
35
|
+
}
|
|
36
|
+
/** An existing subscription this purchase might attach to. */
|
|
37
|
+
export interface ExistingSubscription {
|
|
38
|
+
ID: string;
|
|
39
|
+
Status: string;
|
|
40
|
+
/** Who holds it — an explicit renewal adopts these rather than re-resolving from the line. */
|
|
41
|
+
HolderOrganizationID?: string | null;
|
|
42
|
+
BeneficiaryPersonID?: string | null;
|
|
43
|
+
/** End of the latest term, if any. */
|
|
44
|
+
LatestTermEnd?: Date | null;
|
|
45
|
+
LatestTermNumber?: number | null;
|
|
46
|
+
}
|
|
47
|
+
/** What the caller must do with this purchase. */
|
|
48
|
+
export interface SubscriptionDecision {
|
|
49
|
+
Action: 'CreateNew' | 'ExtendExisting' | 'Reactivate' | 'Reject';
|
|
50
|
+
SubscriptionID?: string;
|
|
51
|
+
/** Term to create; absent only when Action = 'Reject'. */
|
|
52
|
+
Term?: {
|
|
53
|
+
StartDate: Date;
|
|
54
|
+
EndDate: Date;
|
|
55
|
+
TermNumber: number;
|
|
56
|
+
IsProrated: boolean;
|
|
57
|
+
ProrationFactor: number | null;
|
|
58
|
+
/** Line amount × proration — what this term actually costs. */
|
|
59
|
+
Amount: number;
|
|
60
|
+
};
|
|
61
|
+
/** Populated when Action = 'Reject'. */
|
|
62
|
+
RejectReason?: string;
|
|
63
|
+
}
|
|
64
|
+
/** The term being canceled, as plain data. */
|
|
65
|
+
export interface CancellableTerm {
|
|
66
|
+
StartDate: Date;
|
|
67
|
+
EndDate: Date;
|
|
68
|
+
/** What was charged for this term — the ceiling on any refund. */
|
|
69
|
+
Amount: number;
|
|
70
|
+
TermNumber: number;
|
|
71
|
+
}
|
|
72
|
+
export interface CancellationContext {
|
|
73
|
+
Rules: SubscriptionTypeRules;
|
|
74
|
+
/** When the customer asked to cancel. */
|
|
75
|
+
RequestDate: Date;
|
|
76
|
+
/** The term whose window covers the request (or the latest one). */
|
|
77
|
+
Term: CancellableTerm;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* What cancelling actually does. Like {@link SubscriptionDecision} this is COMPUTED ONLY — the
|
|
81
|
+
* caller performs the reversal, updates the rows, and logs the event.
|
|
82
|
+
*/
|
|
83
|
+
export interface CancellationDecision {
|
|
84
|
+
/** When coverage ends for revenue purposes. Never before the request, never after the term. */
|
|
85
|
+
EffectiveDate: Date;
|
|
86
|
+
/** When ACCESS ends. Equals EffectiveDate plus GracePeriodDays — grace extends access, not revenue. */
|
|
87
|
+
AccessThroughDate: Date;
|
|
88
|
+
/** What to give back. 0 under NoRefund, and never more than the term charged. */
|
|
89
|
+
RefundAmount: number;
|
|
90
|
+
/**
|
|
91
|
+
* Fraction of the term to reverse — the reversal order line's quantity is the NEGATIVE of this
|
|
92
|
+
* (plan D16). Derived from RefundAmount so the reversed revenue and the refunded cash always
|
|
93
|
+
* agree; a term cancelled with no refund reverses nothing.
|
|
94
|
+
*/
|
|
95
|
+
ReversalFraction: number;
|
|
96
|
+
/**
|
|
97
|
+
* `Canceled` when coverage is cut short, `Completed` when the customer simply declines renewal
|
|
98
|
+
* and rides the term out — the difference matters for "why did this lapse" reporting.
|
|
99
|
+
*/
|
|
100
|
+
TermStatus: 'Canceled' | 'Completed';
|
|
101
|
+
/** Which rules produced this, in a sentence. Surfaced to the user, so it names the policy. */
|
|
102
|
+
Explanation: string;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* The two roles a subscription has, which `SubscriberScope` alone could not express.
|
|
106
|
+
*
|
|
107
|
+
* A trade-association company membership has an organization and no person — every employee
|
|
108
|
+
* benefits by virtue of the company holding it. An individual membership has a person. A corporate
|
|
109
|
+
* seat has both: the org holds and pays, the person benefits.
|
|
110
|
+
*/
|
|
111
|
+
export interface SubscriberIdentity {
|
|
112
|
+
OrganizationID?: string | null;
|
|
113
|
+
PersonID?: string | null;
|
|
114
|
+
}
|
|
115
|
+
export interface SubscriptionPurchaseContext {
|
|
116
|
+
Rules: SubscriptionTypeRules;
|
|
117
|
+
/** Order date — when the purchase happened. */
|
|
118
|
+
PurchaseDate: Date;
|
|
119
|
+
/** The line's net amount before proration. */
|
|
120
|
+
Amount: number;
|
|
121
|
+
/** The active/most-recent subscription for (product, subscriber), if one exists. */
|
|
122
|
+
Existing?: ExistingSubscription | null;
|
|
123
|
+
/**
|
|
124
|
+
* WHO this purchase is for, already resolved from the line's ship-to falling back to the
|
|
125
|
+
* order header. Both sides may be present: a corporate seat is held by the org and benefits a
|
|
126
|
+
* named person.
|
|
127
|
+
*/
|
|
128
|
+
Subscriber: SubscriberIdentity;
|
|
129
|
+
/**
|
|
130
|
+
* True when this purchase is a RENEWAL of `Existing` rather than a fresh buy (D55).
|
|
131
|
+
*
|
|
132
|
+
* Renewals bypass `ConcurrencyMode` deliberately. That rule answers "may this subscriber hold a
|
|
133
|
+
* SECOND concurrent subscription?" — and a renewal is not a second one, it is the same one
|
|
134
|
+
* continuing. Without this, a `RejectDuplicate` type would refuse to renew itself: the engine
|
|
135
|
+
* would find an active subscription, apply the concurrency rule, and reject its own renewal
|
|
136
|
+
* order every cycle.
|
|
137
|
+
*/
|
|
138
|
+
IsRenewal?: boolean;
|
|
139
|
+
}
|
|
140
|
+
export declare class SubscriptionBehavior {
|
|
141
|
+
/**
|
|
142
|
+
* The one entry point: given the rules and what already exists, decide what to do and compute
|
|
143
|
+
* the term. Override individual `protected` members below to customize a single aspect.
|
|
144
|
+
*/
|
|
145
|
+
Decide(rawContext: SubscriptionPurchaseContext): SubscriptionDecision;
|
|
146
|
+
protected ValidateSubscriberScope(ctx: SubscriptionPurchaseContext): string | null;
|
|
147
|
+
/**
|
|
148
|
+
* The identity a duplicate is judged against — the heart of the concurrency rule.
|
|
149
|
+
*
|
|
150
|
+
* `Organization` keys on the ORG alone: a company holds ONE membership however many
|
|
151
|
+
* employees benefit, so a second purchase extends it. `Individual` keys on the PAIR, so
|
|
152
|
+
* ten seats bought for ten staff on one order are ten distinct subscriptions rather than ten
|
|
153
|
+
* collisions with each other. Getting this wrong is what made a bulk seat purchase impossible.
|
|
154
|
+
*/
|
|
155
|
+
DedupeIdentity(rules: SubscriptionTypeRules, subscriber: SubscriberIdentity): SubscriberIdentity;
|
|
156
|
+
protected ChooseAction(ctx: SubscriptionPurchaseContext): SubscriptionDecision['Action'];
|
|
157
|
+
/** Immediate / Deferred / snap-forward to the calendar anchor. */
|
|
158
|
+
protected ComputeStartDate(ctx: SubscriptionPurchaseContext): Date;
|
|
159
|
+
protected ComputeEndDate(ctx: SubscriptionPurchaseContext, start: Date, extending: boolean): Date;
|
|
160
|
+
/**
|
|
161
|
+
* Only `Prorate` actually reduces the amount. `ChargeFull` deliberately charges a whole term for
|
|
162
|
+
* a partial window — the customer pays full freight to join mid-cycle — and
|
|
163
|
+
* `ExtendToNextAnchor` has no partial window at all.
|
|
164
|
+
*/
|
|
165
|
+
protected ComputeProration(ctx: SubscriptionPurchaseContext, start: Date, end: Date, extending: boolean): {
|
|
166
|
+
isProrated: boolean;
|
|
167
|
+
factor: number;
|
|
168
|
+
};
|
|
169
|
+
/** The next occurrence of AnchorMonth/AnchorDay strictly after `from`. */
|
|
170
|
+
protected NextAnchor(ctx: SubscriptionPurchaseContext, from: Date): Date;
|
|
171
|
+
protected MonthsForCadence(cadence: SubscriptionTypeRules['BillingCadence'], customDays?: number | null): number;
|
|
172
|
+
/** Months per recognition slice — `MatchBilling` follows the billing cadence. */
|
|
173
|
+
RecognitionMonths(rules: SubscriptionTypeRules): number;
|
|
174
|
+
/**
|
|
175
|
+
* Apply the type's cancellation rules to a request. Pure — computes dates and amounts, touches
|
|
176
|
+
* nothing. Override any protected piece below to change one aspect.
|
|
177
|
+
*/
|
|
178
|
+
DecideCancellation(rawContext: CancellationContext): CancellationDecision;
|
|
179
|
+
/** When coverage ends, per `CancellationMode`. */
|
|
180
|
+
protected ComputeCancellationDate(ctx: CancellationContext): Date;
|
|
181
|
+
/**
|
|
182
|
+
* The last day of the billing cycle the request falls in. Cycles are counted forward from the
|
|
183
|
+
* TERM START, not from the request — a monthly subscription started on the 12th bills on the
|
|
184
|
+
* 12th, and cancelling on the 20th runs to the 11th of the next month.
|
|
185
|
+
*/
|
|
186
|
+
protected EndOfBillingPeriod(ctx: CancellationContext): Date;
|
|
187
|
+
/** Cancellation never resurrects a finished term, and never ends coverage before it began. */
|
|
188
|
+
protected ClampToTerm(ctx: CancellationContext, date: Date): Date;
|
|
189
|
+
/** What to give back, per `CancellationRefundMode`. */
|
|
190
|
+
protected ComputeRefund(ctx: CancellationContext, effective: Date): {
|
|
191
|
+
amount: number;
|
|
192
|
+
explanation: string;
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
/** Tree-shaking anchor — the base behaviour must be registered before booking runs. */
|
|
196
|
+
export declare function LoadSubscriptionBehavior(): void;
|
|
197
|
+
//# sourceMappingURL=SubscriptionBehavior.d.ts.map
|