@classytic/revenue 2.1.0 → 2.2.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 +95 -0
- package/README.md +41 -10
- package/dist/{bank-feed-DJtLvz_7.mjs → bank-feed-ClxNob_I.mjs} +1 -1
- package/dist/core/state-machines.mjs +2 -2
- package/dist/{engine-types-txFXOiQS.d.mts → engine-types-ChFPg3kw.d.mts} +230 -80
- package/dist/enums/index.mjs +1 -1
- package/dist/{errors-Dt46UZL_.mjs → errors-Bt5NRVMq.mjs} +19 -2
- package/dist/{escrow.schema-C-b41z_G.mjs → escrow.schema-BcKdzrJ7.mjs} +5 -0
- package/dist/{escrow.schema-9yh4Q-aQ.d.mts → escrow.schema-BdDHuQ8C.d.mts} +80 -20
- package/dist/{event-constants-CTiDNWzc.mjs → event-constants-DM_-A57b.mjs} +7 -0
- package/dist/events/index.d.mts +1 -1
- package/dist/events/index.mjs +2 -2
- package/dist/index.d.mts +38 -7
- package/dist/index.mjs +38 -9
- package/dist/providers/index.mjs +1 -1
- package/dist/repositories/create-repositories.d.mts +1 -1
- package/dist/repositories/create-repositories.mjs +1 -1
- package/dist/{revenue-event-catalog-CgZ57M-f.mjs → revenue-event-catalog-B9aZmNpL.mjs} +90 -2
- package/dist/{revenue-event-catalog-JpJcyK1E.d.mts → revenue-event-catalog-BU_KYN2-.d.mts} +645 -0
- package/dist/{settlement.repository-Ba2U17zY.mjs → settlement.repository-CfvgX3et.mjs} +315 -123
- package/dist/shared/index.mjs +1 -1
- package/dist/validators/index.d.mts +1 -1
- package/dist/validators/index.mjs +1 -1
- package/package.json +7 -7
- /package/dist/{splits-D8XkNWgX.mjs → splits-CNfQj92L.mjs} +0 -0
- /package/dist/{subscription.enums-DoIr56O6.mjs → subscription.enums-95othr0i.mjs} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,101 @@
|
|
|
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.2.0] - 2026-05-26
|
|
7
|
+
|
|
8
|
+
### Added — auth/capture + dispute event coverage
|
|
9
|
+
|
|
10
|
+
Seven new event constants on `REVENUE_EVENTS` aligning with the
|
|
11
|
+
`@classytic/primitives@0.7.1` payment event catalogue:
|
|
12
|
+
`PAYMENT_AUTHORIZED`, `PAYMENT_CAPTURED`, `PAYMENT_AUTH_VOIDED`,
|
|
13
|
+
`PAYMENT_DISPUTED`, `PAYMENT_DISPUTE_WON`, `PAYMENT_DISPUTE_LOST`,
|
|
14
|
+
`PAYMENT_SETTLED`. Catalog payload schemas updated to match.
|
|
15
|
+
|
|
16
|
+
### Added — `RevenueError.httpStatus`
|
|
17
|
+
|
|
18
|
+
`RevenueError` carries an optional `httpStatus` field so Arc / Express
|
|
19
|
+
hosts can map errors to response codes without per-error switch
|
|
20
|
+
statements. Defaults to 500 in the host mapper when unset.
|
|
21
|
+
|
|
22
|
+
### Added — `MethodKindLockedError` (409)
|
|
23
|
+
|
|
24
|
+
New error thrown by `TransactionRepository.backfillMethodKind` when the
|
|
25
|
+
existing doc is not backfill-eligible (methodKind already specific OR
|
|
26
|
+
status no longer `pending`). 409 because the request is well-formed but
|
|
27
|
+
conflicts with current resource state.
|
|
28
|
+
|
|
29
|
+
### Changed — peer bump: `@classytic/primitives` `>=0.7.1`
|
|
30
|
+
|
|
31
|
+
Catalogue now imports `PAYMENT_METHOD_KIND` from
|
|
32
|
+
`@classytic/primitives/payment-method-kind`. Hosts must bump primitives
|
|
33
|
+
to `>=0.7.1`.
|
|
34
|
+
|
|
35
|
+
## [2.1.1] — multi-tenant scope correctness across all repos
|
|
36
|
+
|
|
37
|
+
**Fix.** `SubscriptionRepository` and `SettlementRepository` lifecycle verbs
|
|
38
|
+
were calling internal `getById` / `update` / `getAll` without threading
|
|
39
|
+
`ctx.organizationId` into the mongokit options bag. The moment a host
|
|
40
|
+
enabled `multiTenantPlugin` (the recommended default — see PACKAGE_RULES
|
|
41
|
+
§9), every verb threw `Missing 'organizationId' in context for 'getById'`
|
|
42
|
+
mid-flow and the lifecycle was unusable.
|
|
43
|
+
|
|
44
|
+
Affected verbs (all now threaded correctly):
|
|
45
|
+
|
|
46
|
+
- `SubscriptionRepository.{activate,cancel,pause,resume}` — every internal
|
|
47
|
+
`getById` and `update` now forwards `ctx`.
|
|
48
|
+
- `SettlementRepository.{schedule,processPending,complete,fail}` — every
|
|
49
|
+
internal `getById`, `getAll`, `update` now forwards `ctx`.
|
|
50
|
+
|
|
51
|
+
**Refactor.** Introduces `RevenueRepositoryBase<TDoc, TDeps>` (abstract;
|
|
52
|
+
internal — not exported) consolidating the two cross-cutting concerns
|
|
53
|
+
that were previously hand-rolled in three places:
|
|
54
|
+
|
|
55
|
+
- `protected optsFromCtx(ctx, extra?)` — thin adapter over mongokit's
|
|
56
|
+
canonical `repoOptionsFromCtx` extractor, plus revenue's `_bypassTenant`
|
|
57
|
+
flag for platform-admin cross-org reads. **Adding a new canonical context
|
|
58
|
+
field is now a single edit in `repo-options.ts` upstream, not three.**
|
|
59
|
+
- `protected dispatch(event, ctx)` — outbox-save (session-bound when
|
|
60
|
+
`ctx.session` is present) → transport-publish, with isolated try/catch
|
|
61
|
+
on each step (PACKAGE_RULES P8 / §5.5).
|
|
62
|
+
|
|
63
|
+
`TransactionRepository`, `SubscriptionRepository`, `SettlementRepository`
|
|
64
|
+
all extend the base. `BaseRevenueRepoDeps` (the shared `events` / `outbox`
|
|
65
|
+
/ `logger` trio) is now the canonical superset every per-repo `Deps`
|
|
66
|
+
interface extends.
|
|
67
|
+
|
|
68
|
+
### Added
|
|
69
|
+
|
|
70
|
+
- **`tests/scenarios/subscription-tenancy.scenario.test.ts`** — 6 tests
|
|
71
|
+
proving each lifecycle verb works under `scope: { enabled, required }`,
|
|
72
|
+
cross-tenant access is rejected with `SubscriptionNotFoundError`, and
|
|
73
|
+
`multiTenantPlugin` is wired (canary: omitting ctx throws
|
|
74
|
+
`Missing organizationId`).
|
|
75
|
+
- **`tests/scenarios/settlement-tenancy.scenario.test.ts`** — 5 tests for
|
|
76
|
+
the same matrix on settlements: schedule, processPending, complete, fail,
|
|
77
|
+
cross-tenant rejection.
|
|
78
|
+
|
|
79
|
+
### Internal
|
|
80
|
+
|
|
81
|
+
- `RevenueRepositoryBase` is unexported on purpose — kept private to
|
|
82
|
+
the package. Adding a new repo means subclassing it; consumers stay
|
|
83
|
+
on the existing engine factory surface (`createRevenue(...)`).
|
|
84
|
+
- No public API change. Engine factory, repo method signatures, and
|
|
85
|
+
exported types are all byte-stable.
|
|
86
|
+
|
|
87
|
+
### Migration
|
|
88
|
+
|
|
89
|
+
None — this is a behavioural fix. If you were running 2.1.0 with
|
|
90
|
+
`scope: false` as a workaround for the lifecycle bugs, you can now turn
|
|
91
|
+
scope back on. Recommended config:
|
|
92
|
+
|
|
93
|
+
```ts
|
|
94
|
+
await createRevenue({
|
|
95
|
+
connection: mongoose.connection,
|
|
96
|
+
scope: { enabled: true, fieldType: 'objectId', required: true },
|
|
97
|
+
// ...
|
|
98
|
+
});
|
|
99
|
+
```
|
|
100
|
+
|
|
6
101
|
## [2.0.0] — major rewrite
|
|
7
102
|
|
|
8
103
|
Payment lifecycle engine refactored around unified transactions, an
|
package/README.md
CHANGED
|
@@ -48,30 +48,61 @@ const refundTxn = await revenue.repositories.transaction.refund(
|
|
|
48
48
|
// refundTxn.type → 'refund', refundTxn.flow → 'outflow', refundTxn.amount → 5000
|
|
49
49
|
```
|
|
50
50
|
|
|
51
|
+
## Hosted-checkout `methodKind` backfill
|
|
52
|
+
|
|
53
|
+
Every transaction carries a `methodKind: PaymentMethodKind` (`card`, `bank_transfer`, `wallet`, `cash`, `cheque`, `cryptocurrency`, `manual`, `other`). For hosted-checkout flows where the customer picks their method on the gateway's UI (Stripe Checkout, PayPal redirect, Razorpay Checkout), create the PaymentIntent with `methodKind: 'other'` — then call `transactionRepository.backfillMethodKind(transactionId, kind)` from your verification webhook handler once you know the actual choice. The backfill is an atomic CAS — allowed only when the doc still has `methodKind === 'other'` AND `status === 'pending'`; any other transition throws `MethodKindLockedError` (HTTP 409). For the Stripe case, `@classytic/revenue-stripe` exposes `stripePaymentIntentToKind(intent)` so the host doesn't write the mapping table itself.
|
|
54
|
+
|
|
55
|
+
## Bank-feed `import()` requires `methodKind`
|
|
56
|
+
|
|
57
|
+
`TransactionRepository.import()` (and the higher-level `drainSync` / `parseAndImport`) require an explicit `opts.methodKind` — no silent default. Pass `'bank_transfer'` for Plaid/OFX/CAMT/MT940 drains, `'card'` for a Stripe-balance import, `'wallet'` for PayPal exports, `'cryptocurrency'` for exchange CSVs. This forces every feed integration to be intentional about how its rows show up in downstream analytics and accounting reports.
|
|
58
|
+
|
|
51
59
|
## Architecture
|
|
52
60
|
|
|
53
61
|
```
|
|
54
62
|
createRevenue(config) --> RevenueEngine
|
|
55
63
|
|
|
|
56
|
-
|-- repositories.transaction
|
|
57
|
-
|
|
|
64
|
+
|-- repositories.transaction extends RevenueRepositoryBase
|
|
65
|
+
| CRUD inherited (mongokit Repository)
|
|
58
66
|
| createPaymentIntent, verify, refund, handleWebhook (domain verbs)
|
|
59
67
|
| hold, release, split (escrow verbs)
|
|
68
|
+
| import, match, unmatch, journalize, reject, removeByFeed (bank-feed verbs)
|
|
60
69
|
|
|
|
61
|
-
|-- repositories.subscription
|
|
62
|
-
|
|
|
70
|
+
|-- repositories.subscription extends RevenueRepositoryBase
|
|
71
|
+
| CRUD inherited
|
|
63
72
|
| activate, cancel, pause, resume (domain verbs)
|
|
64
73
|
|
|
|
65
|
-
|-- repositories.settlement
|
|
66
|
-
|
|
|
74
|
+
|-- repositories.settlement extends RevenueRepositoryBase
|
|
75
|
+
| CRUD inherited
|
|
67
76
|
| schedule, processPending, complete, fail (domain verbs)
|
|
68
77
|
|
|
|
69
|
-
|-- providers
|
|
70
|
-
|-- events
|
|
71
|
-
|-- models
|
|
78
|
+
|-- providers ProviderRegistry
|
|
79
|
+
|-- events RevenueEventTransport (Arc-compatible)
|
|
80
|
+
|-- models Mongoose models (for Arc adapter)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
RevenueRepositoryBase (internal)
|
|
84
|
+
|
|
|
85
|
+
|-- extends mongokit Repository<TDoc>
|
|
86
|
+
|-- protected optsFromCtx(ctx, extra?) threads RevenueContext into mongokit
|
|
87
|
+
| options bag (uses repoOptionsFromCtx;
|
|
88
|
+
| forwards organizationId, userId,
|
|
89
|
+
| session, requestId + _bypassTenant)
|
|
90
|
+
|-- protected dispatch(event, ctx) outbox.save (session-bound) →
|
|
91
|
+
| events.publish (PACKAGE_RULES P8)
|
|
92
|
+
\-- protected deps: BaseRevenueRepoDeps events / outbox? / logger?
|
|
72
93
|
```
|
|
73
94
|
|
|
74
|
-
|
|
95
|
+
**Three repos. One scope-threading helper. One dispatch helper.** Every
|
|
96
|
+
domain verb routes its mongokit calls through `optsFromCtx(ctx)` so
|
|
97
|
+
multi-tenant scope, audit attribution, and transaction sessions land
|
|
98
|
+
on every read/write without per-method boilerplate. Every domain event
|
|
99
|
+
goes through `dispatch(event, ctx)` so outbox and transport semantics
|
|
100
|
+
stay consistent across the package.
|
|
101
|
+
|
|
102
|
+
CRUD, pagination, querying, and policy hooks come from
|
|
103
|
+
[`@classytic/mongokit`](https://www.npmjs.com/package/@classytic/mongokit).
|
|
104
|
+
Domain verbs contain real business logic (state machine transitions,
|
|
105
|
+
provider calls, event emission). No service layer. No proxy methods.
|
|
75
106
|
|
|
76
107
|
## RevenueConfig
|
|
77
108
|
|
|
@@ -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-
|
|
3
|
-
import { a as InvalidStateTransitionError } from "../errors-
|
|
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-Bt5NRVMq.mjs";
|
|
4
4
|
import { defineStateMachine } from "@classytic/primitives/state-machine";
|
|
5
5
|
|
|
6
6
|
//#region src/core/state-machines.ts
|
|
@@ -6,7 +6,8 @@ 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 {
|
|
9
|
+
import { PaymentMethodKind } from "@classytic/primitives/payment-method-kind";
|
|
10
|
+
import { DomainEvent, EventTransport } from "@classytic/primitives/events";
|
|
10
11
|
import { OutboxStore } from "@classytic/primitives/outbox";
|
|
11
12
|
import { BankImportReport, BankImportRowError, BankTransaction } from "@classytic/primitives/bank-transaction";
|
|
12
13
|
import { ApprovalChain } from "@classytic/primitives/approval";
|
|
@@ -68,6 +69,7 @@ interface TransactionDocument {
|
|
|
68
69
|
originalAmount?: number;
|
|
69
70
|
originalCurrency?: string;
|
|
70
71
|
method: string;
|
|
72
|
+
methodKind: PaymentMethodKind;
|
|
71
73
|
status: string;
|
|
72
74
|
/**
|
|
73
75
|
* Optional embedded approval chain — P7. Hosts that gate manual /
|
|
@@ -325,17 +327,125 @@ interface RevenueSchemaOptions {
|
|
|
325
327
|
};
|
|
326
328
|
}
|
|
327
329
|
//#endregion
|
|
328
|
-
//#region src/repositories/
|
|
329
|
-
|
|
330
|
+
//#region src/repositories/base.repository.d.ts
|
|
331
|
+
/**
|
|
332
|
+
* Cross-cutting deps that every revenue repository needs.
|
|
333
|
+
*
|
|
334
|
+
* Subclasses extend this with their own bridges, providers, configs:
|
|
335
|
+
*
|
|
336
|
+
* ```ts
|
|
337
|
+
* export interface SettlementRepoDeps extends BaseRevenueRepoDeps {
|
|
338
|
+
* bridges: RevenueBridges;
|
|
339
|
+
* }
|
|
340
|
+
* ```
|
|
341
|
+
*/
|
|
342
|
+
interface BaseRevenueRepoDeps {
|
|
343
|
+
/**
|
|
344
|
+
* Domain-event transport (in-process or arc-compatible). All four
|
|
345
|
+
* `revenue:*` event families (`payment.*`, `subscription.*`,
|
|
346
|
+
* `settlement.*`, `escrow.*`) flow through this single channel; hosts
|
|
347
|
+
* subscribe glob-style.
|
|
348
|
+
*/
|
|
330
349
|
events: EventTransport;
|
|
331
350
|
/**
|
|
332
|
-
* Optional host-owned outbox
|
|
333
|
-
* every
|
|
334
|
-
*
|
|
335
|
-
*
|
|
351
|
+
* Optional host-owned outbox (PACKAGE_RULES §5.5 + P8). When wired,
|
|
352
|
+
* every dispatched event is persisted via `outbox.save(event)` BEFORE
|
|
353
|
+
* `events.publish(event)` so a host relay (arc's EventOutbox, a Postgres
|
|
354
|
+
* LISTEN/NOTIFY pump, Kafka Connect, …) can replay on transport
|
|
336
355
|
* failure. When absent, events fire through `events.publish` only.
|
|
337
356
|
*/
|
|
338
357
|
outbox?: OutboxStore | undefined;
|
|
358
|
+
/**
|
|
359
|
+
* Optional structured logger. Outbox/transport failures are logged
|
|
360
|
+
* here rather than thrown — a downstream subscriber error never
|
|
361
|
+
* cancels the upstream business write.
|
|
362
|
+
*/
|
|
363
|
+
logger?: {
|
|
364
|
+
error(...args: unknown[]): void;
|
|
365
|
+
} | undefined;
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Abstract base for `TransactionRepository`, `SubscriptionRepository`,
|
|
369
|
+
* `SettlementRepository`.
|
|
370
|
+
*
|
|
371
|
+
* Concrete subclasses MUST:
|
|
372
|
+
* - declare a `Deps` interface that extends {@link BaseRevenueRepoDeps}
|
|
373
|
+
* - call `super(model, plugins)` from the constructor
|
|
374
|
+
* - call `inject(deps)` once during engine boot
|
|
375
|
+
*
|
|
376
|
+
* Subclasses SHOULD use {@link optsFromCtx} for every mongokit call
|
|
377
|
+
* that takes an options bag, and {@link dispatch} for every event
|
|
378
|
+
* publish.
|
|
379
|
+
*/
|
|
380
|
+
declare abstract class RevenueRepositoryBase<TDoc, TDeps extends BaseRevenueRepoDeps> extends Repository<TDoc> {
|
|
381
|
+
/**
|
|
382
|
+
* Subclass-specific deps. `!` because the engine wires this once via
|
|
383
|
+
* `inject(deps)` immediately after construction; calling any domain
|
|
384
|
+
* verb before injection is a programming error and the runtime
|
|
385
|
+
* will fail-loud with `Cannot read properties of undefined`.
|
|
386
|
+
*/
|
|
387
|
+
protected deps: TDeps;
|
|
388
|
+
constructor(model: Model<TDoc>, plugins?: PluginType[]);
|
|
389
|
+
/**
|
|
390
|
+
* Wire engine-managed deps. Called exactly once per repository
|
|
391
|
+
* instance during {@link createRevenue} bootstrap. Subclasses with
|
|
392
|
+
* extra steps (caching, prebuilding state machine maps) override and
|
|
393
|
+
* call `super.inject(deps)`.
|
|
394
|
+
*/
|
|
395
|
+
inject(deps: TDeps): void;
|
|
396
|
+
/**
|
|
397
|
+
* Translate {@link RevenueContext} into a mongokit options bag.
|
|
398
|
+
*
|
|
399
|
+
* Forwards every canonical field mongokit's bundled plugins read —
|
|
400
|
+
* `organizationId` (multiTenant), `userId` / `user` (audit),
|
|
401
|
+
* `session` (transactions), `requestId` (observability) — plus
|
|
402
|
+
* the revenue-specific `_bypassTenant` flag for platform-admin
|
|
403
|
+
* cross-org reads.
|
|
404
|
+
*
|
|
405
|
+
* Pass `extra` for caller-specific options like `throwOnNotFound`,
|
|
406
|
+
* `lean`, `populate`, `select` — the spread is `extra`-first so
|
|
407
|
+
* ctx wins on a key collision (intentional: callers shouldn't
|
|
408
|
+
* be smuggling tenant fields through `extra`).
|
|
409
|
+
*
|
|
410
|
+
* @param ctx - The request-scoped revenue context.
|
|
411
|
+
* @param extra - Additional mongokit options merged in.
|
|
412
|
+
*/
|
|
413
|
+
protected optsFromCtx(ctx?: RevenueContext, extra?: Record<string, unknown>): Record<string, unknown>;
|
|
414
|
+
/**
|
|
415
|
+
* Persist an event to the host outbox, then publish to the in-process
|
|
416
|
+
* transport. The two sides have asymmetric failure handling — see
|
|
417
|
+
* PACKAGE_RULES §P8.
|
|
418
|
+
*
|
|
419
|
+
* When `ctx.session` is set, the outbox `save` runs inside the same
|
|
420
|
+
* Mongoose transaction (true P8 session-bound write); when absent, the
|
|
421
|
+
* outbox row lands after commit — still durable via the host's relay,
|
|
422
|
+
* with only a small at-most-once window on process crash.
|
|
423
|
+
*
|
|
424
|
+
* 1. **`outbox.save` failures PROPAGATE.** If we can't durably record
|
|
425
|
+
* the event, the caller's transaction MUST roll back so the
|
|
426
|
+
* business doc and the event row land atomically (or neither
|
|
427
|
+
* lands). Swallowing a save failure breaks the transactional-
|
|
428
|
+
* outbox correctness argument — the parent doc would land while
|
|
429
|
+
* the event vanishes.
|
|
430
|
+
*
|
|
431
|
+
* 2. **`events.publish` failures are SWALLOWED.** The host's outbox
|
|
432
|
+
* relay re-publishes from the durable row on its next poll. Even
|
|
433
|
+
* without an outbox, in-process subscribers shouldn't be able to
|
|
434
|
+
* break the business operation — they're best-effort consumers.
|
|
435
|
+
*
|
|
436
|
+
* @param event - Pre-built domain event (use `createEvent(REVENUE_EVENTS.X, payload, ctx, meta)`).
|
|
437
|
+
* @param ctx - The same context that produced the business write.
|
|
438
|
+
*/
|
|
439
|
+
protected dispatch(event: DomainEvent, ctx?: RevenueContext): Promise<void>;
|
|
440
|
+
}
|
|
441
|
+
//#endregion
|
|
442
|
+
//#region src/repositories/transaction.repository.d.ts
|
|
443
|
+
/**
|
|
444
|
+
* Deps for {@link TransactionRepository}. Extends {@link BaseRevenueRepoDeps}
|
|
445
|
+
* (events / outbox? / logger?) with provider/bridge/config wiring specific
|
|
446
|
+
* to the payment-flow + bank-feed lifecycles.
|
|
447
|
+
*/
|
|
448
|
+
interface TransactionRepoDeps extends BaseRevenueRepoDeps {
|
|
339
449
|
providers: ProviderRegistry;
|
|
340
450
|
/**
|
|
341
451
|
* Bank-feed provider registry (3.0). Optional — when omitted, the
|
|
@@ -346,47 +456,30 @@ interface TransactionRepoDeps {
|
|
|
346
456
|
bridges: RevenueBridges;
|
|
347
457
|
commission?: CommissionConfig;
|
|
348
458
|
defaultCurrency: string;
|
|
349
|
-
logger?: {
|
|
350
|
-
error(...args: unknown[]): void;
|
|
351
|
-
} | undefined;
|
|
352
459
|
}
|
|
353
|
-
declare class TransactionRepository extends
|
|
354
|
-
private deps;
|
|
460
|
+
declare class TransactionRepository extends RevenueRepositoryBase<TransactionDocument, TransactionRepoDeps> {
|
|
355
461
|
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
462
|
/**
|
|
366
463
|
* Save an event to the host-owned outbox, session-bound when available.
|
|
367
464
|
*
|
|
368
|
-
*
|
|
369
|
-
* business write (P8 true session-bound write)
|
|
370
|
-
*
|
|
371
|
-
*
|
|
465
|
+
* Called INSIDE a `withTransaction` body. The outbox row commits
|
|
466
|
+
* atomically with the business write (P8 true session-bound write) —
|
|
467
|
+
* if outbox.save fails, this method re-throws so `withTransaction`
|
|
468
|
+
* rolls the parent write back. Without propagation, the parent doc
|
|
469
|
+
* would commit while the event row vanishes, defeating the whole
|
|
470
|
+
* transactional-outbox correctness argument.
|
|
372
471
|
*
|
|
373
|
-
*
|
|
374
|
-
*
|
|
472
|
+
* Logging happens before the re-throw so the failure surfaces in
|
|
473
|
+
* observability without losing the original stack trace.
|
|
375
474
|
*/
|
|
376
|
-
|
|
475
|
+
protected saveToOutbox(event: DomainEvent, session?: unknown): Promise<void>;
|
|
377
476
|
/**
|
|
378
477
|
* Publish an event to the in-process `EventTransport` after commit.
|
|
379
|
-
* Transport failure is logged — the host relay
|
|
380
|
-
* the outbox, so in-process
|
|
381
|
-
|
|
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.
|
|
478
|
+
* Transport failure is logged and swallowed — the host relay re-delivers
|
|
479
|
+
* from the durable outbox row on the next poll, so in-process
|
|
480
|
+
* subscribers missing an event is recoverable. Best-effort by design.
|
|
388
481
|
*/
|
|
389
|
-
|
|
482
|
+
protected publishToTransport(event: DomainEvent): Promise<void>;
|
|
390
483
|
/** Creates transaction + calls provider. Returns the created transaction doc. */
|
|
391
484
|
createPaymentIntent(params: {
|
|
392
485
|
data?: Record<string, unknown>;
|
|
@@ -395,6 +488,7 @@ declare class TransactionRepository extends Repository<TransactionDocument> {
|
|
|
395
488
|
amount: number;
|
|
396
489
|
currency?: string;
|
|
397
490
|
gateway: string;
|
|
491
|
+
methodKind: PaymentMethodKind;
|
|
398
492
|
paymentData?: Record<string, unknown>;
|
|
399
493
|
metadata?: Record<string, unknown>;
|
|
400
494
|
idempotencyKey?: string;
|
|
@@ -487,6 +581,7 @@ declare class TransactionRepository extends Repository<TransactionDocument> {
|
|
|
487
581
|
import(rows: BankTransaction[], opts: {
|
|
488
582
|
bankAccountId: string;
|
|
489
583
|
source: string;
|
|
584
|
+
methodKind: PaymentMethodKind;
|
|
490
585
|
method?: string;
|
|
491
586
|
}, ctx?: RevenueContext): Promise<BankImportReport>;
|
|
492
587
|
/**
|
|
@@ -507,6 +602,7 @@ declare class TransactionRepository extends Repository<TransactionDocument> {
|
|
|
507
602
|
*/
|
|
508
603
|
drainSync(providerName: string, params: FetchTransactionsParams & {
|
|
509
604
|
bankAccountId: string;
|
|
605
|
+
methodKind: PaymentMethodKind;
|
|
510
606
|
}, ctx?: RevenueContext): Promise<{
|
|
511
607
|
totalImported: number;
|
|
512
608
|
totalUpdated: number;
|
|
@@ -526,6 +622,7 @@ declare class TransactionRepository extends Repository<TransactionDocument> {
|
|
|
526
622
|
buffer: Buffer | string | Uint8Array;
|
|
527
623
|
format?: string;
|
|
528
624
|
bankAccountId: string;
|
|
625
|
+
methodKind: PaymentMethodKind;
|
|
529
626
|
}, ctx?: RevenueContext): Promise<BankImportReport>;
|
|
530
627
|
/**
|
|
531
628
|
* Hand-keyed entry — treasurer logs a cash deposit, owner injects
|
|
@@ -539,6 +636,7 @@ declare class TransactionRepository extends Repository<TransactionDocument> {
|
|
|
539
636
|
currency: string;
|
|
540
637
|
flow: 'inflow' | 'outflow';
|
|
541
638
|
type: string;
|
|
639
|
+
methodKind: PaymentMethodKind;
|
|
542
640
|
description?: string;
|
|
543
641
|
counterparty?: TransactionDocument['counterparty'];
|
|
544
642
|
reference?: string;
|
|
@@ -549,6 +647,37 @@ declare class TransactionRepository extends Repository<TransactionDocument> {
|
|
|
549
647
|
sourceModel?: string;
|
|
550
648
|
metadata?: Record<string, unknown>;
|
|
551
649
|
}, ctx?: RevenueContext): Promise<TransactionDocument>;
|
|
650
|
+
/**
|
|
651
|
+
* Backfill the `methodKind` on a Transaction created with kind
|
|
652
|
+
* unknown — the canonical use case is hosted-checkout (Stripe
|
|
653
|
+
* Checkout, PayPal redirect, Razorpay Checkout) where the customer
|
|
654
|
+
* picks their payment method on the gateway's UI, AFTER the host has
|
|
655
|
+
* already created the PaymentIntent + Transaction with
|
|
656
|
+
* `methodKind: 'other'`.
|
|
657
|
+
*
|
|
658
|
+
* Call this from your verification / webhook handler once you know
|
|
659
|
+
* the customer's actual choice — e.g. inside
|
|
660
|
+
* `payment_intent.succeeded`:
|
|
661
|
+
*
|
|
662
|
+
* ```ts
|
|
663
|
+
* await transactionRepository.backfillMethodKind(
|
|
664
|
+
* tx._id,
|
|
665
|
+
* stripePaymentIntentToKind(event.data.object),
|
|
666
|
+
* ctx,
|
|
667
|
+
* );
|
|
668
|
+
* ```
|
|
669
|
+
*
|
|
670
|
+
* **Guard rule.** Atomic CAS — succeeds only when the doc has
|
|
671
|
+
* `methodKind === 'other'` AND `status === 'pending'`. Any other
|
|
672
|
+
* combination throws `MethodKindLockedError` (HTTP 409): once a
|
|
673
|
+
* transaction has a specific kind (or has settled past pending),
|
|
674
|
+
* silently overwriting it would corrupt downstream analytics and
|
|
675
|
+
* accounting reports.
|
|
676
|
+
*
|
|
677
|
+
* Emits `revenue:transaction.updated` with `changedFields:
|
|
678
|
+
* ['methodKind']` so subscribers can re-bucket the row.
|
|
679
|
+
*/
|
|
680
|
+
backfillMethodKind(transactionId: string, methodKind: PaymentMethodKind, ctx?: RevenueContext): Promise<TransactionDocument>;
|
|
552
681
|
/**
|
|
553
682
|
* Match a bank-feed / manual transaction to GL accounts, optionally
|
|
554
683
|
* cross-linking to an upstream payment-flow transaction.
|
|
@@ -677,33 +806,53 @@ declare class TransactionRepository extends Repository<TransactionDocument> {
|
|
|
677
806
|
}
|
|
678
807
|
//#endregion
|
|
679
808
|
//#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
809
|
/**
|
|
689
|
-
*
|
|
810
|
+
* Deps for {@link SubscriptionRepository}. Currently identical to
|
|
811
|
+
* {@link BaseRevenueRepoDeps} (events / outbox? / logger?). Kept as its
|
|
812
|
+
* own type alias so future subscription-specific deps (e.g. a billing
|
|
813
|
+
* engine handle) can land without touching every callsite — and so the
|
|
814
|
+
* `inject(deps)` signature reads as `SubscriptionRepoDeps` at the
|
|
815
|
+
* engine factory.
|
|
816
|
+
*/
|
|
817
|
+
type SubscriptionRepoDeps = BaseRevenueRepoDeps;
|
|
818
|
+
/**
|
|
819
|
+
* SubscriptionRepository — data layer + domain verbs for the recurring-
|
|
820
|
+
* billing lifecycle.
|
|
821
|
+
*
|
|
822
|
+
* **CRUD inherited** from mongokit (via {@link RevenueRepositoryBase}):
|
|
823
|
+
* `getById`, `getByQuery`, `getAll`, `create`, `update`, `delete`,
|
|
824
|
+
* `findOneAndUpdate`, `count`, `exists`, `claim`, `cursor`, `updateMany`,
|
|
825
|
+
* `deleteMany`. All participate in `multiTenantPlugin` scope filtering
|
|
826
|
+
* when wired.
|
|
690
827
|
*
|
|
691
|
-
*
|
|
828
|
+
* **Domain verbs (state transitions):** `activate`, `cancel`, `pause`,
|
|
829
|
+
* `resume`. Each runs the state-machine guard (`SUBSCRIPTION_STATE_MACHINE`
|
|
830
|
+
* — invalid transitions throw, never silently no-op), persists the
|
|
831
|
+
* resulting writes through {@link RevenueRepositoryBase.optsFromCtx} so
|
|
832
|
+
* tenant scope is preserved end-to-end, then dispatches its
|
|
833
|
+
* `revenue:subscription.*` event via {@link RevenueRepositoryBase.dispatch}.
|
|
692
834
|
*
|
|
693
|
-
*
|
|
694
|
-
*
|
|
695
|
-
*
|
|
696
|
-
*
|
|
835
|
+
* **Multi-tenant correctness.** Every internal `getById`/`update` call
|
|
836
|
+
* threads `ctx.organizationId` through `optsFromCtx(ctx)`. Without this
|
|
837
|
+
* threading the inner read would either throw
|
|
838
|
+
* `Missing 'organizationId' in context` (when `multiTenantPlugin` is
|
|
839
|
+
* required) or — worse — return another tenant's subscription matching
|
|
840
|
+
* the same `_id` shape (when `required: false`). 2.1.0 had this bug; 2.1.1+
|
|
841
|
+
* is correct.
|
|
842
|
+
*
|
|
843
|
+
* @example Activate a pending sub
|
|
844
|
+
* ```ts
|
|
845
|
+
* const ctx = { organizationId: 'org_42', actorId: 'user_99' };
|
|
846
|
+
* const sub = await subRepo.create(
|
|
847
|
+
* { customerId: 'cust_1', planKey: 'monthly', amount: 999, currency: 'USD',
|
|
848
|
+
* status: SUBSCRIPTION_STATUS.PENDING, isActive: false },
|
|
849
|
+
* ctx,
|
|
850
|
+
* );
|
|
851
|
+
* await subRepo.activate(String(sub._id), {}, ctx);
|
|
852
|
+
* ```
|
|
697
853
|
*/
|
|
698
|
-
declare class SubscriptionRepository extends
|
|
699
|
-
private deps;
|
|
854
|
+
declare class SubscriptionRepository extends RevenueRepositoryBase<SubscriptionDocument, SubscriptionRepoDeps> {
|
|
700
855
|
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
856
|
activate(subscriptionId: string, options?: {
|
|
708
857
|
timestamp?: Date;
|
|
709
858
|
}, ctx?: RevenueContext): Promise<SubscriptionDocument>;
|
|
@@ -720,37 +869,38 @@ declare class SubscriptionRepository extends Repository<SubscriptionDocument> {
|
|
|
720
869
|
}
|
|
721
870
|
//#endregion
|
|
722
871
|
//#region src/repositories/settlement.repository.d.ts
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
872
|
+
/**
|
|
873
|
+
* Deps for {@link SettlementRepository}. Adds the optional
|
|
874
|
+
* `RevenueBridges` (ledger / notification ports) on top of
|
|
875
|
+
* {@link BaseRevenueRepoDeps}.
|
|
876
|
+
*/
|
|
877
|
+
interface SettlementRepoDeps extends BaseRevenueRepoDeps {
|
|
727
878
|
bridges: RevenueBridges;
|
|
728
|
-
logger?: {
|
|
729
|
-
error(...args: unknown[]): void;
|
|
730
|
-
} | undefined;
|
|
731
879
|
}
|
|
732
880
|
interface SettlementProcessingError {
|
|
733
881
|
settlementId: SettlementDocument['_id'];
|
|
734
882
|
error: unknown;
|
|
735
883
|
}
|
|
736
884
|
/**
|
|
737
|
-
* SettlementRepository —
|
|
885
|
+
* SettlementRepository — payouts to recipients (organizations, vendors,
|
|
886
|
+
* affiliates).
|
|
887
|
+
*
|
|
888
|
+
* **CRUD inherited** via {@link RevenueRepositoryBase}. **Domain verbs:**
|
|
889
|
+
* `schedule`, `processPending`, `complete`, `fail`. State machine:
|
|
890
|
+
* `pending → processing → completed | failed`; `failed` with
|
|
891
|
+
* `retry: true` resets to `pending` with a delayed `scheduledAt`.
|
|
738
892
|
*
|
|
739
|
-
*
|
|
893
|
+
* **Multi-tenant correctness.** Every read/write threads `ctx` through
|
|
894
|
+
* {@link RevenueRepositoryBase.optsFromCtx} so `multiTenantPlugin`
|
|
895
|
+
* scope filters apply. 2.1.0 had several `getById`/`update` calls that
|
|
896
|
+
* dropped ctx — fixed in 2.1.1+.
|
|
740
897
|
*
|
|
741
|
-
*
|
|
742
|
-
*
|
|
743
|
-
*
|
|
898
|
+
* Bridges (`ledger`, `notification`) fire on `complete()` so a host
|
|
899
|
+
* can pin double-entry book-keeping or push a "you got paid" email
|
|
900
|
+
* without the repo knowing about either subsystem.
|
|
744
901
|
*/
|
|
745
|
-
declare class SettlementRepository extends
|
|
746
|
-
private deps;
|
|
902
|
+
declare class SettlementRepository extends RevenueRepositoryBase<SettlementDocument, SettlementRepoDeps> {
|
|
747
903
|
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
904
|
schedule(params: {
|
|
755
905
|
organizationId: string;
|
|
756
906
|
recipientId: string;
|
package/dist/enums/index.mjs
CHANGED
|
@@ -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-
|
|
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 };
|