@classytic/revenue 2.0.1 → 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.
Files changed (45) hide show
  1. package/CHANGELOG.md +66 -0
  2. package/README.md +33 -10
  3. package/dist/bank-feed-BlQeq2rK.mjs +133 -0
  4. package/dist/bank-feed.enums-BadqNJTC.d.mts +118 -0
  5. package/dist/bank-feed.enums-kYTLTTbe.mjs +165 -0
  6. package/dist/bridges/index.d.mts +1 -1
  7. package/dist/core/state-machines.d.mts +25 -2
  8. package/dist/core/state-machines.mjs +43 -3
  9. package/dist/engine-types-Jctrbasz.d.mts +1160 -0
  10. package/dist/enums/index.d.mts +4 -3
  11. package/dist/enums/index.mjs +4 -3
  12. package/dist/{errors-DHa8JVQ-.mjs → errors-LYYg9wcs.mjs} +23 -1
  13. package/dist/{escrow.schema-D5X32LwX.d.mts → escrow.schema-YuBgjL-I.d.mts} +27 -27
  14. package/dist/{event-constants-CEMitnIV.mjs → event-constants-Dn1TKahe.mjs} +6 -0
  15. package/dist/events/index.d.mts +2 -2
  16. package/dist/events/index.mjs +3 -3
  17. package/dist/index.d.mts +32 -13
  18. package/dist/index.mjs +142 -19
  19. package/dist/providers/index.d.mts +2 -2
  20. package/dist/providers/index.mjs +2 -2
  21. package/dist/registry-h8sasoLh.d.mts +145 -0
  22. package/dist/repositories/create-repositories.d.mts +1 -1
  23. package/dist/repositories/create-repositories.mjs +1 -1
  24. package/dist/{revenue-bridges-sdlrR85c.d.mts → revenue-bridges-BtkWFsJu.d.mts} +107 -1
  25. package/dist/{revenue-event-catalog-LqxPnsU_.mjs → revenue-event-catalog-BvjNVnPd.mjs} +77 -3
  26. package/dist/{revenue-event-catalog-BX3g7RUi.d.mts → revenue-event-catalog-JpJcyK1E.d.mts} +198 -2
  27. package/dist/settlement.repository-BAdc9qGl.mjs +1444 -0
  28. package/dist/shared/index.d.mts +1 -1
  29. package/dist/shared/index.mjs +2 -2
  30. package/dist/{subscription.enums-tfoAgsTv.mjs → subscription.enums-95othr0i.mjs} +1 -40
  31. package/dist/{transaction.enums-u4MshXcL.d.mts → subscription.enums-k24kLpF7.d.mts} +1 -36
  32. package/dist/validators/index.d.mts +158 -2
  33. package/dist/validators/index.mjs +95 -2
  34. package/package.json +7 -7
  35. package/dist/engine-types-CcjIb4Fy.d.mts +0 -611
  36. package/dist/registry-DhFMsSn5.mjs +0 -150
  37. package/dist/registry-SvIGPAx_.d.mts +0 -143
  38. package/dist/settlement.repository-DHIPx5S4.mjs +0 -771
  39. /package/dist/{audit-B39B0Sdq.mjs → audit-Ba2XB2C4.mjs} +0 -0
  40. /package/dist/{audit-DZ0eTr9g.d.mts → audit-DRKuLBFO.d.mts} +0 -0
  41. /package/dist/{context-DRqSeTPM.d.mts → context-pjP1QeE3.d.mts} +0 -0
  42. /package/dist/{escrow.schema-BBv9oVEW.mjs → escrow.schema-C-b41z_G.mjs} +0 -0
  43. /package/dist/{monetization.enums-BtiU3t8o.mjs → monetization.enums-B9HBOecd.mjs} +0 -0
  44. /package/dist/{monetization.enums-D2xbxXJM.d.mts → monetization.enums-DzAI4sT7.d.mts} +0 -0
  45. /package/dist/{splits-BAfY-a9P.mjs → splits-CNfQj92L.mjs} +0 -0
