@mostajs/subscriptions-plan 0.4.0 → 0.5.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/CHANGELOG.md CHANGED
@@ -2,6 +2,24 @@
2
2
 
3
3
  **Auteur** : Dr Hamid MADANI <drmdh@msn.com>
4
4
 
5
+ ## 0.5.0 — 2026-10-01
6
+
7
+ ### Ajouté (additif, **non-breaking**) — la facturation de mdseal, remontée dans le module
8
+ - **`createBilling({ dialect, resources, freePlan })`** : ressources de quota **déclarées** par l'application
9
+ (`{ clé: { limit: 'champ du plan', period: 'day' | 'month' } }`) — fin du domaine fermé à trois.
10
+ `check` rend un verdict **permanent** (pas d'abonnement, plan muet ou à 0 : changer de plan) ou **temporaire**
11
+ (forfait épuisé, `retryAfterSeconds`). `consume` réserve **atomiquement** (borne exacte dans le processus,
12
+ jamais dépassée entre processus), `release` rend. `setPlan` clôt avant d'ouvrir. `updatePlanLimits` refuse une
13
+ **échelle inversée** (incident mdseal du 26/09).
14
+ - **`checkLimits`** (verdict pur, pour le palier anonyme), **`assertMonotonicPlans`**, **`secondsUntilReset`**.
15
+ - **`createStripeSale({ billing, stripe, webhookSecret, customers, metadataKeys, urls })`** :
16
+ `openCheckout` (client Stripe créé une fois par un port de l'application ; métadonnées sur la session **et**
17
+ `subscription_data`) ; `handleWebhook(corpsBrut, signature)` — 400/503/500/200, **idempotent par `event.id`**
18
+ (journal `BillingEvent`), **ordre d'arrivée** respecté (un événement plus ancien que le dernier appliqué est
19
+ ignoré ; un `updated` tardif après un `deleted` ne ré-abonne pas), impayé → plan gratuit, facture en échec puis
20
+ payée → « payée ».
21
+ - Schémas `UsageCounter`, `BillingEvent` ; `Subscription` gagne `stripeEventAt`, `stripeEndedAt`.
22
+
5
23
  ## 0.4.0 — 2026-06-10
6
24
 
7
25
  ### Ajouté (additif, **non-breaking**)
package/README.md CHANGED
@@ -44,6 +44,18 @@ Additif et **non-breaking** : sans `scope`, le comportement est identique aux ve
44
44
 
45
45
  ---
46
46
 
47
+ ## Billing engine *(v0.5.0)*
48
+
49
+ Declared quota resources (`{ key: { limit, period: 'day' | 'month' } }`), permanent vs temporary verdicts
50
+ with `Retry-After`, an atomic counter, a monotonic tier scale, and a Stripe sale whose webhook is idempotent
51
+ by `event.id` and robust to out-of-order delivery. See `llms.txt` § 0.5.
52
+
53
+ ```js
54
+ const billing = createBilling({ dialect, freePlan: 'free', resources: { signature: { limit: 'signaturesPerMonth', period: 'month' } } })
55
+ const v = await billing.consume(accountId, 'signature')
56
+ const sale = createStripeSale({ billing, stripe, webhookSecret, customers, urls })
57
+ ```
58
+
47
59
  ## Install
48
60
 
