@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.
- package/CHANGELOG.md +66 -0
- package/README.md +33 -10
- package/dist/bank-feed-BlQeq2rK.mjs +133 -0
- package/dist/bank-feed.enums-BadqNJTC.d.mts +118 -0
- package/dist/bank-feed.enums-kYTLTTbe.mjs +165 -0
- package/dist/bridges/index.d.mts +1 -1
- package/dist/core/state-machines.d.mts +25 -2
- package/dist/core/state-machines.mjs +43 -3
- package/dist/engine-types-Jctrbasz.d.mts +1160 -0
- package/dist/enums/index.d.mts +4 -3
- package/dist/enums/index.mjs +4 -3
- package/dist/{errors-DHa8JVQ-.mjs → errors-LYYg9wcs.mjs} +23 -1
- package/dist/{escrow.schema-D5X32LwX.d.mts → escrow.schema-YuBgjL-I.d.mts} +27 -27
- package/dist/{event-constants-CEMitnIV.mjs → event-constants-Dn1TKahe.mjs} +6 -0
- package/dist/events/index.d.mts +2 -2
- package/dist/events/index.mjs +3 -3
- package/dist/index.d.mts +32 -13
- package/dist/index.mjs +142 -19
- package/dist/providers/index.d.mts +2 -2
- package/dist/providers/index.mjs +2 -2
- package/dist/registry-h8sasoLh.d.mts +145 -0
- package/dist/repositories/create-repositories.d.mts +1 -1
- package/dist/repositories/create-repositories.mjs +1 -1
- package/dist/{revenue-bridges-sdlrR85c.d.mts → revenue-bridges-BtkWFsJu.d.mts} +107 -1
- package/dist/{revenue-event-catalog-LqxPnsU_.mjs → revenue-event-catalog-BvjNVnPd.mjs} +77 -3
- package/dist/{revenue-event-catalog-BX3g7RUi.d.mts → revenue-event-catalog-JpJcyK1E.d.mts} +198 -2
- package/dist/settlement.repository-BAdc9qGl.mjs +1444 -0
- package/dist/shared/index.d.mts +1 -1
- package/dist/shared/index.mjs +2 -2
- package/dist/{subscription.enums-tfoAgsTv.mjs → subscription.enums-95othr0i.mjs} +1 -40
- package/dist/{transaction.enums-u4MshXcL.d.mts → subscription.enums-k24kLpF7.d.mts} +1 -36
- package/dist/validators/index.d.mts +158 -2
- package/dist/validators/index.mjs +95 -2
- package/package.json +7 -7
- package/dist/engine-types-CcjIb4Fy.d.mts +0 -611
- package/dist/registry-DhFMsSn5.mjs +0 -150
- package/dist/registry-SvIGPAx_.d.mts +0 -143
- package/dist/settlement.repository-DHIPx5S4.mjs +0 -771
- /package/dist/{audit-B39B0Sdq.mjs → audit-Ba2XB2C4.mjs} +0 -0
- /package/dist/{audit-DZ0eTr9g.d.mts → audit-DRKuLBFO.d.mts} +0 -0
- /package/dist/{context-DRqSeTPM.d.mts → context-pjP1QeE3.d.mts} +0 -0
- /package/dist/{escrow.schema-BBv9oVEW.mjs → escrow.schema-C-b41z_G.mjs} +0 -0
- /package/dist/{monetization.enums-BtiU3t8o.mjs → monetization.enums-B9HBOecd.mjs} +0 -0
- /package/dist/{monetization.enums-D2xbxXJM.d.mts → monetization.enums-DzAI4sT7.d.mts} +0 -0
- /package/dist/{splits-BAfY-a9P.mjs → splits-CNfQj92L.mjs} +0 -0
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
|
|
57
|
-
|
|
|
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
|
|
62
|
-
|
|
|
62
|
+
|-- repositories.subscription extends RevenueRepositoryBase
|
|
63
|
+
| CRUD inherited
|
|
63
64
|
| activate, cancel, pause, resume (domain verbs)
|
|
64
65
|
|
|
|
65
|
-
|-- repositories.settlement
|
|
66
|
-
|
|
|
66
|
+
|-- repositories.settlement extends RevenueRepositoryBase
|
|
67
|
+
| CRUD inherited
|
|
67
68
|
| schedule, processPending, complete, fail (domain verbs)
|
|
68
69
|
|
|
|
69
|
-
|-- providers
|
|
70
|
-
|-- events
|
|
71
|
-
|-- models
|
|
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
|
-
|
|
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
|
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { l as ProviderNotFoundError } from "./errors-LYYg9wcs.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/providers/base.ts
|
|
4
|
+
/**
|
|
5
|
+
* Abstract `PaymentProvider` — the contract revenue's repositories
|
|
6
|
+
* consume. Provider implementations may extend this for the default
|
|
7
|
+
* config plumbing, or just satisfy the structural shape.
|
|
8
|
+
*/
|
|
9
|
+
var PaymentProvider = class {
|
|
10
|
+
config;
|
|
11
|
+
name;
|
|
12
|
+
_defaultCurrency = "USD";
|
|
13
|
+
constructor(config = {}) {
|
|
14
|
+
this.config = config;
|
|
15
|
+
this.name = "base";
|
|
16
|
+
if (config.defaultCurrency && typeof config.defaultCurrency === "string") this._defaultCurrency = config.defaultCurrency;
|
|
17
|
+
}
|
|
18
|
+
get defaultCurrency() {
|
|
19
|
+
return this._defaultCurrency;
|
|
20
|
+
}
|
|
21
|
+
setDefaultCurrency(currency) {
|
|
22
|
+
this._defaultCurrency = currency;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Default: accept all signatures (manual / dev provider). Real
|
|
26
|
+
* gateways MUST override with HMAC / timing-safe verification.
|
|
27
|
+
*/
|
|
28
|
+
verifyWebhookSignature(_payload, _signature) {
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
getCapabilities() {
|
|
32
|
+
return {
|
|
33
|
+
supportsWebhooks: false,
|
|
34
|
+
supportsRefunds: false,
|
|
35
|
+
supportsPartialRefunds: false,
|
|
36
|
+
requiresManualVerification: true
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
//#endregion
|
|
42
|
+
//#region src/providers/registry.ts
|
|
43
|
+
var ProviderRegistry = class {
|
|
44
|
+
providers = /* @__PURE__ */ new Map();
|
|
45
|
+
register(name, provider) {
|
|
46
|
+
this.providers.set(name, provider);
|
|
47
|
+
}
|
|
48
|
+
get(name) {
|
|
49
|
+
const provider = this.providers.get(name);
|
|
50
|
+
if (!provider) throw new ProviderNotFoundError(name);
|
|
51
|
+
return provider;
|
|
52
|
+
}
|
|
53
|
+
has(name) {
|
|
54
|
+
return this.providers.has(name);
|
|
55
|
+
}
|
|
56
|
+
list() {
|
|
57
|
+
return Array.from(this.providers.keys());
|
|
58
|
+
}
|
|
59
|
+
setDefaultCurrency(currency) {
|
|
60
|
+
for (const provider of this.providers.values()) provider.setDefaultCurrency(currency);
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
function createProviderRegistry(providers = {}, defaultCurrency) {
|
|
64
|
+
const registry = new ProviderRegistry();
|
|
65
|
+
for (const [name, provider] of Object.entries(providers)) {
|
|
66
|
+
if (defaultCurrency) provider.setDefaultCurrency(defaultCurrency);
|
|
67
|
+
registry.register(name, provider);
|
|
68
|
+
}
|
|
69
|
+
return registry;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
//#endregion
|
|
73
|
+
//#region src/providers/bank-feed.ts
|
|
74
|
+
/**
|
|
75
|
+
* Bank-feed provider — implement one method or both depending on the
|
|
76
|
+
* upstream's capabilities. Mirrors the optional-method pattern that
|
|
77
|
+
* works well across PaymentProvider's gateway plurality.
|
|
78
|
+
*/
|
|
79
|
+
var BankFeedProvider = class {
|
|
80
|
+
config;
|
|
81
|
+
name;
|
|
82
|
+
constructor(name, config = {}) {
|
|
83
|
+
this.name = name;
|
|
84
|
+
this.config = config;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Async drain — yields one batch per call until the upstream is
|
|
88
|
+
* caught up. Default implementation pulls `fetchTransactions` in a
|
|
89
|
+
* loop; providers can override for more efficient pagination
|
|
90
|
+
* (e.g. SSE / long-poll) or to interleave `removed[]` correctly.
|
|
91
|
+
*/
|
|
92
|
+
async *drain(params = {}) {
|
|
93
|
+
if (!this.fetchTransactions) throw new Error(`Provider ${this.name} does not support fetchTransactions()`);
|
|
94
|
+
let cursor = params.cursor;
|
|
95
|
+
const MAX_PAGES = 1e4;
|
|
96
|
+
for (let i = 0; i < MAX_PAGES; i++) {
|
|
97
|
+
const result = await this.fetchTransactions({
|
|
98
|
+
...params,
|
|
99
|
+
cursor
|
|
100
|
+
});
|
|
101
|
+
yield result;
|
|
102
|
+
const tooLittle = result.transactions.length === 0 && (result.removed?.length ?? 0) === 0;
|
|
103
|
+
if (result.hasMore === false || tooLittle) return;
|
|
104
|
+
if (!result.nextCursor || result.nextCursor === cursor) return;
|
|
105
|
+
cursor = result.nextCursor;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
var BankFeedProviderRegistry = class {
|
|
110
|
+
providers = /* @__PURE__ */ new Map();
|
|
111
|
+
register(name, provider) {
|
|
112
|
+
this.providers.set(name, provider);
|
|
113
|
+
}
|
|
114
|
+
get(name) {
|
|
115
|
+
const provider = this.providers.get(name);
|
|
116
|
+
if (!provider) throw new ProviderNotFoundError(name);
|
|
117
|
+
return provider;
|
|
118
|
+
}
|
|
119
|
+
has(name) {
|
|
120
|
+
return this.providers.has(name);
|
|
121
|
+
}
|
|
122
|
+
list() {
|
|
123
|
+
return Array.from(this.providers.keys());
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
function createBankFeedProviderRegistry(providers = {}) {
|
|
127
|
+
const registry = new BankFeedProviderRegistry();
|
|
128
|
+
for (const [name, provider] of Object.entries(providers)) registry.register(name, provider);
|
|
129
|
+
return registry;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
//#endregion
|
|
133
|
+
export { createProviderRegistry as a, ProviderRegistry as i, BankFeedProviderRegistry as n, PaymentProvider as o, createBankFeedProviderRegistry as r, BankFeedProvider as t };
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
//#region src/enums/transaction.enums.d.ts
|
|
2
|
+
declare const TRANSACTION_FLOW: {
|
|
3
|
+
readonly INFLOW: "inflow";
|
|
4
|
+
readonly OUTFLOW: "outflow";
|
|
5
|
+
};
|
|
6
|
+
type TransactionFlow = typeof TRANSACTION_FLOW;
|
|
7
|
+
type TransactionFlowValue = TransactionFlow[keyof TransactionFlow];
|
|
8
|
+
declare const TRANSACTION_FLOW_VALUES: TransactionFlowValue[];
|
|
9
|
+
declare const TRANSACTION_STATUS: {
|
|
10
|
+
readonly PENDING: "pending";
|
|
11
|
+
readonly PAYMENT_INITIATED: "payment_initiated";
|
|
12
|
+
readonly PROCESSING: "processing";
|
|
13
|
+
readonly REQUIRES_ACTION: "requires_action";
|
|
14
|
+
readonly VERIFIED: "verified";
|
|
15
|
+
readonly COMPLETED: "completed";
|
|
16
|
+
readonly FAILED: "failed";
|
|
17
|
+
readonly CANCELLED: "cancelled";
|
|
18
|
+
readonly EXPIRED: "expired";
|
|
19
|
+
readonly REFUNDED: "refunded";
|
|
20
|
+
readonly PARTIALLY_REFUNDED: "partially_refunded";
|
|
21
|
+
readonly IMPORTED: "imported";
|
|
22
|
+
readonly MATCHED: "matched";
|
|
23
|
+
readonly JOURNALIZED: "journalized";
|
|
24
|
+
readonly REJECTED: "rejected";
|
|
25
|
+
};
|
|
26
|
+
type TransactionStatus = typeof TRANSACTION_STATUS;
|
|
27
|
+
type TransactionStatusValue = TransactionStatus[keyof TransactionStatus];
|
|
28
|
+
declare const TRANSACTION_STATUS_VALUES: TransactionStatusValue[];
|
|
29
|
+
declare const LIBRARY_CATEGORIES: {
|
|
30
|
+
readonly SUBSCRIPTION: "subscription";
|
|
31
|
+
readonly PURCHASE: "purchase";
|
|
32
|
+
};
|
|
33
|
+
type LibraryCategories = typeof LIBRARY_CATEGORIES;
|
|
34
|
+
type LibraryCategoryValue = LibraryCategories[keyof LibraryCategories];
|
|
35
|
+
declare const LIBRARY_CATEGORY_VALUES: LibraryCategoryValue[];
|
|
36
|
+
declare function isLibraryCategory(value: unknown): value is LibraryCategoryValue;
|
|
37
|
+
declare function isTransactionFlow(value: unknown): value is TransactionFlowValue;
|
|
38
|
+
declare function isTransactionStatus(value: unknown): value is TransactionStatusValue;
|
|
39
|
+
//#endregion
|
|
40
|
+
//#region src/enums/bank-feed.enums.d.ts
|
|
41
|
+
declare const TRANSACTION_KIND: {
|
|
42
|
+
/**
|
|
43
|
+
* Payment-gateway flow — the original revenue lifecycle. Stripe / SSL /
|
|
44
|
+
* Bkash / manual all share this graph.
|
|
45
|
+
* pending → payment_initiated → processing → requires_action → verified
|
|
46
|
+
* → completed → refunded | partially_refunded
|
|
47
|
+
*/
|
|
48
|
+
readonly PAYMENT_FLOW: "payment_flow";
|
|
49
|
+
/**
|
|
50
|
+
* Bank / accounting feed — OFX upload, Plaid sync, QBO/Xero CDC.
|
|
51
|
+
* imported → matched → journalized (happy path)
|
|
52
|
+
* imported → rejected (operator skip)
|
|
53
|
+
* matched → imported (un-match)
|
|
54
|
+
*/
|
|
55
|
+
readonly BANK_FEED: "bank_feed";
|
|
56
|
+
/**
|
|
57
|
+
* Hand-keyed entry — treasurer logs a cash deposit, owner injects
|
|
58
|
+
* capital. Cleaner two-step lifecycle than payment_flow.
|
|
59
|
+
* pending → matched → journalized | rejected
|
|
60
|
+
*/
|
|
61
|
+
readonly MANUAL: "manual";
|
|
62
|
+
};
|
|
63
|
+
type TransactionKind = typeof TRANSACTION_KIND;
|
|
64
|
+
type TransactionKindValue = TransactionKind[keyof TransactionKind];
|
|
65
|
+
declare const TRANSACTION_KIND_VALUES: TransactionKindValue[];
|
|
66
|
+
declare function isTransactionKind(value: unknown): value is TransactionKindValue;
|
|
67
|
+
declare const BANK_FEED_STATUS: {
|
|
68
|
+
readonly IMPORTED: "imported";
|
|
69
|
+
readonly MATCHED: "matched";
|
|
70
|
+
readonly JOURNALIZED: "journalized";
|
|
71
|
+
readonly REJECTED: "rejected";
|
|
72
|
+
};
|
|
73
|
+
type BankFeedStatusValue = (typeof BANK_FEED_STATUS)[keyof typeof BANK_FEED_STATUS];
|
|
74
|
+
declare const BANK_FEED_STATUS_VALUES: BankFeedStatusValue[];
|
|
75
|
+
declare function isBankFeedStatus(value: unknown): value is BankFeedStatusValue;
|
|
76
|
+
declare const BANK_FEED_SOURCE: {
|
|
77
|
+
readonly OFX: "ofx";
|
|
78
|
+
readonly CAMT053: "camt.053";
|
|
79
|
+
readonly MT940: "mt940";
|
|
80
|
+
readonly CSV: "csv";
|
|
81
|
+
readonly IIF: "iif";
|
|
82
|
+
readonly QBO: "qbo";
|
|
83
|
+
readonly XERO: "xero";
|
|
84
|
+
readonly PLAID: "plaid";
|
|
85
|
+
readonly MANUAL: "manual";
|
|
86
|
+
};
|
|
87
|
+
type BankFeedSourceValue = (typeof BANK_FEED_SOURCE)[keyof typeof BANK_FEED_SOURCE];
|
|
88
|
+
declare const BANK_FEED_SOURCE_VALUES: BankFeedSourceValue[];
|
|
89
|
+
declare function isBankFeedSource(value: unknown): value is BankFeedSourceValue;
|
|
90
|
+
/**
|
|
91
|
+
* Initial status for a freshly created row of a given kind. Centralized so
|
|
92
|
+
* the schema, repo verbs, and validators agree.
|
|
93
|
+
*
|
|
94
|
+
* - `payment_flow` → `pending` — provider may flip to verified instantly
|
|
95
|
+
* for zero-amount rows (see `createPaymentIntent`).
|
|
96
|
+
* - `bank_feed` → `imported` — bulk upsert from a feed/upload.
|
|
97
|
+
* - `manual` → `pending` — treasurer reviews then `match()`es.
|
|
98
|
+
*/
|
|
99
|
+
declare function initialStatusFor(kind: TransactionKindValue): TransactionStatusValue;
|
|
100
|
+
/**
|
|
101
|
+
* True iff `status` is a legal value for a transaction of the given
|
|
102
|
+
* `kind`. Use at API boundaries (admin status filters, list-page query
|
|
103
|
+
* params, JSON imports) to reject `?kind=payment_flow&status=imported`
|
|
104
|
+
* before it reaches the repository.
|
|
105
|
+
*
|
|
106
|
+
* @example
|
|
107
|
+
* if (!isStatusValidForKind(req.query.status, req.query.kind)) {
|
|
108
|
+
* throw new ValidationError('status invalid for kind');
|
|
109
|
+
* }
|
|
110
|
+
*/
|
|
111
|
+
declare function isStatusValidForKind(status: unknown, kind: TransactionKindValue): boolean;
|
|
112
|
+
/**
|
|
113
|
+
* The set of status values legal for a given kind. Useful for building
|
|
114
|
+
* dropdown options or `$in` filters at the API layer.
|
|
115
|
+
*/
|
|
116
|
+
declare function statusesForKind(kind: TransactionKindValue): readonly string[];
|
|
117
|
+
//#endregion
|
|
118
|
+
export { isTransactionFlow as A, TRANSACTION_STATUS as C, TransactionStatus as D, TransactionFlowValue as E, TransactionStatusValue as O, TRANSACTION_FLOW_VALUES as S, TransactionFlow as T, LIBRARY_CATEGORIES as _, BankFeedSourceValue as a, LibraryCategoryValue as b, TRANSACTION_KIND_VALUES as c, initialStatusFor as d, isBankFeedSource as f, statusesForKind as g, isTransactionKind as h, BANK_FEED_STATUS_VALUES as i, isTransactionStatus as j, isLibraryCategory as k, TransactionKind as l, isStatusValidForKind as m, BANK_FEED_SOURCE_VALUES as n, BankFeedStatusValue as o, isBankFeedStatus as p, BANK_FEED_STATUS as r, TRANSACTION_KIND as s, BANK_FEED_SOURCE as t, TransactionKindValue as u, LIBRARY_CATEGORY_VALUES as v, TRANSACTION_STATUS_VALUES as w, TRANSACTION_FLOW as x, LibraryCategories as y };
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
//#region src/enums/transaction.enums.ts
|
|
2
|
+
const TRANSACTION_FLOW = {
|
|
3
|
+
INFLOW: "inflow",
|
|
4
|
+
OUTFLOW: "outflow"
|
|
5
|
+
};
|
|
6
|
+
const TRANSACTION_FLOW_VALUES = Object.values(TRANSACTION_FLOW);
|
|
7
|
+
const TRANSACTION_STATUS = {
|
|
8
|
+
PENDING: "pending",
|
|
9
|
+
PAYMENT_INITIATED: "payment_initiated",
|
|
10
|
+
PROCESSING: "processing",
|
|
11
|
+
REQUIRES_ACTION: "requires_action",
|
|
12
|
+
VERIFIED: "verified",
|
|
13
|
+
COMPLETED: "completed",
|
|
14
|
+
FAILED: "failed",
|
|
15
|
+
CANCELLED: "cancelled",
|
|
16
|
+
EXPIRED: "expired",
|
|
17
|
+
REFUNDED: "refunded",
|
|
18
|
+
PARTIALLY_REFUNDED: "partially_refunded",
|
|
19
|
+
IMPORTED: "imported",
|
|
20
|
+
MATCHED: "matched",
|
|
21
|
+
JOURNALIZED: "journalized",
|
|
22
|
+
REJECTED: "rejected"
|
|
23
|
+
};
|
|
24
|
+
const TRANSACTION_STATUS_VALUES = Object.values(TRANSACTION_STATUS);
|
|
25
|
+
const LIBRARY_CATEGORIES = {
|
|
26
|
+
SUBSCRIPTION: "subscription",
|
|
27
|
+
PURCHASE: "purchase"
|
|
28
|
+
};
|
|
29
|
+
const LIBRARY_CATEGORY_VALUES = Object.values(LIBRARY_CATEGORIES);
|
|
30
|
+
const transactionFlowSet = new Set(TRANSACTION_FLOW_VALUES);
|
|
31
|
+
const transactionStatusSet = new Set(TRANSACTION_STATUS_VALUES);
|
|
32
|
+
const libraryCategorySet = new Set(LIBRARY_CATEGORY_VALUES);
|
|
33
|
+
function isLibraryCategory(value) {
|
|
34
|
+
return typeof value === "string" && libraryCategorySet.has(value);
|
|
35
|
+
}
|
|
36
|
+
function isTransactionFlow(value) {
|
|
37
|
+
return typeof value === "string" && transactionFlowSet.has(value);
|
|
38
|
+
}
|
|
39
|
+
function isTransactionStatus(value) {
|
|
40
|
+
return typeof value === "string" && transactionStatusSet.has(value);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
//#endregion
|
|
44
|
+
//#region src/enums/bank-feed.enums.ts
|
|
45
|
+
/**
|
|
46
|
+
* Bank-feed lifecycle enums.
|
|
47
|
+
*
|
|
48
|
+
* Revenue 3.0 generalizes the Transaction model from "payment-gateway-only"
|
|
49
|
+
* to a unified cashflow ledger. The `kind` discriminator selects which
|
|
50
|
+
* state machine governs the row (see `core/state-machines.ts`); these
|
|
51
|
+
* enums own the bank-feed and manual lifecycles plus the canonical
|
|
52
|
+
* `TransactionKind` literals every consumer should branch on.
|
|
53
|
+
*
|
|
54
|
+
* Why a discriminator instead of a separate model — same collection wins
|
|
55
|
+
* the unified-audit-ledger query ("everything that touched cash this
|
|
56
|
+
* quarter"), keeps soft-delete + retention policies single-sourced, and
|
|
57
|
+
* lets `relatedTransactionId` cross-link a Stripe charge to its Plaid
|
|
58
|
+
* deposit without a polymorphic ref. See PACKAGE_RULES §30 / §35.
|
|
59
|
+
*/
|
|
60
|
+
const TRANSACTION_KIND = {
|
|
61
|
+
PAYMENT_FLOW: "payment_flow",
|
|
62
|
+
BANK_FEED: "bank_feed",
|
|
63
|
+
MANUAL: "manual"
|
|
64
|
+
};
|
|
65
|
+
const TRANSACTION_KIND_VALUES = Object.values(TRANSACTION_KIND);
|
|
66
|
+
const transactionKindSet = new Set(TRANSACTION_KIND_VALUES);
|
|
67
|
+
function isTransactionKind(value) {
|
|
68
|
+
return typeof value === "string" && transactionKindSet.has(value);
|
|
69
|
+
}
|
|
70
|
+
const BANK_FEED_STATUS = {
|
|
71
|
+
IMPORTED: "imported",
|
|
72
|
+
MATCHED: "matched",
|
|
73
|
+
JOURNALIZED: "journalized",
|
|
74
|
+
REJECTED: "rejected"
|
|
75
|
+
};
|
|
76
|
+
const BANK_FEED_STATUS_VALUES = Object.values(BANK_FEED_STATUS);
|
|
77
|
+
const bankFeedStatusSet = new Set(BANK_FEED_STATUS_VALUES);
|
|
78
|
+
function isBankFeedStatus(value) {
|
|
79
|
+
return typeof value === "string" && bankFeedStatusSet.has(value);
|
|
80
|
+
}
|
|
81
|
+
const BANK_FEED_SOURCE = {
|
|
82
|
+
OFX: "ofx",
|
|
83
|
+
CAMT053: "camt.053",
|
|
84
|
+
MT940: "mt940",
|
|
85
|
+
CSV: "csv",
|
|
86
|
+
IIF: "iif",
|
|
87
|
+
QBO: "qbo",
|
|
88
|
+
XERO: "xero",
|
|
89
|
+
PLAID: "plaid",
|
|
90
|
+
MANUAL: "manual"
|
|
91
|
+
};
|
|
92
|
+
const BANK_FEED_SOURCE_VALUES = Object.values(BANK_FEED_SOURCE);
|
|
93
|
+
const bankFeedSourceSet = new Set(BANK_FEED_SOURCE_VALUES);
|
|
94
|
+
function isBankFeedSource(value) {
|
|
95
|
+
return typeof value === "string" && bankFeedSourceSet.has(value);
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Initial status for a freshly created row of a given kind. Centralized so
|
|
99
|
+
* the schema, repo verbs, and validators agree.
|
|
100
|
+
*
|
|
101
|
+
* - `payment_flow` → `pending` — provider may flip to verified instantly
|
|
102
|
+
* for zero-amount rows (see `createPaymentIntent`).
|
|
103
|
+
* - `bank_feed` → `imported` — bulk upsert from a feed/upload.
|
|
104
|
+
* - `manual` → `pending` — treasurer reviews then `match()`es.
|
|
105
|
+
*/
|
|
106
|
+
function initialStatusFor(kind) {
|
|
107
|
+
switch (kind) {
|
|
108
|
+
case TRANSACTION_KIND.BANK_FEED: return BANK_FEED_STATUS.IMPORTED;
|
|
109
|
+
case TRANSACTION_KIND.MANUAL: return TRANSACTION_STATUS.PENDING;
|
|
110
|
+
case TRANSACTION_KIND.PAYMENT_FLOW:
|
|
111
|
+
default: return TRANSACTION_STATUS.PENDING;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
const STATUSES_BY_KIND = {
|
|
115
|
+
[TRANSACTION_KIND.PAYMENT_FLOW]: new Set([
|
|
116
|
+
TRANSACTION_STATUS.PENDING,
|
|
117
|
+
TRANSACTION_STATUS.PAYMENT_INITIATED,
|
|
118
|
+
TRANSACTION_STATUS.PROCESSING,
|
|
119
|
+
TRANSACTION_STATUS.REQUIRES_ACTION,
|
|
120
|
+
TRANSACTION_STATUS.VERIFIED,
|
|
121
|
+
TRANSACTION_STATUS.COMPLETED,
|
|
122
|
+
TRANSACTION_STATUS.FAILED,
|
|
123
|
+
TRANSACTION_STATUS.CANCELLED,
|
|
124
|
+
TRANSACTION_STATUS.EXPIRED,
|
|
125
|
+
TRANSACTION_STATUS.REFUNDED,
|
|
126
|
+
TRANSACTION_STATUS.PARTIALLY_REFUNDED
|
|
127
|
+
]),
|
|
128
|
+
[TRANSACTION_KIND.BANK_FEED]: new Set([
|
|
129
|
+
TRANSACTION_STATUS.IMPORTED,
|
|
130
|
+
TRANSACTION_STATUS.MATCHED,
|
|
131
|
+
TRANSACTION_STATUS.JOURNALIZED,
|
|
132
|
+
TRANSACTION_STATUS.REJECTED
|
|
133
|
+
]),
|
|
134
|
+
[TRANSACTION_KIND.MANUAL]: new Set([
|
|
135
|
+
TRANSACTION_STATUS.PENDING,
|
|
136
|
+
TRANSACTION_STATUS.MATCHED,
|
|
137
|
+
TRANSACTION_STATUS.JOURNALIZED,
|
|
138
|
+
TRANSACTION_STATUS.REJECTED
|
|
139
|
+
])
|
|
140
|
+
};
|
|
141
|
+
/**
|
|
142
|
+
* True iff `status` is a legal value for a transaction of the given
|
|
143
|
+
* `kind`. Use at API boundaries (admin status filters, list-page query
|
|
144
|
+
* params, JSON imports) to reject `?kind=payment_flow&status=imported`
|
|
145
|
+
* before it reaches the repository.
|
|
146
|
+
*
|
|
147
|
+
* @example
|
|
148
|
+
* if (!isStatusValidForKind(req.query.status, req.query.kind)) {
|
|
149
|
+
* throw new ValidationError('status invalid for kind');
|
|
150
|
+
* }
|
|
151
|
+
*/
|
|
152
|
+
function isStatusValidForKind(status, kind) {
|
|
153
|
+
if (typeof status !== "string") return false;
|
|
154
|
+
return STATUSES_BY_KIND[kind].has(status);
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* The set of status values legal for a given kind. Useful for building
|
|
158
|
+
* dropdown options or `$in` filters at the API layer.
|
|
159
|
+
*/
|
|
160
|
+
function statusesForKind(kind) {
|
|
161
|
+
return [...STATUSES_BY_KIND[kind]];
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
//#endregion
|
|
165
|
+
export { TRANSACTION_STATUS as _, TRANSACTION_KIND as a, isTransactionFlow as b, isBankFeedSource as c, isTransactionKind as d, statusesForKind as f, TRANSACTION_FLOW_VALUES as g, TRANSACTION_FLOW as h, BANK_FEED_STATUS_VALUES as i, isBankFeedStatus as l, LIBRARY_CATEGORY_VALUES as m, BANK_FEED_SOURCE_VALUES as n, TRANSACTION_KIND_VALUES as o, LIBRARY_CATEGORIES as p, BANK_FEED_STATUS as r, initialStatusFor as s, BANK_FEED_SOURCE as t, isStatusValidForKind as u, TRANSACTION_STATUS_VALUES as v, isTransactionStatus as x, isLibraryCategory as y };
|
package/dist/bridges/index.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as CurrencyBridge, c as LedgerBridge, i as CustomerBridge, n as SourceBridge, o as NotificationBridge, r as AnalyticsBridge, s as TaxBridge, t as RevenueBridges } from "../revenue-bridges-
|
|
1
|
+
import { a as CurrencyBridge, c as LedgerBridge, i as CustomerBridge, n as SourceBridge, o as NotificationBridge, r as AnalyticsBridge, s as TaxBridge, t as RevenueBridges } from "../revenue-bridges-BtkWFsJu.mjs";
|
|
2
2
|
export { type AnalyticsBridge, type CurrencyBridge, type CustomerBridge, type LedgerBridge, type NotificationBridge, type RevenueBridges, type SourceBridge, type TaxBridge };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { U as HoldStatusValue, b as SplitStatusValue, c as SubscriptionStatusValue, j as SettlementStatusValue } from "../subscription.enums-k24kLpF7.mjs";
|
|
2
|
+
import { O as TransactionStatusValue, u as TransactionKindValue } from "../bank-feed.enums-BadqNJTC.mjs";
|
|
2
3
|
|
|
3
4
|
//#region src/core/state-machines.d.ts
|
|
4
5
|
/**
|
|
@@ -49,5 +50,27 @@ declare const SUBSCRIPTION_STATE_MACHINE: StateMachine<SubscriptionStatusValue>;
|
|
|
49
50
|
declare const SETTLEMENT_STATE_MACHINE: StateMachine<SettlementStatusValue>;
|
|
50
51
|
declare const HOLD_STATE_MACHINE: StateMachine<HoldStatusValue>;
|
|
51
52
|
declare const SPLIT_STATE_MACHINE: StateMachine<SplitStatusValue>;
|
|
53
|
+
declare const PAYMENT_FLOW_STATE_MACHINE: StateMachine<TransactionStatusValue>;
|
|
54
|
+
declare const BANK_FEED_STATE_MACHINE: StateMachine<TransactionStatusValue>;
|
|
55
|
+
declare const MANUAL_STATE_MACHINE: StateMachine<TransactionStatusValue>;
|
|
56
|
+
/**
|
|
57
|
+
* Select the state machine that governs a transaction row, given its
|
|
58
|
+
* `kind`. Repo verbs call this at the start of every state transition
|
|
59
|
+
* so the same `from → to` rules apply on both the in-memory check
|
|
60
|
+
* (state machine) and the atomic CAS (mongokit `claim()` with the
|
|
61
|
+
* `where: { kind }` predicate).
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* ```ts
|
|
65
|
+
* const machine = smFor(transaction.kind);
|
|
66
|
+
* machine.validate(transaction.status, 'matched', String(transaction._id));
|
|
67
|
+
* await this.claim(id, {
|
|
68
|
+
* from: ['imported', 'matched'],
|
|
69
|
+
* to: 'matched',
|
|
70
|
+
* where: { kind: transaction.kind },
|
|
71
|
+
* }, patch, opts);
|
|
72
|
+
* ```
|
|
73
|
+
*/
|
|
74
|
+
declare function smFor(kind: TransactionKindValue): StateMachine<TransactionStatusValue>;
|
|
52
75
|
//#endregion
|
|
53
|
-
export { HOLD_STATE_MACHINE, SETTLEMENT_STATE_MACHINE, SPLIT_STATE_MACHINE, SUBSCRIPTION_STATE_MACHINE, StateChangeEvent, StateMachine, TRANSACTION_STATE_MACHINE };
|
|
76
|
+
export { BANK_FEED_STATE_MACHINE, HOLD_STATE_MACHINE, MANUAL_STATE_MACHINE, PAYMENT_FLOW_STATE_MACHINE, SETTLEMENT_STATE_MACHINE, SPLIT_STATE_MACHINE, SUBSCRIPTION_STATE_MACHINE, StateChangeEvent, StateMachine, TRANSACTION_STATE_MACHINE, smFor };
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { r as
|
|
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-95othr0i.mjs";
|
|
3
|
+
import { a as InvalidStateTransitionError } from "../errors-LYYg9wcs.mjs";
|
|
3
4
|
import { defineStateMachine } from "@classytic/primitives/state-machine";
|
|
4
5
|
|
|
5
6
|
//#region src/core/state-machines.ts
|
|
@@ -148,6 +149,45 @@ const SPLIT_STATE_MACHINE = new StateMachine(new Map([
|
|
|
148
149
|
[SPLIT_STATUS.WAIVED, /* @__PURE__ */ new Set([])],
|
|
149
150
|
[SPLIT_STATUS.CANCELLED, /* @__PURE__ */ new Set([])]
|
|
150
151
|
]), "split");
|
|
152
|
+
const PAYMENT_FLOW_STATE_MACHINE = TRANSACTION_STATE_MACHINE;
|
|
153
|
+
const BANK_FEED_STATE_MACHINE = new StateMachine(new Map([
|
|
154
|
+
[TRANSACTION_STATUS.IMPORTED, new Set([TRANSACTION_STATUS.MATCHED, TRANSACTION_STATUS.REJECTED])],
|
|
155
|
+
[TRANSACTION_STATUS.MATCHED, new Set([TRANSACTION_STATUS.IMPORTED, TRANSACTION_STATUS.JOURNALIZED])],
|
|
156
|
+
[TRANSACTION_STATUS.JOURNALIZED, /* @__PURE__ */ new Set([])],
|
|
157
|
+
[TRANSACTION_STATUS.REJECTED, /* @__PURE__ */ new Set([])]
|
|
158
|
+
]), "transaction.bank_feed");
|
|
159
|
+
const MANUAL_STATE_MACHINE = new StateMachine(new Map([
|
|
160
|
+
[TRANSACTION_STATUS.PENDING, new Set([TRANSACTION_STATUS.MATCHED, TRANSACTION_STATUS.REJECTED])],
|
|
161
|
+
[TRANSACTION_STATUS.MATCHED, new Set([TRANSACTION_STATUS.JOURNALIZED])],
|
|
162
|
+
[TRANSACTION_STATUS.JOURNALIZED, /* @__PURE__ */ new Set([])],
|
|
163
|
+
[TRANSACTION_STATUS.REJECTED, /* @__PURE__ */ new Set([])]
|
|
164
|
+
]), "transaction.manual");
|
|
165
|
+
/**
|
|
166
|
+
* Select the state machine that governs a transaction row, given its
|
|
167
|
+
* `kind`. Repo verbs call this at the start of every state transition
|
|
168
|
+
* so the same `from → to` rules apply on both the in-memory check
|
|
169
|
+
* (state machine) and the atomic CAS (mongokit `claim()` with the
|
|
170
|
+
* `where: { kind }` predicate).
|
|
171
|
+
*
|
|
172
|
+
* @example
|
|
173
|
+
* ```ts
|
|
174
|
+
* const machine = smFor(transaction.kind);
|
|
175
|
+
* machine.validate(transaction.status, 'matched', String(transaction._id));
|
|
176
|
+
* await this.claim(id, {
|
|
177
|
+
* from: ['imported', 'matched'],
|
|
178
|
+
* to: 'matched',
|
|
179
|
+
* where: { kind: transaction.kind },
|
|
180
|
+
* }, patch, opts);
|
|
181
|
+
* ```
|
|
182
|
+
*/
|
|
183
|
+
function smFor(kind) {
|
|
184
|
+
switch (kind) {
|
|
185
|
+
case TRANSACTION_KIND.BANK_FEED: return BANK_FEED_STATE_MACHINE;
|
|
186
|
+
case TRANSACTION_KIND.MANUAL: return MANUAL_STATE_MACHINE;
|
|
187
|
+
case TRANSACTION_KIND.PAYMENT_FLOW:
|
|
188
|
+
default: return PAYMENT_FLOW_STATE_MACHINE;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
151
191
|
|
|
152
192
|
//#endregion
|
|
153
|
-
export { HOLD_STATE_MACHINE, SETTLEMENT_STATE_MACHINE, SPLIT_STATE_MACHINE, SUBSCRIPTION_STATE_MACHINE, StateMachine, TRANSACTION_STATE_MACHINE };
|
|
193
|
+
export { BANK_FEED_STATE_MACHINE, HOLD_STATE_MACHINE, MANUAL_STATE_MACHINE, PAYMENT_FLOW_STATE_MACHINE, SETTLEMENT_STATE_MACHINE, SPLIT_STATE_MACHINE, SUBSCRIPTION_STATE_MACHINE, StateMachine, TRANSACTION_STATE_MACHINE, smFor };
|