@saasicat/nest 1.0.0-rc.2 → 1.0.0-rc.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +44 -8
- package/dist/.build-stamp +1 -1
- package/dist/_entries.cjs +10214 -4796
- package/dist/admin/index.d.cts +5 -4
- package/dist/admin/index.d.ts +5 -4
- package/dist/admin/index.js +13 -9
- package/dist/{admin-resources.module-CpvzcfCc.d.cts → admin-resources.module-B4XNvdH-.d.cts} +1 -2
- package/dist/{admin-resources.module-DF5_Q7RD.d.ts → admin-resources.module-CWqdI9BV.d.ts} +1 -2
- package/dist/{admin-stats.service-TYiSUwIR.d.ts → admin-stats.service-CsLNt19Z.d.ts} +11 -7
- package/dist/{admin-stats.service-DfKupywR.d.cts → admin-stats.service-DrZrU8IJ.d.cts} +11 -7
- package/dist/{aggregation-B3CD0v_d.d.cts → aggregation-ChpZDh0g.d.ts} +39 -6
- package/dist/{aggregation-BFzW07DE.d.ts → aggregation-OpnhwzF5.d.cts} +39 -6
- package/dist/billing/index.d.cts +448 -108
- package/dist/billing/index.d.ts +448 -108
- package/dist/billing/index.js +108 -41
- package/dist/catalog/index.d.cts +74 -45
- package/dist/catalog/index.d.ts +74 -45
- package/dist/catalog/index.js +21 -13
- package/dist/catalog.module-C9O7BxRq.d.ts +180 -0
- package/dist/catalog.module-T-I5Ricz.d.cts +180 -0
- package/dist/checkout-offer/index.d.cts +17 -22
- package/dist/checkout-offer/index.d.ts +17 -22
- package/dist/checkout-offer/index.js +21 -7
- package/dist/checkout-offer.module-DFhGy6JO.d.cts +58 -0
- package/dist/checkout-offer.module-x7SP_dX4.d.ts +58 -0
- package/dist/checkout-offer.service-BeHgYikI.d.ts +242 -0
- package/dist/checkout-offer.service-CCVxG9b2.d.cts +242 -0
- package/dist/chunk-34XVPR6P.js +222 -0
- package/dist/{chunk-AOQJEYOL.js → chunk-3QVGA6JX.js} +3 -1
- package/dist/chunk-3RLWML2S.js +896 -0
- package/dist/{chunk-TXC3LHHB.js → chunk-4SDSYV4A.js} +21 -13
- package/dist/chunk-55QEDAMX.js +946 -0
- package/dist/chunk-5CKC7O52.js +140 -0
- package/dist/chunk-6PJE7EWO.js +185 -0
- package/dist/chunk-7NJKVC5X.js +353 -0
- package/dist/{chunk-RUNZ3X4C.js → chunk-AOS2JPDD.js} +1263 -185
- package/dist/{chunk-WOVQPXV4.js → chunk-CQ2ZZMTD.js} +702 -725
- package/dist/{chunk-NA6O63B7.js → chunk-F5X66HF5.js} +5 -5
- package/dist/chunk-FEQZCWUL.js +174 -0
- package/dist/{chunk-VIJ5NJWC.js → chunk-G6EZWECL.js} +107 -31
- package/dist/chunk-G7RQO2XO.js +0 -0
- package/dist/{chunk-N3L3AICU.js → chunk-GPQWGA6B.js} +5 -6
- package/dist/chunk-GZTL64QK.js +7 -0
- package/dist/{chunk-SABTXESR.js → chunk-J7NJRK3K.js} +17 -1
- package/dist/{chunk-SEPN52AD.js → chunk-JBCC6C3M.js} +566 -307
- package/dist/{chunk-I7GTA3RX.js → chunk-JQRA724W.js} +1 -1
- package/dist/{chunk-WXYJHZCN.js → chunk-JVAEKTJ4.js} +2 -14
- package/dist/chunk-KPEMTBOP.js +26 -0
- package/dist/chunk-LF6J4YYN.js +301 -0
- package/dist/{chunk-LTT736P3.js → chunk-LLYVYRGJ.js} +228 -387
- package/dist/{chunk-6Z7JR4EW.js → chunk-M47BNEY2.js} +1897 -633
- package/dist/{chunk-KFT5AIIH.js → chunk-NDLC5GYK.js} +206 -24
- package/dist/{chunk-O2J2HDXA.js → chunk-OOTEXP47.js} +3 -8
- package/dist/chunk-QPVDCKYS.js +702 -0
- package/dist/{chunk-NHVDCYK5.js → chunk-R5YCJBHY.js} +71 -106
- package/dist/{chunk-XBYAFEOR.js → chunk-S33SO5XX.js} +1 -1
- package/dist/chunk-SZ7RFPXA.js +10 -0
- package/dist/chunk-TBBZWZQT.js +98 -0
- package/dist/chunk-TOEFN7DN.js +295 -0
- package/dist/{chunk-7ZEGFL42.js → chunk-WHJSYKNC.js} +6 -6
- package/dist/chunk-WOEJ6K7M.js +55 -0
- package/dist/{chunk-AU3OOREM.js → chunk-XSSWYPP5.js} +16 -3
- package/dist/contract-line-item-money-B0Z3Ogur.d.cts +43 -0
- package/dist/contract-line-item-money-B0Z3Ogur.d.ts +43 -0
- package/dist/discovery/index.d.cts +2 -2
- package/dist/discovery/index.d.ts +2 -2
- package/dist/discovery/index.js +6 -6
- package/dist/{discovery.scanner-9cMqF95o.d.cts → discovery.scanner-DXKc6JkV.d.cts} +1 -1
- package/dist/{discovery.scanner-9cMqF95o.d.ts → discovery.scanner-DXKc6JkV.d.ts} +1 -1
- package/dist/{enforce-quota.interceptor-Df0e3OWe.d.cts → enforce-quota.interceptor-BFyVdWe8.d.cts} +6 -6
- package/dist/{enforce-quota.interceptor-DW3etVyI.d.ts → enforce-quota.interceptor-GaBXboGx.d.ts} +6 -6
- package/dist/entitlement/index.d.cts +27 -4
- package/dist/entitlement/index.d.ts +27 -4
- package/dist/entitlement/index.js +13 -8
- package/dist/index.d.cts +209 -28
- package/dist/index.d.ts +209 -28
- package/dist/index.js +285 -163
- package/dist/issuer-identity.check-CBO0vcsq.d.cts +197 -0
- package/dist/issuer-identity.check-Cz9CEMkZ.d.ts +197 -0
- package/dist/{module-options-COSEb0VW.d.ts → module-options-DmQ3G4sZ.d.ts} +135 -41
- package/dist/{module-options-BG3MzQva.d.cts → module-options-qAOwinHR.d.cts} +135 -41
- package/dist/payment-callback.service-CR84Xw1Z.d.cts +125 -0
- package/dist/payment-callback.service-CR84Xw1Z.d.ts +125 -0
- package/dist/payments/index.cjs +4 -0
- package/dist/payments/index.d.cts +187 -0
- package/dist/payments/index.d.ts +187 -0
- package/dist/payments/index.js +141 -0
- package/dist/payments.module-BdUq9ot_.d.cts +57 -0
- package/dist/payments.module-CQGv_VG7.d.ts +57 -0
- package/dist/plan-catalog-source-DWe-BGY1.d.cts +21 -0
- package/dist/plan-catalog-source-DWe-BGY1.d.ts +21 -0
- package/dist/{plan-resolution-Cgo_TR1H.d.cts → plan-resolution-CwUy_OqC.d.cts} +14 -0
- package/dist/{plan-resolution-Cgo_TR1H.d.ts → plan-resolution-CwUy_OqC.d.ts} +14 -0
- package/dist/{plan-versions.service-DChhbK6h.d.ts → plan-versions.service-C0N-Em2W.d.ts} +38 -64
- package/dist/{plan-versions.service-TPEtbN0r.d.cts → plan-versions.service-DARLh-qI.d.cts} +38 -64
- package/dist/platform/index.d.cts +52 -23
- package/dist/platform/index.d.ts +52 -23
- package/dist/platform/index.js +57 -36
- package/dist/promo/index.d.cts +23 -8
- package/dist/promo/index.d.ts +23 -8
- package/dist/promo/index.js +15 -7
- package/dist/{promo.module-DtvIycpt.d.cts → promo.module-CKkECmU8.d.cts} +9 -3
- package/dist/{promo.module-Z8h1PNX-.d.ts → promo.module-DuIJdK3U.d.ts} +9 -3
- package/dist/promo.service-BYBu6dy3.d.cts +215 -0
- package/dist/promo.service-CFjfF0Xm.d.ts +215 -0
- package/dist/registration/index.d.cts +104 -47
- package/dist/registration/index.d.ts +104 -47
- package/dist/registration/index.js +15 -9
- package/dist/subscriber/index.cjs +4 -0
- package/dist/subscriber/index.d.cts +28 -0
- package/dist/subscriber/index.d.ts +28 -0
- package/dist/subscriber/index.js +16 -0
- package/dist/subscriber.service-5W6Gi-1B.d.cts +68 -0
- package/dist/subscriber.service-5W6Gi-1B.d.ts +68 -0
- package/dist/subscription-contract/index.d.cts +4 -2
- package/dist/subscription-contract/index.d.ts +4 -2
- package/dist/subscription-contract/index.js +13 -5
- package/dist/{subscription-contract.module-fDFdQ0jm.d.ts → subscription-contract.module-BfIRpc1o.d.ts} +8 -2
- package/dist/{subscription-contract.module-CEK8HS5-.d.cts → subscription-contract.module-DTMuUJYd.d.cts} +8 -2
- package/dist/{subscription-contract.service-hD87MKyg.d.cts → subscription-contract.service-CPZrTM9C.d.ts} +32 -3
- package/dist/{subscription-contract.service-hD87MKyg.d.ts → subscription-contract.service-CfNApLuP.d.cts} +32 -3
- package/dist/tenant-billing.controller-BLqbsONe.d.ts +812 -0
- package/dist/tenant-billing.controller-DKbAqTbM.d.cts +812 -0
- package/dist/tenant-billing.module-ClbjzocC.d.cts +364 -0
- package/dist/tenant-billing.module-DohgJJ2m.d.ts +364 -0
- package/dist/tenant-billing.tokens-G-1xOlMr.d.cts +129 -0
- package/dist/tenant-billing.tokens-G-1xOlMr.d.ts +129 -0
- package/dist/testing/index.d.cts +44 -17
- package/dist/testing/index.d.ts +44 -17
- package/dist/testing/index.js +189 -40
- package/package.json +24 -4
- package/dist/catalog.module-BDDO6iwq.d.cts +0 -103
- package/dist/catalog.module-BTnMsE6u.d.ts +0 -103
- package/dist/checkout-offer.module-CMtiQJTT.d.ts +0 -39
- package/dist/checkout-offer.module-CVpWrzbe.d.cts +0 -39
- package/dist/checkout-offer.service-BLOv2HOo.d.cts +0 -49
- package/dist/checkout-offer.service-BLOv2HOo.d.ts +0 -49
- package/dist/chunk-57V6ZTI6.js +0 -22
- package/dist/chunk-BSK6YBLI.js +0 -87
- package/dist/chunk-DQKCK7DX.js +0 -217
- package/dist/chunk-VXEYLNIB.js +0 -632
- package/dist/chunk-WUDYIPYH.js +0 -715
- package/dist/define-saasicat-DROBe-b1.d.ts +0 -69
- package/dist/define-saasicat-vGBCkFys.d.cts +0 -69
- package/dist/promo.service-BJbKAmw3.d.cts +0 -112
- package/dist/promo.service-BJbKAmw3.d.ts +0 -112
- package/dist/tenant-billing.controller-BQ67mlmI.d.ts +0 -438
- package/dist/tenant-billing.controller-DS8kB8a7.d.cts +0 -438
- package/dist/tenant-billing.module-BznC3XtW.d.cts +0 -299
- package/dist/tenant-billing.module-wUbsQSFG.d.ts +0 -299
- /package/dist/{chunk-2FR6ZL7R.js → chunk-BHZOH2DY.js} +0 -0
- /package/dist/{chunk-2SLTRKXC.js → chunk-DFOW3JVO.js} +0 -0
- /package/dist/{chunk-DBZGV3HC.js → chunk-DU56UKEH.js} +0 -0
- /package/dist/{chunk-OYNHOY45.js → chunk-GAUEVIAE.js} +0 -0
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import { PlanCatalogSettings, PlanRepository, BundleRepository, PromotionRepository, CheckoutOfferPriceBreakdown, CheckoutOfferLineItem, CheckoutOfferPromotionSnapshot, CheckoutOfferPromoCodeSnapshot, CheckoutOfferRow, CheckoutOfferRepository, CatalogEntryRepository, TransactionRunner, CheckoutOfferFilter, CheckoutOfferSelection, CheckoutOfferSelectionUpdate, NewSubscriberDetails, TransactionContext, SubscriptionContractRecord } from '@saasicat/core';
|
|
2
|
+
import { S as SubscriptionContractService, C as CreateContractFromOfferOptions } from './subscription-contract.service-CfNApLuP.cjs';
|
|
3
|
+
import { S as SubscriberService } from './subscriber.service-5W6Gi-1B.cjs';
|
|
4
|
+
import { a as PromoCodesService } from './promo.service-BYBu6dy3.cjs';
|
|
5
|
+
|
|
6
|
+
type Cycle = 'monthly' | 'yearly';
|
|
7
|
+
/** What pricing needs to know about a selection. */
|
|
8
|
+
interface CheckoutOfferPricingInput {
|
|
9
|
+
planKey: string;
|
|
10
|
+
billingCycle: Cycle;
|
|
11
|
+
bundleVersionIds: readonly string[];
|
|
12
|
+
promoCode: string | null;
|
|
13
|
+
locale: string;
|
|
14
|
+
/**
|
|
15
|
+
* The offer when it is priced again. A slot of its promo code held for its
|
|
16
|
+
* checkout is its own, not counted against it.
|
|
17
|
+
*/
|
|
18
|
+
checkoutOfferId?: string;
|
|
19
|
+
/**
|
|
20
|
+
* Priced as the offer is concluded: its held slot also keeps the code as it
|
|
21
|
+
* stood when the slot was taken.
|
|
22
|
+
*/
|
|
23
|
+
concluding?: boolean;
|
|
24
|
+
}
|
|
25
|
+
/** The part of a stored offer the server computes. */
|
|
26
|
+
interface PricedCheckoutOffer {
|
|
27
|
+
planVersionId: string;
|
|
28
|
+
bundles: string[];
|
|
29
|
+
bundleVersionIds: string[];
|
|
30
|
+
promotionId: string | null;
|
|
31
|
+
promoCode: string | null;
|
|
32
|
+
priceBreakdown: CheckoutOfferPriceBreakdown;
|
|
33
|
+
lineItems: CheckoutOfferLineItem[];
|
|
34
|
+
promotionSnapshots: CheckoutOfferPromotionSnapshot[];
|
|
35
|
+
promoCodeSnapshot: CheckoutOfferPromoCodeSnapshot | null;
|
|
36
|
+
}
|
|
37
|
+
declare class CheckoutOfferPricing {
|
|
38
|
+
private readonly settings;
|
|
39
|
+
private readonly plans;
|
|
40
|
+
private readonly bundles;
|
|
41
|
+
private readonly promotions;
|
|
42
|
+
private readonly promoCodes;
|
|
43
|
+
constructor(settings: PlanCatalogSettings, plans: PlanRepository, bundles?: BundleRepository | null, promotions?: PromotionRepository | null, promoCodes?: PromoCodesService | null);
|
|
44
|
+
/** Prices a selection against the plan version on sale now. */
|
|
45
|
+
price(input: CheckoutOfferPricingInput, asOf?: Date): Promise<PricedCheckoutOffer>;
|
|
46
|
+
/**
|
|
47
|
+
* Throws unless the offer's stored amounts are what this pricing makes of
|
|
48
|
+
* its own selection: the plan and bundle versions it froze, and the
|
|
49
|
+
* promotions as they stood when it was last priced.
|
|
50
|
+
*/
|
|
51
|
+
assertPricedByCatalogue(offer: CheckoutOfferRow): Promise<void>;
|
|
52
|
+
private compute;
|
|
53
|
+
/** The plan version on sale for new bookings, as the public catalogue resolves it. */
|
|
54
|
+
private planVersionOnSale;
|
|
55
|
+
/** A plan without a list price is not sold at one (`SC-PRIC-011`). */
|
|
56
|
+
private assertPlanPriced;
|
|
57
|
+
private bundleVersionsFor;
|
|
58
|
+
private pricePlan;
|
|
59
|
+
private priceBundle;
|
|
60
|
+
private line;
|
|
61
|
+
/**
|
|
62
|
+
* The promo code's discount on the plan, after its promotion.
|
|
63
|
+
*
|
|
64
|
+
* Eligibility is the promo module's to decide, and it decides on its own
|
|
65
|
+
* terms: status, validity, the plan and rhythm the code is limited to. Only
|
|
66
|
+
* the amount is computed here, on the price this offer actually carries.
|
|
67
|
+
*/
|
|
68
|
+
private pricePromoCode;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** What concluding an offer produced: the consumed offer and its contract. */
|
|
72
|
+
interface ConcludedCheckoutOffer {
|
|
73
|
+
offer: CheckoutOfferRow;
|
|
74
|
+
contract: SubscriptionContractRecord;
|
|
75
|
+
}
|
|
76
|
+
/** How an offer is concluded, and for whom. */
|
|
77
|
+
interface ConcludeCheckoutOfferOptions extends CreateContractFromOfferOptions {
|
|
78
|
+
/**
|
|
79
|
+
* The subscriber of a tenant that does not have one yet, created on the
|
|
80
|
+
* conclusion's transaction before its contract — what a sign-up passes,
|
|
81
|
+
* `subscriberFromRegistration(pending)` giving the details. Left out, the
|
|
82
|
+
* tenant's existing subscriber is the party; a tenant with neither is
|
|
83
|
+
* refused with `SUBSCRIBER_REQUIRED`, and one that already has a subscriber
|
|
84
|
+
* with `SUBSCRIBER_ALREADY_EXISTS`.
|
|
85
|
+
*/
|
|
86
|
+
subscriber?: NewSubscriberDetails;
|
|
87
|
+
/**
|
|
88
|
+
* The transaction to conclude on, when the caller already holds one. A
|
|
89
|
+
* sign-up's activation does: the gateway's confirmation is claimed on it,
|
|
90
|
+
* and the offer, the subscriber, the contract and `within` are written on
|
|
91
|
+
* it and undone with it. Left out, the conclusion opens a transaction of
|
|
92
|
+
* its own.
|
|
93
|
+
*/
|
|
94
|
+
tx?: TransactionContext;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* The application's own writes that belong to concluding an offer, such as
|
|
98
|
+
* starting its subscription. They run on the same transaction, after the offer
|
|
99
|
+
* is consumed and its contract written; throwing undoes all of it.
|
|
100
|
+
*/
|
|
101
|
+
type ConcludeCheckoutOfferWithin = (tx: TransactionContext, concluded: ConcludedCheckoutOffer) => Promise<void>;
|
|
102
|
+
declare class CheckoutOfferService {
|
|
103
|
+
private readonly repo;
|
|
104
|
+
private readonly pricing;
|
|
105
|
+
private readonly bundles;
|
|
106
|
+
private readonly plans;
|
|
107
|
+
private readonly catalogEntries;
|
|
108
|
+
private readonly contracts;
|
|
109
|
+
private readonly transactions;
|
|
110
|
+
private readonly subscribers;
|
|
111
|
+
private readonly promoCodes;
|
|
112
|
+
constructor(repo: CheckoutOfferRepository, pricing: CheckoutOfferPricing, bundles?: BundleRepository | null, plans?: PlanRepository | null, catalogEntries?: CatalogEntryRepository | null, contracts?: SubscriptionContractService | null, transactions?: TransactionRunner | null, subscribers?: SubscriberService | null, promoCodes?: PromoCodesService | null);
|
|
113
|
+
list(filter: CheckoutOfferFilter): Promise<CheckoutOfferRow[]>;
|
|
114
|
+
getById(id: string): Promise<CheckoutOfferRow>;
|
|
115
|
+
/** Prices what the caller chose and stores it as an open offer. */
|
|
116
|
+
create(selection: CheckoutOfferSelection): Promise<CheckoutOfferRow>;
|
|
117
|
+
/**
|
|
118
|
+
* Customization in onboarding — only while the offer is `open`. The plan
|
|
119
|
+
* stays; whatever else changes, the whole offer is priced again. A slot of
|
|
120
|
+
* its promo code held for its checkout stays with it while the code does,
|
|
121
|
+
* and is given back when the code is changed or removed.
|
|
122
|
+
*/
|
|
123
|
+
update(id: string, change: CheckoutOfferSelectionUpdate): Promise<CheckoutOfferRow>;
|
|
124
|
+
/**
|
|
125
|
+
* Holds a slot of the offer's promo code for its checkout until `until` —
|
|
126
|
+
* what a sign-up does as its checkout starts, until a confirmation of its
|
|
127
|
+
* payment form can no longer arrive — so that the code cannot run out
|
|
128
|
+
* before the payment is confirmed and the offer concluded. A live slot the
|
|
129
|
+
* offer holds of that code already moves to `until`, never earlier. The
|
|
130
|
+
* conclusion turns the slot into the redemption; the slot goes back when
|
|
131
|
+
* the offer's code changes or `until` passes.
|
|
132
|
+
*
|
|
133
|
+
* Refused, and nothing held, when the offer is no longer open or its code
|
|
134
|
+
* cannot be held: `PROMO_CODE_NOT_REDEEMABLE` with the reason a preview
|
|
135
|
+
* gives, `EXHAUSTED` when every slot is redeemed or held. An offer without
|
|
136
|
+
* a code holds nothing.
|
|
137
|
+
*/
|
|
138
|
+
holdPromoCode(id: string, hold: {
|
|
139
|
+
email?: string;
|
|
140
|
+
until: Date;
|
|
141
|
+
}): Promise<void>;
|
|
142
|
+
/**
|
|
143
|
+
* Gives back the slot held for the offer's checkout while it is still held
|
|
144
|
+
* until `heldUntil`, as the caller wrote it. A slot another start of the
|
|
145
|
+
* checkout moved later stays with the form that start opened.
|
|
146
|
+
*/
|
|
147
|
+
releasePromoCodeHold(id: string, heldUntil: Date): Promise<void>;
|
|
148
|
+
/**
|
|
149
|
+
* Freezes the offer (`status = 'consumed'`). Returns the final
|
|
150
|
+
* snapshot — the caller (registration/billing) writes it
|
|
151
|
+
* as `Subscription.packageSnapshot`.
|
|
152
|
+
*
|
|
153
|
+
* Refused unless every add-on is still bookable and the stored amounts are
|
|
154
|
+
* what the catalogue makes of the offer's own selection, so an offer whose
|
|
155
|
+
* amounts were written by anything but the pricing never becomes a contract.
|
|
156
|
+
*
|
|
157
|
+
* The plan, the add-ons and the promotions are priced as they stood when the
|
|
158
|
+
* offer was priced; a promo code is checked with the promo module as it
|
|
159
|
+
* stands now. A code is redeemed when the contract is concluded, so one that
|
|
160
|
+
* has expired or run out of redemptions since refuses the offer with
|
|
161
|
+
* `CHECKOUT_OFFER_PROMO_CODE_NOT_ACCEPTED`; `CHECKOUT_OFFER_PRICE_NOT_CURRENT`
|
|
162
|
+
* is kept for amounts that no longer match.
|
|
163
|
+
*/
|
|
164
|
+
consume(id: string): Promise<CheckoutOfferRow>;
|
|
165
|
+
/**
|
|
166
|
+
* Consumes an offer and creates its contract in one transaction, together
|
|
167
|
+
* with the application's own writes in `within`, so an offer is never
|
|
168
|
+
* consumed without its contract, and no contract is written for an offer
|
|
169
|
+
* that stays open.
|
|
170
|
+
*
|
|
171
|
+
* Everything that can refuse runs before the transaction: what `consume`
|
|
172
|
+
* checks, and the checks the contract the offer becomes is held to. Inside
|
|
173
|
+
* it, the offer is consumed on the condition that it is still open, the
|
|
174
|
+
* contract is written, and `within` runs; an error in any of them undoes
|
|
175
|
+
* the others, and the offer can be concluded again.
|
|
176
|
+
*
|
|
177
|
+
* An offer that is already concluded for the same tenant answers with its
|
|
178
|
+
* contract rather than a refusal, so a caller retrying after a lost answer,
|
|
179
|
+
* or losing a race to another call for that tenant, gets the conclusion that
|
|
180
|
+
* stands; `within` does not run again, its writes committed with it. A
|
|
181
|
+
* conclusion for another tenant is refused with
|
|
182
|
+
* `CHECKOUT_OFFER_ALREADY_CONSUMED`, and an offer changed between the checks
|
|
183
|
+
* and the transaction with `CHECKOUT_OFFER_CHANGED`, nothing written.
|
|
184
|
+
*
|
|
185
|
+
* The contract is between the tenant's subscriber and the configured
|
|
186
|
+
* issuer. A sign-up creates the tenant in `within` and has no subscriber
|
|
187
|
+
* yet, so it passes `subscriber`, which is created on the same transaction
|
|
188
|
+
* before the contract; whether one may be created, or whether the tenant
|
|
189
|
+
* has one, is asked before the transaction with the other checks.
|
|
190
|
+
*
|
|
191
|
+
* A promo code on the offer is redeemed in `within`, with
|
|
192
|
+
* `PromoCodesService.redeemInTransaction` on the same transaction. Before
|
|
193
|
+
* the transaction the code is only checked the way pricing checks it; the
|
|
194
|
+
* redemption checks the rest, the customer and the remaining redemptions,
|
|
195
|
+
* where a refusal still undoes everything. Nothing checks the code again
|
|
196
|
+
* after `within`, so a redemption that takes its last slot does not refuse
|
|
197
|
+
* the offer it was redeemed for.
|
|
198
|
+
*
|
|
199
|
+
* A slot held for the offer's checkout (`holdPromoCode`) is the offer's own
|
|
200
|
+
* throughout: the checks before the transaction do not count it against
|
|
201
|
+
* the code and accept the code as it stood when the slot was held, and the
|
|
202
|
+
* redemption in `within` takes that slot rather than a free one. A slot
|
|
203
|
+
* `within` did not redeem is given back before the conclusion commits.
|
|
204
|
+
*/
|
|
205
|
+
conclude(id: string, options: ConcludeCheckoutOfferOptions, within?: ConcludeCheckoutOfferWithin): Promise<ConcludedCheckoutOffer>;
|
|
206
|
+
private assertConsumable;
|
|
207
|
+
/**
|
|
208
|
+
* The conclusion that stands for this tenant, if the offer has one. An offer
|
|
209
|
+
* carries no tenant and its id travels in a link, so a conclusion for
|
|
210
|
+
* another tenant is not an answer: that caller is refused as before.
|
|
211
|
+
*/
|
|
212
|
+
private standingConclusion;
|
|
213
|
+
private conclusionPorts;
|
|
214
|
+
/**
|
|
215
|
+
* Before anything is written: the subscriber to create is one that may be
|
|
216
|
+
* created, or the tenant already has the party the contract is with.
|
|
217
|
+
*/
|
|
218
|
+
private assertParty;
|
|
219
|
+
/**
|
|
220
|
+
* #35 P6 — server-side requires validation: the dependencies of all
|
|
221
|
+
* features (plan ∪ selected bundles) must be covered within the
|
|
222
|
+
* selection, otherwise an offer would be created whose features cannot
|
|
223
|
+
* work at the tenant (an app-specific validateModuleDependencies check is
|
|
224
|
+
* thereby replaced on the platform side). The requires source are the
|
|
225
|
+
* curated FeatureCatalogEntries; without CatalogEntryRepository it is
|
|
226
|
+
* skipped (graceful — no requires data available).
|
|
227
|
+
*/
|
|
228
|
+
private assertFeatureRequiresSatisfied;
|
|
229
|
+
/**
|
|
230
|
+
* Plan features preferably from the server SSOT (PlanRepository); fallback
|
|
231
|
+
* is the frozen featuresSnapshot of the plan LineItem (adapter without
|
|
232
|
+
* PlanRepository wiring).
|
|
233
|
+
*/
|
|
234
|
+
private resolvePlanFeatures;
|
|
235
|
+
/** Bundle features from the BundleVersions; fallback featuresSnapshot. */
|
|
236
|
+
private resolveBundleFeatures;
|
|
237
|
+
private assertBundleVersionsStillBookable;
|
|
238
|
+
private assertOpen;
|
|
239
|
+
private isExpired;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export { CheckoutOfferPricing as C, type PricedCheckoutOffer as P, type CheckoutOfferPricingInput as a, CheckoutOfferService as b, type ConcludeCheckoutOfferOptions as c, type ConcludeCheckoutOfferWithin as d, type ConcludedCheckoutOffer as e };
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import {
|
|
2
|
+
MFA_PORT_TOKEN
|
|
3
|
+
} from "./chunk-SZ7RFPXA.js";
|
|
4
|
+
import {
|
|
5
|
+
codedError
|
|
6
|
+
} from "./chunk-J7NJRK3K.js";
|
|
7
|
+
import {
|
|
8
|
+
__name
|
|
9
|
+
} from "./chunk-SHUYVCID.js";
|
|
10
|
+
|
|
11
|
+
// src/admin/mfa.service.ts
|
|
12
|
+
import { Inject, Injectable, Logger } from "@nestjs/common";
|
|
13
|
+
import { generateSecret, generateURI, verify as verifyTotpCode } from "otplib";
|
|
14
|
+
function _ts_decorate(decorators, target, key, desc) {
|
|
15
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
16
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
17
|
+
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;
|
|
18
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
19
|
+
}
|
|
20
|
+
__name(_ts_decorate, "_ts_decorate");
|
|
21
|
+
function _ts_metadata(k, v) {
|
|
22
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
23
|
+
}
|
|
24
|
+
__name(_ts_metadata, "_ts_metadata");
|
|
25
|
+
function _ts_param(paramIndex, decorator) {
|
|
26
|
+
return function(target, key) {
|
|
27
|
+
decorator(target, key, paramIndex);
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
__name(_ts_param, "_ts_param");
|
|
31
|
+
var CLOCK_DRIFT_TOLERANCE_SECONDS = 30;
|
|
32
|
+
var OTPLIB_TOKEN_ERROR_NAMES = /* @__PURE__ */ new Set([
|
|
33
|
+
"TokenError",
|
|
34
|
+
"TokenFormatError",
|
|
35
|
+
"TokenLengthError"
|
|
36
|
+
]);
|
|
37
|
+
function isOtplibTokenError(error) {
|
|
38
|
+
return error instanceof Error && OTPLIB_TOKEN_ERROR_NAMES.has(error.name);
|
|
39
|
+
}
|
|
40
|
+
__name(isOtplibTokenError, "isOtplibTokenError");
|
|
41
|
+
var MfaService = class _MfaService {
|
|
42
|
+
static {
|
|
43
|
+
__name(this, "MfaService");
|
|
44
|
+
}
|
|
45
|
+
mfa;
|
|
46
|
+
logger = new Logger(_MfaService.name);
|
|
47
|
+
constructor(mfa) {
|
|
48
|
+
this.mfa = mfa;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Generates a new TOTP secret, persists it via `MfaPort.setSecret`
|
|
52
|
+
* and returns the secret + the otpauth URI for the QR code.
|
|
53
|
+
*
|
|
54
|
+
* `issuer` and `label` shape the display in the authenticator (e.g.
|
|
55
|
+
* "DemoApp:taci@example.com").
|
|
56
|
+
*/
|
|
57
|
+
async setup(userId, label, issuer) {
|
|
58
|
+
const secret = generateSecret();
|
|
59
|
+
const otpauthUri = generateURI({
|
|
60
|
+
issuer,
|
|
61
|
+
label,
|
|
62
|
+
secret
|
|
63
|
+
});
|
|
64
|
+
await this.mfa.setSecret(userId, secret);
|
|
65
|
+
return {
|
|
66
|
+
secret,
|
|
67
|
+
otpauthUri
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Verifies the entered code against the stored secret,
|
|
72
|
+
* clock-drift-tolerant by ±1 time step.
|
|
73
|
+
*/
|
|
74
|
+
async verify(input) {
|
|
75
|
+
const secret = await this.mfa.getSecret(input.userId);
|
|
76
|
+
if (!secret) return false;
|
|
77
|
+
try {
|
|
78
|
+
const result = await verifyTotpCode({
|
|
79
|
+
secret,
|
|
80
|
+
token: input.code.trim(),
|
|
81
|
+
epochTolerance: CLOCK_DRIFT_TOLERANCE_SECONDS
|
|
82
|
+
});
|
|
83
|
+
return result.valid;
|
|
84
|
+
} catch (error) {
|
|
85
|
+
if (isOtplibTokenError(error)) {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
89
|
+
this.logger.warn(`TOTP verification for user ${input.userId} failed unexpectedly: ${message}`);
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Deletes a user's secret — used by `ahp admin mfa-reset`.
|
|
95
|
+
*/
|
|
96
|
+
async disable(userId) {
|
|
97
|
+
await this.mfa.setSecret(userId, null);
|
|
98
|
+
}
|
|
99
|
+
/** Delegates to `MfaPort.isEnabled`. */
|
|
100
|
+
async isEnabled(userId) {
|
|
101
|
+
return this.mfa.isEnabled(userId);
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
MfaService = _ts_decorate([
|
|
105
|
+
Injectable(),
|
|
106
|
+
_ts_param(0, Inject(MFA_PORT_TOKEN)),
|
|
107
|
+
_ts_metadata("design:type", Function),
|
|
108
|
+
_ts_metadata("design:paramtypes", [
|
|
109
|
+
typeof MfaPort === "undefined" ? Object : MfaPort
|
|
110
|
+
])
|
|
111
|
+
], MfaService);
|
|
112
|
+
|
|
113
|
+
// src/admin/mfa.guard.ts
|
|
114
|
+
import { Inject as Inject2, Injectable as Injectable2, Logger as Logger2, SetMetadata, UnauthorizedException } from "@nestjs/common";
|
|
115
|
+
import { Reflector } from "@nestjs/core";
|
|
116
|
+
import { AUTH_ERROR_CODES } from "@saasicat/core";
|
|
117
|
+
function _ts_decorate2(decorators, target, key, desc) {
|
|
118
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
119
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
120
|
+
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;
|
|
121
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
122
|
+
}
|
|
123
|
+
__name(_ts_decorate2, "_ts_decorate");
|
|
124
|
+
function _ts_metadata2(k, v) {
|
|
125
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
126
|
+
}
|
|
127
|
+
__name(_ts_metadata2, "_ts_metadata");
|
|
128
|
+
function _ts_param2(paramIndex, decorator) {
|
|
129
|
+
return function(target, key) {
|
|
130
|
+
decorator(target, key, paramIndex);
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
__name(_ts_param2, "_ts_param");
|
|
134
|
+
var REQUIRE_MFA_KEY = "saasicat/nest/require-mfa";
|
|
135
|
+
var RequireMfa = /* @__PURE__ */ __name(() => SetMetadata(REQUIRE_MFA_KEY, true), "RequireMfa");
|
|
136
|
+
var MfaGuard = class _MfaGuard {
|
|
137
|
+
static {
|
|
138
|
+
__name(this, "MfaGuard");
|
|
139
|
+
}
|
|
140
|
+
reflector;
|
|
141
|
+
mfaService;
|
|
142
|
+
logger = new Logger2(_MfaGuard.name);
|
|
143
|
+
constructor(reflector, mfaService) {
|
|
144
|
+
this.reflector = reflector;
|
|
145
|
+
this.mfaService = mfaService;
|
|
146
|
+
}
|
|
147
|
+
async canActivate(context) {
|
|
148
|
+
const required = this.reflector.getAllAndOverride(REQUIRE_MFA_KEY, [
|
|
149
|
+
context.getHandler(),
|
|
150
|
+
context.getClass()
|
|
151
|
+
]);
|
|
152
|
+
if (!required) return true;
|
|
153
|
+
if (process.env.SAAS_PLATFORM_SKIP_MFA === "1" && process.env.NODE_ENV !== "production") {
|
|
154
|
+
return true;
|
|
155
|
+
}
|
|
156
|
+
const request = context.switchToHttp().getRequest();
|
|
157
|
+
const userId = request.user?.id ?? request.user?.userId;
|
|
158
|
+
if (!userId) {
|
|
159
|
+
throw new UnauthorizedException({
|
|
160
|
+
...codedError(AUTH_ERROR_CODES.NOT_AUTHENTICATED),
|
|
161
|
+
// `reason` is superseded by `code` and will be removed.
|
|
162
|
+
reason: "NOT_AUTHENTICATED"
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
const enabled = await this.mfaService.isEnabled(userId);
|
|
166
|
+
if (!enabled) {
|
|
167
|
+
throw new UnauthorizedException({
|
|
168
|
+
code: AUTH_ERROR_CODES.MFA_NOT_SET_UP,
|
|
169
|
+
// `reason` is superseded by `code` and will be removed.
|
|
170
|
+
reason: "MFA_NOT_SET_UP",
|
|
171
|
+
message: "Run the MFA setup via the CLI first."
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
const headerVal = request.headers?.["x-mfa-code"];
|
|
175
|
+
const code = Array.isArray(headerVal) ? headerVal[0] : headerVal;
|
|
176
|
+
if (!code) {
|
|
177
|
+
throw new UnauthorizedException({
|
|
178
|
+
code: AUTH_ERROR_CODES.MFA_REQUIRED,
|
|
179
|
+
// `reason` is superseded by `code` and will be removed.
|
|
180
|
+
reason: "MFA_REQUIRED",
|
|
181
|
+
message: "TOTP code required in the X-Mfa-Code header."
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
const valid = await this.mfaService.verify({
|
|
185
|
+
userId,
|
|
186
|
+
code: String(code)
|
|
187
|
+
});
|
|
188
|
+
if (!valid) {
|
|
189
|
+
throw new UnauthorizedException({
|
|
190
|
+
code: AUTH_ERROR_CODES.MFA_FAILED,
|
|
191
|
+
// `reason` is superseded by `code` and will be removed.
|
|
192
|
+
reason: "MFA_FAILED",
|
|
193
|
+
message: "Invalid TOTP code."
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
return true;
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
MfaGuard = _ts_decorate2([
|
|
200
|
+
Injectable2(),
|
|
201
|
+
_ts_param2(0, Inject2(Reflector)),
|
|
202
|
+
_ts_metadata2("design:type", Function),
|
|
203
|
+
_ts_metadata2("design:paramtypes", [
|
|
204
|
+
typeof Reflector === "undefined" ? Object : Reflector,
|
|
205
|
+
typeof MfaService === "undefined" ? Object : MfaService
|
|
206
|
+
])
|
|
207
|
+
], MfaGuard);
|
|
208
|
+
|
|
209
|
+
// src/admin/enforce-mfa.decorator.ts
|
|
210
|
+
import { applyDecorators, UseGuards } from "@nestjs/common";
|
|
211
|
+
function EnforceMfa() {
|
|
212
|
+
return applyDecorators(RequireMfa(), UseGuards(MfaGuard));
|
|
213
|
+
}
|
|
214
|
+
__name(EnforceMfa, "EnforceMfa");
|
|
215
|
+
|
|
216
|
+
export {
|
|
217
|
+
MfaService,
|
|
218
|
+
REQUIRE_MFA_KEY,
|
|
219
|
+
RequireMfa,
|
|
220
|
+
MfaGuard,
|
|
221
|
+
EnforceMfa
|
|
222
|
+
};
|
|
@@ -7,6 +7,7 @@ var PROMOTION_REPOSITORY_TOKEN = /* @__PURE__ */ Symbol.for("saasicat/nest/Promo
|
|
|
7
7
|
var MARKETING_SETTINGS_REPOSITORY_TOKEN = /* @__PURE__ */ Symbol.for("saasicat/nest/MarketingSettingsRepository");
|
|
8
8
|
var CATALOG_SERVICE_CONFIG_TOKEN = /* @__PURE__ */ Symbol.for("saasicat/nest/CatalogServiceConfig");
|
|
9
9
|
var CATALOG_FEATURE_UI_REGISTRY_TOKEN = /* @__PURE__ */ Symbol.for("saasicat/nest/CatalogFeatureUiRegistry");
|
|
10
|
+
var NEW_PAYMENT_METHODS_SOURCE_TOKEN = /* @__PURE__ */ Symbol.for("saasicat/nest/NewPaymentMethodsSource");
|
|
10
11
|
|
|
11
12
|
export {
|
|
12
13
|
PLAN_REPOSITORY_TOKEN,
|
|
@@ -16,5 +17,6 @@ export {
|
|
|
16
17
|
PROMOTION_REPOSITORY_TOKEN,
|
|
17
18
|
MARKETING_SETTINGS_REPOSITORY_TOKEN,
|
|
18
19
|
CATALOG_SERVICE_CONFIG_TOKEN,
|
|
19
|
-
CATALOG_FEATURE_UI_REGISTRY_TOKEN
|
|
20
|
+
CATALOG_FEATURE_UI_REGISTRY_TOKEN,
|
|
21
|
+
NEW_PAYMENT_METHODS_SOURCE_TOKEN
|
|
20
22
|
};
|