@classytic/revenue 2.1.0 → 2.1.3

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
@@ -3,6 +3,72 @@
3
3
  Format based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
4
4
  adhering to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
5
5
 
6
+ ## [2.1.1] — multi-tenant scope correctness across all repos
7
+
8
+ **Fix.** `SubscriptionRepository` and `SettlementRepository` lifecycle verbs
9
+ were calling internal `getById` / `update` / `getAll` without threading
10
+ `ctx.organizationId` into the mongokit options bag. The moment a host
11
+ enabled `multiTenantPlugin` (the recommended default — see PACKAGE_RULES
12
+ §9), every verb threw `Missing 'organizationId' in context for 'getById'`
13
+ mid-flow and the lifecycle was unusable.
14
+
15
+ Affected verbs (all now threaded correctly):
16
+
17
+ - `SubscriptionRepository.{activate,cancel,pause,resume}` — every internal
18
+ `getById` and `update` now forwards `ctx`.
19
+ - `SettlementRepository.{schedule,processPending,complete,fail}` — every
20
+ internal `getById`, `getAll`, `update` now forwards `ctx`.
21
+
22
+ **Refactor.** Introduces `RevenueRepositoryBase<TDoc, TDeps>` (abstract;
23
+ internal — not exported) consolidating the two cross-cutting concerns
24
+ that were previously hand-rolled in three places:
25
+
26
+ - `protected optsFromCtx(ctx, extra?)` — thin adapter over mongokit's
27
+ canonical `repoOptionsFromCtx` extractor, plus revenue's `_bypassTenant`
28
+ flag for platform-admin cross-org reads. **Adding a new canonical context
29
+ field is now a single edit in `repo-options.ts` upstream, not three.**
30
+ - `protected dispatch(event, ctx)` — outbox-save (session-bound when
31
+ `ctx.session` is present) → transport-publish, with isolated try/catch
32
+ on each step (PACKAGE_RULES P8 / §5.5).
33
+
34
+ `TransactionRepository`, `SubscriptionRepository`, `SettlementRepository`
35
+ all extend the base. `BaseRevenueRepoDeps` (the shared `events` / `outbox`
36
+ / `logger` trio) is now the canonical superset every per-repo `Deps`
37
+ interface extends.
38
+
39
+ ### Added
40
+
41
+ - **`tests/scenarios/subscription-tenancy.scenario.test.ts`** — 6 tests
42
+ proving each lifecycle verb works under `scope: { enabled, required }`,
43
+ cross-tenant access is rejected with `SubscriptionNotFoundError`, and
44
+ `multiTenantPlugin` is wired (canary: omitting ctx throws
45
+ `Missing organizationId`).
46
+ - **`tests/scenarios/settlement-tenancy.scenario.test.ts`** — 5 tests for
47
+ the same matrix on settlements: schedule, processPending, complete, fail,
48
+ cross-tenant rejection.
49
+
50
+ ### Internal
51
+
52
+ - `RevenueRepositoryBase` is unexported on purpose — kept private to
53
+ the package. Adding a new repo means subclassing it; consumers stay
54
+ on the existing engine factory surface (`createRevenue(...)`).
55
+ - No public API change. Engine factory, repo method signatures, and
56
+ exported types are all byte-stable.
57
+
58
+ ### Migration
59
+
60
+ None — this is a behavioural fix. If you were running 2.1.0 with
61
+ `scope: false` as a workaround for the lifecycle bugs, you can now turn
62
+ scope back on. Recommended config:
63
+
64
+ ```ts
65
+ await createRevenue({
66
+ connection: mongoose.connection,
67
+ scope: { enabled: true, fieldType: 'objectId', required: true },
68
+ // ...
69
+ });
70
+ ```
71
+
6
72
  ## [2.0.0] — major rewrite
7
73
 
8
74
  Payment lifecycle engine refactored around unified transactions, an