49
61
  ```bash
@@ -0,0 +1,136 @@
1
+ import type { EntitySchema } from '@mostajs/data-plug';
2
+ type Dialect = any;
3
+ /** Le compteur : une ligne par compte × ressource × période (jour `YYYY-MM-DD`, mois `YYYY-MM`). */
4
+ export declare const UsageCounterSchema: EntitySchema;
5
+ /** Le journal des événements de paiement déjà reçus : l'idempotence par `event.id`. */
6
+ export declare const BillingEventSchema: EntitySchema;
7
+ /** Tous les schémas qu'emploie la facturation 0.5 (à initialiser avec le schéma `Account` de l'hôte). */
8
+ export declare function billingSchemas(): EntitySchema[];
9
+ export type Period = 'day' | 'month';
10
+ export interface ResourceDef {
11
+ limit: string;
12
+ period: Period;
13
+ }
14
+ export interface Verdict {
15
+ allowed: boolean;
16
+ /** `true` : attendre n'y changera rien (pas d'abonnement, plan muet ou à 0) — changer de plan. */
17
+ permanent?: boolean;
18
+ resource?: string;
19
+ current: number;
20
+ /** -1 = sans limite. */
21
+ limit: number;
22
+ period: Period;
23
+ plan?: string;
24
+ reason?: string;
25
+ /** Pour l'en-tête `Retry-After` d'un refus TEMPORAIRE. */
26
+ retryAfterSeconds?: number;
27
+ }
28
+ export declare const UNLIMITED = -1;
29
+ /** Secondes jusqu'à la réouverture : minuit UTC (jour) ou le 1er du mois suivant (mois). */
30
+ export declare function secondsUntilReset(period: Period, now?: Date): number;
31
+ /**
32
+ * Le verdict PUR : des limites, une ressource, une consommation. Sert au compte comme au demandeur
33
+ * sans compte (palier anonyme). Un plan MUET sur une ressource ne l'autorise pas : le silence d'un
34
+ * catalogue ne vaut pas permission.
35
+ */
36
+ export declare function checkLimits(limits: Record<string, any>, def: ResourceDef, current: number, now?: Date): Verdict;
37
+ export declare class BillingError extends Error {
38
+ code: string;
39
+ constructor(code: string, message: string);
40
+ }
41
+ /**
42
+ * L'échelle monte : rangés par `sortOrder`, un palier ne donne jamais MOINS que celui d'en dessous,
43
+ * sur aucune des clés. -1 (sans limite) vaut l'infini.
44
+ */
45
+ export declare function assertMonotonicPlans(plans: Array<{
46
+ slug: string;
47
+ sortOrder?: number;
48
+ limits: any;
49
+ }>, keys: string[]): void;
50
+ export interface BillingOptions {
51
+ dialect: Dialect;
52
+ resources: Record<string, ResourceDef>;
53
+ /** Le plan de repli (résiliation, impayé). */
54
+ freePlan?: string;
55
+ clock?: () => Date;
56
+ }
57
+ export declare function createBilling(opts: BillingOptions): {
58
+ /** La base où vivent plans, abonnements, compteurs et factures. */
59
+ dialect: any;
60
+ resources: Readonly<{
61
+ [x: string]: ResourceDef;
62
+ }>;
63
+ freePlan: string;
64
+ activePlan: (accountId: string) => Promise<{
65
+ subscription: any;
66
+ plan: any;
67
+ limits: Record<string, any>;
68
+ } | null>;
69
+ check: (accountId: string, resource: string) => Promise<Verdict>;
70
+ setPlan: (accountId: string, slug: string, extra?: Record<string, unknown>) => Promise<any>;
71
+ /** Réserve `n` unités : atomique, ne dépasse jamais la borne. À rendre (`release`) si le travail échoue. */
72
+ consume(accountId: string, resource: string, n?: number): Promise<Verdict>;
73
+ release(accountId: string, resource: string, n?: number): Promise<void>;
74
+ /** La consommation de la période courante, par ressource déclarée. */
75
+ usage(accountId: string): Promise<Record<string, number>>;
76
+ /** Modifier les limites d'un plan — refusé si l'échelle s'inverse (tous les plans, vendus ou non). */
77
+ updatePlanLimits(slug: string, limits: Record<string, any>): Promise<any>;
78
+ };
79
+ export type Billing = ReturnType<typeof createBilling>;
80
+ /** Le port de l'application : où elle range l'identifiant client Stripe de ses comptes. */
81
+ export interface CustomerPort {
82
+ get(accountId: string): Promise<string | null>;
83
+ set(accountId: string, customerId: string): Promise<void>;
84
+ describe(accountId: string): Promise<{
85
+ email?: string;
86
+ name?: string;
87
+ }>;
88
+ accountOf?(customerId: string): Promise<string | null>;
89
+ }
90
+ export interface StripeSaleOptions {
91
+ billing: Billing;
92
+ /** Une instance `stripe` (ou tout objet de même forme : customers, checkout.sessions, webhooks). */
93
+ stripe: any;
94
+ webhookSecret?: string;
95
+ customers: CustomerPort;
96
+ /** Les clés de métadonnées qui relient Stripe à l'application. */
97
+ metadataKeys?: {
98
+ account: string;
99
+ plan: string;
100
+ };
101
+ urls: {
102
+ success?: string;
103
+ cancel?: string;
104
+ };
105
+ /** Statuts Stripe d'un abonnement qui ne paie plus : retour au plan gratuit. */
106
+ unpaidStatuses?: string[];
107
+ }
108
+ export declare function createStripeSale(opts: StripeSaleOptions): {
109
+ openCheckout: ({ accountId, planSlug, locale }: {
110
+ accountId: string;
111
+ planSlug: string;
112
+ locale?: string;
113
+ }) => Promise<{
114
+ ok: false;
115
+ reason: string;
116
+ url?: undefined;
117
+ id?: undefined;
118
+ } | {
119
+ ok: true;
120
+ url: string;
121
+ id: string;
122
+ reason?: undefined;
123
+ }>;
124
+ /**
125
+ * Vérifie et applique un événement. `rawBody` : le corps EXACT (Buffer), jamais ré-sérialisé.
126
+ * 400 signature refusée (Stripe cesse de réessayer) · 503 non configuré · 500 échec d'application
127
+ * (Stripe réessaiera ; l'événement n'est pas marqué reçu) · 200 sinon, rejeu compris.
128
+ */
129
+ handleWebhook(rawBody: Buffer | string, signature: string): Promise<{
130
+ status: number;
131
+ result?: string;
132
+ type?: string;
133
+ }>;
134
+ };
135
+ export {};
136
+ //# sourceMappingURL=billing.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"billing.d.ts","sourceRoot":"","sources":["../../src/lib/billing.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,388 @@
1
+ // @mostajs/subscriptions-plan 0.5 — quotas DÉCLARÉS, compteur atomique, paliers, vente et webhooks Stripe.
2
+ // Author: Dr Hamid MADANI drmdh@msn.com
3
+ //
4
+ // Ce fichier remonte dans le module ce que mdseal fait en production (`plans.mjs`, `acces.mjs`,
5
+ // `administration.mjs`, `vente.mjs`) et ferme ce que l'audit du 01/10/2026 y a trouvé :
6
+ //
7
+ // - le domaine de ressources était FERMÉ à trois (`requests | projects | apiKeys`) : la signature
8
+ // de mdseal n'y entrait pas, et l'application avait réécrit le contrôle à côté. Les ressources
9
+ // sont maintenant DÉCLARÉES par l'application : { clé, champ de limite du plan, période } ;
10
+ // - deux refus qui ne se corrigent pas pareil : PERMANENT (« ton plan ne comprend pas ça » : en
11
+ // changer) et TEMPORAIRE (« forfait épuisé » : il rouvre, et la date se calcule, donc se dit) ;
12
+ // - le compteur se lisait puis s'écrivait : deux requêtes simultanées passaient toutes deux sous
13
+ // la borne. Il s'INCRÉMENTE d'abord, atomiquement, et rend l'unité s'il a dépassé ;
14
+ // - l'échelle des paliers MONTE toujours (incident mdseal du 26/09 : l'anonyme signait, le gratuit
15
+ // non — s'inscrire faisait perdre un droit) : contrôlée ici, pas dans l'écran ;
16
+ // - webhooks : idempotents par `event.id` ; un événement plus ancien que le dernier appliqué est
17
+ // ignoré, et un `updated` tardif après un `deleted` ne ré-abonne plus ; une facture en échec
18
+ // puis payée passe à « payée » (elle restait figée en échec).
19
+ import { PlanSchema } from '../schemas/plan.schema.js';
20
+ import { SubscriptionSchema } from '../schemas/subscription.schema.js';
21
+ import { InvoiceSchema } from '../schemas/invoice.schema.js';
22
+ /** Le compteur : une ligne par compte × ressource × période (jour `YYYY-MM-DD`, mois `YYYY-MM`). */
23
+ export const UsageCounterSchema = {
24
+ name: 'UsageCounter',
25
+ collection: 'usage_counters',
26
+ timestamps: true,
27
+ fields: {
28
+ account: { type: 'string', required: true },
29
+ resource: { type: 'string', required: true },
30
+ period: { type: 'string', required: true },
31
+ count: { type: 'number', default: 0 },
32
+ },
33
+ relations: {},
34
+ indexes: [{ fields: { account: 'asc', resource: 'asc', period: 'asc' }, unique: true }],
35
+ };
36
+ /** Le journal des événements de paiement déjà reçus : l'idempotence par `event.id`. */
37
+ export const BillingEventSchema = {
38
+ name: 'BillingEvent',
39
+ collection: 'billing_events',
40
+ timestamps: true,
41
+ fields: {
42
+ eventId: { type: 'string', required: true },
43
+ type: { type: 'string' },
44
+ result: { type: 'string' },
45
+ },
46
+ relations: {},
47
+ indexes: [{ fields: { eventId: 'asc' }, unique: true }],
48
+ };
49
+ /** Tous les schémas qu'emploie la facturation 0.5 (à initialiser avec le schéma `Account` de l'hôte). */
50
+ export function billingSchemas() {
51
+ return [PlanSchema, SubscriptionSchema, InvoiceSchema, UsageCounterSchema, BillingEventSchema];
52
+ }
53
+ export const UNLIMITED = -1;
54
+ /** Secondes jusqu'à la réouverture : minuit UTC (jour) ou le 1er du mois suivant (mois). */
55
+ export function secondsUntilReset(period, now = new Date()) {
56
+ const next = period === 'month'
57
+ ? Date.UTC(now.getUTCFullYear(), now.getUTCMonth() + 1, 1)
58
+ : Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate() + 1);
59
+ return Math.max(1, Math.ceil((next - now.getTime()) / 1000));
60
+ }
61
+ const periodKey = (period, now) => (period === 'month' ? now.toISOString().slice(0, 7) : now.toISOString().slice(0, 10));
62
+ const limitsOf = (plan) => (typeof plan?.limits === 'string' ? JSON.parse(plan.limits) : (plan?.limits ?? {}));
63
+ /**
64
+ * Le verdict PUR : des limites, une ressource, une consommation. Sert au compte comme au demandeur
65
+ * sans compte (palier anonyme). Un plan MUET sur une ressource ne l'autorise pas : le silence d'un
66
+ * catalogue ne vaut pas permission.
67
+ */
68
+ export function checkLimits(limits, def, current, now = new Date()) {
69
+ const limit = limits?.[def.limit];
70
+ if (limit === undefined || limit === null || Number(limit) === 0) {
71
+ return { allowed: false, permanent: true, current, limit: 0, period: def.period, reason: 'plan does not include this resource' };
72
+ }
73
+ if (Number(limit) === UNLIMITED)
74
+ return { allowed: true, current, limit: UNLIMITED, period: def.period };
75
+ if (current < Number(limit))
76
+ return { allowed: true, current, limit: Number(limit), period: def.period };
77
+ return {
78
+ allowed: false, permanent: false, current, limit: Number(limit), period: def.period,
79
+ reason: 'quota reached', retryAfterSeconds: secondsUntilReset(def.period, now),
80
+ };
81
+ }
82
+ export class BillingError extends Error {
83
+ code;
84
+ constructor(code, message) {
85
+ super(message);
86
+ this.code = code;
87
+ this.name = 'BillingError';
88
+ }
89
+ }
90
+ /**
91
+ * L'échelle monte : rangés par `sortOrder`, un palier ne donne jamais MOINS que celui d'en dessous,
92
+ * sur aucune des clés. -1 (sans limite) vaut l'infini.
93
+ */
94
+ export function assertMonotonicPlans(plans, keys) {
95
+ const v = (x) => (Number(x) === UNLIMITED ? Infinity : Number(x ?? 0));
96
+ const tries = [...plans].sort((a, b) => (a.sortOrder ?? 0) - (b.sortOrder ?? 0));
97
+ for (let i = 1; i < tries.length; i++) {
98
+ const bas = limitsOf(tries[i - 1]);
99
+ const haut = limitsOf(tries[i]);
100
+ for (const k of keys) {
101
+ if (v(haut[k]) < v(bas[k])) {
102
+ throw new BillingError('PLAN_SCALE_INVERTED', `« ${tries[i].slug} » donnerait ${haut[k]} en ${k}, moins que « ${tries[i - 1].slug} » (${bas[k]}) : un palier supérieur ne peut pas donner moins.`);
103
+ }
104
+ }
105
+ }
106
+ }
107
+ export function createBilling(opts) {
108
+ const d = opts.dialect;
109
+ const clock = opts.clock ?? (() => new Date());
110
+ const freePlan = opts.freePlan ?? 'free';
111
+ for (const [k, r] of Object.entries(opts.resources ?? {})) {
112
+ if (!r?.limit || !['day', 'month'].includes(r.period))
113
+ throw new BillingError('RESOURCE_INVALID', `ressource « ${k} » : { limit, period: 'day' | 'month' } attendu`);
114
+ }
115
+ const resources = { ...opts.resources };
116
+ const def = (resource) => {
117
+ const r = resources[resource];
118
+ if (!r)
119
+ throw new BillingError('RESOURCE_UNKNOWN', `ressource non déclarée : ${resource} (déclarées : ${Object.keys(resources).join(', ')})`);
120
+ return r;
121
+ };
122
+ // Sérialisation par clé, dans le processus : rend la borne EXACTE ici. Entre processus, l'incrément
123
+ // atomique garantit qu'on ne la DÉPASSE jamais — mais une rafale concurrente peut alors être
124
+ // refusée en entier (chacun voit le compteur gonflé par les autres avant leur rendu). Mesuré le
125
+ // 01/10/2026 : 20 demandes simultanées sans sérialisation, borne 3 → 0 accordée. Un service à
126
+ // plusieurs processus doit donc router un même compte vers un même processus.
127
+ const files = new Map();
128
+ function enFile(cle, f) {
129
+ const p = (files.get(cle) ?? Promise.resolve()).then(f, f);
130
+ const fin = p.catch(() => { });
131
+ files.set(cle, fin);
132
+ fin.then(() => { if (files.get(cle) === fin)
133
+ files.delete(cle); });
134
+ return p;
135
+ }
136
+ async function activePlan(accountId) {
137
+ const subs = await d.find(SubscriptionSchema, { account: accountId, status: 'active' });
138
+ if (!subs.length)
139
+ return null;
140
+ const plan = await d.findById(PlanSchema, String(subs[0].plan ?? subs[0].planId));
141
+ return plan ? { subscription: subs[0], plan, limits: limitsOf(plan) } : null;
142
+ }
143
+ async function ligneCompteur(accountId, resource, period) {
144
+ const f = { account: accountId, resource, period };
145
+ const l = await d.findOne(UsageCounterSchema, f);
146
+ if (l)
147
+ return l;
148
+ try {
149
+ return await d.create(UsageCounterSchema, { ...f, count: 0 });
150
+ }
151
+ catch {
152
+ return d.findOne(UsageCounterSchema, f);
153
+ } // créée en parallèle
154
+ }
155
+ async function courant(accountId, resource) {
156
+ const r = def(resource);
157
+ const l = await d.findOne(UsageCounterSchema, { account: accountId, resource, period: periodKey(r.period, clock()) });
158
+ return Number(l?.count ?? 0);
159
+ }
160
+ async function check(accountId, resource) {
161
+ const r = def(resource);
162
+ const actif = await activePlan(accountId);
163
+ if (!actif)
164
+ return { allowed: false, permanent: true, resource, current: 0, limit: 0, period: r.period, reason: 'no active subscription' };
165
+ return { ...checkLimits(actif.limits, r, await courant(accountId, resource), clock()), resource, plan: actif.plan.slug };
166
+ }
167
+ /** Clôt avant d'ouvrir : jamais deux abonnements actifs. Sérialisé par compte. */
168
+ function setPlan(accountId, slug, extra = {}) {
169
+ return enFile(`plan:${accountId}`, async () => {
170
+ const plan = await d.findOne(PlanSchema, { slug });
171
+ if (!plan)
172
+ throw new BillingError('PLAN_UNKNOWN', `plan inconnu : ${slug}`);
173
+ await d.updateMany(SubscriptionSchema, { account: accountId, status: 'active' }, { status: 'canceled' });
174
+ const stripeSubId = extra.stripeSubId;
175
+ const existante = stripeSubId ? await d.findOne(SubscriptionSchema, { stripeSubId }) : null;
176
+ if (existante)
177
+ await d.update(SubscriptionSchema, String(existante.id), { ...extra, plan: plan.id, status: 'active' });
178
+ else
179
+ await d.create(SubscriptionSchema, { ...extra, account: accountId, plan: plan.id, status: 'active' });
180
+ return plan;
181
+ });
182
+ }
183
+ return {
184
+ /** La base où vivent plans, abonnements, compteurs et factures. */
185
+ dialect: d,
186
+ resources: Object.freeze({ ...resources }),
187
+ freePlan,
188
+ activePlan,
189
+ check,
190
+ setPlan,
191
+ /** Réserve `n` unités : atomique, ne dépasse jamais la borne. À rendre (`release`) si le travail échoue. */
192
+ async consume(accountId, resource, n = 1) {
193
+ const r = def(resource);
194
+ return enFile(`q:${accountId}|${resource}`, async () => {
195
+ const v = await check(accountId, resource);
196
+ if (!v.allowed)
197
+ return v;
198
+ const l = await ligneCompteur(accountId, resource, periodKey(r.period, clock()));
199
+ const apres = Number((await d.increment(UsageCounterSchema, String(l.id), 'count', n))?.count ?? 0);
200
+ if (v.limit !== UNLIMITED && apres > v.limit) {
201
+ await d.increment(UsageCounterSchema, String(l.id), 'count', -n);
202
+ return { ...v, allowed: false, permanent: false, current: v.limit, reason: 'quota reached', retryAfterSeconds: secondsUntilReset(r.period, clock()) };
203
+ }
204
+ return { ...v, current: apres };
205
+ });
206
+ },
207
+ async release(accountId, resource, n = 1) {
208
+ const r = def(resource);
209
+ await enFile(`q:${accountId}|${resource}`, async () => {
210
+ const l = await d.findOne(UsageCounterSchema, { account: accountId, resource, period: periodKey(r.period, clock()) });
211
+ if (l && Number(l.count) > 0)
212
+ await d.increment(UsageCounterSchema, String(l.id), 'count', -Math.min(n, Number(l.count)));
213
+ });
214
+ },
215
+ /** La consommation de la période courante, par ressource déclarée. */
216
+ async usage(accountId) {
217
+ const out = {};
218
+ for (const k of Object.keys(resources))
219
+ out[k] = await courant(accountId, k);
220
+ return out;
221
+ },
222
+ /** Modifier les limites d'un plan — refusé si l'échelle s'inverse (tous les plans, vendus ou non). */
223
+ async updatePlanLimits(slug, limits) {
224
+ const tous = await d.find(PlanSchema, {});
225
+ const cible = tous.find((p) => p.slug === slug);
226
+ if (!cible)
227
+ throw new BillingError('PLAN_UNKNOWN', `plan inconnu : ${slug}`);
228
+ const nouvelles = { ...limitsOf(cible), ...limits };
229
+ assertMonotonicPlans(tous.map((p) => (p.slug === slug ? { ...p, limits: nouvelles } : p)), Object.values(resources).map((r) => r.limit));
230
+ return d.update(PlanSchema, String(cible.id), { limits: nouvelles });
231
+ },
232
+ };
233
+ }
234
+ export function createStripeSale(opts) {
235
+ const { billing, stripe, customers } = opts;
236
+ const K = opts.metadataKeys ?? { account: 'account', plan: 'plan' };
237
+ const IMPAYES = new Set(opts.unpaidStatuses ?? ['canceled', 'unpaid', 'incomplete_expired']);
238
+ const dialect = () => billing.dialect;
239
+ async function openCheckout({ accountId, planSlug, locale }) {
240
+ const plan = await dialect().findOne(PlanSchema, { slug: planSlug });
241
+ if (!plan)
242
+ return { ok: false, reason: `plan inconnu : ${planSlug}` };
243
+ if (!plan.price)
244
+ return { ok: false, reason: 'plan gratuit : il ne passe pas par le paiement' };
245
+ if (!plan.stripePriceId)
246
+ return { ok: false, reason: `le plan « ${planSlug} » n’a pas de tarif Stripe` };
247
+ let client = await customers.get(accountId);
248
+ if (!client) {
249
+ // UNE fois par compte : sinon chaque tentative fabrique un client de plus dans Stripe.
250
+ const qui = await customers.describe(accountId);
251
+ client = (await stripe.customers.create({ email: qui.email, name: qui.name, metadata: { [K.account]: accountId } })).id;
252
+ await customers.set(accountId, client);
253
+ }
254
+ const metadata = { [K.account]: accountId, [K.plan]: planSlug };
255
+ const s = await stripe.checkout.sessions.create({
256
+ mode: 'subscription', customer: client,
257
+ line_items: [{ price: plan.stripePriceId, quantity: 1 }],
258
+ ...(locale ? { locale } : {}),
259
+ success_url: opts.urls.success, cancel_url: opts.urls.cancel,
260
+ // Sur la session ET sur l'abonnement : la session vit quelques heures, l'abonnement des années,
261
+ // et c'est lui que porteront renouvellements et résiliations.
262
+ metadata, subscription_data: { metadata },
263
+ });
264
+ return { ok: true, url: s.url, id: s.id };
265
+ }
266
+ async function compteDe(o) {
267
+ const direct = o?.metadata?.[K.account] ?? o?.subscription_details?.metadata?.[K.account];
268
+ if (direct)
269
+ return String(direct);
270
+ const sub = typeof o?.subscription === 'string' ? await dialect().findOne(SubscriptionSchema, { stripeSubId: o.subscription }) : null;
271
+ if (sub)
272
+ return String(sub.account);
273
+ const client = typeof o?.customer === 'string' ? o.customer : o?.customer?.id;
274
+ return client && customers.accountOf ? customers.accountOf(client) : null;
275
+ }
276
+ async function appliquer(evt) {
277
+ const o = evt.data?.object ?? {};
278
+ const at = Number(evt.created ?? 0);
279
+ const accountId = await compteDe(o);
280
+ if (!accountId)
281
+ return `ignored (no account): ${evt.type}`;
282
+ const D = dialect();
283
+ switch (evt.type) {
284
+ case 'checkout.session.completed':
285
+ case 'customer.subscription.created':
286
+ case 'customer.subscription.updated': {
287
+ const stripeSubId = evt.type === 'checkout.session.completed' ? (typeof o.subscription === 'string' ? o.subscription : o.subscription?.id) : o.id;
288
+ const ligne = stripeSubId ? await D.findOne(SubscriptionSchema, { stripeSubId }) : null;
289
+ if (ligne?.stripeEndedAt)
290
+ return `ignored: ${stripeSubId} already ended`;
291
+ if (ligne && Number(ligne.stripeEventAt ?? 0) > at)
292
+ return `ignored: older than last applied event (${stripeSubId})`;
293
+ const statut = evt.type === 'checkout.session.completed' ? 'active' : String(o.status ?? 'active');
294
+ if (IMPAYES.has(statut)) {
295
+ // Seul l'abonnement EN COURS fait redescendre : un ancien abonnement remplacé n'a plus la main.
296
+ if (ligne && ligne.status !== 'active') {
297
+ await D.update(SubscriptionSchema, String(ligne.id), { stripeEventAt: at });
298
+ return `subscription ${stripeSubId} ${statut} (was not active)`;
299
+ }
300
+ await billing.setPlan(accountId, billing.freePlan);
301
+ if (ligne)
302
+ await D.update(SubscriptionSchema, String(ligne.id), { stripeEventAt: at, status: 'canceled' });
303
+ return `account ${accountId} back to ${billing.freePlan} (${statut})`;
304
+ }
305
+ const slug = o.metadata?.[K.plan];
306
+ if (!slug)
307
+ return `ignored (no plan in metadata): ${evt.type}`;
308
+ await billing.setPlan(accountId, String(slug), { stripeSubId, stripeEventAt: at });
309
+ return `account ${accountId} on plan ${slug}`;
310
+ }
311
+ case 'customer.subscription.deleted': {
312
+ const ligne = await D.findOne(SubscriptionSchema, { stripeSubId: o.id });
313
+ if (!ligne)
314
+ return `ignored: unknown subscription ${o.id}`;
315
+ if (ligne.stripeEndedAt)
316
+ return 'replay: already ended';
317
+ const etaitActive = ligne.status === 'active';
318
+ await D.update(SubscriptionSchema, String(ligne.id), { stripeEndedAt: at || 1, status: 'canceled' });
319
+ if (etaitActive)
320
+ await billing.setPlan(accountId, billing.freePlan);
321
+ return etaitActive ? `account ${accountId} back to ${billing.freePlan} (ended)` : `subscription ${o.id} ended (was not active)`;
322
+ }
323
+ case 'invoice.paid':
324
+ case 'invoice.payment_failed': {
325
+ const paye = evt.type === 'invoice.paid';
326
+ const existante = await D.findOne(InvoiceSchema, { stripeInvoiceId: o.id });
327
+ const champs = {
328
+ amount: Number(paye ? (o.amount_paid ?? o.amount_due ?? 0) : (o.amount_due ?? 0)),
329
+ currency: o.currency ?? 'eur',
330
+ status: paye ? 'paid' : 'failed',
331
+ ...(paye && o.status_transitions?.paid_at ? { paidAt: new Date(o.status_transitions.paid_at * 1000) } : {}),
332
+ ...(o.hosted_invoice_url ? { hostedUrl: o.hosted_invoice_url } : {}),
333
+ ...(o.invoice_pdf ? { pdfUrl: o.invoice_pdf } : {}),
334
+ };
335
+ if (!existante) {
336
+ await D.create(InvoiceSchema, { ...champs, account: accountId, stripeInvoiceId: o.id });
337
+ return `invoice ${o.id} recorded (${champs.status})`;
338
+ }
339
+ if (existante.status === 'paid')
340
+ return `invoice ${o.id} already paid`; // un échec tardif ne défait pas un paiement
341
+ if (!paye)
342
+ return `invoice ${o.id} still failed`;
343
+ await D.update(InvoiceSchema, String(existante.id), champs);
344
+ return `invoice ${o.id} now paid`;
345
+ }
346
+ default:
347
+ return `no effect: ${evt.type}`;
348
+ }
349
+ }
350
+ return {
351
+ openCheckout,
352
+ /**
353
+ * Vérifie et applique un événement. `rawBody` : le corps EXACT (Buffer), jamais ré-sérialisé.
354
+ * 400 signature refusée (Stripe cesse de réessayer) · 503 non configuré · 500 échec d'application
355
+ * (Stripe réessaiera ; l'événement n'est pas marqué reçu) · 200 sinon, rejeu compris.
356
+ */
357
+ async handleWebhook(rawBody, signature) {
358
+ if (!opts.webhookSecret)
359
+ return { status: 503, result: 'webhook not configured' };
360
+ let evt;
361
+ try {
362
+ evt = stripe.webhooks.constructEvent(rawBody, signature, opts.webhookSecret);
363
+ }
364
+ catch (e) {
365
+ return { status: 400, result: `signature refused: ${e?.message ?? e}` };
366
+ }
367
+ const D = dialect();
368
+ // Inscrire d'abord : l'index unique fait d'une double livraison simultanée un rejeu.
369
+ let marque;
370
+ try {
371
+ marque = await D.create(BillingEventSchema, { eventId: evt.id, type: evt.type, result: 'pending' });
372
+ }
373
+ catch {
374
+ return { status: 200, type: evt.type, result: 'replay: no effect' };
375
+ }
376
+ try {
377
+ const result = await appliquer(evt);
378
+ await D.update(BillingEventSchema, String(marque.id), { result });
379
+ return { status: 200, type: evt.type, result };
380
+ }
381
+ catch (e) {
382
+ await D.delete(BillingEventSchema, String(marque.id)).catch(() => { });
383
+ return { status: 500, type: evt.type, result: String(e?.message ?? e) };
384
+ }
385
+ },
386
+ };
387
+ }
388
+ //# sourceMappingURL=billing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"billing.js","sourceRoot":"","sources":["../../src/lib/billing.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"subscription.schema.d.ts","sourceRoot":"","sources":["../../src/schemas/subscription.schema.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAEtD,eAAO,MAAM,kBAAkB,EAAE,YA+BhC,CAAA"}
1
+ {"version":3,"file":"subscription.schema.d.ts","sourceRoot":"","sources":["../../src/schemas/subscription.schema.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAEtD,eAAO,MAAM,kBAAkB,EAAE,YAkChC,CAAA"}
@@ -17,6 +17,9 @@ export const SubscriptionSchema = {
17
17
  // un par entité scopée. Cf. findActiveSubscription().
18
18
  scopeType: { type: 'string', default: 'account' },
19
19
  scopeId: { type: 'string' },
20
+ // 0.5 — ordre d'arrivée des webhooks : `created` (s) du dernier événement appliqué, et de la fin.
21
+ stripeEventAt: { type: 'number' },
22
+ stripeEndedAt: { type: 'number' },
20
23
  },
21
24
  relations: {
22
25
  account: { type: 'many-to-one', target: 'Account', required: true },
@@ -1 +1 @@
1
- {"version":3,"file":"subscription.schema.js","sourceRoot":"","sources":["../../src/schemas/subscription.schema.ts"],"names":[],"mappings":"AAAA,oDAAoD;AACpD,wCAAwC;AAIxC,MAAM,CAAC,MAAM,kBAAkB,GAAiB;IAC9C,IAAI,EAAE,cAAc;IACpB,UAAU,EAAE,eAAe;IAC3B,UAAU,EAAE,IAAI;IAEhB,MAAM,EAAE;QACN,MAAM,EAAc,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE;QACzE,WAAW,EAAS,EAAE,IAAI,EAAE,QAAQ,EAAE;QACtC,kBAAkB,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE;QACpC,gBAAgB,EAAI,EAAE,IAAI,EAAE,MAAM,EAAE;QACpC,QAAQ,EAAY,EAAE,IAAI,EAAE,MAAM,EAAE;QACpC,QAAQ,EAAY,EAAE,IAAI,EAAE,MAAM,EAAE;QACpC,mFAAmF;QACnF,oFAAoF;QACpF,iFAAiF;QACjF,sDAAsD;QACtD,SAAS,EAAW,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,SAAS,EAAE;QAC1D,OAAO,EAAa,EAAE,IAAI,EAAE,QAAQ,EAAE;KACvC;IAED,SAAS,EAAE;QACT,OAAO,EAAE,EAAE,IAAI,EAAE,aAAa,EAAE,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,IAAI,EAAE;QACnE,IAAI,EAAK,EAAE,IAAI,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE;KACjE;IAED,OAAO,EAAE;QACP,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE;QAC7B,EAAE,MAAM,EAAE,EAAE,WAAW,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE;QAChD,qDAAqD;QACrD,EAAE,MAAM,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE;KACjD;CACF,CAAA"}
1
+ {"version":3,"file":"subscription.schema.js","sourceRoot":"","sources":["../../src/schemas/subscription.schema.ts"],"names":[],"mappings":"AAAA,oDAAoD;AACpD,wCAAwC;AAIxC,MAAM,CAAC,MAAM,kBAAkB,GAAiB;IAC9C,IAAI,EAAE,cAAc;IACpB,UAAU,EAAE,eAAe;IAC3B,UAAU,EAAE,IAAI;IAEhB,MAAM,EAAE;QACN,MAAM,EAAc,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE;QACzE,WAAW,EAAS,EAAE,IAAI,EAAE,QAAQ,EAAE;QACtC,kBAAkB,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE;QACpC,gBAAgB,EAAI,EAAE,IAAI,EAAE,MAAM,EAAE;QACpC,QAAQ,EAAY,EAAE,IAAI,EAAE,MAAM,EAAE;QACpC,QAAQ,EAAY,EAAE,IAAI,EAAE,MAAM,EAAE;QACpC,mFAAmF;QACnF,oFAAoF;QACpF,iFAAiF;QACjF,sDAAsD;QACtD,SAAS,EAAW,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,SAAS,EAAE;QAC1D,OAAO,EAAa,EAAE,IAAI,EAAE,QAAQ,EAAE;QACtC,kGAAkG;QAClG,aAAa,EAAO,EAAE,IAAI,EAAE,QAAQ,EAAE;QACtC,aAAa,EAAO,EAAE,IAAI,EAAE,QAAQ,EAAE;KACvC;IAED,SAAS,EAAE;QACT,OAAO,EAAE,EAAE,IAAI,EAAE,aAAa,EAAE,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,IAAI,EAAE;QACnE,IAAI,EAAK,EAAE,IAAI,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE;KACjE;IAED,OAAO,EAAE;QACP,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE;QAC7B,EAAE,MAAM,EAAE,EAAE,WAAW,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE;QAChD,qDAAqD;QACrD,EAAE,MAAM,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE;KACjD;CACF,CAAA"}
package/dist/server.d.ts CHANGED
@@ -8,4 +8,6 @@ export { createSubscriptionHandlers } from './api/subscriptions.route.js';
8
8
  export { createUsageHandlers } from './api/usage.route.js';
9
9
  export { getSchemas, moduleInfo } from './lib/module-info.js';
10
10
  export { subscriptionPlanRegistration } from './register.js';
11
+ export { createBilling, createStripeSale, checkLimits, assertMonotonicPlans, secondsUntilReset, billingSchemas, UsageCounterSchema, BillingEventSchema, BillingError, UNLIMITED, } from './lib/billing.js';
12
+ export type { Billing, BillingOptions, ResourceDef, Verdict, Period, CustomerPort, StripeSaleOptions } from './lib/billing.js';
11
13
  //# sourceMappingURL=server.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAIA,OAAO,EACL,WAAW,EACX,mBAAmB,EACnB,cAAc,EACd,eAAe,EACf,UAAU,GACX,MAAM,uBAAuB,CAAA;AAG9B,OAAO,EACL,UAAU,EACV,gBAAgB,EAChB,kBAAkB,EAClB,eAAe,EACf,cAAc,EACd,aAAa,GACd,MAAM,sBAAsB,CAAA;AAG7B,OAAO,EACL,oBAAoB,EACpB,mBAAmB,EACnB,oBAAoB,EACpB,kBAAkB,EAClB,sBAAsB,EACtB,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,yBAAyB,CAAA;AAGhC,OAAO,EAAE,eAAe,EAAE,yBAAyB,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAA;AACvG,YAAY,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAA;AAG1E,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAA;AACzD,OAAO,EAAE,0BAA0B,EAAE,MAAM,8BAA8B,CAAA;AACzE,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAA;AAG1D,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAA;AAG7D,OAAO,EAAE,4BAA4B,EAAE,MAAM,eAAe,CAAA"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAIA,OAAO,EACL,WAAW,EACX,mBAAmB,EACnB,cAAc,EACd,eAAe,EACf,UAAU,GACX,MAAM,uBAAuB,CAAA;AAG9B,OAAO,EACL,UAAU,EACV,gBAAgB,EAChB,kBAAkB,EAClB,eAAe,EACf,cAAc,EACd,aAAa,GACd,MAAM,sBAAsB,CAAA;AAG7B,OAAO,EACL,oBAAoB,EACpB,mBAAmB,EACnB,oBAAoB,EACpB,kBAAkB,EAClB,sBAAsB,EACtB,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,yBAAyB,CAAA;AAGhC,OAAO,EAAE,eAAe,EAAE,yBAAyB,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAA;AACvG,YAAY,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAA;AAG1E,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAA;AACzD,OAAO,EAAE,0BAA0B,EAAE,MAAM,8BAA8B,CAAA;AACzE,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAA;AAG1D,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAA;AAG7D,OAAO,EAAE,4BAA4B,EAAE,MAAM,eAAe,CAAA;AAG5D,OAAO,EACL,aAAa,EAAE,gBAAgB,EAAE,WAAW,EAAE,oBAAoB,EAAE,iBAAiB,EACrF,cAAc,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,YAAY,EAAE,SAAS,GAChF,MAAM,kBAAkB,CAAA;AACzB,YAAY,EAAE,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAA"}
package/dist/server.js CHANGED
@@ -16,4 +16,6 @@ export { createUsageHandlers } from './api/usage.route.js';
16
16
  export { getSchemas, moduleInfo } from './lib/module-info.js';
17
17
  // Registration
18
18
  export { subscriptionPlanRegistration } from './register.js';
19
+ // 0.5 — quotas déclarés, compteur atomique, paliers, vente et webhooks Stripe idempotents
20
+ export { createBilling, createStripeSale, checkLimits, assertMonotonicPlans, secondsUntilReset, billingSchemas, UsageCounterSchema, BillingEventSchema, BillingError, UNLIMITED, } from './lib/billing.js';
19
21
  //# sourceMappingURL=server.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAC7E,wCAAwC;AAExC,eAAe;AACf,OAAO,EACL,WAAW,EACX,mBAAmB,EACnB,cAAc,EACd,eAAe,EACf,UAAU,GACX,MAAM,uBAAuB,CAAA;AAE9B,oBAAoB;AACpB,OAAO,EACL,UAAU,EACV,gBAAgB,EAChB,kBAAkB,EAClB,eAAe,EACf,cAAc,EACd,aAAa,GACd,MAAM,sBAAsB,CAAA;AAE7B,iBAAiB;AACjB,OAAO,EACL,oBAAoB,EACpB,mBAAmB,EACnB,oBAAoB,EACpB,kBAAkB,EAClB,sBAAsB,EACtB,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,yBAAyB,CAAA;AAEhC,kDAAkD;AAClD,OAAO,EAAE,eAAe,EAAE,yBAAyB,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAA;AAGvG,eAAe;AACf,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAA;AACzD,OAAO,EAAE,0BAA0B,EAAE,MAAM,8BAA8B,CAAA;AACzE,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAA;AAE1D,wBAAwB;AACxB,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAA;AAE7D,eAAe;AACf,OAAO,EAAE,4BAA4B,EAAE,MAAM,eAAe,CAAA"}
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAC7E,wCAAwC;AAExC,eAAe;AACf,OAAO,EACL,WAAW,EACX,mBAAmB,EACnB,cAAc,EACd,eAAe,EACf,UAAU,GACX,MAAM,uBAAuB,CAAA;AAE9B,oBAAoB;AACpB,OAAO,EACL,UAAU,EACV,gBAAgB,EAChB,kBAAkB,EAClB,eAAe,EACf,cAAc,EACd,aAAa,GACd,MAAM,sBAAsB,CAAA;AAE7B,iBAAiB;AACjB,OAAO,EACL,oBAAoB,EACpB,mBAAmB,EACnB,oBAAoB,EACpB,kBAAkB,EAClB,sBAAsB,EACtB,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,yBAAyB,CAAA;AAEhC,kDAAkD;AAClD,OAAO,EAAE,eAAe,EAAE,yBAAyB,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAA;AAGvG,eAAe;AACf,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAA;AACzD,OAAO,EAAE,0BAA0B,EAAE,MAAM,8BAA8B,CAAA;AACzE,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAA;AAE1D,wBAAwB;AACxB,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAA;AAE7D,eAAe;AACf,OAAO,EAAE,4BAA4B,EAAE,MAAM,eAAe,CAAA;AAE5D,0FAA0F;AAC1F,OAAO,EACL,aAAa,EAAE,gBAAgB,EAAE,WAAW,EAAE,oBAAoB,EAAE,iBAAiB,EACrF,cAAc,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,YAAY,EAAE,SAAS,GAChF,MAAM,kBAAkB,CAAA"}
package/llms.txt CHANGED
@@ -73,6 +73,24 @@ const q = await checkQuota(dialect, 'acc-1', 'projects')
73
73
  if (!q.allowed) throw new Error(q.reason)
74
74
  ```
