@antelopejs/dms-saas 0.3.7 → 0.3.9
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 +47 -1
- package/dist/hooks/register.js +5 -0
- package/dist/hooks/register.js.map +1 -1
- package/dist/index.js +0 -1
- package/dist/index.js.map +1 -1
- package/dist/invoice-line-items/injection.js +3 -4
- package/dist/invoice-line-items/injection.js.map +1 -1
- package/dist/plans/stripe-sync.d.ts +1 -0
- package/dist/plans/stripe-sync.js +4 -0
- package/dist/plans/stripe-sync.js.map +1 -1
- package/dist/routes/tenant/index.d.ts +1 -0
- package/dist/routes/tenant/index.js +1 -0
- package/dist/routes/tenant/index.js.map +1 -1
- package/dist/routes/tenant/tenant-plan-checkout.d.ts +5 -0
- package/dist/routes/tenant/tenant-plan-checkout.js +220 -0
- package/dist/routes/tenant/tenant-plan-checkout.js.map +1 -0
- package/dist/routes/tenant/tenant-plan-ops.d.ts +0 -1
- package/dist/routes/tenant/tenant-plan-ops.js +0 -100
- package/dist/routes/tenant/tenant-plan-ops.js.map +1 -1
- package/dist/routes/tenant/tenant-plan.js +2 -1
- package/dist/routes/tenant/tenant-plan.js.map +1 -1
- package/dist/stripe/client.d.ts +1 -0
- package/dist/stripe/client.js +3 -2
- package/dist/stripe/client.js.map +1 -1
- package/dist/stripe/index.d.ts +1 -0
- package/dist/stripe/index.js +1 -0
- package/dist/stripe/index.js.map +1 -1
- package/dist/stripe/payload-shapes.d.ts +17 -0
- package/dist/stripe/payload-shapes.js +58 -0
- package/dist/stripe/payload-shapes.js.map +1 -0
- package/dist/stripe/plan-schedule.js +2 -1
- package/dist/stripe/plan-schedule.js.map +1 -1
- package/dist/stripe/upcoming-invoice.d.ts +5 -1
- package/dist/stripe/upcoming-invoice.js +20 -5
- package/dist/stripe/upcoming-invoice.js.map +1 -1
- package/dist/stripe/webhook-checkout.js +2 -1
- package/dist/stripe/webhook-checkout.js.map +1 -1
- package/dist/stripe/webhook-credit-notes.js +4 -8
- package/dist/stripe/webhook-credit-notes.js.map +1 -1
- package/dist/stripe/webhook-handlers.js +7 -4
- package/dist/stripe/webhook-handlers.js.map +1 -1
- package/dist/stripe/webhook-shared.js +9 -5
- package/dist/stripe/webhook-shared.js.map +1 -1
- package/dist/stripe/workspace-checkout.d.ts +1 -0
- package/dist/stripe/workspace-checkout.js +35 -1
- package/dist/stripe/workspace-checkout.js.map +1 -1
- package/dist/upcoming-invoice/mapping.d.ts +2 -2
- package/dist/upcoming-invoice/mapping.js +25 -14
- package/dist/upcoming-invoice/mapping.js.map +1 -1
- package/dist/upcoming-invoice/pricing.js +3 -3
- package/dist/upcoming-invoice/pricing.js.map +1 -1
- package/dist/workspaces/provisioning.js +2 -4
- package/dist/workspaces/provisioning.js.map +1 -1
- package/frontend-vue/i18n/locales/saas-en-GB.json +6 -2
- package/frontend-vue/i18n/locales/saas-fr-FR.json +6 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -177,6 +177,51 @@ the catalogue has no free plan, nothing is attached and a warning is logged.
|
|
|
177
177
|
Once attached, the plan's permissions cap the workspace's members like any
|
|
178
178
|
other plan (see [Tenant owner permissions](#tenant-owner-permissions)).
|
|
179
179
|
|
|
180
|
+
## Stripe API version and webhooks
|
|
181
|
+
|
|
182
|
+
Every Stripe request is made with API version `2025-08-27.basil`, the version
|
|
183
|
+
the installed `stripe` SDK is generated for (`STRIPE_API_VERSION` in
|
|
184
|
+
`src/stripe/client.ts`; the build fails if the two drift apart).
|
|
185
|
+
|
|
186
|
+
A webhook endpoint renders its events in its **own** API version, set in the
|
|
187
|
+
Stripe Dashboard (Developers → Webhooks → the endpoint → API version), not in
|
|
188
|
+
the version of the requests. Set every endpoint that targets
|
|
189
|
+
`/api/saas/webhooks/stripe` to `2025-08-27.basil`. Until then dms-saas still
|
|
190
|
+
accepts events rendered in older versions: the fields basil moved — the
|
|
191
|
+
subscription billing period (now on each subscription item), the invoice's
|
|
192
|
+
subscription (`parent.subscription_details`), invoice and credit note taxes
|
|
193
|
+
(`total_taxes`), and the credit note refund (`refunds`) — are read from their
|
|
194
|
+
basil location first and from their older one second.
|
|
195
|
+
|
|
196
|
+
Checkout Sessions opt out of Stripe Managed Payments
|
|
197
|
+
(`managed_payments[enabled]=false`). An account that enabled Managed Payments
|
|
198
|
+
applies it to every session by default, but dms-saas is its own merchant of
|
|
199
|
+
record: it collects tax ids, runs Stripe Tax on its invoices, injects invoice
|
|
200
|
+
items and issues credit notes, none of which a Managed Payments session allows.
|
|
201
|
+
|
|
202
|
+
## Starting a paid plan
|
|
203
|
+
|
|
204
|
+
Moving a workspace without a Stripe subscription to a paid plan opens a Stripe
|
|
205
|
+
Checkout Session. The request is recorded on the subscription as a pending
|
|
206
|
+
checkout before Stripe is called, and the session's completion or expiry
|
|
207
|
+
webhook resolves it. A pending checkout never blocks the owner for good:
|
|
208
|
+
|
|
209
|
+
- when Stripe refuses to open the session (a 4xx answer), the pending
|
|
210
|
+
checkout, the trial it reserved and the Stripe customer it created are
|
|
211
|
+
rolled back, and the request answers
|
|
212
|
+
`422 saas.errors.billing.checkout_rejected`; the owner can retry at once;
|
|
213
|
+
- when Stripe cannot be reached or answers a 5xx, a session may exist, so the
|
|
214
|
+
pending checkout stays and the request answers
|
|
215
|
+
`503 saas.errors.billing.checkout_unavailable`;
|
|
216
|
+
- the next attempt reconciles what it finds: a pending checkout whose recorded
|
|
217
|
+
session has expired, or which never recorded a session within 15 minutes, is
|
|
218
|
+
released and a new session is opened. Its redirect URL only reaches the
|
|
219
|
+
owner once the session is recorded, so an unrecorded session is one nobody
|
|
220
|
+
can pay;
|
|
221
|
+
- a recorded session that is still open answers
|
|
222
|
+
`409 saas.errors.plan.checkout_in_progress`, and any other pending plan
|
|
223
|
+
change `409 saas.errors.plan.change_in_progress`.
|
|
224
|
+
|
|
180
225
|
## Plans and Stripe
|
|
181
226
|
|
|
182
227
|
dms-saas keeps every plan billed through Stripe linked to a Stripe product and
|
|
@@ -194,7 +239,8 @@ The sync runs:
|
|
|
194
239
|
|
|
195
240
|
- when the plan editor creates or saves a plan, after writing it (a Stripe
|
|
196
241
|
error is reported to the editor, and the next pass retries the sync);
|
|
197
|
-
- at startup, in the background, for
|
|
242
|
+
- once DMS has initialised the database at startup, in the background, for
|
|
243
|
+
every active plan;
|
|
198
244
|
- whenever a plan is about to be offered or billed — the tenant plan catalogue,
|
|
199
245
|
a plan change, an operator action, a new workspace — so a plan written after
|
|
200
246
|
startup is linked the first time it matters.
|
package/dist/hooks/register.js
CHANGED
|
@@ -8,6 +8,7 @@ const billing_state_1 = require("../billing-state");
|
|
|
8
8
|
const db_1 = require("../db");
|
|
9
9
|
const operator_actions_1 = require("../operator-actions");
|
|
10
10
|
const module_1 = require("../pages/module");
|
|
11
|
+
const stripe_sync_1 = require("../plans/stripe-sync");
|
|
11
12
|
const routes_1 = require("../routes");
|
|
12
13
|
const workers_1 = require("../workers");
|
|
13
14
|
const default_plan_1 = require("../workspaces/default-plan");
|
|
@@ -44,6 +45,10 @@ function registerSaasHookListeners() {
|
|
|
44
45
|
await emitDeletedTenant(tenantId, context);
|
|
45
46
|
return undefined;
|
|
46
47
|
});
|
|
48
|
+
(0, hooks_1.RegisterHook)(hooks_1.Hook.DATABASE_INITIALIZED, () => {
|
|
49
|
+
(0, stripe_sync_1.startPlanReconciliation)();
|
|
50
|
+
return undefined;
|
|
51
|
+
});
|
|
47
52
|
(0, hooks_1.RegisterHook)(hooks_1.Hook.DATABASE_INITIALIZED, async () => {
|
|
48
53
|
await (0, routes_1.ensureLegalDocumentsSingleton)();
|
|
49
54
|
const results = await Promise.allSettled([
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"register.js","sourceRoot":"","sources":["../../src/hooks/register.ts"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"register.js","sourceRoot":"","sources":["../../src/hooks/register.ts"],"names":[],"mappings":";;AAoDA,8DAgCC;AApFD,6FAAqE;AACrE,2DAKyC;AACzC,8CAA4E;AAC5E,oDAAmE;AACnE,8BAAgD;AAChD,0DAA4E;AAC5E,4CAAiD;AACjD,sDAA+D;AAC/D,sCAA0D;AAC1D,wCAAyD;AACzD,6DAA0E;AAC1E,mDAAuD;AAEvD,SAAS,mCAAmC,CAC1C,OAAwC;IAExC,MAAM,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAU,CAAC,MAAM,EAAE,EAAE,CACnD,MAAM,CAAC,MAAM,KAAK,UAAU,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,CACpD,CAAC;IACF,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO;IAClC,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;QAAE,MAAM,QAAQ,CAAC,CAAC,CAAC,CAAC;IAC7C,MAAM,IAAI,cAAc,CAAC,QAAQ,EAAE,gCAAgC,CAAC,CAAC;AACvE,CAAC;AAED,KAAK,UAAU,iBAAiB,CAC9B,QAAgB,EAChB,OAA+B;IAE/B,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,IAAA,gCAAmB,EAAC,qBAAqB,EAAE;YACzC,QAAQ;YACR,EAAE,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;SAC7B,CAAC,CAAC;QACH,OAAO;IACT,CAAC;IACD,MAAM,SAAS,GAAG,MAAM,IAAA,wCAAQ,EAAC,4BAAuB,CAAC,CAAC,YAAY,CACpE,QAAQ,CACT,CAAC;IACF,IAAI,CAAC,SAAS,EAAE,SAAS;QACvB,MAAM,IAAI,KAAK,CAAC,sCAAsC,CAAC,CAAC;IAC1D,MAAM,IAAA,mCAAsB,EAAC;QAC3B,QAAQ;QACR,EAAE,EAAE,SAAS,CAAC,SAAS,CAAC,WAAW,EAAE;QACrC,WAAW,EAAE,OAAO,CAAC,WAAW;KACjC,CAAC,CAAC;AACL,CAAC;AAED,SAAgB,yBAAyB;IAGvC,IAAA,2CAAmC,EAAC,uBAAc,EAAE,oCAAoB,CAAC,CAAC;IAC1E,IAAA,oBAAY,EACV,YAAI,CAAC,cAAc,EACnB,KAAK,EAAE,QAAgB,EAAE,OAA+B,EAAE,EAAE;QAC1D,MAAM,IAAA,wCAAQ,EAAC,4BAAuB,CAAC,CAAC,eAAe,CAAC,QAAQ,CAAC,CAAC;QAClE,MAAM,iBAAiB,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QAC3C,OAAO,SAAS,CAAC;IACnB,CAAC,CACF,CAAC;IAKF,IAAA,oBAAY,EAAC,YAAI,CAAC,oBAAoB,EAAE,GAAG,EAAE;QAC3C,IAAA,qCAAuB,GAAE,CAAC;QAC1B,OAAO,SAAS,CAAC;IACnB,CAAC,CAAC,CAAC;IACH,IAAA,oBAAY,EAAC,YAAI,CAAC,oBAAoB,EAAE,KAAK,IAAI,EAAE;QACjD,MAAM,IAAA,sCAA6B,GAAE,CAAC;QACtC,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,UAAU,CAAC;YACvC,IAAA,qCAA2B,GAAE;YAG7B,IAAA,2CAA4B,GAAE,CAAC,IAAI,CAAC,+CAA+B,CAAC;YACpE,IAAA,wDAAqC,GAAE;SACxC,CAAC,CAAC;QACH,mCAAmC,CAAC,OAAO,CAAC,CAAC;QAC7C,OAAO,SAAS,CAAC;IACnB,CAAC,CAAC,CAAC;AACL,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -99,7 +99,6 @@ async function construct(config) {
|
|
|
99
99
|
async function start() {
|
|
100
100
|
registeredCronTasks = (0, crons_1.registerSaasCrons)();
|
|
101
101
|
(0, automation_1.registerAutomationNodes)();
|
|
102
|
-
void (0, plans_1.reconcilePlansWithStripe)().catch((error) => logging_1.Logging.Error("[dms-saas:plans] Stripe plan reconciliation failed", error));
|
|
103
102
|
}
|
|
104
103
|
async function stop() {
|
|
105
104
|
const teardowns = await Promise.allSettled(registeredCronTasks.map(async (task) => task.destroy()));
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0CA,8BAwCC;AAED,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0CA,8BAwCC;AAED,sBAGC;AAED,oBAWC;AApGD,0DAA6B;AAC7B,+DAAgE;AAChE,gEAA6D;AAC7D,yDAAmE;AACnE,yEAA6E;AAC7E,yFAA2E;AAC3E,6GAA+F;AAC/F,qFAAuE;AACvE,gHAAkG;AAElG,iCAGgB;AAChB,6CAGsB;AACtB,mDAAwD;AACxD,qCAIkB;AAClB,mCAA4C;AAC5C,mCAAoD;AACpD,0FAA4E;AAC5E,8GAAgG;AAChG,sFAAwE;AACxE,iHAAmG;AACnG,mCAAgD;AAChD,mCAA4C;AAC5C,qCAA4C;AAG5C,sBAAoB;AACpB,gBAAc;AACd,2BAAyB;AACzB,oBAAkB;AAElB,IAAI,mBAAmB,GAAoB,EAAE,CAAC;AAEvC,KAAK,UAAU,SAAS,CAAC,MAAqB;IACnD,IAAA,yBAAgB,EAAC,MAAM,CAAC,CAAC;IAGzB,IAAA,4BAAgB,GAAE,CAAC;IACnB,MAAM,IAAA,uBAAc,GAAE,CAAC;IACvB,IAAA,yBAAgB,EAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAChC,IAAA,iCAAyB,GAAE,CAAC;IAC5B,IAAA,sCAA+B,GAAE,CAAC;IAClC,IAAA,qCAA8B,GAAE,CAAC;IACjC,IAAA,yBAAiB,GAAE,CAAC;IACpB,IAAA,qCAAqB,GAAE,CAAC;IACxB,IAAA,mCAAkB,EAAC,gBAAgB,EAAE,qBAAqB,CAAC,CAAC;IAC5D,IAAA,mCAAkB,EAAC,yBAAyB,EAAE,8BAA8B,CAAC,CAAC;IAC9E,IAAA,mCAAkB,EAAC,cAAc,EAAE,mBAAmB,CAAC,CAAC;IACxD,IAAA,mCAAkB,EAChB,2BAA2B,EAC3B,gCAAgC,CACjC,CAAC;IACF,MAAM,IAAA,6BAAqB,EAAC,MAAM,CAAC,CAAC;IAEpC,MAAM,IAAA,wBAAiB,EAAC;QACtB,IAAI,EAAE,mCAAmC;QACzC,UAAU,EAAE,mBAAI,CAAC,IAAI,CAAC,SAAS,EAAE,iBAAiB,CAAC;QACnD,QAAQ,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE;QACvC,QAAQ,EAAE,CAAC;QAMX,sBAAsB,EAAE,CAAC,6BAA6B,CAAC;QACvD,OAAO,EAAE;YACP,OAAO,EAAE;gBACP,oBAAoB,EAAE,MAAM,CAAC,MAAM,CAAC,cAAc;gBAClD,aAAa,EAAE,MAAM,CAAC,aAAa,IAAI,MAAM;gBAC7C,yBAAyB,EAAE,IAAA,2CAAkC,GAAE;aAChE;SACF;KACF,CAAC,CAAC;AACL,CAAC;AAEM,KAAK,UAAU,KAAK;IACzB,mBAAmB,GAAG,IAAA,yBAAiB,GAAE,CAAC;IAC1C,IAAA,oCAAuB,GAAE,CAAC;AAC5B,CAAC;AAEM,KAAK,UAAU,IAAI;IACxB,MAAM,SAAS,GAAG,MAAM,OAAO,CAAC,UAAU,CACxC,mBAAmB,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CACxD,CAAC;IACF,KAAK,MAAM,QAAQ,IAAI,SAAS,EAAE,CAAC;QACjC,IAAI,QAAQ,CAAC,MAAM,KAAK,UAAU,EAAE,CAAC;YACnC,iBAAO,CAAC,KAAK,CAAC,4BAA4B,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;QAC/D,CAAC;IACH,CAAC;IACD,mBAAmB,GAAG,EAAE,CAAC;IACzB,IAAA,sCAAyB,GAAE,CAAC;AAC9B,CAAC"}
|
|
@@ -10,6 +10,7 @@ const interface_database_decorators_1 = require("@antelopejs/interface-database-
|
|
|
10
10
|
const invoice_line_items_1 = require("@antelopejs/interface-dms-saas/invoice-line-items");
|
|
11
11
|
const invoice_line_items_2 = require("../implementations/dms-saas/invoice-line-items");
|
|
12
12
|
const client_1 = require("../stripe/client");
|
|
13
|
+
const payload_shapes_1 = require("../stripe/payload-shapes");
|
|
13
14
|
const db_1 = require("../db");
|
|
14
15
|
const billing_window_1 = require("./billing-window");
|
|
15
16
|
exports.LINE_KEY_METADATA = "saasLineKey";
|
|
@@ -47,13 +48,11 @@ async function buildInjectionTarget(invoice, tenantId, customerId) {
|
|
|
47
48
|
const subscription = await (0, interface_database_decorators_1.GetModel)(db_1.TenantSubscriptionModel, tenantId).findOne();
|
|
48
49
|
const context = (0, billing_window_1.clipInvoiceUsageWindow)({
|
|
49
50
|
tenantId,
|
|
50
|
-
invoiceId: invoice
|
|
51
|
+
invoiceId: (0, payload_shapes_1.requireInvoiceId)(invoice),
|
|
51
52
|
currency: invoice.currency,
|
|
52
53
|
periodStart: stripeSecondsToDate(invoice.period_start),
|
|
53
54
|
periodEnd: stripeSecondsToDate(invoice.period_end),
|
|
54
|
-
}, subscription,
|
|
55
|
-
? invoice.subscription
|
|
56
|
-
: (invoice.subscription?.id ?? null));
|
|
55
|
+
}, subscription, (0, payload_shapes_1.readInvoiceSubscriptionId)(invoice));
|
|
57
56
|
return context ? { customerId, context } : null;
|
|
58
57
|
}
|
|
59
58
|
async function fetchInjectedLineKeys(invoiceId) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"injection.js","sourceRoot":"","sources":["../../src/invoice-line-items/injection.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"injection.js","sourceRoot":"","sources":["../../src/invoice-line-items/injection.ts"],"names":[],"mappings":"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"}
|
|
@@ -4,3 +4,4 @@ export declare function syncPlanStripeRefs(plan: Plan, store?: PlanRefsStore): P
|
|
|
4
4
|
export declare function ensurePlanStripeRefs(plan: Plan, store?: PlanRefsStore): Promise<Plan>;
|
|
5
5
|
export declare function loadBillablePlan(planId: string): Promise<Plan | undefined>;
|
|
6
6
|
export declare function reconcilePlansWithStripe(): Promise<void>;
|
|
7
|
+
export declare function startPlanReconciliation(): void;
|
|
@@ -4,6 +4,7 @@ exports.syncPlanStripeRefs = syncPlanStripeRefs;
|
|
|
4
4
|
exports.ensurePlanStripeRefs = ensurePlanStripeRefs;
|
|
5
5
|
exports.loadBillablePlan = loadBillablePlan;
|
|
6
6
|
exports.reconcilePlansWithStripe = reconcilePlansWithStripe;
|
|
7
|
+
exports.startPlanReconciliation = startPlanReconciliation;
|
|
7
8
|
const logging_1 = require("@antelopejs/interface-core/logging");
|
|
8
9
|
const interface_database_decorators_1 = require("@antelopejs/interface-database-decorators");
|
|
9
10
|
const db_1 = require("../db");
|
|
@@ -79,4 +80,7 @@ async function reconcilePlansWithStripe() {
|
|
|
79
80
|
await ensurePlanStripeRefs(plan, model);
|
|
80
81
|
}
|
|
81
82
|
}
|
|
83
|
+
function startPlanReconciliation() {
|
|
84
|
+
void reconcilePlansWithStripe().catch((error) => logging_1.Logging.Error(`${LOG_PREFIX} Stripe plan reconciliation failed`, error));
|
|
85
|
+
}
|
|
82
86
|
//# sourceMappingURL=stripe-sync.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stripe-sync.js","sourceRoot":"","sources":["../../src/plans/stripe-sync.ts"],"names":[],"mappings":";;AA8EA,gDAaC;AAUD,oDAUC;AAOD,4CAMC;
|
|
1
|
+
{"version":3,"file":"stripe-sync.js","sourceRoot":"","sources":["../../src/plans/stripe-sync.ts"],"names":[],"mappings":"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"}
|
|
@@ -3,6 +3,7 @@ export * from "./billing-self-refund";
|
|
|
3
3
|
export * from "./data-export";
|
|
4
4
|
export * from "./tenant-billing";
|
|
5
5
|
export * from "./tenant-plan";
|
|
6
|
+
export * from "./tenant-plan-checkout";
|
|
6
7
|
export * from "./tenant-plan-ops";
|
|
7
8
|
export * from "./tenant-seats";
|
|
8
9
|
export * from "./workspaces";
|
|
@@ -19,6 +19,7 @@ __exportStar(require("./billing-self-refund"), exports);
|
|
|
19
19
|
__exportStar(require("./data-export"), exports);
|
|
20
20
|
__exportStar(require("./tenant-billing"), exports);
|
|
21
21
|
__exportStar(require("./tenant-plan"), exports);
|
|
22
|
+
__exportStar(require("./tenant-plan-checkout"), exports);
|
|
22
23
|
__exportStar(require("./tenant-plan-ops"), exports);
|
|
23
24
|
__exportStar(require("./tenant-seats"), exports);
|
|
24
25
|
__exportStar(require("./workspaces"), exports);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/routes/tenant/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,mDAAiC;AACjC,wDAAsC;AACtC,gDAA8B;AAC9B,mDAAiC;AACjC,gDAA8B;AAC9B,oDAAkC;AAClC,iDAA+B;AAC/B,+CAA6B"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/routes/tenant/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,mDAAiC;AACjC,wDAAsC;AACtC,gDAA8B;AAC9B,mDAAiC;AACjC,gDAA8B;AAC9B,yDAAuC;AACvC,oDAAkC;AAClC,iDAA+B;AAC/B,+CAA6B"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { type TenantSubscription, type TenantSubscriptionModel } from "../../db";
|
|
2
|
+
import { type ChangePlanResult, type PlanChangeRequest } from "./tenant-plan-ops";
|
|
3
|
+
export declare const UNRECORDED_CHECKOUT_TTL_MS: number;
|
|
4
|
+
export declare function releaseAbandonedCheckout(subscription: TenantSubscription, model: TenantSubscriptionModel, now?: Date): Promise<TenantSubscription>;
|
|
5
|
+
export declare function startPaidCheckout(request: PlanChangeRequest): Promise<ChangePlanResult>;
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.UNRECORDED_CHECKOUT_TTL_MS = void 0;
|
|
4
|
+
exports.releaseAbandonedCheckout = releaseAbandonedCheckout;
|
|
5
|
+
exports.startPaidCheckout = startPaidCheckout;
|
|
6
|
+
const node_crypto_1 = require("node:crypto");
|
|
7
|
+
const interface_api_1 = require("@antelopejs/interface-api");
|
|
8
|
+
const interface_api_util_1 = require("@antelopejs/interface-api-util");
|
|
9
|
+
const logging_1 = require("@antelopejs/interface-core/logging");
|
|
10
|
+
const interface_database_decorators_1 = require("@antelopejs/interface-database-decorators");
|
|
11
|
+
const tenant_lifecycle_1 = require("@antelopejs/interface-dms/tenant-lifecycle");
|
|
12
|
+
const billing_state_1 = require("../../billing-state");
|
|
13
|
+
const config_1 = require("../../config");
|
|
14
|
+
const db_1 = require("../../db");
|
|
15
|
+
const client_1 = require("../../stripe/client");
|
|
16
|
+
const workspace_checkout_1 = require("../../stripe/workspace-checkout");
|
|
17
|
+
const utils_1 = require("../../utils");
|
|
18
|
+
const complimentary_1 = require("../../workspaces/complimentary");
|
|
19
|
+
const trial_identity_model_1 = require("../../workspaces/db/trial-identity.model");
|
|
20
|
+
const tenant_plan_ops_1 = require("./tenant-plan-ops");
|
|
21
|
+
const HTTP_UNPROCESSABLE = 422;
|
|
22
|
+
const HTTP_SERVICE_UNAVAILABLE = 503;
|
|
23
|
+
const PENDING_PAYMENT_STATUS = "pending_payment";
|
|
24
|
+
const PLACEHOLDER_PLAN_ID = null;
|
|
25
|
+
const CHECKOUT_KIND = "checkout";
|
|
26
|
+
const EXPIRED_SESSION_STATUS = "expired";
|
|
27
|
+
const MS_PER_MINUTE = 60_000;
|
|
28
|
+
const LOG_PREFIX = "[dms-saas:checkout]";
|
|
29
|
+
exports.UNRECORDED_CHECKOUT_TTL_MS = 15 * MS_PER_MINUTE;
|
|
30
|
+
const NO_TRIAL = { days: 0, reservedIdentityId: null };
|
|
31
|
+
function asNonEmptyString(value) {
|
|
32
|
+
return typeof value === "string" && value.trim().length > 0
|
|
33
|
+
? value.trim()
|
|
34
|
+
: null;
|
|
35
|
+
}
|
|
36
|
+
function validateCheckoutRedirectInput(body) {
|
|
37
|
+
const successUrl = asNonEmptyString(body.successUrl);
|
|
38
|
+
const cancelUrl = asNonEmptyString(body.cancelUrl);
|
|
39
|
+
(0, interface_api_util_1.assert)(successUrl && (0, config_1.isAllowedRedirectUrl)(successUrl), tenant_plan_ops_1.HTTP_BAD_REQUEST, "saas.errors.billing.invalid_return_url");
|
|
40
|
+
(0, interface_api_util_1.assert)(cancelUrl && (0, config_1.isAllowedRedirectUrl)(cancelUrl), tenant_plan_ops_1.HTTP_BAD_REQUEST, "saas.errors.billing.invalid_return_url");
|
|
41
|
+
return { successUrl, cancelUrl };
|
|
42
|
+
}
|
|
43
|
+
async function resolveCheckoutTrial(request) {
|
|
44
|
+
const { newPlan, user, tenantId } = request;
|
|
45
|
+
if (!newPlan.trialDays || newPlan.trialDays <= 0)
|
|
46
|
+
return NO_TRIAL;
|
|
47
|
+
const trialConsumptionModel = (0, interface_database_decorators_1.GetModel)(db_1.TrialConsumptionModel);
|
|
48
|
+
const emailHash = (0, utils_1.hashEmail)(user.email);
|
|
49
|
+
if (await trialConsumptionModel.existsForIdentity(emailHash, null)) {
|
|
50
|
+
return NO_TRIAL;
|
|
51
|
+
}
|
|
52
|
+
const identities = (0, interface_database_decorators_1.GetModel)(trial_identity_model_1.TrialIdentityModel);
|
|
53
|
+
const id = (0, trial_identity_model_1.trialIdentityId)("email", emailHash);
|
|
54
|
+
if ((await identities.get(id))?.tenantId)
|
|
55
|
+
return NO_TRIAL;
|
|
56
|
+
if (!(await identities.reserve(id, tenantId)))
|
|
57
|
+
return NO_TRIAL;
|
|
58
|
+
return { days: newPlan.trialDays, reservedIdentityId: id };
|
|
59
|
+
}
|
|
60
|
+
async function initializeCheckoutSubscription(request) {
|
|
61
|
+
if (request.subscription)
|
|
62
|
+
return request.subscription;
|
|
63
|
+
await request.tenantSubscriptionModel.insert({
|
|
64
|
+
_id: request.tenantId,
|
|
65
|
+
planId: PLACEHOLDER_PLAN_ID,
|
|
66
|
+
status: PENDING_PAYMENT_STATUS,
|
|
67
|
+
isComplimentary: false,
|
|
68
|
+
paidUsagePeriods: [],
|
|
69
|
+
stripeCustomerId: null,
|
|
70
|
+
stripeSubscriptionId: null,
|
|
71
|
+
stripeCheckoutSessionId: null,
|
|
72
|
+
createdBy: request.user._id,
|
|
73
|
+
createdAt: new Date(),
|
|
74
|
+
updatedAt: new Date(),
|
|
75
|
+
});
|
|
76
|
+
const subscription = await request.tenantSubscriptionModel.get(request.tenantId);
|
|
77
|
+
if (!subscription)
|
|
78
|
+
throw new Error("Checkout subscription acknowledgement requires reconciliation");
|
|
79
|
+
return subscription;
|
|
80
|
+
}
|
|
81
|
+
async function retrieveRecordedSession(subscription, transition) {
|
|
82
|
+
if (!subscription.stripeCheckoutSessionId)
|
|
83
|
+
return null;
|
|
84
|
+
const session = await (0, client_1.getStripeClient)().checkout.sessions.retrieve(subscription.stripeCheckoutSessionId);
|
|
85
|
+
return session.metadata?.operationId === transition.operationId
|
|
86
|
+
? session
|
|
87
|
+
: null;
|
|
88
|
+
}
|
|
89
|
+
function isUnrecordedPastTtl(transition, now) {
|
|
90
|
+
const age = now.getTime() - new Date(transition.requestedAt).getTime();
|
|
91
|
+
return age >= exports.UNRECORDED_CHECKOUT_TTL_MS;
|
|
92
|
+
}
|
|
93
|
+
async function isAbandonedCheckout(subscription, transition, now) {
|
|
94
|
+
const session = await retrieveRecordedSession(subscription, transition);
|
|
95
|
+
if (session)
|
|
96
|
+
return session.status === EXPIRED_SESSION_STATUS;
|
|
97
|
+
return isUnrecordedPastTtl(transition, now);
|
|
98
|
+
}
|
|
99
|
+
async function releaseAbandonedCheckout(subscription, model, now = new Date()) {
|
|
100
|
+
const transition = subscription.domainTransition;
|
|
101
|
+
if (transition?.kind !== CHECKOUT_KIND)
|
|
102
|
+
return subscription;
|
|
103
|
+
if (!(await isAbandonedCheckout(subscription, transition, now))) {
|
|
104
|
+
return subscription;
|
|
105
|
+
}
|
|
106
|
+
logging_1.Logging.Warn(`${LOG_PREFIX} releasing abandoned checkout ${transition.operationId} of tenant ${subscription._id}`);
|
|
107
|
+
const outcome = await model.mutateRevision(subscription, {
|
|
108
|
+
domainTransition: null,
|
|
109
|
+
stripeCheckoutSessionId: null,
|
|
110
|
+
});
|
|
111
|
+
if (outcome !== "applied")
|
|
112
|
+
throw new Error(`Checkout release ${outcome}; reload before retry`);
|
|
113
|
+
const released = await model.get(subscription._id);
|
|
114
|
+
if (!released)
|
|
115
|
+
throw new Error("Subscription vanished during release");
|
|
116
|
+
return released;
|
|
117
|
+
}
|
|
118
|
+
async function attachCheckoutSession(checkout, subscription, model, operationId) {
|
|
119
|
+
await model.updateDuringTransition(subscription._id, operationId, {
|
|
120
|
+
isComplimentary: (0, complimentary_1.isComplimentarySubscription)(subscription),
|
|
121
|
+
stripeCustomerId: checkout.customerId,
|
|
122
|
+
stripeCheckoutSessionId: checkout.sessionId,
|
|
123
|
+
updatedAt: new Date(),
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
async function expireUnusableSession(error) {
|
|
127
|
+
if (!(error instanceof workspace_checkout_1.UnusableCheckoutSessionError))
|
|
128
|
+
return;
|
|
129
|
+
await (0, client_1.getStripeClient)().checkout.sessions.expire(error.sessionId);
|
|
130
|
+
}
|
|
131
|
+
async function rollbackCheckoutAttempt(attempt, model) {
|
|
132
|
+
await model.completeTransition(attempt.subscriptionId, attempt.operationId, {});
|
|
133
|
+
if (attempt.reservedTrialIdentityId) {
|
|
134
|
+
await (0, interface_database_decorators_1.GetModel)(trial_identity_model_1.TrialIdentityModel)
|
|
135
|
+
.releaseUnused(attempt.reservedTrialIdentityId, attempt.tenantId)
|
|
136
|
+
.catch((releaseError) => logging_1.Logging.Warn(`${LOG_PREFIX} failed to release the trial reserved by checkout ${attempt.operationId}`, releaseError));
|
|
137
|
+
}
|
|
138
|
+
if (attempt.createdCustomerId) {
|
|
139
|
+
await (0, workspace_checkout_1.deleteStripeCustomerIfExists)(attempt.createdCustomerId).catch((cleanupError) => logging_1.Logging.Warn(`${LOG_PREFIX} failed to delete unused Stripe customer ${attempt.createdCustomerId}`, cleanupError));
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
async function failCheckoutAttempt(error, attempt, model) {
|
|
143
|
+
logging_1.Logging.Error(`${LOG_PREFIX} Stripe Checkout failed for tenant ${attempt.tenantId}`, error);
|
|
144
|
+
const isUnusableSession = error instanceof workspace_checkout_1.UnusableCheckoutSessionError;
|
|
145
|
+
if (!(0, workspace_checkout_1.isDefinitiveStripeRejection)(error) && !isUnusableSession) {
|
|
146
|
+
throw new interface_api_1.HTTPResult(HTTP_SERVICE_UNAVAILABLE, "saas.errors.billing.checkout_unavailable");
|
|
147
|
+
}
|
|
148
|
+
await expireUnusableSession(error);
|
|
149
|
+
await rollbackCheckoutAttempt(attempt, model);
|
|
150
|
+
throw new interface_api_1.HTTPResult(HTTP_UNPROCESSABLE, "saas.errors.billing.checkout_rejected");
|
|
151
|
+
}
|
|
152
|
+
async function admitCheckout(request) {
|
|
153
|
+
const model = request.tenantSubscriptionModel;
|
|
154
|
+
const subscription = await releaseAbandonedCheckout(await initializeCheckoutSubscription(request), model);
|
|
155
|
+
(0, interface_api_util_1.assert)(!subscription.domainTransition, tenant_plan_ops_1.HTTP_CONFLICT, subscription.domainTransition?.kind === CHECKOUT_KIND
|
|
156
|
+
? "saas.errors.plan.checkout_in_progress"
|
|
157
|
+
: "saas.errors.plan.change_in_progress");
|
|
158
|
+
const operationId = (0, node_crypto_1.randomUUID)();
|
|
159
|
+
await model.beginTransition(subscription, {
|
|
160
|
+
operationId,
|
|
161
|
+
kind: CHECKOUT_KIND,
|
|
162
|
+
targetPlanId: request.newPlan._id,
|
|
163
|
+
requestedAt: new Date(),
|
|
164
|
+
});
|
|
165
|
+
return {
|
|
166
|
+
subscription,
|
|
167
|
+
operationId,
|
|
168
|
+
tenantId: request.tenantId,
|
|
169
|
+
subscriptionId: subscription._id,
|
|
170
|
+
createdCustomerId: null,
|
|
171
|
+
reservedTrialIdentityId: null,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
async function openCheckoutSession(request, attempt, target) {
|
|
175
|
+
const { existingCustomerId, redirect } = target;
|
|
176
|
+
const trial = await resolveCheckoutTrial(request);
|
|
177
|
+
attempt.reservedTrialIdentityId = trial.reservedIdentityId;
|
|
178
|
+
const customerId = existingCustomerId ??
|
|
179
|
+
(await (0, workspace_checkout_1.createTenantCheckoutCustomer)({
|
|
180
|
+
operationId: attempt.operationId,
|
|
181
|
+
tenantId: request.tenantId,
|
|
182
|
+
ownerEmail: request.user.email,
|
|
183
|
+
}));
|
|
184
|
+
if (!existingCustomerId)
|
|
185
|
+
attempt.createdCustomerId = customerId;
|
|
186
|
+
return (0, workspace_checkout_1.createWorkspaceCheckoutSession)({
|
|
187
|
+
operationId: attempt.operationId,
|
|
188
|
+
tenantId: request.tenantId,
|
|
189
|
+
ownerEmail: request.user.email,
|
|
190
|
+
stripePriceId: target.stripePriceId,
|
|
191
|
+
trialDays: trial.days,
|
|
192
|
+
successUrl: redirect.successUrl,
|
|
193
|
+
cancelUrl: redirect.cancelUrl,
|
|
194
|
+
planId: request.newPlan._id,
|
|
195
|
+
existingCustomerId: customerId,
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
async function createAdmittedCheckout(request) {
|
|
199
|
+
const { tenantId, newPlan, body, tenantSubscriptionModel } = request;
|
|
200
|
+
const redirect = validateCheckoutRedirectInput(body);
|
|
201
|
+
const stripePriceId = newPlan.paymentProviderRefs?.stripePriceId;
|
|
202
|
+
(0, interface_api_util_1.assert)(stripePriceId, tenant_plan_ops_1.HTTP_BAD_REQUEST, "saas.errors.plan.no_stripe_price");
|
|
203
|
+
const { subscription, ...attempt } = await admitCheckout(request);
|
|
204
|
+
const checkout = await openCheckoutSession(request, attempt, {
|
|
205
|
+
stripePriceId,
|
|
206
|
+
existingCustomerId: subscription.stripeCustomerId ?? null,
|
|
207
|
+
redirect,
|
|
208
|
+
}).catch((error) => failCheckoutAttempt(error, attempt, tenantSubscriptionModel));
|
|
209
|
+
await attachCheckoutSession(checkout, subscription, tenantSubscriptionModel, attempt.operationId);
|
|
210
|
+
await (0, billing_state_1.recomputeTenantBillingState)(tenantId);
|
|
211
|
+
return {
|
|
212
|
+
...tenant_plan_ops_1.UNCHANGED_RESULT_BASE,
|
|
213
|
+
planId: newPlan._id,
|
|
214
|
+
checkoutUrl: checkout.url,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
async function startPaidCheckout(request) {
|
|
218
|
+
return (0, tenant_lifecycle_1.runTenantLifecycleOperation)(request.tenantId, () => createAdmittedCheckout(request));
|
|
219
|
+
}
|
|
220
|
+
//# sourceMappingURL=tenant-plan-checkout.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tenant-plan-checkout.js","sourceRoot":"","sources":["../../../src/routes/tenant/tenant-plan-checkout.ts"],"names":[],"mappings":"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"}
|
|
@@ -50,7 +50,6 @@ export declare function loadAndValidateTargetPlan(planModel: PlanModel, planId:
|
|
|
50
50
|
export declare function assertSeatLimit(tenantId: string, plan: Plan): Promise<void>;
|
|
51
51
|
export declare function finalizeImmediateChange(tenantId: string, subscription: TenantSubscription, newPlan: Plan): Promise<void>;
|
|
52
52
|
export declare function isPaidPlan(plan: Plan): boolean;
|
|
53
|
-
export declare function startPaidCheckout(request: PlanChangeRequest): Promise<ChangePlanResult>;
|
|
54
53
|
export declare function insertFreeSubscription(user: User, newPlanId: string, tenantId: string, tenantSubscriptionModel: TenantSubscriptionModel): Promise<ChangePlanResult>;
|
|
55
54
|
export declare function scheduleDowngrade(tenantId: string, subscription: TenantSubscription, newPlan: Plan): Promise<ChangePlanResult>;
|
|
56
55
|
export declare function dropPendingChange(tenantId: string, subscription: TenantSubscription): Promise<ChangePlanResult>;
|
|
@@ -5,26 +5,18 @@ exports.loadAndValidateTargetPlan = loadAndValidateTargetPlan;
|
|
|
5
5
|
exports.assertSeatLimit = assertSeatLimit;
|
|
6
6
|
exports.finalizeImmediateChange = finalizeImmediateChange;
|
|
7
7
|
exports.isPaidPlan = isPaidPlan;
|
|
8
|
-
exports.startPaidCheckout = startPaidCheckout;
|
|
9
8
|
exports.insertFreeSubscription = insertFreeSubscription;
|
|
10
9
|
exports.scheduleDowngrade = scheduleDowngrade;
|
|
11
10
|
exports.dropPendingChange = dropPendingChange;
|
|
12
11
|
exports.applyImmediateChange = applyImmediateChange;
|
|
13
12
|
const node_crypto_1 = require("node:crypto");
|
|
14
13
|
const interface_api_util_1 = require("@antelopejs/interface-api-util");
|
|
15
|
-
const interface_database_decorators_1 = require("@antelopejs/interface-database-decorators");
|
|
16
14
|
const tenant_lifecycle_1 = require("@antelopejs/interface-dms/tenant-lifecycle");
|
|
17
15
|
const billing_state_1 = require("../../billing-state");
|
|
18
|
-
const config_1 = require("../../config");
|
|
19
|
-
const db_1 = require("../../db");
|
|
20
16
|
const plan_changes_1 = require("../../plan-changes");
|
|
21
17
|
const plans_1 = require("../../plans");
|
|
22
18
|
const client_1 = require("../../stripe/client");
|
|
23
|
-
const workspace_checkout_1 = require("../../stripe/workspace-checkout");
|
|
24
|
-
const utils_1 = require("../../utils");
|
|
25
19
|
const workers_1 = require("../../workers");
|
|
26
|
-
const complimentary_1 = require("../../workspaces/complimentary");
|
|
27
|
-
const trial_identity_model_1 = require("../../workspaces/db/trial-identity.model");
|
|
28
20
|
exports.HTTP_NOT_FOUND = 404;
|
|
29
21
|
exports.HTTP_BAD_REQUEST = 400;
|
|
30
22
|
const HTTP_PAYMENT_REQUIRED = 402;
|
|
@@ -32,26 +24,12 @@ exports.HTTP_CONFLICT = 409;
|
|
|
32
24
|
exports.PAST_DUE_STATUS = "past_due";
|
|
33
25
|
const PRORATION_BEHAVIOR = "create_prorations";
|
|
34
26
|
const ACTIVE_STATUS = "active";
|
|
35
|
-
const PENDING_PAYMENT_STATUS = "pending_payment";
|
|
36
|
-
const PLACEHOLDER_PLAN_ID = null;
|
|
37
27
|
exports.UNCHANGED_RESULT_BASE = {
|
|
38
28
|
changed: false,
|
|
39
29
|
scheduled: false,
|
|
40
30
|
effectiveAt: null,
|
|
41
31
|
checkoutUrl: null,
|
|
42
32
|
};
|
|
43
|
-
function asNonEmptyString(value) {
|
|
44
|
-
return typeof value === "string" && value.trim().length > 0
|
|
45
|
-
? value.trim()
|
|
46
|
-
: null;
|
|
47
|
-
}
|
|
48
|
-
function validateCheckoutRedirectInput(body) {
|
|
49
|
-
const successUrl = asNonEmptyString(body.successUrl);
|
|
50
|
-
const cancelUrl = asNonEmptyString(body.cancelUrl);
|
|
51
|
-
(0, interface_api_util_1.assert)(successUrl && (0, config_1.isAllowedRedirectUrl)(successUrl), exports.HTTP_BAD_REQUEST, "saas.errors.billing.invalid_return_url");
|
|
52
|
-
(0, interface_api_util_1.assert)(cancelUrl && (0, config_1.isAllowedRedirectUrl)(cancelUrl), exports.HTTP_BAD_REQUEST, "saas.errors.billing.invalid_return_url");
|
|
53
|
-
return { successUrl, cancelUrl };
|
|
54
|
-
}
|
|
55
33
|
async function loadAndValidateTargetPlan(planModel, planId, customerType) {
|
|
56
34
|
const storedPlan = await planModel.get(planId);
|
|
57
35
|
(0, interface_api_util_1.assert)(storedPlan && !storedPlan.isDeleted && storedPlan.isActive, exports.HTTP_BAD_REQUEST, "saas.errors.plan.invalid");
|
|
@@ -102,84 +80,6 @@ async function syncStripePlanChange(subscription, newPlan, operationId) {
|
|
|
102
80
|
function isPaidPlan(plan) {
|
|
103
81
|
return !!plan.paymentProviderRefs?.stripePriceId;
|
|
104
82
|
}
|
|
105
|
-
async function resolveCheckoutTrialDays(newPlan, ownerEmail, tenantId) {
|
|
106
|
-
if (!newPlan.trialDays || newPlan.trialDays <= 0)
|
|
107
|
-
return 0;
|
|
108
|
-
const trialConsumptionModel = (0, interface_database_decorators_1.GetModel)(db_1.TrialConsumptionModel);
|
|
109
|
-
const alreadyConsumed = await trialConsumptionModel.existsForIdentity((0, utils_1.hashEmail)(ownerEmail), null);
|
|
110
|
-
if (alreadyConsumed)
|
|
111
|
-
return 0;
|
|
112
|
-
const identities = (0, interface_database_decorators_1.GetModel)(trial_identity_model_1.TrialIdentityModel);
|
|
113
|
-
const id = (0, trial_identity_model_1.trialIdentityId)("email", (0, utils_1.hashEmail)(ownerEmail));
|
|
114
|
-
if ((await identities.get(id))?.tenantId)
|
|
115
|
-
return 0;
|
|
116
|
-
return (await identities.reserve(id, tenantId)) ? newPlan.trialDays : 0;
|
|
117
|
-
}
|
|
118
|
-
async function attachCheckoutSession(checkout, subscription, tenantSubscriptionModel, operationId) {
|
|
119
|
-
await tenantSubscriptionModel.updateDuringTransition(subscription._id, operationId, {
|
|
120
|
-
isComplimentary: (0, complimentary_1.isComplimentarySubscription)(subscription),
|
|
121
|
-
stripeCustomerId: checkout.customerId,
|
|
122
|
-
stripeCheckoutSessionId: checkout.sessionId,
|
|
123
|
-
updatedAt: new Date(),
|
|
124
|
-
});
|
|
125
|
-
}
|
|
126
|
-
async function initializeCheckoutSubscription(request) {
|
|
127
|
-
if (request.subscription)
|
|
128
|
-
return request.subscription;
|
|
129
|
-
await request.tenantSubscriptionModel.insert({
|
|
130
|
-
_id: request.tenantId,
|
|
131
|
-
planId: PLACEHOLDER_PLAN_ID,
|
|
132
|
-
status: PENDING_PAYMENT_STATUS,
|
|
133
|
-
isComplimentary: false,
|
|
134
|
-
paidUsagePeriods: [],
|
|
135
|
-
stripeCustomerId: null,
|
|
136
|
-
stripeSubscriptionId: null,
|
|
137
|
-
stripeCheckoutSessionId: null,
|
|
138
|
-
createdBy: request.user._id,
|
|
139
|
-
createdAt: new Date(),
|
|
140
|
-
updatedAt: new Date(),
|
|
141
|
-
});
|
|
142
|
-
const subscription = await request.tenantSubscriptionModel.get(request.tenantId);
|
|
143
|
-
if (!subscription)
|
|
144
|
-
throw new Error("Checkout subscription acknowledgement requires reconciliation");
|
|
145
|
-
return subscription;
|
|
146
|
-
}
|
|
147
|
-
async function createAdmittedCheckout(request) {
|
|
148
|
-
const { tenantId, user, newPlan, body, tenantSubscriptionModel } = request;
|
|
149
|
-
const redirect = validateCheckoutRedirectInput(body);
|
|
150
|
-
const stripePriceId = newPlan.paymentProviderRefs?.stripePriceId;
|
|
151
|
-
(0, interface_api_util_1.assert)(stripePriceId, exports.HTTP_BAD_REQUEST, "saas.errors.plan.no_stripe_price");
|
|
152
|
-
const subscription = await initializeCheckoutSubscription(request);
|
|
153
|
-
const operationId = (0, node_crypto_1.randomUUID)();
|
|
154
|
-
await tenantSubscriptionModel.beginTransition(subscription, {
|
|
155
|
-
operationId,
|
|
156
|
-
kind: "checkout",
|
|
157
|
-
targetPlanId: newPlan._id,
|
|
158
|
-
requestedAt: new Date(),
|
|
159
|
-
});
|
|
160
|
-
const trialDays = await resolveCheckoutTrialDays(newPlan, user.email, tenantId);
|
|
161
|
-
const checkout = await (0, workspace_checkout_1.createWorkspaceCheckoutSession)({
|
|
162
|
-
operationId,
|
|
163
|
-
tenantId,
|
|
164
|
-
ownerEmail: user.email,
|
|
165
|
-
stripePriceId,
|
|
166
|
-
trialDays,
|
|
167
|
-
successUrl: redirect.successUrl,
|
|
168
|
-
cancelUrl: redirect.cancelUrl,
|
|
169
|
-
planId: newPlan._id,
|
|
170
|
-
existingCustomerId: subscription?.stripeCustomerId ?? undefined,
|
|
171
|
-
});
|
|
172
|
-
await attachCheckoutSession(checkout, subscription, tenantSubscriptionModel, operationId);
|
|
173
|
-
await (0, billing_state_1.recomputeTenantBillingState)(tenantId);
|
|
174
|
-
return {
|
|
175
|
-
...exports.UNCHANGED_RESULT_BASE,
|
|
176
|
-
planId: newPlan._id,
|
|
177
|
-
checkoutUrl: checkout.url,
|
|
178
|
-
};
|
|
179
|
-
}
|
|
180
|
-
async function startPaidCheckout(request) {
|
|
181
|
-
return (0, tenant_lifecycle_1.runTenantLifecycleOperation)(request.tenantId, () => createAdmittedCheckout(request));
|
|
182
|
-
}
|
|
183
83
|
async function insertFreeSubscription(user, newPlanId, tenantId, tenantSubscriptionModel) {
|
|
184
84
|
await (0, tenant_lifecycle_1.runTenantLifecycleOperation)(tenantId, () => tenantSubscriptionModel.insert([
|
|
185
85
|
{
|