@@ -0,0 +1,1160 @@
1
+ import { t as RevenueContext } from "./context-pjP1QeE3.mjs";
2
+ import { t as RevenueBridges } from "./revenue-bridges-BtkWFsJu.mjs";
3
+ import { u as TransactionKindValue } from "./bank-feed.enums-BadqNJTC.mjs";
4
+ import { a as BankFeedProviderRegistry, d as PaymentProvider, o as FetchTransactionsParams, r as BankFeedProvider, t as ProviderRegistry } from "./registry-h8sasoLh.mjs";
5
+ import { RepositoryPluginBundle, RevenueRepositories } from "./repositories/create-repositories.mjs";
6
+ import { PluginType, Repository } from "@classytic/mongokit";
7
+ import { TenantConfig } from "@classytic/repo-core/tenant";
8
+ import mongoose, { Connection, Model, Schema } from "mongoose";
9
+ import { DomainEvent, EventTransport } from "@classytic/primitives/events";
10
+ import { OutboxStore } from "@classytic/primitives/outbox";
11
+ import { BankImportReport, BankImportRowError, BankTransaction } from "@classytic/primitives/bank-transaction";
12
+ import { ApprovalChain } from "@classytic/primitives/approval";
13
+
14
+ //#region src/models/transaction.schema.d.ts
15
+ /**
16
+ * Counterparty on a bank-feed entry. Untyped Mixed at the Mongoose level
17
+ * (banks populate different fields), but typed here so consumers and
18
+ * validators can introspect.
19
+ */
20
+ interface TransactionCounterparty {
21
+ name?: string;
22
+ identifier?: string;
23
+ iban?: string;
24
+ accountNumber?: string;
25
+ bic?: string;
26
+ routingNumber?: string;
27
+ }
28
+ /**
29
+ * Polymorphic ledger journal-entry reference. String-typed (PACKAGE_RULES
30
+ * §7) so it works for ledger documents stored in another connection,
31
+ * Postgres ledgers, or external accounting systems (QBO Journal IDs).
32
+ */
33
+ interface JournalEntryRef {
34
+ type: string;
35
+ id: string;
36
+ }
37
+ interface TransactionDocument {
38
+ _id: mongoose.Types.ObjectId;
39
+ publicId: string;
40
+ organizationId?: string;
41
+ customerId?: string | null;
42
+ /**
43
+ * Selects the state machine that governs this row's `status` field.
44
+ * Defaults to `'payment_flow'` so existing data reads identically.
45
+ * See `core/state-machines.ts:smFor()`.
46
+ */
47
+ kind: TransactionKindValue;
48
+ type: string;
49
+ flow: 'inflow' | 'outflow';
50
+ tags: string[];
51
+ amount: number;
52
+ currency: string;
53
+ fee: number;
54
+ tax: number;
55
+ net: number;
56
+ taxDetails?: {
57
+ type?: string;
58
+ rate?: number;
59
+ isInclusive?: boolean;
60
+ };
61
+ /**
62
+ * Multi-currency reconciliation (3.0). When the bank deposit clears
63
+ * in a currency different from the originating charge, store the
64
+ * other side here. Cross-currency `findMatchCandidates` reads these
65
+ * to compare same-currency-equivalent amounts.
66
+ */
67
+ fxRate?: number;
68
+ originalAmount?: number;
69
+ originalCurrency?: string;
70
+ method: string;
71
+ status: string;
72
+ /**
73
+ * Optional embedded approval chain — P7. Hosts that gate manual /
74
+ * non-gateway transactions on a maker-checker review attach a chain via
75
+ * `createChain()` from `@classytic/primitives/approval`; the host's
76
+ * approval action checks `isApproved(doc.approvals)` before flipping
77
+ * status to `succeeded`. Auto-gateway transactions (Stripe, SSLCommerz,
78
+ * etc.) bypass this entirely — synchronous gateway success is the gate.
79
+ *
80
+ * Use cases:
81
+ * - Manual cash receipts (front desk → bookkeeper verify)
82
+ * - Manual bank transfers (supplier-paid → finance verify)
83
+ * - Cheque deposits (queued until cleared)
84
+ * - **Refunds especially** — the audit-defining moment
85
+ */
86
+ approvals?: ApprovalChain;
87
+ gateway?: {
88
+ type: string;
89
+ sessionId?: string;
90
+ paymentIntentId?: string;
91
+ chargeId?: string;
92
+ metadata?: Record<string, unknown>;
93
+ verificationData?: Record<string, unknown>;
94
+ };
95
+ paymentDetails?: Record<string, unknown>;
96
+ commission?: {
97
+ rate: number;
98
+ grossAmount: number;
99
+ gatewayFeeRate: number;
100
+ gatewayFeeAmount: number;
101
+ netAmount: number;
102
+ status: string;
103
+ };
104
+ splits?: Array<{
105
+ type: string;
106
+ recipientId: string;
107
+ recipientType: string;
108
+ rate: number;
109
+ grossAmount: number;
110
+ gatewayFeeRate: number;
111
+ gatewayFeeAmount: number;
112
+ netAmount: number;
113
+ status: string;
114
+ }>;
115
+ hold?: {
116
+ status: string;
117
+ heldAmount: number;
118
+ releasedAmount: number;
119
+ reason: string;
120
+ heldAt: Date;
121
+ holdUntil?: Date;
122
+ releasedAt?: Date;
123
+ cancelledAt?: Date;
124
+ releases: Array<{
125
+ amount: number;
126
+ recipientId: string;
127
+ recipientType: string;
128
+ releasedAt: Date;
129
+ releasedBy?: string;
130
+ reason?: string;
131
+ metadata?: Record<string, unknown>;
132
+ }>;
133
+ metadata?: Record<string, unknown>;
134
+ };
135
+ /**
136
+ * Vendor-stable id from the upstream feed (FITID, NtryRef, qbo Id,
137
+ * Plaid transaction_id). Used to enforce idempotent re-import via the
138
+ * `(orgId, bankAccountId, externalId)` partial unique index.
139
+ *
140
+ * Distinct from `idempotencyKey` (host-chosen, request-scoped). See
141
+ * `@classytic/primitives/bank-transaction` and PACKAGE_RULES §8.
142
+ */
143
+ externalId?: string;
144
+ /** When the bank booked the entry. */
145
+ postedDate?: Date;
146
+ /** When funds clear / become available. */
147
+ valueDate?: Date;
148
+ /** Free-text bank description. */
149
+ description?: string;
150
+ counterparty?: TransactionCounterparty;
151
+ /** Check number, payment reference, end-to-end ID. */
152
+ reference?: string;
153
+ /** Running balance on the account after this entry, if the format provides it. */
154
+ balanceAfter?: number;
155
+ /** Bank's own category (rare in OFX, common in Plaid / Mint exports). */
156
+ vendorCategory?: string;
157
+ /**
158
+ * Polymorphic external ref to the bank account this row belongs to.
159
+ * String per PACKAGE_RULES §7 — accepts ObjectId hex, UUID, external
160
+ * IDs (Plaid `account_id`, QBO Account Id). Distinct from
161
+ * `customerId`; the `bankAccount` resource lives on the host side.
162
+ */
163
+ bankAccountId?: string;
164
+ /**
165
+ * Provenance — which feed produced this row. One of the values from
166
+ * `BANK_FEED_SOURCE` (`'ofx'`, `'plaid'`, `'qbo'`, …). Drives admin UI
167
+ * filtering and duplicate-detection rules.
168
+ */
169
+ source?: string;
170
+ /**
171
+ * Bidirectional link to the journal entry that posted this row to the
172
+ * GL. Stamped by `journalize()` after the host's LedgerBridge confirms
173
+ * the JE was created. Polymorphic — `type` names the foreign model
174
+ * (`'JournalEntry'`, `'QboJournalEntry'`, …).
175
+ */
176
+ journalEntryRef?: JournalEntryRef;
177
+ /**
178
+ * Operator-supplied mapping from bank line → GL accounts. Stored at
179
+ * match time so re-running the journalize step is deterministic.
180
+ */
181
+ matching?: {
182
+ debitAccount?: string;
183
+ creditAccount?: string;
184
+ notes?: string;
185
+ matchedAt?: Date;
186
+ matchedBy?: string;
187
+ };
188
+ sourceId?: string;
189
+ sourceModel?: string;
190
+ relatedTransactionId?: mongoose.Types.ObjectId;
191
+ refundedAmount?: number;
192
+ refundedAt?: Date;
193
+ failureReason?: string;
194
+ failedAt?: Date;
195
+ verifiedAt?: Date;
196
+ verifiedBy?: string;
197
+ webhook?: {
198
+ eventId: string;
199
+ eventType: string;
200
+ receivedAt: Date;
201
+ processedAt: Date;
202
+ data: Record<string, unknown>;
203
+ };
204
+ idempotencyKey?: string;
205
+ metadata?: Record<string, unknown>;
206
+ deletedAt?: Date | null;
207
+ createdAt: Date;
208
+ updatedAt: Date;
209
+ }
210
+ //#endregion
211
+ //#region src/models/subscription.schema.d.ts
212
+ interface SubscriptionDocument {
213
+ _id: mongoose.Types.ObjectId;
214
+ publicId: string;
215
+ organizationId?: string;
216
+ customerId?: string | null;
217
+ planKey: string;
218
+ amount: number;
219
+ currency?: string;
220
+ status: string;
221
+ isActive: boolean;
222
+ transactionId?: mongoose.Types.ObjectId | null;
223
+ paymentIntentId?: string | null;
224
+ startDate?: Date;
225
+ endDate?: Date;
226
+ activatedAt?: Date;
227
+ pausedAt?: Date;
228
+ pauseReason?: string;
229
+ canceledAt?: Date;
230
+ cancelAt?: Date;
231
+ cancellationReason?: string;
232
+ /**
233
+ * Optional embedded approval chain — P7. Hosts that gate high-value
234
+ * subscription transitions on a maker-checker review attach a chain via
235
+ * `createChain()` from `@classytic/primitives/approval`; the host's
236
+ * approval action checks `isApproved(doc.approvals)` before applying
237
+ * the change. Routine renewals/auto-pause flows leave it undefined.
238
+ *
239
+ * Use cases:
240
+ * - Cancel-with-refund (manager sign-off before issuing credit)
241
+ * - Plan change with credit note (finance verifies proration)
242
+ * - Manual reactivation after dunning failure
243
+ */
244
+ approvals?: ApprovalChain;
245
+ renewalTransactionId?: mongoose.Types.ObjectId;
246
+ renewalCount: number;
247
+ metadata?: Record<string, unknown>;
248
+ deletedAt?: Date | null;
249
+ createdAt: Date;
250
+ updatedAt: Date;
251
+ }
252
+ //#endregion
253
+ //#region src/models/settlement.schema.d.ts
254
+ interface SettlementDocument {
255
+ _id: mongoose.Types.ObjectId;
256
+ publicId: string;
257
+ organizationId: string;
258
+ recipientId: string;
259
+ recipientType: string;
260
+ type: string;
261
+ status: string;
262
+ /**
263
+ * Optional embedded approval chain — P7. Hosts that gate payout release
264
+ * on a maker-checker review attach a chain via `createChain()` from
265
+ * `@classytic/primitives/approval`; the host's approval action checks
266
+ * `isApproved(doc.approvals)` before transitioning the settlement to
267
+ * `processed`/`completed`. Auto-disbursement flows leave it undefined.
268
+ *
269
+ * Use cases:
270
+ * - Vendor payout sign-off (finance verifies before funds release)
271
+ * - High-value mobile-wallet / crypto disbursement review
272
+ * - Manual bank-transfer payouts to recipients
273
+ */
274
+ approvals?: ApprovalChain;
275
+ payoutMethod: string;
276
+ amount: number;
277
+ currency: string;
278
+ sourceTransactionIds: mongoose.Types.ObjectId[];
279
+ sourceSplitIds: string[];
280
+ bankTransferDetails?: Record<string, unknown>;
281
+ mobileWalletDetails?: Record<string, unknown>;
282
+ cryptoDetails?: Record<string, unknown>;
283
+ scheduledAt: Date;
284
+ processedAt?: Date;
285
+ completedAt?: Date;
286
+ failedAt?: Date;
287
+ cancelledAt?: Date;
288
+ failureReason?: string;
289
+ failureCode?: string;
290
+ retryCount: number;
291
+ notes?: string;
292
+ metadata?: Record<string, unknown>;
293
+ deletedAt?: Date | null;
294
+ createdAt: Date;
295
+ updatedAt: Date;
296
+ }
297
+ //#endregion
298
+ //#region src/models/create-models.d.ts
299
+ interface RevenueModels {
300
+ Transaction: Model<TransactionDocument>;
301
+ Subscription?: Model<SubscriptionDocument>;
302
+ Settlement?: Model<SettlementDocument>;
303
+ }
304
+ interface RevenueSchemaOptions {
305
+ transaction?: {
306
+ extraFields?: Record<string, unknown>;
307
+ extraIndexes?: Array<{
308
+ fields: Record<string, 1 | -1>;
309
+ options?: Record<string, unknown>;
310
+ }>;
311
+ };
312
+ subscription?: {
313
+ extraFields?: Record<string, unknown>;
314
+ extraIndexes?: Array<{
315
+ fields: Record<string, 1 | -1>;
316
+ options?: Record<string, unknown>;
317
+ }>;
318
+ };
319
+ settlement?: {
320
+ extraFields?: Record<string, unknown>;
321
+ extraIndexes?: Array<{
322
+ fields: Record<string, 1 | -1>;
323
+ options?: Record<string, unknown>;
324
+ }>;
325
+ };
326
+ }
327
+ //#endregion
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
+ */
347
+ events: EventTransport;
348
+ /**
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
353
+ * failure. When absent, events fire through `events.publish` only.
354
+ */
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 {
447
+ providers: ProviderRegistry;
448
+ /**
449
+ * Bank-feed provider registry (3.0). Optional — when omitted, the
450
+ * `drainSync` and `parseAndImport` verbs throw on use. The host typically
451
+ * wires Plaid / fin-io / a custom CSV provider here.
452
+ */
453
+ bankFeedProviders?: BankFeedProviderRegistry | undefined;
454
+ bridges: RevenueBridges;
455
+ commission?: CommissionConfig;
456
+ defaultCurrency: string;
457
+ }
458
+ declare class TransactionRepository extends RevenueRepositoryBase<TransactionDocument, TransactionRepoDeps> {
459
+ constructor(model: Model<TransactionDocument>, plugins?: PluginType[]);
460
+ /**
461
+ * Save an event to the host-owned outbox, session-bound when available.
462
+ *
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.
469
+ *
470
+ * Logging happens before the re-throw so the failure surfaces in
471
+ * observability without losing the original stack trace.
472
+ */
473
+ protected saveToOutbox(event: DomainEvent, session?: unknown): Promise<void>;
474
+ /**
475
+ * Publish an event to the in-process `EventTransport` after commit.
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.
479
+ */
480
+ protected publishToTransport(event: DomainEvent): Promise<void>;
481
+ /** Creates transaction + calls provider. Returns the created transaction doc. */
482
+ createPaymentIntent(params: {
483
+ data?: Record<string, unknown>;
484
+ planKey?: string;
485
+ monetizationType?: string;
486
+ amount: number;
487
+ currency?: string;
488
+ gateway: string;
489
+ paymentData?: Record<string, unknown>;
490
+ metadata?: Record<string, unknown>;
491
+ idempotencyKey?: string;
492
+ }, ctx?: RevenueContext): Promise<TransactionDocument>;
493
+ /** Verifies payment via provider, updates status. Returns the updated doc. */
494
+ verify(paymentIntentId: string, options?: {
495
+ verifiedBy?: string;
496
+ }, ctx?: RevenueContext): Promise<TransactionDocument>;
497
+ /**
498
+ * Creates refund transaction, updates original. Returns the refund transaction doc.
499
+ *
500
+ * The provider call happens OUTSIDE the transaction — it's a non-idempotent external
501
+ * side effect we can't roll back. The two Mongo writes (create refund + update original)
502
+ * run inside `withTransaction` so they commit atomically or both abort. Bridges and
503
+ * event emission run AFTER commit because they're independent side effects; rolling
504
+ * them back would not undo external state anyway.
505
+ *
506
+ * Powered by mongokit 3.6's module-level `withTransaction` helper. Automatically
507
+ * retries on `TransientTransactionError` / `UnknownTransactionCommitResult`.
508
+ */
509
+ refund(transactionId: string, amount?: number | null, options?: {
510
+ reason?: string;
511
+ }, ctx?: RevenueContext): Promise<TransactionDocument>;
512
+ /** Handles provider webhook. Returns the updated transaction doc (or null if not found). */
513
+ handleWebhook(providerName: string, payload: unknown, headers?: Record<string, string>, ctx?: RevenueContext): Promise<TransactionDocument | null>;
514
+ /** Places hold on verified transaction. Returns the updated doc. */
515
+ hold(transactionId: string, options?: {
516
+ amount?: number;
517
+ reason?: string;
518
+ holdUntil?: Date;
519
+ metadata?: Record<string, unknown>;
520
+ }, ctx?: RevenueContext): Promise<TransactionDocument>;
521
+ /**
522
+ * Releases held funds. Returns the updated transaction doc.
523
+ *
524
+ * The hold update and the escrow_release transaction create happen inside
525
+ * `withTransaction` — a mid-flow crash can't leave the hold marked released
526
+ * without the corresponding outflow record (or vice versa).
527
+ */
528
+ release(transactionId: string, options: {
529
+ amount?: number;
530
+ recipientId: string;
531
+ recipientType: string;
532
+ reason?: string;
533
+ releasedBy?: string;
534
+ createTransaction?: boolean;
535
+ metadata?: Record<string, unknown>;
536
+ }, ctx?: RevenueContext): Promise<TransactionDocument>;
537
+ /**
538
+ * Splits payment among recipients. Returns the updated transaction doc.
539
+ *
540
+ * N + 2 writes (one create per recipient, one update on the parent, one
541
+ * platform_revenue create) all commit atomically. Partial splits are the
542
+ * worst class of bug in a payments system — this is exactly what
543
+ * `withTransaction` is for.
544
+ */
545
+ split(transactionId: string, rules: Array<{
546
+ type: string;
547
+ recipientId: string;
548
+ recipientType: string;
549
+ rate: number;
550
+ }>, ctx?: RevenueContext): Promise<TransactionDocument>;
551
+ /**
552
+ * Idempotent bulk import of bank-feed rows.
553
+ *
554
+ * Each row is upserted by `(orgId, bankAccountId, externalId)` — the
555
+ * partial unique index declared in `create-models.ts`. Re-running the
556
+ * same Plaid sync, OFX upload, or QBO CDC drain produces zero new
557
+ * inserts on the second call (modified counts may rise as
558
+ * descriptions/categories evolve upstream).
559
+ *
560
+ * Signed bank `amount` is normalized into the (`amount` >= 0, `flow`)
561
+ * shape revenue uses internally so downstream queries (`flow:
562
+ * 'inflow'`) work uniformly across kinds.
563
+ *
564
+ * Emits one `revenue:transaction.imported` event per **inserted** row
565
+ * (not per row in `rows` — re-imports do not re-fire). Hosts wanting
566
+ * batch-level signal subscribe to the per-doc events and aggregate.
567
+ *
568
+ * Per-row failures (validation, hash collisions on a non-unique
569
+ * `externalId`) collect into `errors[]` instead of aborting the whole
570
+ * batch — the typical Plaid drain pulls thousands of rows; one bad
571
+ * row should not block the rest.
572
+ *
573
+ * @param rows Canonical bank transactions, structurally compatible
574
+ * with `@classytic/fin-io` parsers' output.
575
+ * @param opts `bankAccountId` (required, polymorphic ID) and
576
+ * `source` (provenance — `'plaid'`, `'ofx'`, …).
577
+ */
578
+ import(rows: BankTransaction[], opts: {
579
+ bankAccountId: string;
580
+ source: string;
581
+ method?: string;
582
+ }, ctx?: RevenueContext): Promise<BankImportReport>;
583
+ /**
584
+ * Drain a bank-feed provider into the collection.
585
+ *
586
+ * Pulls pages from `provider.fetchTransactions()` (Plaid cursor, QBO
587
+ * CDC) and feeds each batch through `import()`. Yields the running
588
+ * report so a host cron can stream-progress-report to logs / metrics.
589
+ *
590
+ * Stops when the provider returns no new rows AND no removals AND no
591
+ * `nextCursor`. Caller is responsible for persisting the final cursor
592
+ * in their own checkpoint table — `result.nextCursor` is returned so
593
+ * the host can write it after a successful drain.
594
+ *
595
+ * Plaid `removed[]` rows (and any provider that retracts entries) are
596
+ * routed through `removeByFeed` so the host's LedgerBridge can void
597
+ * any JE that was already posted.
598
+ */
599
+ drainSync(providerName: string, params: FetchTransactionsParams & {
600
+ bankAccountId: string;
601
+ }, ctx?: RevenueContext): Promise<{
602
+ totalImported: number;
603
+ totalUpdated: number;
604
+ totalRemoved: number;
605
+ nextCursor?: string;
606
+ errors: BankImportRowError[];
607
+ }>;
608
+ /**
609
+ * Parse an upload (OFX / CAMT.053 / MT940 / CSV) via a registered
610
+ * bank-feed provider, then `import()` the result.
611
+ *
612
+ * Convenience over manually calling `provider.parseUpload()` and
613
+ * threading the canonical rows into `import()` — the file-upload
614
+ * route handler is one line.
615
+ */
616
+ parseAndImport(providerName: string, upload: {
617
+ buffer: Buffer | string | Uint8Array;
618
+ format?: string;
619
+ bankAccountId: string;
620
+ }, ctx?: RevenueContext): Promise<BankImportReport>;
621
+ /**
622
+ * Hand-keyed entry — treasurer logs a cash deposit, owner injects
623
+ * capital, refund correction. Created in `pending` (manual SM); host
624
+ * proceeds with `match()` → `journalize()` to post it to the ledger.
625
+ *
626
+ * `kind: 'manual'` is enforced — calls passing other kinds throw.
627
+ */
628
+ createManual(data: {
629
+ amount: number;
630
+ currency: string;
631
+ flow: 'inflow' | 'outflow';
632
+ type: string;
633
+ description?: string;
634
+ counterparty?: TransactionDocument['counterparty'];
635
+ reference?: string;
636
+ postedDate?: Date;
637
+ valueDate?: Date;
638
+ bankAccountId?: string;
639
+ sourceId?: string;
640
+ sourceModel?: string;
641
+ metadata?: Record<string, unknown>;
642
+ }, ctx?: RevenueContext): Promise<TransactionDocument>;
643
+ /**
644
+ * Match a bank-feed / manual transaction to GL accounts, optionally
645
+ * cross-linking to an upstream payment-flow transaction.
646
+ *
647
+ * Atomic state CAS via `claim()` — the `where: { kind: { $in: [...] } }`
648
+ * predicate prevents a payment-flow row from being matched through this
649
+ * verb. Multi-source `from` (`['imported', 'matched']`) supports
650
+ * re-match after `unmatch()` (`matched → imported → matched`) without
651
+ * losing the prior mapping if the host wants to overwrite it.
652
+ *
653
+ * After a successful claim, `LedgerBridge.onTransactionMatched` runs
654
+ * — the canonical implementation creates a journal entry and chains
655
+ * `journalize()` to record the JE ref. The bridge call is OUTSIDE the
656
+ * claim's CAS window because JE posting is a side effect that may
657
+ * take seconds (cross-process call to ledger).
658
+ */
659
+ match(id: string, data: {
660
+ mapping: {
661
+ debitAccount?: string;
662
+ creditAccount?: string;
663
+ notes?: string;
664
+ };
665
+ relatedTransactionId?: string;
666
+ matchedBy?: string;
667
+ }, ctx?: RevenueContext): Promise<TransactionDocument>;
668
+ /**
669
+ * Revert a matched transaction back to `imported`. Clears the
670
+ * `matching` block and `relatedTransactionId`. Notifies the
671
+ * LedgerBridge (which typically voids the journal entry) AFTER the
672
+ * state CAS lands.
673
+ *
674
+ * Only legal for `kind: 'bank_feed'` — manual entries don't allow
675
+ * un-match (the manual SM has no `matched → pending` edge).
676
+ */
677
+ unmatch(id: string, options?: {
678
+ unmatchedBy?: string;
679
+ }, ctx?: RevenueContext): Promise<TransactionDocument>;
680
+ /**
681
+ * Stamp the journal entry reference and transition `matched →
682
+ * journalized`. Typical caller is the `LedgerBridge.onTransactionMatched`
683
+ * implementation — after creating a JE, it calls this verb so the row
684
+ * carries the back-reference.
685
+ */
686
+ journalize(id: string, data: {
687
+ journalEntryRef: {
688
+ type: string;
689
+ id: string;
690
+ };
691
+ journalizedBy?: string;
692
+ }, ctx?: RevenueContext): Promise<TransactionDocument>;
693
+ /**
694
+ * Operator skip — marks an imported / matched / pending row as
695
+ * rejected (terminal). Use cases: duplicate of an already-imported
696
+ * row, non-cash entry the host doesn't want in the ledger, manual
697
+ * correction overrides.
698
+ *
699
+ * `relatedTransactionId` is preserved; reversal is the host's call.
700
+ */
701
+ reject(id: string, data: {
702
+ reason: string;
703
+ rejectedBy?: string;
704
+ }, ctx?: RevenueContext): Promise<TransactionDocument>;
705
+ /**
706
+ * Soft-delete bank-feed rows that the upstream feed has retracted
707
+ * (Plaid `removed[]`, OFX correction).
708
+ *
709
+ * Each row is matched by `(orgId, bankAccountId, externalId)`; rows
710
+ * already journalized are NOT silently kept — they're surfaced in
711
+ * `retainedJournalized` so the caller can surface them in the UI
712
+ * ("the feed retracted these N rows but they're posted; reverse
713
+ * manually"). The host's `LedgerBridge` should post a reversing JE
714
+ * for those before any subsequent `delete()` can succeed.
715
+ *
716
+ * @returns `removed` (count soft-deleted), `retainedJournalized`
717
+ * (rows kept because they're already in the GL).
718
+ */
719
+ removeByFeed(externalIds: string[], opts: {
720
+ bankAccountId: string;
721
+ source: string;
722
+ }, ctx?: RevenueContext): Promise<{
723
+ removed: number;
724
+ retainedJournalized: TransactionDocument[];
725
+ }>;
726
+ /**
727
+ * Find candidate matches for cross-referencing a payment-flow row to
728
+ * its bank deposit (or vice-versa).
729
+ *
730
+ * Heuristic:
731
+ * - same currency by default; cross-currency requires `fxRate` on
732
+ * the candidate row (multi-currency reconciliation).
733
+ * - amount within `amountTolerancePct` (default 1%) — accounts for
734
+ * gateway fees / FX rounding.
735
+ * - posted/created within `toleranceDays` of the target date
736
+ * (default 3 days — covers ACH delays, weekend settlement).
737
+ * - terminal verified states only (`verified` / `completed` for
738
+ * payment_flow, `imported` / `matched` for bank_feed).
739
+ *
740
+ * Returned candidates are unsorted; callers rank by their own
741
+ * confidence model (counterparty fuzzy match, currency identity,
742
+ * exact-amount preference, …).
743
+ */
744
+ findMatchCandidates(filter: {
745
+ amount: number;
746
+ currency?: string;
747
+ postedDate: Date;
748
+ toleranceDays?: number;
749
+ amountTolerancePct?: number;
750
+ counterpartyName?: string;
751
+ kind?: TransactionKindValue;
752
+ }, ctx?: RevenueContext): Promise<TransactionDocument[]>;
753
+ /**
754
+ * Running balance for a bank account as of `asOf` (defaults to now).
755
+ *
756
+ * Uses mongokit's tenant-scoped read via `findAll` — inflows minus
757
+ * outflows over `kind: 'bank_feed'`, terminal states only. For audit
758
+ * pages where exact-to-the-cent reconciliation is required, prefer
759
+ * the most recent row's `balanceAfter` (banks ship that field on
760
+ * every entry).
761
+ */
762
+ getRunningBalance(bankAccountId: string, asOf?: Date, ctx?: RevenueContext): Promise<{
763
+ balance: number;
764
+ currency: string | null;
765
+ rowCount: number;
766
+ asOf: Date;
767
+ }>;
768
+ }
769
+ //#endregion
770
+ //#region src/repositories/subscription.repository.d.ts
771
+ /**
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}.
796
+ *
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.
804
+ *
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
+ * ```
815
+ */
816
+ declare class SubscriptionRepository extends RevenueRepositoryBase<SubscriptionDocument, SubscriptionRepoDeps> {
817
+ constructor(model: Model<SubscriptionDocument>, plugins?: PluginType[]);
818
+ activate(subscriptionId: string, options?: {
819
+ timestamp?: Date;
820
+ }, ctx?: RevenueContext): Promise<SubscriptionDocument>;
821
+ cancel(subscriptionId: string, options?: {
822
+ immediate?: boolean;
823
+ reason?: string;
824
+ }, ctx?: RevenueContext): Promise<SubscriptionDocument>;
825
+ pause(subscriptionId: string, options?: {
826
+ reason?: string;
827
+ }, ctx?: RevenueContext): Promise<SubscriptionDocument>;
828
+ resume(subscriptionId: string, options?: {
829
+ extendPeriod?: boolean;
830
+ }, ctx?: RevenueContext): Promise<SubscriptionDocument>;
831
+ }
832
+ //#endregion
833
+ //#region src/repositories/settlement.repository.d.ts
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 {
840
+ bridges: RevenueBridges;
841
+ }
842
+ interface SettlementProcessingError {
843
+ settlementId: SettlementDocument['_id'];
844
+ error: unknown;
845
+ }
846
+ /**
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`.
854
+ *
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+.
859
+ *
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.
863
+ */
864
+ declare class SettlementRepository extends RevenueRepositoryBase<SettlementDocument, SettlementRepoDeps> {
865
+ constructor(model: Model<SettlementDocument>, plugins?: PluginType[]);
866
+ schedule(params: {
867
+ organizationId: string;
868
+ recipientId: string;
869
+ recipientType: string;
870
+ type: string;
871
+ amount: number;
872
+ currency: string;
873
+ payoutMethod: string;
874
+ sourceTransactionIds?: string[];
875
+ sourceSplitIds?: string[];
876
+ scheduledAt?: Date;
877
+ bankTransferDetails?: Record<string, unknown>;
878
+ mobileWalletDetails?: Record<string, unknown>;
879
+ cryptoDetails?: Record<string, unknown>;
880
+ notes?: string;
881
+ metadata?: Record<string, unknown>;
882
+ }, ctx?: RevenueContext): Promise<SettlementDocument>;
883
+ processPending(options?: {
884
+ limit?: number;
885
+ organizationId?: string;
886
+ payoutMethod?: string;
887
+ dryRun?: boolean;
888
+ }, ctx?: RevenueContext): Promise<{
889
+ processed: number;
890
+ succeeded: number;
891
+ failed: number;
892
+ settlements: SettlementDocument[];
893
+ errors: SettlementProcessingError[];
894
+ }>;
895
+ complete(settlementId: string, details?: {
896
+ transferReference?: string;
897
+ transferredAt?: Date;
898
+ transactionHash?: string;
899
+ notes?: string;
900
+ metadata?: Record<string, unknown>;
901
+ }, ctx?: RevenueContext): Promise<SettlementDocument>;
902
+ fail(settlementId: string, reason: string, options?: {
903
+ code?: string;
904
+ retry?: boolean;
905
+ }, ctx?: RevenueContext): Promise<SettlementDocument>;
906
+ }
907
+ //#endregion
908
+ //#region src/engine/engine-types.d.ts
909
+ interface CommissionConfig {
910
+ defaultRate: number;
911
+ gatewayFeeRate?: number | undefined;
912
+ categoryRates?: Record<string, number> | undefined;
913
+ gatewayRates?: Record<string, number> | undefined;
914
+ }
915
+ interface RetryConfig {
916
+ maxAttempts?: number | undefined;
917
+ baseDelay?: number | undefined;
918
+ }
919
+ /**
920
+ * Per-index opt-in for the bank-feed lifecycle. Each flag controls one
921
+ * MongoDB index on the `Transaction` collection. Defaults are tuned for
922
+ * "moderate use" — required indexes are on, dashboard indexes are on,
923
+ * heavy reconciliation indexes are off.
924
+ *
925
+ * Toggle these to match real usage. Index builds are non-trivial on a
926
+ * collection with millions of rows; turning unused ones off saves disk +
927
+ * write amplification.
928
+ */
929
+ interface BankFeedIndexConfig {
930
+ /**
931
+ * `(orgId, bankAccountId, externalId)` partial unique index. Required
932
+ * for `import()` to enforce idempotent re-import — turning this off
933
+ * means a Plaid drain that retries can produce duplicate rows.
934
+ *
935
+ * Default: `true`. Disable only if you don't use `import()` /
936
+ * `drainSync()` / `parseAndImport()`.
937
+ */
938
+ idempotentImport?: boolean | undefined;
939
+ /**
940
+ * `(bankAccountId, postedDate -1)` partial — drives the treasurer
941
+ * dashboard ("show me last 30 days of transactions for account X").
942
+ * Cheap; on by default.
943
+ */
944
+ byAccount?: boolean | undefined;
945
+ /**
946
+ * `(kind, amount, postedDate)` and `(kind, amount, createdAt)` —
947
+ * back the cross-reference query in `findMatchCandidates`. Two
948
+ * compound indexes; turn on when you actively reconcile bank
949
+ * deposits to gateway charges.
950
+ *
951
+ * Default: `false`. Enable when running a recon dashboard.
952
+ */
953
+ matchCandidates?: boolean | undefined;
954
+ }
955
+ /**
956
+ * Bank-feed module configuration. Pass `true` for defaults, `false` to
957
+ * disable the module (skips the bulkWrite plugin + every bank-feed
958
+ * index), or an object to fine-tune indexes.
959
+ */
960
+ interface BankFeedModuleConfig {
961
+ enabled?: boolean | undefined;
962
+ indexes?: BankFeedIndexConfig | undefined;
963
+ }
964
+ interface RevenueConfig {
965
+ connection: Connection;
966
+ defaultCurrency: string;
967
+ /**
968
+ * Event transport — structurally compatible with `@classytic/arc`'s
969
+ * `EventTransport`. Drop in any arc transport (Memory/Redis/Kafka) and it
970
+ * works without an adapter. When omitted, the engine uses
971
+ * `InProcessRevenueBus` (a ~50-line match of arc's `MemoryEventTransport`).
972
+ */
973
+ eventTransport?: EventTransport | undefined;
974
+ /**
975
+ * Host-owned transactional outbox store (PACKAGE_RULES §5.5 + P8).
976
+ * Structurally identical to `@classytic/arc`'s `OutboxStore` by design —
977
+ * primitives is the source of truth for the contract and arc mirrors it.
978
+ *
979
+ * **Host responsibility.** Revenue does NOT ship a durable store. Arc 2.10
980
+ * only ships `MemoryOutboxStore` (dev) + the `OutboxStore` interface — the
981
+ * host wires durability. The canonical production wiring uses arc's
982
+ * `repositoryAsOutboxStore` adapter over a mongokit `Repository`:
983
+ *
984
+ * ```ts
985
+ * import mongoose, { Schema } from 'mongoose';
986
+ * import {
987
+ * Repository,
988
+ * methodRegistryPlugin,
989
+ * batchOperationsPlugin,
990
+ * } from '@classytic/mongokit';
991
+ * import { EventOutbox, repositoryAsOutboxStore } from '@classytic/arc/events';
992
+ * import { createRevenue } from '@classytic/revenue';
993
+ *
994
+ * // Arc owns the on-disk doc shape — strict:false forwards every field
995
+ * // (see arc's events.mdx "Why strict: false").
996
+ * const OutboxModel = mongoose.model(
997
+ * 'ArcOutbox',
998
+ * new Schema({}, { strict: false, timestamps: false, _id: false }),
999
+ * 'event_outbox',
1000
+ * );
1001
+ *
1002
+ * // Required plugins: the adapter calls `create` / `findAll` /
1003
+ * // `findOneAndUpdate` (base Repository) + `deleteMany` (batchOperations).
1004
+ * const outboxRepo = new Repository(OutboxModel, [
1005
+ * methodRegistryPlugin(),
1006
+ * batchOperationsPlugin(),
1007
+ * ]);
1008
+ * const outbox = repositoryAsOutboxStore(outboxRepo);
1009
+ *
1010
+ * const engine = await createRevenue({
1011
+ * connection: mongoose.connection,
1012
+ * defaultCurrency: 'USD',
1013
+ * outbox, // revenue's dispatch saves here
1014
+ * eventTransport: app.events, // arc transport for in-process subscribers
1015
+ * // ...
1016
+ * });
1017
+ *
1018
+ * // Relay + DLQ live in the host, not the package:
1019
+ * const relay = new EventOutbox({ store: outbox, transport: app.events });
1020
+ * setInterval(() => relay.relay(), 1_000);
1021
+ * ```
1022
+ *
1023
+ * **Session-bound atomicity.** Revenue's transactional verbs (`refund`,
1024
+ * `release`, `split`) open a mongokit `withTransaction` and pass the
1025
+ * mongoose `ClientSession` into `outbox.save(event, { session })`, so the
1026
+ * outbox row commits atomically with the business writes. Non-transactional
1027
+ * verbs (`createPaymentIntent`, `verify`, `handleWebhook`, `hold`) forward
1028
+ * `ctx.session` when the host is coordinating its own transaction — pass
1029
+ * `{ session }` in `RevenueContext` to participate.
1030
+ *
1031
+ * **Non-arc hosts.** Any `OutboxStore` works — implement the three-method
1032
+ * floor (`save` / `getPending` / `acknowledge`) over Postgres / Redis /
1033
+ * Kafka / SQS. When omitted, events flow to `eventTransport` only
1034
+ * (durability becomes transport-level, not at-least-once).
1035
+ */
1036
+ outbox?: OutboxStore | undefined;
1037
+ modules?: {
1038
+ subscription?: boolean | undefined;
1039
+ escrow?: boolean | undefined;
1040
+ settlement?: boolean | undefined;
1041
+ commission?: CommissionConfig | boolean | undefined;
1042
+ /**
1043
+ * Bank-feed / accounting-feed module (3.0). Default: enabled (the
1044
+ * schema fields are always present so the discriminator works
1045
+ * uniformly). Disabling this suppresses the auto-wiring of
1046
+ * `bankFeedProviders`, the bulk-write plugin, AND every bank-feed
1047
+ * index — set to `false` for hosts that purely use the payment-
1048
+ * flow lifecycle and want to omit those costs.
1049
+ *
1050
+ * Pass an object to fine-tune which bank-feed indexes are built.
1051
+ * Examples:
1052
+ * `{ bankFeed: { indexes: { matchCandidates: true } } }`
1053
+ * — turn on cross-ref indexes for an active recon dashboard.
1054
+ * `{ bankFeed: { indexes: { idempotentImport: false, byAccount: false } } }`
1055
+ * — host doesn't use `import()` and doesn't need treasurer dashboards.
1056
+ */
1057
+ bankFeed?: boolean | BankFeedModuleConfig | undefined;
1058
+ } | undefined;
1059
+ providers?: Record<string, PaymentProvider> | undefined;
1060
+ /**
1061
+ * Bank-feed providers — Plaid, fin-io OFX/CAMT/MT940/CSV, QBO/Xero CDC
1062
+ * adapters. Wired into `engine.bankFeedProviders` and consumed by
1063
+ * `transactionRepository.drainSync()` and `parseAndImport()`.
1064
+ *
1065
+ * @example
1066
+ * ```ts
1067
+ * import { PlaidBankFeedProvider } from '@classytic/revenue-plaid';
1068
+ * import { FinIoBankFeedProvider } from '@classytic/revenue-fin-io';
1069
+ *
1070
+ * const engine = await createRevenue({
1071
+ * ...,
1072
+ * bankFeedProviders: {
1073
+ * plaid: new PlaidBankFeedProvider({ clientId, secret }),
1074
+ * ofx: new FinIoBankFeedProvider(),
1075
+ * },
1076
+ * });
1077
+ * ```
1078
+ */
1079
+ bankFeedProviders?: Record<string, BankFeedProvider> | undefined;
1080
+ bridges?: RevenueBridges | undefined;
1081
+ repositoryPlugins?: RepositoryPluginBundle | undefined;
1082
+ schemaOptions?: RevenueSchemaOptions | undefined;
1083
+ /**
1084
+ * Tenant scope configuration. Delegates to `@classytic/primitives`'
1085
+ * `TenantConfig`. Field names match mongokit's `MultiTenantOptions` so
1086
+ * the resolved config forwards directly into `multiTenantPlugin(...)`.
1087
+ *
1088
+ * - `undefined` / `true` → default field strategy, ObjectId storage.
1089
+ * - `false` → single-tenant (no plugin, field still present, not required).
1090
+ * - `{ fieldType: 'string' }` → string orgIds (UUID/slug hosts).
1091
+ * - `{ strategy: 'custom', resolve: ... }` → composite / derived scope.
1092
+ *
1093
+ * See PACKAGE_RULES.md §9.
1094
+ */
1095
+ scope?: TenantConfig | boolean | undefined;
1096
+ commission?: CommissionConfig | undefined;
1097
+ retry?: RetryConfig | undefined;
1098
+ circuitBreaker?: boolean | undefined;
1099
+ /**
1100
+ * Set `false` to disable Mongoose auto-index on boot. Indexes are then
1101
+ * managed explicitly via `engine.syncIndexes()` or a deploy-time script.
1102
+ */
1103
+ autoIndex?: boolean | Partial<Record<'Transaction' | 'Subscription' | 'Settlement', boolean>> | undefined;
1104
+ /**
1105
+ * Optional prefix prepended to every physical collection this package
1106
+ * creates (see PACKAGE_RULES.md §20.1). Unset → default names
1107
+ * (`revenue_transactions`, `revenue_subscriptions`, `revenue_settlements`).
1108
+ * Model names and `ref:` populate are unaffected.
1109
+ */
1110
+ collectionPrefix?: string | undefined;
1111
+ /**
1112
+ * When true, existing Mongoose models with revenue's names are deleted
1113
+ * from the connection before re-registering. Hot-reload / test fixtures
1114
+ * only. Default `false` — collision throws `RevenueModelCollisionError`.
1115
+ * Hosts that need two revenue engines should use two Mongoose connections
1116
+ * (`mongoose.createConnection(...)`). See PACKAGE_RULES.md §21.
1117
+ */
1118
+ forceRecreate?: boolean | undefined;
1119
+ logger?: {
1120
+ info: (...args: unknown[]) => void;
1121
+ error: (...args: unknown[]) => void;
1122
+ warn: (...args: unknown[]) => void;
1123
+ debug: (...args: unknown[]) => void;
1124
+ } | undefined;
1125
+ }
1126
+ /**
1127
+ * RevenueEngine — no service facade.
1128
+ *
1129
+ * Repositories ARE the domain layer. CRUD is inherited from mongokit.
1130
+ * Domain verbs (verify, refund, hold, activate, etc.) live on repositories.
1131
+ * Arc's BaseController/adapter plugs into repositories directly.
1132
+ *
1133
+ * `events` is structurally compatible with `@classytic/arc`'s
1134
+ * `EventTransport`. Hosts subscribe glob-style:
1135
+ *
1136
+ * await revenue.events.subscribe('revenue:payment.*', handler);
1137
+ *
1138
+ * and the same transport can be wired into the outbox relay for durable
1139
+ * delivery (see mongokit's `outbox-recipe.ts`). See PACKAGE_RULES §13.
1140
+ */
1141
+ interface RevenueEngine {
1142
+ config: Readonly<RevenueConfig>;
1143
+ models: RevenueModels;
1144
+ repositories: RevenueRepositories;
1145
+ providers: ProviderRegistry;
1146
+ /**
1147
+ * Bank-feed providers registry (3.0). Populated when `bankFeed`
1148
+ * module is enabled and `bankFeedProviders` config is non-empty.
1149
+ * Used by `transactionRepository.drainSync()` and
1150
+ * `parseAndImport()`. Hosts can `register` providers at runtime too
1151
+ * (e.g. after the engine boots, to add a per-tenant Plaid client).
1152
+ */
1153
+ bankFeedProviders: BankFeedProviderRegistry;
1154
+ events: EventTransport;
1155
+ /** Explicitly build all schema-declared indexes. Non-destructive. */
1156
+ syncIndexes(): Promise<void>;
1157
+ destroy(): Promise<void>;
1158
+ }
1159
+ //#endregion
1160
+ export { RevenueConfig as a, SubscriptionRepository as c, RevenueSchemaOptions as d, SettlementDocument as f, RetryConfig as i, TransactionRepository as l, TransactionDocument as m, BankFeedModuleConfig as n, RevenueEngine as o, SubscriptionDocument as p, CommissionConfig as r, SettlementRepository as s, BankFeedIndexConfig as t, RevenueModels as u };