75
75
 
76
+ ## 0.5 — FACTURATION (`@mostajs/subscriptions-plan/server`) — À PRÉFÉRER à checkQuota/incrementUsage
77
+ ```js
78
+ const d = await openIsolatedDialect(cfg, [AccountSchema, ...billingSchemas()])
79
+ const billing = createBilling({ dialect: d, freePlan: 'gratuit', resources: {
80
+ conversion: { limit: 'conversionsParJour', period: 'day' },
81
+ signature: { limit: 'signaturesParMois', period: 'month' } } })
82
+ const v = await billing.consume(compteId, 'signature') // AVANT le travail ; billing.release(...) s'il échoue
83
+ if (!v.allowed) return v.permanent ? 403 : 429 + `Retry-After: ${v.retryAfterSeconds}`
84
+ const vente = createStripeSale({ billing, stripe, webhookSecret, customers /* get,set,describe,accountOf */,
85
+ metadataKeys: { account: 'qatrax_compte', plan: 'qatrax_plan' }, urls: { success, cancel } })
86
+ await vente.openCheckout({ accountId, planSlug }) // → { ok, url }
87
+ const r = await vente.handleWebhook(corpsBrutBuffer, req.headers['stripe-signature']) // → { status, result }
88
+ ```
89
+ - Verdict : `{ allowed, permanent?, current, limit (-1 = sans limite), period, plan?, reason?, retryAfterSeconds? }`.
90
+ - `checkLimits(limits, def, courant)` : le même verdict pour un demandeur sans compte (palier anonyme : un plan `active: false`, `sortOrder` le plus bas).
91
+ - `updatePlanLimits(slug, limits)` lève `PLAN_SCALE_INVERTED` si un palier supérieur donnerait moins.
92
+ - PIÈGES : le compteur est exact DANS un processus ; entre processus il ne dépasse jamais la borne mais une rafale peut être refusée en entier → router un même compte vers un même processus. Le webhook se vérifie sur le corps BRUT. Le plan n'est changé QUE par le webhook (jamais au retour de la page de paiement).
93
+
76
94
  ## DÉPEND DE
