@mostajs/subscriptions-plan 0.3.6 → 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 ADDED
@@ -0,0 +1,44 @@
1
+ # Changelog — `@mostajs/subscriptions-plan`
2
+
3
+ **Auteur** : Dr Hamid MADANI <drmdh@msn.com>
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
+
23
+ ## 0.4.0 — 2026-06-10
24
+
25
+ ### Ajouté (additif, **non-breaking**)
26
+ - **Scope générique sur `Subscription`** : champs optionnels `scopeType` (défaut `'account'`) et
27
+ `scopeId`. Permet de rattacher un abonnement à une **entité** autre que le compte
28
+ (`'course'` pour race-event, `'project'`, …) → un compte peut avoir **plusieurs abonnements
29
+ actifs**, un par entité scopée. L'annulation des abos existants ne touche que le **même scope**.
30
+ - **`subscribeToPlan(dialect, { …, scope?: { type, id } })`** : propage le scope (chemin direct
31
+ et `metadata` du provider de paiement).
32
+ - **`findActiveSubscription(dialect, { accountId?, scope? })`** : trouve l'abo actif d'un compte
33
+ ou d'une entité scopée (exporté depuis `./server`).
34
+ - **Routes `/subscription`** : `GET` accepte `?scopeType=&scopeId=` (ou `accountId`) ; `POST`
35
+ persiste `scopeType/scopeId` et annule les abos actifs du même scope.
36
+ - **`Plan.interval`** accepte désormais `'one_time'` (en plus de `'month' | 'year'`) — corrige
37
+ l'incohérence des plans ponctuels (sponsoring par édition) ; le défaut reste récurrent.
38
+
39
+ ### Migration
40
+ Aucune action requise : les champs sont optionnels (auto-ALTER ORM). Les abonnements existants
41
+ (compte-scopés) restent valides ; en l'absence de scope, le comportement est identique à 0.3.x.
42
+
43
+ ## 0.3.6
44
+ - Souscription multi-provider (`subscribeToPlan`), facturation Stripe, factures, quotas/usage.
package/README.md CHANGED
@@ -20,6 +20,42 @@ C'est un module **système** *(au sens : ses 4 tables vivent dans la base systè
20
20
 
21
21
  ---
22
22
 
23
+ ## Scope d'abonnement *(v0.4.0)*
24
+
25
+ Par défaut un abonnement est au niveau **compte** (`scopeType: 'account'`). Depuis 0.4.0, on peut
26
+ le **scoper à une entité** via les champs optionnels `scopeType` / `scopeId` — un même compte
27
+ peut alors avoir **plusieurs abonnements actifs**, un par entité :
28
+
29
+ ```ts
30
+ import { subscribeToPlan, findActiveSubscription } from '@mostajs/subscriptions-plan/server'
31
+
32
+ // Sponsoriser une COURSE précise (race-event)
33
+ await subscribeToPlan(dialect, {
34
+ accountId, planId, provider: 'chargily',
35
+ scope: { type: 'course', id: courseId }, // ← abo rattaché à la course
36
+ })
37
+
38
+ // Retrouver l'abo actif de cette course
39
+ const sub = await findActiveSubscription(dialect, { scope: { type: 'course', id: courseId } })
40
+ ```
41
+
42
+ Additif et **non-breaking** : sans `scope`, le comportement est identique aux versions 0.3.x
43
+ (abo compte-scopé, annulation account-wide). Voir `CHANGELOG.md`.
44
+
45
+ ---
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
+
23
59
  ## Install
24
60
 