package/README.md CHANGED
@@ -53,25 +53,48 @@ const refundTxn = await revenue.repositories.transaction.refund(
53
53
  ```
54
54
  createRevenue(config) --> RevenueEngine
55
55
  |
56
- |-- repositories.transaction extends mongokit Repository
57
- | getAll, getById, getByQuery, create, update, delete, count (inherited)
56
+ |-- repositories.transaction extends RevenueRepositoryBase
57
+ | CRUD inherited (mongokit Repository)
58
58
  | createPaymentIntent, verify, refund, handleWebhook (domain verbs)
59
59
  | hold, release, split (escrow verbs)
60
+ | import, match, unmatch, journalize, reject, removeByFeed (bank-feed verbs)
60
61
  |
61
- |-- repositories.subscription extends mongokit Repository
62
- | getAll, getById, create, update, delete, count (inherited)
62
+ |-- repositories.subscription extends RevenueRepositoryBase
63
+ | CRUD inherited
63
64
  | activate, cancel, pause, resume (domain verbs)
64
65
  |
65
- |-- repositories.settlement extends mongokit Repository
66
- | getAll, getById, create, update, delete, count (inherited)
66
+ |-- repositories.settlement extends RevenueRepositoryBase
67
+ | CRUD inherited
67
68
  | schedule, processPending, complete, fail (domain verbs)
68
69
  |
69
- |-- providers ProviderRegistry
70
- |-- events RevenueEventTransport (Arc-compatible)
71
- |-- models Mongoose models (for Arc adapter)
70
+ |-- providers ProviderRegistry
71
+ |-- events RevenueEventTransport (Arc-compatible)
72
+ |-- models Mongoose models (for Arc adapter)
73
+
74
+
75
+ RevenueRepositoryBase (internal)
76
+ |
77
+ |-- extends mongokit Repository<TDoc>
78
+ |-- protected optsFromCtx(ctx, extra?) threads RevenueContext into mongokit
79
+ | options bag (uses repoOptionsFromCtx;
80
+ | forwards organizationId, userId,
81
+ | session, requestId + _bypassTenant)
82
+ |-- protected dispatch(event, ctx) outbox.save (session-bound) →
83
+ | events.publish (PACKAGE_RULES P8)
84
+ \-- protected deps: BaseRevenueRepoDeps events / outbox? / logger?
72
85
  ```
73
86
 
74
- Repositories extend mongokit `Repository`. CRUD + pagination + query is inherited. Domain verbs contain real business logic (state machine transitions, provider calls, event emission). No service layer. No proxy methods.
87
+ **Three repos. One scope-threading helper. One dispatch helper.** Every
88
+ domain verb routes its mongokit calls through `optsFromCtx(ctx)` so
89
+ multi-tenant scope, audit attribution, and transaction sessions land
90
+ on every read/write without per-method boilerplate. Every domain event
91
+ goes through `dispatch(event, ctx)` so outbox and transport semantics
92
+ stay consistent across the package.
93
+
94
+ CRUD, pagination, querying, and policy hooks come from
95
+ [`@classytic/mongokit`](https://www.npmjs.com/package/@classytic/mongokit).
96
+ Domain verbs contain real business logic (state machine transitions,
97
+ provider calls, event emission). No service layer. No proxy methods.
75
98
 
76
99
  ## RevenueConfig
77
100
 
@@ -1,4 +1,4 @@
1
- import { l as ProviderNotFoundError } from "./errors-Dt46UZL_.mjs";
1
+ import { l as ProviderNotFoundError } from "./errors-LYYg9wcs.mjs";
2
2
 
3
3
  //#region src/providers/base.ts
4
4
  /**
@@ -1,6 +1,6 @@
1
1
  import { _ as TRANSACTION_STATUS, a as TRANSACTION_KIND } from "../bank-feed.enums-kYTLTTbe.mjs";
2
- import { g as SETTLEMENT_STATUS, l as SPLIT_STATUS, r as SUBSCRIPTION_STATUS, w as HOLD_STATUS } from "../subscription.enums-DoIr56O6.mjs";
3
- import { a as InvalidStateTransitionError } from "../errors-Dt46UZL_.mjs";
2
+ import { g as SETTLEMENT_STATUS, l as SPLIT_STATUS, r as SUBSCRIPTION_STATUS, w as HOLD_STATUS } from "../subscription.enums-95othr0i.mjs";
3
+ import { a as InvalidStateTransitionError } from "../errors-LYYg9wcs.mjs";
4
4
  import { defineStateMachine } from "@classytic/primitives/state-machine";
5
5
 
6
6
  //#region src/core/state-machines.ts
@@ -6,7 +6,7 @@ import { RepositoryPluginBundle, RevenueRepositories } from "./repositories/crea
6
6
  import { PluginType, Repository } from "@classytic/mongokit";
7
7
  import { TenantConfig } from "@classytic/repo-core/tenant";
8
8
  import mongoose, { Connection, Model, Schema } from "mongoose";
9
- import { EventTransport } from "@classytic/primitives/events";
9
+ import { DomainEvent, EventTransport } from "@classytic/primitives/events";
10
10
  import { OutboxStore } from "@classytic/primitives/outbox";
11
11
  import { BankImportReport, BankImportRowError, BankTransaction } from "@classytic/primitives/bank-transaction";
12
12
  import { ApprovalChain } from "@classytic/primitives/approval";
@@ -325,17 +325,125 @@ interface RevenueSchemaOptions {
325
325
  };
326
326
  }
327
327
  //#endregion
328
- //#region src/repositories/transaction.repository.d.ts
329
- interface TransactionRepoDeps {
328
+ //#region src/repositories/base.repository.d.ts
329
+ /**
330
+ * Cross-cutting deps that every revenue repository needs.
331
+ *
332
+ * Subclasses extend this with their own bridges, providers, configs:
333
+ *
334
+ * ```ts
335
+ * export interface SettlementRepoDeps extends BaseRevenueRepoDeps {
336
+ * bridges: RevenueBridges;
337
+ * }
338
+ * ```
339
+ */
340
+ interface BaseRevenueRepoDeps {
341
+ /**
342
+ * Domain-event transport (in-process or arc-compatible). All four
343
+ * `revenue:*` event families (`payment.*`, `subscription.*`,
344
+ * `settlement.*`, `escrow.*`) flow through this single channel; hosts
345
+ * subscribe glob-style.
346
+ */
330
347
  events: EventTransport;
331
348
  /**
332
- * Optional host-owned outbox store (PACKAGE_RULES §5.5 + P8). When present,
333
- * every domain event is persisted via `outbox.save(event)` before the
334
- * in-process `events.publish(event)` so the host's relay (arc's EventOutbox,
335
- * a Postgres LISTEN/NOTIFY pump, Kafka Connect, …) can replay on transport
349
+ * Optional host-owned outbox (PACKAGE_RULES §5.5 + P8). When wired,
350
+ * every dispatched event is persisted via `outbox.save(event)` BEFORE
351
+ * `events.publish(event)` so a host relay (arc's EventOutbox, a Postgres
352
+ * LISTEN/NOTIFY pump, Kafka Connect, …) can replay on transport
336
353
  * failure. When absent, events fire through `events.publish` only.
337
354
  */
338
355
  outbox?: OutboxStore | undefined;
356
+ /**
357
+ * Optional structured logger. Outbox/transport failures are logged
358
+ * here rather than thrown — a downstream subscriber error never
359
+ * cancels the upstream business write.
360
+ */
361
+ logger?: {
362
+ error(...args: unknown[]): void;
363
+ } | undefined;
364
+ }
365
+ /**
366
+ * Abstract base for `TransactionRepository`, `SubscriptionRepository`,
367
+ * `SettlementRepository`.
368
+ *
369
+ * Concrete subclasses MUST:
370
+ * - declare a `Deps` interface that extends {@link BaseRevenueRepoDeps}
371
+ * - call `super(model, plugins)` from the constructor
372
+ * - call `inject(deps)` once during engine boot
373
+ *
374
+ * Subclasses SHOULD use {@link optsFromCtx} for every mongokit call
375
+ * that takes an options bag, and {@link dispatch} for every event
376
+ * publish.
377
+ */
378
+ declare abstract class RevenueRepositoryBase<TDoc, TDeps extends BaseRevenueRepoDeps> extends Repository<TDoc> {
379
+ /**
380
+ * Subclass-specific deps. `!` because the engine wires this once via
381
+ * `inject(deps)` immediately after construction; calling any domain
382
+ * verb before injection is a programming error and the runtime
383
+ * will fail-loud with `Cannot read properties of undefined`.
384
+ */
385
+ protected deps: TDeps;
386
+ constructor(model: Model<TDoc>, plugins?: PluginType[]);
387
+ /**
388
+ * Wire engine-managed deps. Called exactly once per repository
389
+ * instance during {@link createRevenue} bootstrap. Subclasses with
390
+ * extra steps (caching, prebuilding state machine maps) override and
391
+ * call `super.inject(deps)`.
392
+ */
393
+ inject(deps: TDeps): void;
394
+ /**
395
+ * Translate {@link RevenueContext} into a mongokit options bag.
396
+ *
397
+ * Forwards every canonical field mongokit's bundled plugins read —
398
+ * `organizationId` (multiTenant), `userId` / `user` (audit),
399
+ * `session` (transactions), `requestId` (observability) — plus
400
+ * the revenue-specific `_bypassTenant` flag for platform-admin
401
+ * cross-org reads.
402
+ *
403
+ * Pass `extra` for caller-specific options like `throwOnNotFound`,
404
+ * `lean`, `populate`, `select` — the spread is `extra`-first so
405
+ * ctx wins on a key collision (intentional: callers shouldn't
406
+ * be smuggling tenant fields through `extra`).
407
+ *
408
+ * @param ctx - The request-scoped revenue context.
409
+ * @param extra - Additional mongokit options merged in.
410
+ */
411
+ protected optsFromCtx(ctx?: RevenueContext, extra?: Record<string, unknown>): Record<string, unknown>;
412
+ /**
413
+ * Persist an event to the host outbox, then publish to the in-process
414
+ * transport. The two sides have asymmetric failure handling — see
415
+ * PACKAGE_RULES §P8.
416
+ *
417
+ * When `ctx.session` is set, the outbox `save` runs inside the same
418
+ * Mongoose transaction (true P8 session-bound write); when absent, the
419
+ * outbox row lands after commit — still durable via the host's relay,
420
+ * with only a small at-most-once window on process crash.
421
+ *
422
+ * 1. **`outbox.save` failures PROPAGATE.** If we can't durably record
423
+ * the event, the caller's transaction MUST roll back so the
424
+ * business doc and the event row land atomically (or neither
425
+ * lands). Swallowing a save failure breaks the transactional-
426
+ * outbox correctness argument — the parent doc would land while
427
+ * the event vanishes.
428
+ *
429
+ * 2. **`events.publish` failures are SWALLOWED.** The host's outbox
430
+ * relay re-publishes from the durable row on its next poll. Even
431
+ * without an outbox, in-process subscribers shouldn't be able to
432
+ * break the business operation — they're best-effort consumers.
433
+ *
434
+ * @param event - Pre-built domain event (use `createEvent(REVENUE_EVENTS.X, payload, ctx, meta)`).
435
+ * @param ctx - The same context that produced the business write.
436
+ */
437
+ protected dispatch(event: DomainEvent, ctx?: RevenueContext): Promise<void>;
438
+ }
439
+ //#endregion
440
+ //#region src/repositories/transaction.repository.d.ts
441
+ /**
442
+ * Deps for {@link TransactionRepository}. Extends {@link BaseRevenueRepoDeps}
443
+ * (events / outbox? / logger?) with provider/bridge/config wiring specific
444
+ * to the payment-flow + bank-feed lifecycles.
445
+ */
446
+ interface TransactionRepoDeps extends BaseRevenueRepoDeps {
339
447
  providers: ProviderRegistry;
340
448
  /**
341
449
  * Bank-feed provider registry (3.0). Optional — when omitted, the
@@ -346,47 +454,30 @@ interface TransactionRepoDeps {
346
454
  bridges: RevenueBridges;
347
455
  commission?: CommissionConfig;
348
456
  defaultCurrency: string;
349
- logger?: {
350
- error(...args: unknown[]): void;
351
- } | undefined;
352
457
  }
353
- declare class TransactionRepository extends Repository<TransactionDocument> {
354
- private deps;
458
+ declare class TransactionRepository extends RevenueRepositoryBase<TransactionDocument, TransactionRepoDeps> {
355
459
  constructor(model: Model<TransactionDocument>, plugins?: PluginType[]);
356
- inject(deps: TransactionRepoDeps): void;
357
- /**
358
- * Thread `ctx.organizationId` (and future ctx fields) into mongokit
359
- * options so the `multiTenantPlugin` can auto-scope filters, queries,
360
- * and inserts. Merges any caller-supplied extras. Centralizing this
361
- * here means every domain verb participates in scope isolation without
362
- * per-call boilerplate.
363
- */
364
- private optsFromCtx;
365
460
  /**
366
461
  * Save an event to the host-owned outbox, session-bound when available.
367
462
  *
368
- * When `session` is passed, the outbox row commits atomically with the
369
- * business write (P8 true session-bound write). When absent, the save
370
- * happens after commit still durable via the host's relay, but with a
371
- * small at-most-once window on process crash.
463
+ * Called INSIDE a `withTransaction` body. The outbox row commits
464
+ * atomically with the business write (P8 true session-bound write)
465
+ * if outbox.save fails, this method re-throws so `withTransaction`
466
+ * rolls the parent write back. Without propagation, the parent doc
467
+ * would commit while the event row vanishes, defeating the whole
468
+ * transactional-outbox correctness argument.
372
469
  *
373
- * Isolated try/catch: an outbox failure never throws out of this helper;
374
- * the caller still issues a transport.publish.
470
+ * Logging happens before the re-throw so the failure surfaces in
471
+ * observability without losing the original stack trace.
375
472
  */
376
- private saveToOutbox;
473
+ protected saveToOutbox(event: DomainEvent, session?: unknown): Promise<void>;
377
474
  /**
378
475
  * Publish an event to the in-process `EventTransport` after commit.
379
- * Transport failure is logged — the host relay will still redeliver from
380
- * the outbox, so in-process subscribers missing an event is recoverable.
476
+ * Transport failure is logged and swallowed — the host relay re-delivers
477
+ * from the durable outbox row on the next poll, so in-process
478
+ * subscribers missing an event is recoverable. Best-effort by design.
381
479
  */
382
- private publishToTransport;
383
- /**
384
- * Non-transactional dispatch (used by verbs that don't open their own
385
- * `withTransaction` block): outbox.save (session-bound when ctx provides
386
- * one) → transport.publish. Matches arc's EventOutbox + MemoryEventTransport
387
- * wiring bit-for-bit.
388
- */
389
- private dispatch;
480
+ protected publishToTransport(event: DomainEvent): Promise<void>;
390
481
  /** Creates transaction + calls provider. Returns the created transaction doc. */
391
482
  createPaymentIntent(params: {
392
483
  data?: Record<string, unknown>;
@@ -677,33 +768,53 @@ declare class TransactionRepository extends Repository<TransactionDocument> {
677
768
  }
678
769
  //#endregion
679
770
  //#region src/repositories/subscription.repository.d.ts
680
- interface SubscriptionRepoDeps {
681
- events: EventTransport;
682
- /** Host-owned outbox (PACKAGE_RULES §5.5 + P8). See TransactionRepoDeps. */
683
- outbox?: OutboxStore | undefined;
684
- logger?: {
685
- error(...args: unknown[]): void;
686
- } | undefined;
687
- }
688
771
  /**
689
- * SubscriptionRepository data layer + domain verbs.
772
+ * Deps for {@link SubscriptionRepository}. Currently identical to
773
+ * {@link BaseRevenueRepoDeps} (events / outbox? / logger?). Kept as its
774
+ * own type alias so future subscription-specific deps (e.g. a billing
775
+ * engine handle) can land without touching every callsite — and so the
776
+ * `inject(deps)` signature reads as `SubscriptionRepoDeps` at the
777
+ * engine factory.
778
+ */
779
+ type SubscriptionRepoDeps = BaseRevenueRepoDeps;
780
+ /**
781
+ * SubscriptionRepository — data layer + domain verbs for the recurring-
782
+ * billing lifecycle.
783
+ *
784
+ * **CRUD inherited** from mongokit (via {@link RevenueRepositoryBase}):
785
+ * `getById`, `getByQuery`, `getAll`, `create`, `update`, `delete`,
786
+ * `findOneAndUpdate`, `count`, `exists`, `claim`, `cursor`, `updateMany`,
787
+ * `deleteMany`. All participate in `multiTenantPlugin` scope filtering
788
+ * when wired.
789
+ *
790
+ * **Domain verbs (state transitions):** `activate`, `cancel`, `pause`,
791
+ * `resume`. Each runs the state-machine guard (`SUBSCRIPTION_STATE_MACHINE`
792
+ * — invalid transitions throw, never silently no-op), persists the
793
+ * resulting writes through {@link RevenueRepositoryBase.optsFromCtx} so
794
+ * tenant scope is preserved end-to-end, then dispatches its
795
+ * `revenue:subscription.*` event via {@link RevenueRepositoryBase.dispatch}.
690
796
  *
691
- * CRUD inherited from mongokit. Domain verbs: activate, cancel, pause, resume.
797
+ * **Multi-tenant correctness.** Every internal `getById`/`update` call
798
+ * threads `ctx.organizationId` through `optsFromCtx(ctx)`. Without this
799
+ * threading the inner read would either throw
800
+ * `Missing 'organizationId' in context` (when `multiTenantPlugin` is
801
+ * required) or — worse — return another tenant's subscription matching
802
+ * the same `_id` shape (when `required: false`). 2.1.0 had this bug; 2.1.1+
803
+ * is correct.
692
804
  *
693
- * Events: each domain verb calls `this.deps.events.publish(createEvent(...))`
694
- * with a fully-qualified `REVENUE_EVENTS.*` name. Hosts can subscribe glob-style
695
- * via `revenue.events.subscribe('revenue:subscription.*', handler)` the
696
- * injected transport is arc-compatible (PACKAGE_RULES §13–§14).
805
+ * @example Activate a pending sub
806
+ * ```ts
807
+ * const ctx = { organizationId: 'org_42', actorId: 'user_99' };
808
+ * const sub = await subRepo.create(
809
+ * { customerId: 'cust_1', planKey: 'monthly', amount: 999, currency: 'USD',
810
+ * status: SUBSCRIPTION_STATUS.PENDING, isActive: false },
811
+ * ctx,
812
+ * );
813
+ * await subRepo.activate(String(sub._id), {}, ctx);
814
+ * ```
697
815
  */
698
- declare class SubscriptionRepository extends Repository<SubscriptionDocument> {
699
- private deps;
816
+ declare class SubscriptionRepository extends RevenueRepositoryBase<SubscriptionDocument, SubscriptionRepoDeps> {
700
817
  constructor(model: Model<SubscriptionDocument>, plugins?: PluginType[]);
701
- inject(deps: SubscriptionRepoDeps): void;
702
- /**
703
- * Host-owned outbox save → in-process transport publish (PACKAGE_RULES P8).
704
- * Session-bound when `ctx.session` is present (atomic outbox row write).
705
- */
706
- private dispatch;
707
818
  activate(subscriptionId: string, options?: {
708
819
  timestamp?: Date;
709
820
  }, ctx?: RevenueContext): Promise<SubscriptionDocument>;
@@ -720,37 +831,38 @@ declare class SubscriptionRepository extends Repository<SubscriptionDocument> {
720
831
  }
721
832
  //#endregion
722
833
  //#region src/repositories/settlement.repository.d.ts
723
- interface SettlementRepoDeps {
724
- events: EventTransport;
725
- /** Host-owned outbox (PACKAGE_RULES §5.5 + P8). See TransactionRepoDeps. */
726
- outbox?: OutboxStore | undefined;
834
+ /**
835
+ * Deps for {@link SettlementRepository}. Adds the optional
836
+ * `RevenueBridges` (ledger / notification ports) on top of
837
+ * {@link BaseRevenueRepoDeps}.
838
+ */
839
+ interface SettlementRepoDeps extends BaseRevenueRepoDeps {
727
840
  bridges: RevenueBridges;
728
- logger?: {
729
- error(...args: unknown[]): void;
730
- } | undefined;
731
841
  }
732
842
  interface SettlementProcessingError {
733
843
  settlementId: SettlementDocument['_id'];
734
844
  error: unknown;
735
845
  }
736
846
  /**
737
- * SettlementRepository — data layer + domain verbs.
847
+ * SettlementRepository — payouts to recipients (organizations, vendors,
848
+ * affiliates).
849
+ *
850
+ * **CRUD inherited** via {@link RevenueRepositoryBase}. **Domain verbs:**
851
+ * `schedule`, `processPending`, `complete`, `fail`. State machine:
852
+ * `pending → processing → completed | failed`; `failed` with
853
+ * `retry: true` resets to `pending` with a delayed `scheduledAt`.
738
854
  *
739
- * CRUD inherited from mongokit. Domain verbs: schedule, processPending, complete, fail.
855
+ * **Multi-tenant correctness.** Every read/write threads `ctx` through
856
+ * {@link RevenueRepositoryBase.optsFromCtx} so `multiTenantPlugin`
857
+ * scope filters apply. 2.1.0 had several `getById`/`update` calls that
858
+ * dropped ctx — fixed in 2.1.1+.
740
859
  *
741
- * Events are published via the injected `events` transport (arc-compatible).
742
- * Hosts subscribe glob-style via `revenue.events.subscribe('revenue:settlement.*', h)`.
743
- * See PACKAGE_RULES §13–§14.
860
+ * Bridges (`ledger`, `notification`) fire on `complete()` so a host
861
+ * can pin double-entry book-keeping or push a "you got paid" email
862
+ * without the repo knowing about either subsystem.
744
863
  */
745
- declare class SettlementRepository extends Repository<SettlementDocument> {
746
- private deps;
864
+ declare class SettlementRepository extends RevenueRepositoryBase<SettlementDocument, SettlementRepoDeps> {
747
865
  constructor(model: Model<SettlementDocument>, plugins?: PluginType[]);
748
- inject(deps: SettlementRepoDeps): void;
749
- /**
750
- * Host-owned outbox save → in-process transport publish (PACKAGE_RULES P8).
751
- * Session-bound when `ctx.session` is present (atomic outbox row write).
752
- */
753
- private dispatch;
754
866
  schedule(params: {
755
867
  organizationId: string;
756
868
  recipientId: string;
@@ -1,5 +1,5 @@
1
1
  import { _ as TRANSACTION_STATUS, a as TRANSACTION_KIND, b as isTransactionFlow, c as isBankFeedSource, d as isTransactionKind, f as statusesForKind, g as TRANSACTION_FLOW_VALUES, h as TRANSACTION_FLOW, i as BANK_FEED_STATUS_VALUES, l as isBankFeedStatus, m as LIBRARY_CATEGORY_VALUES, n as BANK_FEED_SOURCE_VALUES, o as TRANSACTION_KIND_VALUES, p as LIBRARY_CATEGORIES, r as BANK_FEED_STATUS, s as initialStatusFor, t as BANK_FEED_SOURCE, u as isStatusValidForKind, v as TRANSACTION_STATUS_VALUES, x as isTransactionStatus, y as isLibraryCategory } from "../bank-feed.enums-kYTLTTbe.mjs";
2
- import { A as isReleaseReason, C as HOLD_REASON_VALUES, D as RELEASE_REASON_VALUES, E as RELEASE_REASON, O as isHoldReason, S as HOLD_REASON, T as HOLD_STATUS_VALUES, _ as SETTLEMENT_STATUS_VALUES, a as isPlanKey, b as isSettlementStatus, c as PAYOUT_METHOD_VALUES, d as SPLIT_TYPE, f as SPLIT_TYPE_VALUES, g as SETTLEMENT_STATUS, h as isSplitType, i as SUBSCRIPTION_STATUS_VALUES, k as isHoldStatus, l as SPLIT_STATUS, m as isSplitStatus, n as PLAN_KEY_VALUES, o as isSubscriptionStatus, p as isPayoutMethod, r as SUBSCRIPTION_STATUS, s as PAYOUT_METHOD, t as PLAN_KEYS, u as SPLIT_STATUS_VALUES, v as SETTLEMENT_TYPE, w as HOLD_STATUS, x as isSettlementType, y as SETTLEMENT_TYPE_VALUES } from "../subscription.enums-DoIr56O6.mjs";
2
+ import { A as isReleaseReason, C as HOLD_REASON_VALUES, D as RELEASE_REASON_VALUES, E as RELEASE_REASON, O as isHoldReason, S as HOLD_REASON, T as HOLD_STATUS_VALUES, _ as SETTLEMENT_STATUS_VALUES, a as isPlanKey, b as isSettlementStatus, c as PAYOUT_METHOD_VALUES, d as SPLIT_TYPE, f as SPLIT_TYPE_VALUES, g as SETTLEMENT_STATUS, h as isSplitType, i as SUBSCRIPTION_STATUS_VALUES, k as isHoldStatus, l as SPLIT_STATUS, m as isSplitStatus, n as PLAN_KEY_VALUES, o as isSubscriptionStatus, p as isPayoutMethod, r as SUBSCRIPTION_STATUS, s as PAYOUT_METHOD, t as PLAN_KEYS, u as SPLIT_STATUS_VALUES, v as SETTLEMENT_TYPE, w as HOLD_STATUS, x as isSettlementType, y as SETTLEMENT_TYPE_VALUES } from "../subscription.enums-95othr0i.mjs";
3
3
  import { a as PAYMENT_GATEWAY_TYPE_VALUES, c as isPaymentGatewayType, i as PAYMENT_GATEWAY_TYPE, l as isPaymentStatus, n as MONETIZATION_TYPE_VALUES, o as PAYMENT_STATUS, r as isMonetizationType, s as PAYMENT_STATUS_VALUES, t as MONETIZATION_TYPES } from "../monetization.enums-B9HBOecd.mjs";
4
4
 
5
5
  export { BANK_FEED_SOURCE, BANK_FEED_SOURCE_VALUES, BANK_FEED_STATUS, BANK_FEED_STATUS_VALUES, HOLD_REASON, HOLD_REASON_VALUES, HOLD_STATUS, HOLD_STATUS_VALUES, LIBRARY_CATEGORIES, LIBRARY_CATEGORY_VALUES, MONETIZATION_TYPES, MONETIZATION_TYPE_VALUES, PAYMENT_GATEWAY_TYPE, PAYMENT_GATEWAY_TYPE_VALUES, PAYMENT_STATUS, PAYMENT_STATUS_VALUES, PAYOUT_METHOD, PAYOUT_METHOD_VALUES, PLAN_KEYS, PLAN_KEY_VALUES, RELEASE_REASON, RELEASE_REASON_VALUES, SETTLEMENT_STATUS, SETTLEMENT_STATUS_VALUES, SETTLEMENT_TYPE, SETTLEMENT_TYPE_VALUES, SPLIT_STATUS, SPLIT_STATUS_VALUES, SPLIT_TYPE, SPLIT_TYPE_VALUES, SUBSCRIPTION_STATUS, SUBSCRIPTION_STATUS_VALUES, TRANSACTION_FLOW, TRANSACTION_FLOW_VALUES, TRANSACTION_KIND, TRANSACTION_KIND_VALUES, TRANSACTION_STATUS, TRANSACTION_STATUS_VALUES, initialStatusFor, isBankFeedSource, isBankFeedStatus, isHoldReason, isHoldStatus, isLibraryCategory, isMonetizationType, isPaymentGatewayType, isPaymentStatus, isPayoutMethod, isPlanKey, isReleaseReason, isSettlementStatus, isSettlementType, isSplitStatus, isSplitType, isStatusValidForKind, isSubscriptionStatus, isTransactionFlow, isTransactionKind, isTransactionStatus, statusesForKind };
@@ -95,18 +95,16 @@ declare const transactionBaseSchema: z.ZodObject<{
95
95
  metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
96
96
  }, z.core.$strip>;
97
97
  declare const transactionCreateSchema: z.ZodObject<{
98
- organizationId: z.ZodOptional<z.ZodString>;
99
- customerId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
100
98
  type: z.ZodString;
101
99
  amount: z.ZodNumber;
102
100
  currency: z.ZodString;
103
- status: z.ZodDefault<z.ZodString>;
104
- metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
105
101
  relatedTransactionId: z.ZodOptional<z.ZodString>;
106
102
  flow: z.ZodEnum<{
107
103
  inflow: "inflow";
108
104
  outflow: "outflow";
109
105
  }>;
106
+ metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
107
+ status: z.ZodDefault<z.ZodString>;
110
108
  gateway: z.ZodOptional<z.ZodObject<{
111
109
  type: z.ZodString;
112
110
  sessionId: z.ZodOptional<z.ZodString>;
@@ -115,9 +113,11 @@ declare const transactionCreateSchema: z.ZodObject<{
115
113
  metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
116
114
  verificationData: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
117
115
  }, z.core.$strip>>;
116
+ customerId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
118
117
  sourceId: z.ZodOptional<z.ZodString>;
119
118
  sourceModel: z.ZodOptional<z.ZodString>;
120
119
  idempotencyKey: z.ZodOptional<z.ZodString>;
120
+ organizationId: z.ZodOptional<z.ZodString>;
121
121
  tags: z.ZodDefault<z.ZodArray<z.ZodString>>;
122
122
  fee: z.ZodDefault<z.ZodNumber>;
123
123
  tax: z.ZodDefault<z.ZodNumber>;
@@ -315,20 +315,20 @@ declare const subscriptionBaseSchema: z.ZodObject<{
315
315
  metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
316
316
  }, z.core.$strip>;
317
317
  declare const subscriptionCreateSchema: z.ZodObject<{
318
- organizationId: z.ZodOptional<z.ZodString>;
319
- customerId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
320
- planKey: z.ZodString;
321
318
  amount: z.ZodNumber;
322
319
  currency: z.ZodOptional<z.ZodString>;
323
- transactionId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
320
+ metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
321
+ customerId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
322
+ planKey: z.ZodString;
324
323
  paymentIntentId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
324
+ transactionId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
325
+ organizationId: z.ZodOptional<z.ZodString>;
325
326
  startDate: z.ZodOptional<z.ZodCoercedDate<unknown>>;
326
327
  endDate: z.ZodOptional<z.ZodCoercedDate<unknown>>;
327
328
  pauseReason: z.ZodOptional<z.ZodString>;
328
329
  cancelAt: z.ZodOptional<z.ZodCoercedDate<unknown>>;
329
330
  cancellationReason: z.ZodOptional<z.ZodString>;
330
331
  renewalTransactionId: z.ZodOptional<z.ZodString>;
331
- metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
332
332
  }, z.core.$strip>;
333
333
  declare const subscriptionUpdateSchema: z.ZodObject<{
334
334
  publicId: z.ZodOptional<z.ZodOptional<z.ZodString>>;
@@ -433,7 +433,6 @@ declare const settlementBaseSchema: z.ZodObject<{
433
433
  metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
434
434
  }, z.core.$strip>;
435
435
  declare const settlementCreateSchema: z.ZodObject<{
436
- organizationId: z.ZodString;
437
436
  type: z.ZodEnum<{
438
437
  split_payout: "split_payout";
439
438
  platform_withdrawal: "platform_withdrawal";
@@ -442,8 +441,8 @@ declare const settlementCreateSchema: z.ZodObject<{
442
441
  }>;
443
442
  amount: z.ZodNumber;
444
443
  currency: z.ZodString;
445
- metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
446
444
  notes: z.ZodOptional<z.ZodString>;
445
+ metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
447
446
  recipientId: z.ZodString;
448
447
  recipientType: z.ZodEnum<{
449
448
  platform: "platform";
@@ -452,6 +451,7 @@ declare const settlementCreateSchema: z.ZodObject<{
452
451
  affiliate: "affiliate";
453
452
  partner: "partner";
454
453
  }>;
454
+ organizationId: z.ZodString;
455
455
  payoutMethod: z.ZodEnum<{
456
456
  manual: "manual";
457
457
  bank_transfer: "bank_transfer";
@@ -1,4 +1,4 @@
1
- import { n as createEvent, t as REVENUE_EVENTS } from "../event-constants-CTiDNWzc.mjs";
2
- import { A as TransactionUpdated, C as SubscriptionResumed, D as TransactionRejected, E as TransactionMatched, M as revenueEventDefinitions, N as InProcessRevenueBus, O as TransactionRemovedByFeed, S as SubscriptionRenewed, T as TransactionJournalized, _ as SettlementScheduled, a as FreeCreated, b as SubscriptionCreated, c as PaymentProcessing, d as PaymentVerified, f as PurchaseCreated, g as SettlementProcessing, h as SettlementFailed, i as EscrowSplit, j as WebhookProcessed, k as TransactionUnmatched, l as PaymentRefunded, m as SettlementCreated, n as EscrowHeld, o as MonetizationCreated, p as SettlementCompleted, r as EscrowReleased, s as PaymentFailed, t as EscrowCancelled, u as PaymentRequiresAction, v as SubscriptionActivated, w as TransactionImported, x as SubscriptionPaused, y as SubscriptionCancelled } from "../revenue-event-catalog-CgZ57M-f.mjs";
1
+ import { n as createEvent, t as REVENUE_EVENTS } from "../event-constants-Dn1TKahe.mjs";
2
+ import { A as TransactionUpdated, C as SubscriptionResumed, D as TransactionRejected, E as TransactionMatched, M as revenueEventDefinitions, N as InProcessRevenueBus, O as TransactionRemovedByFeed, S as SubscriptionRenewed, T as TransactionJournalized, _ as SettlementScheduled, a as FreeCreated, b as SubscriptionCreated, c as PaymentProcessing, d as PaymentVerified, f as PurchaseCreated, g as SettlementProcessing, h as SettlementFailed, i as EscrowSplit, j as WebhookProcessed, k as TransactionUnmatched, l as PaymentRefunded, m as SettlementCreated, n as EscrowHeld, o as MonetizationCreated, p as SettlementCompleted, r as EscrowReleased, s as PaymentFailed, t as EscrowCancelled, u as PaymentRequiresAction, v as SubscriptionActivated, w as TransactionImported, x as SubscriptionPaused, y as SubscriptionCancelled } from "../revenue-event-catalog-BvjNVnPd.mjs";
3
3
 
4
4
  export { EscrowCancelled, EscrowHeld, EscrowReleased, EscrowSplit, FreeCreated, InProcessRevenueBus, MonetizationCreated, PaymentFailed, PaymentProcessing, PaymentRefunded, PaymentRequiresAction, PaymentVerified, PurchaseCreated, REVENUE_EVENTS, SettlementCompleted, SettlementCreated, SettlementFailed, SettlementProcessing, SettlementScheduled, SubscriptionActivated, SubscriptionCancelled, SubscriptionCreated, SubscriptionPaused, SubscriptionRenewed, SubscriptionResumed, TransactionImported, TransactionJournalized, TransactionMatched, TransactionRejected, TransactionRemovedByFeed, TransactionUnmatched, TransactionUpdated, WebhookProcessed, createEvent, revenueEventDefinitions };