77
95
  - `@mostajs/data-plug` (peer dep `^1.2.4`) — façade ORM : `BaseRepository`, `IDialect`, `EntitySchema`.
78
96
  - `@mostajs/config` (peer dep) — résolution env.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mostajs/subscriptions-plan",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Subscription plans, billing, invoices and usage tracking for @mostajs",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -20,7 +20,7 @@
20
20
  "scripts": {
21
21
  "build": "tsc",
22
22
  "dev": "tsc --watch",
23
- "test": "tsx test-scripts/test-unit.ts",
23
+ "test": "npm run build && tsx test-scripts/test-unit.ts && node node_modules/@mostajs/mjs-unit/bin/cli.mjs test-scripts/*.test.mjs --json=test-scripts/.out/report.json",
24
24
  "prepublishOnly": "npm run build"
25
25
  },
26
26
  "files": [
@@ -55,6 +55,7 @@
55
55
  },
56
56
  "devDependencies": {
57
57
  "@mostajs/data-plug": "^1.2.4",
58
+ "@mostajs/mjs-unit": "^0.7.0",
58
59
  "@mostajs/orm": "^1.13.1",
59
60
  "@mostajs/payment": "^0.4.1",
60
61
  "@types/node": "^22.0.0",
@@ -65,5 +66,15 @@
65
66
  "funding": {
66
67
  "type": "github",
67
68
  "url": "https://github.com/sponsors/apolocine"
69
+ },
70
+ "mostajs": {
71
+ "niveau": "N2",
72
+ "nature": "compose",
73
+ "reutilisabilite": "multi-domaine",
74
+ "depend_de": [
75
+ "@mostajs/config",
76
+ "@mostajs/data-plug",
77
+ "@mostajs/payment"
78
+ ]
68
79
  }
69
80
  }