25
61
  ```bash
@@ -1 +1 @@
1
- {"version":3,"file":"subscriptions.route.d.ts","sourceRoot":"","sources":["../../src/api/subscriptions.route.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAA;AAIlD;;GAEG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,QAAQ;eAKlC,OAAO,KAAG,OAAO,CAAC,QAAQ,CAAC;gBAoB1B,OAAO,KAAG,OAAO,CAAC,QAAQ,CAAC;eAqC5B,OAAO,MAAM,MAAM,KAAG,OAAO,CAAC,QAAQ,CAAC;EAUhE"}
1
+ {"version":3,"file":"subscriptions.route.d.ts","sourceRoot":"","sources":["../../src/api/subscriptions.route.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAA;AAIlD;;GAEG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,QAAQ;eAKlC,OAAO,KAAG,OAAO,CAAC,QAAQ,CAAC;gBA0B1B,OAAO,KAAG,OAAO,CAAC,QAAQ,CAAC;eAyC5B,OAAO,MAAM,MAAM,KAAG,OAAO,CAAC,QAAQ,CAAC;EAUhE"}
@@ -11,10 +11,20 @@ export function createSubscriptionHandlers(dialect) {
11
11
  async function GET(req) {
12
12
  const url = new URL(req.url);
13
13
  const accountId = url.searchParams.get('accountId');
14
- if (!accountId) {
15
- return Response.json({ error: 'accountId required' }, { status: 400 });
14
+ const scopeType = url.searchParams.get('scopeType');
15
+ const scopeId = url.searchParams.get('scopeId');
16
+ // Au moins un critère : compte OU scope (course, …).
17
+ if (!accountId && !(scopeType && scopeId)) {
18
+ return Response.json({ error: 'accountId or (scopeType & scopeId) required' }, { status: 400 });
16
19
  }
17
- const subs = await subRepo.findAll({ account: accountId, status: 'active' });
20
+ const filter = { status: 'active' };
21
+ if (accountId)
22
+ filter.account = accountId;
23
+ if (scopeType && scopeId) {
24
+ filter.scopeType = scopeType;
25
+ filter.scopeId = scopeId;
26
+ }
27
+ const subs = await subRepo.findAll(filter);
18
28
  if (subs.length === 0) {
19
29
  return Response.json(null);
20
30
  }
@@ -34,8 +44,13 @@ export function createSubscriptionHandlers(dialect) {
34
44
  if (!plan) {
35
45
  return Response.json({ error: 'Plan not found' }, { status: 404 });
36
46
  }
37
- // Cancel existing active subscriptions
38
- const existing = await subRepo.findAll({ account: data.accountId, status: 'active' });
47
+ // Cancel existing active subscriptions of the SAME scope (account-wide si pas de scope).
48
+ const cancelFilter = { account: data.accountId, status: 'active' };
49
+ if (data.scopeType && data.scopeId) {
50
+ cancelFilter.scopeType = data.scopeType;
51
+ cancelFilter.scopeId = data.scopeId;
52
+ }
53
+ const existing = await subRepo.findAll(cancelFilter);
39
54
  for (const old of existing) {
40
55
  await subRepo.update(old.id, { status: 'canceled' });
41
56
  }
@@ -48,6 +63,8 @@ export function createSubscriptionHandlers(dialect) {
48
63
  currentPeriodStart: data.currentPeriodStart ?? new Date().toISOString(),
49
64
  currentPeriodEnd: data.currentPeriodEnd,
50
65
  trialEnd: data.trialEnd,
66
+ ...(data.scopeType ? { scopeType: data.scopeType } : {}),
67
+ ...(data.scopeId ? { scopeId: data.scopeId } : {}),
51
68
  });
52
69
  return Response.json(sub, { status: 201 });
53
70
  }
@@ -1 +1 @@
1
- {"version":3,"file":"subscriptions.route.js","sourceRoot":"","sources":["../../src/api/subscriptions.route.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"subscriptions.route.js","sourceRoot":"","sources":["../../src/api/subscriptions.route.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export type { PlanDTO, PlanLimits, SubscriptionDTO, SubscriptionStatus, InvoiceDTO, InvoiceStatus, UsageLogDTO, BillingConfig, QuotaCheckResult, } from './types/index.js';
1
+ export type { PlanDTO, PlanLimits, SubscriptionDTO, SubscriptionStatus, SubscriptionScope, InvoiceDTO, InvoiceStatus, UsageLogDTO, BillingConfig, QuotaCheckResult, } from './types/index.js';
2
2
  export { PlanSchema } from './schemas/plan.schema.js';
3
3
  export { SubscriptionSchema } from './schemas/subscription.schema.js';
4
4
  export { InvoiceSchema } from './schemas/invoice.schema.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,YAAY,EACV,OAAO,EACP,UAAU,EACV,eAAe,EACf,kBAAkB,EAClB,UAAU,EACV,aAAa,EACb,WAAW,EACX,aAAa,EACb,gBAAgB,GACjB,MAAM,kBAAkB,CAAA;AAGzB,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAA;AACrD,OAAO,EAAE,kBAAkB,EAAE,MAAM,kCAAkC,CAAA;AACrE,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAA;AAC3D,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAA;AAG9D,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,YAAY,EACV,OAAO,EACP,UAAU,EACV,eAAe,EACf,kBAAkB,EAClB,iBAAiB,EACjB,UAAU,EACV,aAAa,EACb,WAAW,EACX,aAAa,EACb,gBAAgB,GACjB,MAAM,kBAAkB,CAAA;AAGzB,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAA;AACrD,OAAO,EAAE,kBAAkB,EAAE,MAAM,kCAAkC,CAAA;AACrE,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAA;AAC3D,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAA;AAG9D,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAA"}
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,oDAAoD;AACpD,2DAA2D;AAC3D,wCAAwC;AAexC,UAAU;AACV,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAA;AACrD,OAAO,EAAE,kBAAkB,EAAE,MAAM,kCAAkC,CAAA;AACrE,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAA;AAC3D,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAA;AAE9D,cAAc;AACd,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,oDAAoD;AACpD,2DAA2D;AAC3D,wCAAwC;AAgBxC,UAAU;AACV,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAA;AACrD,OAAO,EAAE,kBAAkB,EAAE,MAAM,kCAAkC,CAAA;AACrE,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAA;AAC3D,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAA;AAE9D,cAAc;AACd,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAA"}
@@ -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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"}
@@ -14,6 +14,16 @@ export interface SubscribeParams {
14
14
  customerName?: string;
15
15
  /** Stripe customer ID (if already exists) */
16
16
  stripeCustomerId?: string;
17
+ /**
18
+ * Scope optionnel de l'abonnement. Par défaut { type:'account' } (historique).
19
+ * { type:'course', id:courseId } → abo rattaché à une course (race-event) : permet
20
+ * PLUSIEURS abos actifs par compte, un par entité scopée. L'annulation des abos
21
+ * existants ne touche que le MÊME scope.
22
+ */
23
+ scope?: {
24
+ type: string;
25
+ id: string;
26
+ };
17
27
  }
18
28
  export interface SubscribeResult {
19
29
  ok: boolean;
@@ -40,4 +50,20 @@ export declare function subscribeToPlan(dialect: IDialect, params: SubscribePara
40
50
  * @returns { ok, subscription } with the new Free subscription
41
51
  */
42
52
  export declare function cancelCurrentSubscription(dialect: import('@mostajs/data-plug').IDialect, accountId: string): Promise<SubscribeResult>;
53
+ /**
54
+ * Trouve l'abonnement ACTIF pour un scope donné.
55
+ *
56
+ * - `{ accountId }` seul → abo actif du compte (historique).
57
+ * - `{ scope:{type,id} }` → abo actif rattaché à cette entité (ex. course).
58
+ * `accountId` reste optionnel pour restreindre au compte.
59
+ *
60
+ * @returns la Subscription active ou `null`.
61
+ */
62
+ export declare function findActiveSubscription(dialect: import('@mostajs/data-plug').IDialect, params: {
63
+ accountId?: string;
64
+ scope?: {
65
+ type: string;
66
+ id: string;
67
+ };
68
+ }): Promise<any | null>;
43
69
  //# sourceMappingURL=subscribe.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"subscribe.d.ts","sourceRoot":"","sources":["../../src/lib/subscribe.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAA;AAGlD,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,MAAM,CAAA;IACd,wFAAwF;IACxF,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,gCAAgC;IAChC,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,gDAAgD;IAChD,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,4CAA4C;IAC5C,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,6CAA6C;IAC7C,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAC1B;AAED,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,OAAO,CAAA;IACX,wCAAwC;IACxC,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACnB,kEAAkE;IAClE,YAAY,CAAC,EAAE,GAAG,CAAA;IAClB,oBAAoB;IACpB,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED;;;;;;GAMG;AACH,wBAAsB,eAAe,CACnC,OAAO,EAAE,QAAQ,EACjB,MAAM,EAAE,eAAe,GACtB,OAAO,CAAC,eAAe,CAAC,CAwC1B;AA+DD;;;;;;GAMG;AACH,wBAAsB,yBAAyB,CAC7C,OAAO,EAAE,OAAO,oBAAoB,EAAE,QAAQ,EAC9C,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,eAAe,CAAC,CAuB1B"}
1
+ {"version":3,"file":"subscribe.d.ts","sourceRoot":"","sources":["../../src/lib/subscribe.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAA;AAGlD,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,MAAM,CAAA;IACd,wFAAwF;IACxF,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,gCAAgC;IAChC,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,gDAAgD;IAChD,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,4CAA4C;IAC5C,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,6CAA6C;IAC7C,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB;;;;;OAKG;IACH,KAAK,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,CAAA;CACrC;AAED,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,OAAO,CAAA;IACX,wCAAwC;IACxC,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACnB,kEAAkE;IAClE,YAAY,CAAC,EAAE,GAAG,CAAA;IAClB,oBAAoB;IACpB,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED;;;;;;GAMG;AACH,wBAAsB,eAAe,CACnC,OAAO,EAAE,QAAQ,EACjB,MAAM,EAAE,eAAe,GACtB,OAAO,CAAC,eAAe,CAAC,CA4C1B;AA2ED;;;;;;GAMG;AACH,wBAAsB,yBAAyB,CAC7C,OAAO,EAAE,OAAO,oBAAoB,EAAE,QAAQ,EAC9C,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,eAAe,CAAC,CAuB1B;AAED;;;;;;;;GAQG;AACH,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,OAAO,oBAAoB,EAAE,QAAQ,EAC9C,MAAM,EAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,GACnE,OAAO,CAAC,GAAG,GAAG,IAAI,CAAC,CAOrB"}
@@ -18,7 +18,7 @@ export async function subscribeToPlan(dialect, params) {
18
18
  return { ok: false, error: 'Plan non trouve' };
19
19
  // Free plan or no provider → direct subscription
20
20
  if (!plan.price || plan.price === 0 || !params.provider || params.provider === 'direct') {
21
- return directSubscribe(subRepo, params.accountId, params.planId);
21
+ return directSubscribe(subRepo, params.accountId, params.planId, params.scope);
22
22
  }
23
23
  // Paid plan → try payment provider
24
24
  try {
@@ -36,7 +36,11 @@ export async function subscribeToPlan(dialect, params) {
36
36
  webhookUrl: params.webhookUrl,
37
37
  customerId: params.stripeCustomerId,
38
38
  priceId: plan.stripePriceId,
39
- metadata: { accountId: params.accountId, planId: params.planId },
39
+ metadata: {
40
+ accountId: params.accountId,
41
+ planId: params.planId,
42
+ ...(params.scope ? { scopeType: params.scope.type, scopeId: params.scope.id } : {}),
43
+ },
40
44
  });
41
45
  return { ok: true, url: result.url };
42
46
  }
@@ -47,10 +51,19 @@ export async function subscribeToPlan(dialect, params) {
47
51
  }
48
52
  /**
49
53
  * Direct subscription — no payment required (free plan or fallback).
54
+ *
55
+ * Scope-aware : si un scope est fourni (ex. course), seuls les abos actifs du
56
+ * MÊME scope sont annulés (un compte peut sponsoriser plusieurs courses en //).
57
+ * Sans scope → comportement historique (annule les abos actifs du compte).
50
58
  */
51
- async function directSubscribe(subRepo, accountId, planId) {
52
- // Cancel existing active subscriptions
53
- const existing = await subRepo.findAll({ account: accountId, status: 'active' });
59
+ async function directSubscribe(subRepo, accountId, planId, scope) {
60
+ // Cancel existing active subscriptions of the SAME scope
61
+ const filter = { account: accountId, status: 'active' };
62
+ if (scope) {
63
+ filter.scopeType = scope.type;
64
+ filter.scopeId = scope.id;
65
+ }
66
+ const existing = await subRepo.findAll(filter);
54
67
  for (const sub of existing) {
55
68
  await subRepo.update(sub.id, { status: 'canceled' });
56
69
  }
@@ -59,6 +72,7 @@ async function directSubscribe(subRepo, accountId, planId) {
59
72
  account: accountId,
60
73
  plan: planId,
61
74
  status: 'active',
75
+ ...(scope ? { scopeType: scope.type, scopeId: scope.id } : {}),
62
76
  });
63
77
  return { ok: true, subscription };
64
78
  }
@@ -133,4 +147,25 @@ export async function cancelCurrentSubscription(dialect, accountId) {
133
147
  });
134
148
  return { ok: true, subscription };
135
149
  }
150
+ /**
151
+ * Trouve l'abonnement ACTIF pour un scope donné.
152
+ *
153
+ * - `{ accountId }` seul → abo actif du compte (historique).
154
+ * - `{ scope:{type,id} }` → abo actif rattaché à cette entité (ex. course).
155
+ * `accountId` reste optionnel pour restreindre au compte.
156
+ *
157
+ * @returns la Subscription active ou `null`.
158
+ */
159
+ export async function findActiveSubscription(dialect, params) {
160
+ const subRepo = getSubscriptionRepo(dialect);
161
+ const filter = { status: 'active' };
162
+ if (params.accountId)
163
+ filter.account = params.accountId;
164
+ if (params.scope) {
165
+ filter.scopeType = params.scope.type;
166
+ filter.scopeId = params.scope.id;
167
+ }
168
+ const rows = await subRepo.findAll(filter);
169
+ return rows && rows.length > 0 ? rows[0] : null;
170
+ }
136
171
  //# sourceMappingURL=subscribe.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"subscribe.js","sourceRoot":"","sources":["../../src/lib/subscribe.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"subscribe.js","sourceRoot":"","sources":["../../src/lib/subscribe.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,YAuBhC,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"}
@@ -11,6 +11,15 @@ export const SubscriptionSchema = {
11
11
  currentPeriodEnd: { type: 'date' },
12
12
  cancelAt: { type: 'date' },
13
13
  trialEnd: { type: 'date' },
14
+ // Scope générique (optionnel, additif/non-breaking) : à quelle ENTITÉ l'abonnement
15
+ // est rattaché. Par défaut 'account' = comportement historique (abo niveau compte).
16
+ // 'course' (race-event), 'project', … → permet PLUSIEURS abos actifs par compte,
17
+ // un par entité scopée. Cf. findActiveSubscription().
18
+ scopeType: { type: 'string', default: 'account' },
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' },
14
23
  },
15
24
  relations: {
16
25
  account: { type: 'many-to-one', target: 'Account', required: true },
@@ -19,6 +28,8 @@ export const SubscriptionSchema = {
19
28
  indexes: [
20
29
  { fields: { status: 'asc' } },
21
30
  { fields: { stripeSubId: 'asc' }, unique: true },
31
+ // Recherche d'abo actif par scope (account|course|…)
32
+ { fields: { scopeType: 'asc', scopeId: 'asc' } },
22
33
  ],
23
34
  };
24
35
  //# sourceMappingURL=subscription.schema.js.map
@@ -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;KACrC;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;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
@@ -1,11 +1,13 @@
1
1
  export { getPlanRepo, getSubscriptionRepo, getInvoiceRepo, getUsageLogRepo, resetRepos, } from './lib/plan-factory.js';
2
2
  export { checkQuota, isDialectAllowed, isTransportAllowed, getUsageSummary, incrementUsage, DEFAULT_PLANS, } from './lib/quota-check.js';
3
3
  export { createBillingSession, createPortalSession, createStripeCustomer, cancelSubscription, changeSubscriptionPlan, verifyWebhookEvent, parseBillingEvent, } from './lib/stripe-billing.js';
4
- export { subscribeToPlan, cancelCurrentSubscription } from './lib/subscribe.js';
4
+ export { subscribeToPlan, cancelCurrentSubscription, findActiveSubscription } from './lib/subscribe.js';
5
5
  export type { SubscribeParams, SubscribeResult } from './lib/subscribe.js';
6
6
  export { createPlanHandlers } from './api/plans.route.js';
7
7
  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,MAAM,oBAAoB,CAAA;AAC/E,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
@@ -7,7 +7,7 @@ export { checkQuota, isDialectAllowed, isTransportAllowed, getUsageSummary, incr
7
7
  // Stripe Billing
8
8
  export { createBillingSession, createPortalSession, createStripeCustomer, cancelSubscription, changeSubscriptionPlan, verifyWebhookEvent, parseBillingEvent, } from './lib/stripe-billing.js';
9
9
  // Subscribe (multi-provider via @mostajs/payment)
10
- export { subscribeToPlan, cancelCurrentSubscription } from './lib/subscribe.js';
10
+ export { subscribeToPlan, cancelCurrentSubscription, findActiveSubscription } from './lib/subscribe.js';
11
11
  // API handlers
12
12
  export { createPlanHandlers } from './api/plans.route.js';
13
13
  export { createSubscriptionHandlers } from './api/subscriptions.route.js';
@@ -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,MAAM,oBAAoB,CAAA;AAG/E,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"}
@@ -13,7 +13,7 @@ export interface PlanDTO {
13
13
  slug: string;
14
14
  price: number;
15
15
  currency: string;
16
- interval: 'month' | 'year';
16
+ interval: 'month' | 'year' | 'one_time';
17
17
  stripePriceId?: string;
18
18
  limits: PlanLimits;
19
19
  features: string[];
@@ -33,9 +33,18 @@ export interface SubscriptionDTO {
33
33
  currentPeriodEnd?: string;
34
34
  cancelAt?: string;
35
35
  trialEnd?: string;
36
+ /** Scope générique (optionnel) : 'account' (défaut) | 'course' | 'project' | … */
37
+ scopeType?: string;
38
+ /** Id de l'entité scopée (ex. courseId) quand scopeType ≠ 'account' */
39
+ scopeId?: string;
36
40
  createdAt?: string;
37
41
  updatedAt?: string;
38
42
  }
43
+ /** Scope d'un abonnement — entité à laquelle il est rattaché. */
44
+ export interface SubscriptionScope {
45
+ type: string;
46
+ id: string;
47
+ }
39
48
  export type InvoiceStatus = 'open' | 'paid' | 'void' | 'uncollectible';
40
49
  export interface InvoiceDTO {
41
50
  id: string;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"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"}
package/llms.txt ADDED
@@ -0,0 +1,111 @@
1
+ # @mostajs/subscriptions-plan — fiche LLM
2
+ > Plans d'abonnement, facturation, factures et suivi de consommation (quotas) pour @mostajs.
3
+
4
+ - Version: 0.4.0 · Licence: AGPL-3.0-or-later · Auteur: Dr Hamid MADANI <drmdh@msn.com>
5
+ - Chemin: mostajs/mosta-subscriptions-plan · Statut audit: complet (dist/)
6
+ - v0.4.0: SCOPE générique sur Subscription (`scopeType`/`scopeId`, optionnels, défaut 'account') → un compte peut avoir PLUSIEURS abos actifs, un par entité scopée (ex. 'course' pour race-event). Helper `findActiveSubscription({accountId?,scope?})`. `subscribeToPlan(..., {scope})`. `Plan.interval` accepte 'one_time'. Additif/non-breaking (auto-ALTER ORM).
7
+
8
+ ## RÔLE
9
+ Gère le cycle de vie d'abonnement SaaS : catalogue de plans (Free/Medium/Premium), souscription (gratuite directe ou payante déléguée à `@mostajs/payment`), facturation Stripe, factures, suivi de consommation journalière (UsageLog) et contrôle de quota par ressource. Fournit les 4 schémas ORM (`Plan`, `Subscription`, `Invoice`, `UsageLog`) et des handlers de routes Next.js App Router.
10
+
11
+ ## INSTALLATION
12
+ npm i @mostajs/subscriptions-plan
13
+
14
+ ## EXPORTS
15
+ Entrée `.` (types + schémas, sans dépendance serveur lourde) :
16
+ - Schémas: `PlanSchema`, `SubscriptionSchema`, `InvoiceSchema`, `UsageLogSchema`
17
+ - Constante: `moduleInfo`
18
+ - Types: `PlanDTO`, `PlanLimits`, `SubscriptionDTO`, `SubscriptionStatus`, `InvoiceDTO`, `InvoiceStatus`, `UsageLogDTO`, `BillingConfig`, `QuotaCheckResult`
19
+
20
+ ## EXPORTS PAR SOUS-CHEMIN
21
+ Entrée `@mostajs/subscriptions-plan/server` :
22
+ - Repos: `getPlanRepo`, `getSubscriptionRepo`, `getInvoiceRepo`, `getUsageLogRepo`, `resetRepos`
23
+ - Quota/usage: `checkQuota`, `isDialectAllowed`, `isTransportAllowed`, `getUsageSummary`, `incrementUsage`, constante `DEFAULT_PLANS`
24
+ - Souscription: `subscribeToPlan`, `cancelCurrentSubscription` (+ types `SubscribeParams`, `SubscribeResult`)
25
+ - Billing Stripe: `createBillingSession`, `createPortalSession`, `createStripeCustomer`, `cancelSubscription`, `changeSubscriptionPlan`, `verifyWebhookEvent`, `parseBillingEvent`
26
+ - Routes Next.js: `createPlanHandlers`, `createSubscriptionHandlers`, `createUsageHandlers`
27
+ - Module: `getSchemas`, `moduleInfo`, `subscriptionPlanRegistration`
28
+
29
+ ## API — SIGNATURES
30
+ - `checkQuota(dialect, accountId, resource: 'requests'|'projects'|'apiKeys', currentCount?): Promise<QuotaCheckResult>`
31
+ - `isDialectAllowed(limits: PlanLimits, dialect: string): boolean` · `isTransportAllowed(limits, transport): boolean`
32
+ - `getUsageSummary(dialect, accountId, periodStart): Promise<{ totalRequests; totalReads; totalWrites; totalErrors; days }>`
33
+ - `incrementUsage(dialect, accountId, projectId: string|undefined, type: 'read'|'write'|'error'): Promise<void>` — fire-and-forget
34
+ - `subscribeToPlan(dialect, params: SubscribeParams): Promise<SubscribeResult>`
35
+ - `cancelCurrentSubscription(dialect, accountId): Promise<SubscribeResult>` — annule et repasse en Free
36
+ - `createBillingSession(stripe, config: BillingConfig, params): Promise<{ url; sessionId }>`
37
+ - `createPortalSession(stripe, config, customerId): Promise<{ url }>`
38
+ - `createStripeCustomer(stripe, { email; name; metadata? }): Promise<string>`
39
+ - `cancelSubscription(stripe, stripeSubId, immediate?): Promise<void>` · `changeSubscriptionPlan(stripe, stripeSubId, newPriceId): Promise<void>`
40
+ - `verifyWebhookEvent(stripe, body, sig, secret): Promise<Stripe.Event>`
41
+ - `parseBillingEvent(event: Stripe.Event): { type: 'subscription_created'|'subscription_updated'|'subscription_deleted'|'invoice_paid'|'invoice_failed'|'checkout_completed'|'unknown'; data }`
42
+ - `createPlanHandlers(dialect, checkPermission?): { GET; POST; PUT; DELETE }` — GET public, écritures admin
43
+ - `createSubscriptionHandlers(dialect): { GET; POST; PUT }`
44
+ - `createUsageHandlers(dialect): { GET; GETToday; POST }`
45
+ - Repos: `getPlanRepo(dialect): BaseRepository<PlanDTO>` (idem Subscription/Invoice/UsageLog)
46
+
47
+ ## TYPES CLÉS
48
+ - `PlanLimits { maxProjects: number; maxApiKeys: number; requestsPerDay: number; maxPoolSize: number; dialects: string[]|'*'; transports: string[]|'*'; replication: boolean }`
49
+ - `PlanDTO { id; name; slug; price: number; currency; interval: 'month'|'year'; stripePriceId?; limits: PlanLimits; features: string[]; active: boolean; sortOrder: number; createdAt?; updatedAt? }`
50
+ - `SubscriptionStatus = 'active'|'trialing'|'past_due'|'canceled'|'unpaid'`
51
+ - `SubscriptionDTO { id; accountId; planId; status: SubscriptionStatus; stripeSubId?; currentPeriodStart?; currentPeriodEnd?; cancelAt?; trialEnd?; ... }`
52
+ - `InvoiceStatus = 'open'|'paid'|'void'|'uncollectible'`
53
+ - `InvoiceDTO { id; accountId; subscriptionId?; stripeInvoiceId?; amount; currency; status: InvoiceStatus; paidAt?; periodStart?; periodEnd?; pdfUrl?; hostedUrl?; ... }`
54
+ - `UsageLogDTO { id; accountId; projectId?; date: string; requests; reads; writes; errors; bandwidth; ... }`
55
+ - `BillingConfig { stripeSecretKey; stripeWebhookSecret; successUrl; cancelUrl; portalReturnUrl }`
56
+ - `QuotaCheckResult { allowed: boolean; reason?; current: number; limit: number; resource: string }`
57
+ - `SubscribeParams { accountId; planId; provider?: 'direct'|'stripe'|'chargily'|'paypal'|'satim'; successUrl?; cancelUrl?; webhookUrl?; customerEmail?; customerName?; stripeCustomerId? }`
58
+ - `SubscribeResult { ok: boolean; url?: string|null; subscription?: any; error?: string }`
59
+
60
+ ## PATTERN
61
+ ```ts
62
+ import { subscribeToPlan, checkQuota } from '@mostajs/subscriptions-plan/server'
63
+
64
+ // Souscription : Free → direct en DB, payant → redirect provider
65
+ const r = await subscribeToPlan(dialect, {
66
+ accountId: 'acc-1', planId: 'plan-premium', provider: 'stripe',
67
+ successUrl: '/billing/ok', cancelUrl: '/billing/ko', customerEmail: 'u@x.com',
68
+ })
69
+ if (r.url) redirect(r.url) // paiement requis
70
+
71
+ // Contrôle de quota
72
+ const q = await checkQuota(dialect, 'acc-1', 'projects')
73
+ if (!q.allowed) throw new Error(q.reason)
74
+ ```
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
+
94
+ ## DÉPEND DE
95
+ - `@mostajs/data-plug` (peer dep `^1.2.4`) — façade ORM : `BaseRepository`, `IDialect`, `EntitySchema`.
96
+ - `@mostajs/config` (peer dep) — résolution env.
97
+ - `@mostajs/payment` (peer dep optionnelle `>=0.4.2`) — checkout des plans payants ; `subscribeToPlan` lui délègue.
98
+ - `stripe` (peer dep optionnelle), `next`/`react` (peer deps optionnelles).
99
+
100
+ ## PIÈGES
101
+ - `subscribeToPlan` : plan gratuit (`price=0`) → souscription directe en DB ; plan payant → délègue à `@mostajs/payment` et retourne une `url` de redirect ; si aucun provider dispo, fallback en souscription directe.
102
+ - Vérifier `SubscribeResult.url` : non-nul ⇒ il faut rediriger l'utilisateur ; nul ⇒ souscription déjà créée (`subscription`).
103
+ - `incrementUsage` est fire-and-forget — ne pas s'appuyer sur son `await` pour garantir la persistance avant un check de quota.
104
+ - Le sous-chemin `/server` contient la logique sensible (clés Stripe) — ne pas l'importer côté client.
105
+ - `createPlanHandlers` : `GET` est public (page pricing), `POST/PUT/DELETE` exigent le `PermissionChecker` admin — sans checker, les écritures restent ouvertes.
106
+ - `PlanLimits.dialects`/`transports` acceptent `'*'` (tout autorisé) — `isDialectAllowed`/`isTransportAllowed` gèrent ce cas.
107
+ - Repos mis en cache via WeakMap par dialect ; `resetRepos` est un no-op de rétro-compat.
108
+ - Module distinct de `@mostajs/subscriptions` (qui gère plans/accès clients/activités) — ici l'axe est facturation + quotas.
109
+
110
+ ## RÉFÉRENCES
111
+ README.md
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mostajs/subscriptions-plan",
3
- "version": "0.3.6",
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,12 +20,14 @@
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": [
27
27
  "dist",
28
- "README.md"
28
+ "README.md",
29
+ "CHANGELOG.md",
30
+ "llms.txt"
29
31
  ],
30
32
  "author": "Dr Hamid MADANI <drmdh@msn.com>",
31
33
  "license": "AGPL-3.0-or-later",
@@ -53,6 +55,7 @@
53
55
  },
54
56
  "devDependencies": {
55
57
  "@mostajs/data-plug": "^1.2.4",
58
+ "@mostajs/mjs-unit": "^0.7.0",
56
59
  "@mostajs/orm": "^1.13.1",
57
60
  "@mostajs/payment": "^0.4.1",
58
61
  "@types/node": "^22.0.0",
@@ -63,5 +66,15 @@
63
66
  "funding": {
64
67
  "type": "github",
65
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
+ ]
66
79
  }
67
80
  }