@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
|
@@ -1,43 +1,176 @@
|
|
|
1
1
|
import { _ as TRANSACTION_STATUS, a as TRANSACTION_KIND, s as initialStatusFor } from "./bank-feed.enums-kYTLTTbe.mjs";
|
|
2
|
-
import { n as createEvent, t as REVENUE_EVENTS } from "./event-constants-
|
|
3
|
-
import { g as SETTLEMENT_STATUS, r as SUBSCRIPTION_STATUS, w as HOLD_STATUS } from "./subscription.enums-
|
|
4
|
-
import {
|
|
2
|
+
import { n as createEvent, t as REVENUE_EVENTS } from "./event-constants-DM_-A57b.mjs";
|
|
3
|
+
import { g as SETTLEMENT_STATUS, r as SUBSCRIPTION_STATUS, w as HOLD_STATUS } from "./subscription.enums-95othr0i.mjs";
|
|
4
|
+
import { _ as WrongTransactionKindError, g as ValidationError, h as TransactionNotFoundError, m as SubscriptionNotFoundError, n as BankFeedImportError, o as MethodKindLockedError, p as SettlementNotFoundError } from "./errors-Bt5NRVMq.mjs";
|
|
5
5
|
import { SETTLEMENT_STATE_MACHINE, SUBSCRIPTION_STATE_MACHINE, TRANSACTION_STATE_MACHINE, smFor } from "./core/state-machines.mjs";
|
|
6
|
-
import { a as reverseTax, c as reverseCommission, n as calculateSplits, s as calculateCommission, t as calculateOrganizationPayout } from "./splits-
|
|
7
|
-
import { Repository, withTransaction } from "@classytic/mongokit";
|
|
6
|
+
import { a as reverseTax, c as reverseCommission, n as calculateSplits, s as calculateCommission, t as calculateOrganizationPayout } from "./splits-CNfQj92L.mjs";
|
|
7
|
+
import { Repository, repoOptionsFromCtx, withTransaction } from "@classytic/mongokit";
|
|
8
8
|
|
|
9
|
-
//#region src/repositories/
|
|
10
|
-
|
|
9
|
+
//#region src/repositories/base.repository.ts
|
|
10
|
+
/**
|
|
11
|
+
* RevenueRepositoryBase — shared scaffolding for every revenue repo.
|
|
12
|
+
*
|
|
13
|
+
* Eliminates the three places ctx-threading and event-dispatch were
|
|
14
|
+
* hand-rolled (transaction / subscription / settlement) and previously
|
|
15
|
+
* drifted: subscription + settlement repos forgot to forward
|
|
16
|
+
* `ctx.organizationId` into their inner `getById` / `update` calls,
|
|
17
|
+
* which surfaced as `Missing 'organizationId' in context for 'getById'`
|
|
18
|
+
* the moment a host enabled `multiTenantPlugin` (the canonical 2026-Q2
|
|
19
|
+
* bug — fixed in 2.1.1).
|
|
20
|
+
*
|
|
21
|
+
* Two responsibilities:
|
|
22
|
+
*
|
|
23
|
+
* 1. **Thread request context into mongokit options.** Every domain
|
|
24
|
+
* verb that touches the DB (read or write) ends with a mongokit
|
|
25
|
+
* method whose options bag is what `multiTenantPlugin`,
|
|
26
|
+
* `softDeletePlugin`, the audit plugins, and `withTransaction`
|
|
27
|
+
* read from. {@link optsFromCtx} centralises that translation
|
|
28
|
+
* using mongokit's typed extractor — adding a new canonical field
|
|
29
|
+
* to `RevenueContext` is now a single line, not three.
|
|
30
|
+
*
|
|
31
|
+
* 2. **Dispatch domain events.** Outbox-save (session-bound when the
|
|
32
|
+
* caller threads a Mongoose `ClientSession`) followed by
|
|
33
|
+
* transport-publish, with isolated try/catch so a transport
|
|
34
|
+
* failure never aborts the business write. PACKAGE_RULES P8 / §5.5.
|
|
35
|
+
*
|
|
36
|
+
* Subclasses inject their domain-specific deps via {@link inject}; this
|
|
37
|
+
* base only requires the cross-cutting trio (events / outbox / logger)
|
|
38
|
+
* which every revenue repo has.
|
|
39
|
+
*
|
|
40
|
+
* @typeParam TDoc - The Mongoose document type the subclass operates on.
|
|
41
|
+
* @typeParam TDeps - The full deps shape (must extend `BaseRevenueRepoDeps`).
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```ts
|
|
45
|
+
* export class SubscriptionRepository extends RevenueRepositoryBase<
|
|
46
|
+
* SubscriptionDocument,
|
|
47
|
+
* SubscriptionRepoDeps
|
|
48
|
+
* > {
|
|
49
|
+
* async activate(id: string, ctx: RevenueContext = {}) {
|
|
50
|
+
* const sub = await this.getById(id, this.optsFromCtx(ctx));
|
|
51
|
+
* // ...
|
|
52
|
+
* await this.dispatch(createEvent(...), ctx);
|
|
53
|
+
* return updated;
|
|
54
|
+
* }
|
|
55
|
+
* }
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
/**
|
|
59
|
+
* Abstract base for `TransactionRepository`, `SubscriptionRepository`,
|
|
60
|
+
* `SettlementRepository`.
|
|
61
|
+
*
|
|
62
|
+
* Concrete subclasses MUST:
|
|
63
|
+
* - declare a `Deps` interface that extends {@link BaseRevenueRepoDeps}
|
|
64
|
+
* - call `super(model, plugins)` from the constructor
|
|
65
|
+
* - call `inject(deps)` once during engine boot
|
|
66
|
+
*
|
|
67
|
+
* Subclasses SHOULD use {@link optsFromCtx} for every mongokit call
|
|
68
|
+
* that takes an options bag, and {@link dispatch} for every event
|
|
69
|
+
* publish.
|
|
70
|
+
*/
|
|
71
|
+
var RevenueRepositoryBase = class extends Repository {
|
|
72
|
+
/**
|
|
73
|
+
* Subclass-specific deps. `!` because the engine wires this once via
|
|
74
|
+
* `inject(deps)` immediately after construction; calling any domain
|
|
75
|
+
* verb before injection is a programming error and the runtime
|
|
76
|
+
* will fail-loud with `Cannot read properties of undefined`.
|
|
77
|
+
*/
|
|
11
78
|
deps;
|
|
12
79
|
constructor(model, plugins = []) {
|
|
13
80
|
super(model, plugins);
|
|
14
81
|
}
|
|
82
|
+
/**
|
|
83
|
+
* Wire engine-managed deps. Called exactly once per repository
|
|
84
|
+
* instance during {@link createRevenue} bootstrap. Subclasses with
|
|
85
|
+
* extra steps (caching, prebuilding state machine maps) override and
|
|
86
|
+
* call `super.inject(deps)`.
|
|
87
|
+
*/
|
|
15
88
|
inject(deps) {
|
|
16
89
|
this.deps = deps;
|
|
17
90
|
}
|
|
18
91
|
/**
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
92
|
+
* Translate {@link RevenueContext} into a mongokit options bag.
|
|
93
|
+
*
|
|
94
|
+
* Forwards every canonical field mongokit's bundled plugins read —
|
|
95
|
+
* `organizationId` (multiTenant), `userId` / `user` (audit),
|
|
96
|
+
* `session` (transactions), `requestId` (observability) — plus
|
|
97
|
+
* the revenue-specific `_bypassTenant` flag for platform-admin
|
|
98
|
+
* cross-org reads.
|
|
99
|
+
*
|
|
100
|
+
* Pass `extra` for caller-specific options like `throwOnNotFound`,
|
|
101
|
+
* `lean`, `populate`, `select` — the spread is `extra`-first so
|
|
102
|
+
* ctx wins on a key collision (intentional: callers shouldn't
|
|
103
|
+
* be smuggling tenant fields through `extra`).
|
|
104
|
+
*
|
|
105
|
+
* @param ctx - The request-scoped revenue context.
|
|
106
|
+
* @param extra - Additional mongokit options merged in.
|
|
24
107
|
*/
|
|
25
|
-
optsFromCtx(ctx, extra = {}) {
|
|
26
|
-
const out = {
|
|
27
|
-
|
|
28
|
-
|
|
108
|
+
optsFromCtx(ctx = {}, extra = {}) {
|
|
109
|
+
const out = {
|
|
110
|
+
...extra,
|
|
111
|
+
...repoOptionsFromCtx(ctx)
|
|
112
|
+
};
|
|
113
|
+
if (ctx._bypassTenant === true) out._bypassTenant = true;
|
|
29
114
|
return out;
|
|
30
115
|
}
|
|
31
116
|
/**
|
|
117
|
+
* Persist an event to the host outbox, then publish to the in-process
|
|
118
|
+
* transport. The two sides have asymmetric failure handling — see
|
|
119
|
+
* PACKAGE_RULES §P8.
|
|
120
|
+
*
|
|
121
|
+
* When `ctx.session` is set, the outbox `save` runs inside the same
|
|
122
|
+
* Mongoose transaction (true P8 session-bound write); when absent, the
|
|
123
|
+
* outbox row lands after commit — still durable via the host's relay,
|
|
124
|
+
* with only a small at-most-once window on process crash.
|
|
125
|
+
*
|
|
126
|
+
* 1. **`outbox.save` failures PROPAGATE.** If we can't durably record
|
|
127
|
+
* the event, the caller's transaction MUST roll back so the
|
|
128
|
+
* business doc and the event row land atomically (or neither
|
|
129
|
+
* lands). Swallowing a save failure breaks the transactional-
|
|
130
|
+
* outbox correctness argument — the parent doc would land while
|
|
131
|
+
* the event vanishes.
|
|
132
|
+
*
|
|
133
|
+
* 2. **`events.publish` failures are SWALLOWED.** The host's outbox
|
|
134
|
+
* relay re-publishes from the durable row on its next poll. Even
|
|
135
|
+
* without an outbox, in-process subscribers shouldn't be able to
|
|
136
|
+
* break the business operation — they're best-effort consumers.
|
|
137
|
+
*
|
|
138
|
+
* @param event - Pre-built domain event (use `createEvent(REVENUE_EVENTS.X, payload, ctx, meta)`).
|
|
139
|
+
* @param ctx - The same context that produced the business write.
|
|
140
|
+
*/
|
|
141
|
+
async dispatch(event, ctx = {}) {
|
|
142
|
+
if (this.deps.outbox) try {
|
|
143
|
+
await this.deps.outbox.save(event, ctx.session !== void 0 ? { session: ctx.session } : {});
|
|
144
|
+
} catch (err) {
|
|
145
|
+
this.deps.logger?.error("[revenue] outbox.save failed for", event.type, err);
|
|
146
|
+
throw err;
|
|
147
|
+
}
|
|
148
|
+
try {
|
|
149
|
+
await this.deps.events.publish(event);
|
|
150
|
+
} catch (err) {
|
|
151
|
+
this.deps.logger?.error("[revenue] events.publish failed for", event.type, err);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
//#endregion
|
|
157
|
+
//#region src/repositories/transaction.repository.ts
|
|
158
|
+
var TransactionRepository = class extends RevenueRepositoryBase {
|
|
159
|
+
constructor(model, plugins = []) {
|
|
160
|
+
super(model, plugins);
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
32
163
|
* Save an event to the host-owned outbox, session-bound when available.
|
|
33
164
|
*
|
|
34
|
-
*
|
|
35
|
-
* business write (P8 true session-bound write)
|
|
36
|
-
*
|
|
37
|
-
*
|
|
165
|
+
* Called INSIDE a `withTransaction` body. The outbox row commits
|
|
166
|
+
* atomically with the business write (P8 true session-bound write) —
|
|
167
|
+
* if outbox.save fails, this method re-throws so `withTransaction`
|
|
168
|
+
* rolls the parent write back. Without propagation, the parent doc
|
|
169
|
+
* would commit while the event row vanishes, defeating the whole
|
|
170
|
+
* transactional-outbox correctness argument.
|
|
38
171
|
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
172
|
+
* Logging happens before the re-throw so the failure surfaces in
|
|
173
|
+
* observability without losing the original stack trace.
|
|
41
174
|
*/
|
|
42
175
|
async saveToOutbox(event, session) {
|
|
43
176
|
if (!this.deps.outbox) return;
|
|
@@ -45,12 +178,14 @@ var TransactionRepository = class extends Repository {
|
|
|
45
178
|
await this.deps.outbox.save(event, session !== void 0 ? { session } : {});
|
|
46
179
|
} catch (err) {
|
|
47
180
|
this.deps.logger?.error("[revenue] outbox.save failed for", event.type, err);
|
|
181
|
+
throw err;
|
|
48
182
|
}
|
|
49
183
|
}
|
|
50
184
|
/**
|
|
51
185
|
* Publish an event to the in-process `EventTransport` after commit.
|
|
52
|
-
* Transport failure is logged — the host relay
|
|
53
|
-
* the outbox, so in-process
|
|
186
|
+
* Transport failure is logged and swallowed — the host relay re-delivers
|
|
187
|
+
* from the durable outbox row on the next poll, so in-process
|
|
188
|
+
* subscribers missing an event is recoverable. Best-effort by design.
|
|
54
189
|
*/
|
|
55
190
|
async publishToTransport(event) {
|
|
56
191
|
try {
|
|
@@ -59,16 +194,6 @@ var TransactionRepository = class extends Repository {
|
|
|
59
194
|
this.deps.logger?.error("[revenue] events.publish failed for", event.type, err);
|
|
60
195
|
}
|
|
61
196
|
}
|
|
62
|
-
/**
|
|
63
|
-
* Non-transactional dispatch (used by verbs that don't open their own
|
|
64
|
-
* `withTransaction` block): outbox.save (session-bound when ctx provides
|
|
65
|
-
* one) → transport.publish. Matches arc's EventOutbox + MemoryEventTransport
|
|
66
|
-
* wiring bit-for-bit.
|
|
67
|
-
*/
|
|
68
|
-
async dispatch(event, ctx = {}) {
|
|
69
|
-
await this.saveToOutbox(event, ctx.session);
|
|
70
|
-
await this.publishToTransport(event);
|
|
71
|
-
}
|
|
72
197
|
/** Creates transaction + calls provider. Returns the created transaction doc. */
|
|
73
198
|
async createPaymentIntent(params, ctx = {}) {
|
|
74
199
|
const currency = params.currency ?? this.deps.defaultCurrency;
|
|
@@ -87,6 +212,7 @@ var TransactionRepository = class extends Repository {
|
|
|
87
212
|
amount: params.amount,
|
|
88
213
|
currency
|
|
89
214
|
},
|
|
215
|
+
methodKind: params.methodKind,
|
|
90
216
|
metadata: params.metadata,
|
|
91
217
|
...params.paymentData
|
|
92
218
|
});
|
|
@@ -113,6 +239,7 @@ var TransactionRepository = class extends Repository {
|
|
|
113
239
|
tax: 0,
|
|
114
240
|
net: params.amount - (commission?.gatewayFeeAmount ?? 0),
|
|
115
241
|
method: params.gateway,
|
|
242
|
+
methodKind: params.methodKind,
|
|
116
243
|
status: params.amount === 0 ? TRANSACTION_STATUS.VERIFIED : TRANSACTION_STATUS.PENDING,
|
|
117
244
|
gateway: gatewayData,
|
|
118
245
|
commission: commission ?? void 0,
|
|
@@ -217,6 +344,7 @@ var TransactionRepository = class extends Repository {
|
|
|
217
344
|
tax: reversedTax?.taxAmount ?? 0,
|
|
218
345
|
net: refundAmount - (reversedCommission?.gatewayFeeAmount ?? 0) - (reversedTax?.taxAmount ?? 0),
|
|
219
346
|
method: transaction.method,
|
|
347
|
+
methodKind: transaction.methodKind,
|
|
220
348
|
status: TRANSACTION_STATUS.VERIFIED,
|
|
221
349
|
gateway: transaction.gateway,
|
|
222
350
|
commission: reversedCommission ?? void 0,
|
|
@@ -267,10 +395,10 @@ var TransactionRepository = class extends Repository {
|
|
|
267
395
|
processedAt: /* @__PURE__ */ new Date(),
|
|
268
396
|
data: webhookEvent.data
|
|
269
397
|
};
|
|
270
|
-
const updated = await this.
|
|
398
|
+
const updated = await this.findOneAndUpdate({
|
|
271
399
|
_id: transaction._id,
|
|
272
400
|
"webhook.eventId": { $ne: webhookEvent.id }
|
|
273
|
-
}, { $set: { webhook: nextWebhook } }, { returnDocument: "after" })
|
|
401
|
+
}, { $set: { webhook: nextWebhook } }, { returnDocument: "after" });
|
|
274
402
|
if (!updated) return await this.getByQuery({ _id: transaction._id }, readOpts) ?? transaction;
|
|
275
403
|
await this.dispatch(createEvent(REVENUE_EVENTS.WEBHOOK_PROCESSED, {
|
|
276
404
|
webhookType: webhookEvent.type,
|
|
@@ -354,6 +482,7 @@ var TransactionRepository = class extends Repository {
|
|
|
354
482
|
tax: 0,
|
|
355
483
|
net: releaseAmount,
|
|
356
484
|
method: transaction.method,
|
|
485
|
+
methodKind: transaction.methodKind,
|
|
357
486
|
status: TRANSACTION_STATUS.VERIFIED,
|
|
358
487
|
relatedTransactionId: transaction._id,
|
|
359
488
|
sourceId: transaction.sourceId,
|
|
@@ -408,6 +537,7 @@ var TransactionRepository = class extends Repository {
|
|
|
408
537
|
tax: 0,
|
|
409
538
|
net: s.netAmount,
|
|
410
539
|
method: transaction.method,
|
|
540
|
+
methodKind: transaction.methodKind,
|
|
411
541
|
status: TRANSACTION_STATUS.VERIFIED,
|
|
412
542
|
relatedTransactionId: transaction._id,
|
|
413
543
|
sourceId: transaction.sourceId,
|
|
@@ -432,6 +562,7 @@ var TransactionRepository = class extends Repository {
|
|
|
432
562
|
tax: 0,
|
|
433
563
|
net: orgPayout,
|
|
434
564
|
method: transaction.method,
|
|
565
|
+
methodKind: transaction.methodKind,
|
|
435
566
|
status: TRANSACTION_STATUS.VERIFIED,
|
|
436
567
|
relatedTransactionId: transaction._id,
|
|
437
568
|
verifiedAt: /* @__PURE__ */ new Date()
|
|
@@ -479,6 +610,7 @@ var TransactionRepository = class extends Repository {
|
|
|
479
610
|
* `source` (provenance — `'plaid'`, `'ofx'`, …).
|
|
480
611
|
*/
|
|
481
612
|
async import(rows, opts, ctx = {}) {
|
|
613
|
+
if (!opts.methodKind) throw new BankFeedImportError("`opts.methodKind` is required on TransactionRepository.import() — pick the canonical PaymentMethodKind for the source (e.g. `'bank_transfer'` for Plaid/OFX, `'card'` for a Stripe balance, `'wallet'` for PayPal, `'cryptocurrency'` for an exchange).");
|
|
482
614
|
const startedAt = Date.now();
|
|
483
615
|
if (!Array.isArray(rows) || rows.length === 0) return {
|
|
484
616
|
inserted: 0,
|
|
@@ -538,6 +670,7 @@ var TransactionRepository = class extends Repository {
|
|
|
538
670
|
source: opts.source,
|
|
539
671
|
type: "bank_feed",
|
|
540
672
|
tags: ["bank_feed", opts.source],
|
|
673
|
+
methodKind: opts.methodKind,
|
|
541
674
|
fee: 0,
|
|
542
675
|
tax: 0,
|
|
543
676
|
net: absoluteAmount,
|
|
@@ -622,7 +755,8 @@ var TransactionRepository = class extends Repository {
|
|
|
622
755
|
if (page.transactions && page.transactions.length > 0) {
|
|
623
756
|
const report = await this.import(page.transactions, {
|
|
624
757
|
bankAccountId: params.bankAccountId,
|
|
625
|
-
source: providerName
|
|
758
|
+
source: providerName,
|
|
759
|
+
methodKind: params.methodKind
|
|
626
760
|
}, ctx);
|
|
627
761
|
totalImported += report.inserted;
|
|
628
762
|
totalUpdated += report.updated;
|
|
@@ -664,7 +798,8 @@ var TransactionRepository = class extends Repository {
|
|
|
664
798
|
});
|
|
665
799
|
return this.import(parsed.transactions, {
|
|
666
800
|
bankAccountId: upload.bankAccountId,
|
|
667
|
-
source: providerName
|
|
801
|
+
source: providerName,
|
|
802
|
+
methodKind: upload.methodKind
|
|
668
803
|
}, ctx);
|
|
669
804
|
}
|
|
670
805
|
/**
|
|
@@ -687,6 +822,7 @@ var TransactionRepository = class extends Repository {
|
|
|
687
822
|
tax: 0,
|
|
688
823
|
net: data.amount,
|
|
689
824
|
method: "manual",
|
|
825
|
+
methodKind: data.methodKind,
|
|
690
826
|
status: initialStatusFor(TRANSACTION_KIND.MANUAL),
|
|
691
827
|
source: "manual",
|
|
692
828
|
...data.description !== void 0 ? { description: data.description } : {},
|
|
@@ -701,6 +837,56 @@ var TransactionRepository = class extends Repository {
|
|
|
701
837
|
}, this.optsFromCtx(ctx));
|
|
702
838
|
}
|
|
703
839
|
/**
|
|
840
|
+
* Backfill the `methodKind` on a Transaction created with kind
|
|
841
|
+
* unknown — the canonical use case is hosted-checkout (Stripe
|
|
842
|
+
* Checkout, PayPal redirect, Razorpay Checkout) where the customer
|
|
843
|
+
* picks their payment method on the gateway's UI, AFTER the host has
|
|
844
|
+
* already created the PaymentIntent + Transaction with
|
|
845
|
+
* `methodKind: 'other'`.
|
|
846
|
+
*
|
|
847
|
+
* Call this from your verification / webhook handler once you know
|
|
848
|
+
* the customer's actual choice — e.g. inside
|
|
849
|
+
* `payment_intent.succeeded`:
|
|
850
|
+
*
|
|
851
|
+
* ```ts
|
|
852
|
+
* await transactionRepository.backfillMethodKind(
|
|
853
|
+
* tx._id,
|
|
854
|
+
* stripePaymentIntentToKind(event.data.object),
|
|
855
|
+
* ctx,
|
|
856
|
+
* );
|
|
857
|
+
* ```
|
|
858
|
+
*
|
|
859
|
+
* **Guard rule.** Atomic CAS — succeeds only when the doc has
|
|
860
|
+
* `methodKind === 'other'` AND `status === 'pending'`. Any other
|
|
861
|
+
* combination throws `MethodKindLockedError` (HTTP 409): once a
|
|
862
|
+
* transaction has a specific kind (or has settled past pending),
|
|
863
|
+
* silently overwriting it would corrupt downstream analytics and
|
|
864
|
+
* accounting reports.
|
|
865
|
+
*
|
|
866
|
+
* Emits `revenue:transaction.updated` with `changedFields:
|
|
867
|
+
* ['methodKind']` so subscribers can re-bucket the row.
|
|
868
|
+
*/
|
|
869
|
+
async backfillMethodKind(transactionId, methodKind, ctx = {}) {
|
|
870
|
+
const updated = await this.findOneAndUpdate({
|
|
871
|
+
_id: transactionId,
|
|
872
|
+
methodKind: "other",
|
|
873
|
+
status: TRANSACTION_STATUS.PENDING
|
|
874
|
+
}, { $set: { methodKind } }, { returnDocument: "after" });
|
|
875
|
+
if (!updated) {
|
|
876
|
+
const existing = await this.getById(transactionId, this.optsFromCtx(ctx, { throwOnNotFound: false }));
|
|
877
|
+
if (!existing) throw new TransactionNotFoundError(transactionId);
|
|
878
|
+
throw new MethodKindLockedError(transactionId, existing.methodKind ?? "unknown", existing.status ?? "unknown");
|
|
879
|
+
}
|
|
880
|
+
await this.dispatch(createEvent(REVENUE_EVENTS.TRANSACTION_UPDATED, {
|
|
881
|
+
transaction: updated,
|
|
882
|
+
changedFields: ["methodKind"]
|
|
883
|
+
}, ctx, {
|
|
884
|
+
resource: "transaction",
|
|
885
|
+
resourceId: updated.publicId
|
|
886
|
+
}), ctx);
|
|
887
|
+
return updated;
|
|
888
|
+
}
|
|
889
|
+
/**
|
|
704
890
|
* Match a bank-feed / manual transaction to GL accounts, optionally
|
|
705
891
|
* cross-linking to an upstream payment-flow transaction.
|
|
706
892
|
*
|
|
@@ -937,20 +1123,25 @@ var TransactionRepository = class extends Repository {
|
|
|
937
1123
|
const minAmount = filter.amount * (1 - pct);
|
|
938
1124
|
const maxAmount = filter.amount * (1 + pct);
|
|
939
1125
|
const targetKind = filter.kind ?? TRANSACTION_KIND.PAYMENT_FLOW;
|
|
1126
|
+
const validStatuses = targetKind === TRANSACTION_KIND.PAYMENT_FLOW ? [TRANSACTION_STATUS.VERIFIED, TRANSACTION_STATUS.COMPLETED] : [TRANSACTION_STATUS.IMPORTED, TRANSACTION_STATUS.MATCHED];
|
|
1127
|
+
const dateClauses = targetKind === TRANSACTION_KIND.BANK_FEED ? [{ postedDate: {
|
|
1128
|
+
$gte: start,
|
|
1129
|
+
$lte: end
|
|
1130
|
+
} }] : [{ verifiedAt: {
|
|
1131
|
+
$gte: start,
|
|
1132
|
+
$lte: end
|
|
1133
|
+
} }, { createdAt: {
|
|
1134
|
+
$gte: start,
|
|
1135
|
+
$lte: end
|
|
1136
|
+
} }];
|
|
940
1137
|
const query = {
|
|
941
1138
|
kind: targetKind,
|
|
942
|
-
status: { $in:
|
|
1139
|
+
status: { $in: validStatuses },
|
|
943
1140
|
amount: {
|
|
944
1141
|
$gte: minAmount,
|
|
945
1142
|
$lte: maxAmount
|
|
946
1143
|
},
|
|
947
|
-
$or:
|
|
948
|
-
$gte: start,
|
|
949
|
-
$lte: end
|
|
950
|
-
} }, { createdAt: {
|
|
951
|
-
$gte: start,
|
|
952
|
-
$lte: end
|
|
953
|
-
} }]
|
|
1144
|
+
$or: dateClauses
|
|
954
1145
|
};
|
|
955
1146
|
if (filter.currency !== void 0) query.currency = filter.currency;
|
|
956
1147
|
if (filter.counterpartyName !== void 0) query["counterparty.name"] = {
|
|
@@ -1004,41 +1195,48 @@ function escapeRegex(input) {
|
|
|
1004
1195
|
//#endregion
|
|
1005
1196
|
//#region src/repositories/subscription.repository.ts
|
|
1006
1197
|
/**
|
|
1007
|
-
* SubscriptionRepository — data layer + domain verbs
|
|
1198
|
+
* SubscriptionRepository — data layer + domain verbs for the recurring-
|
|
1199
|
+
* billing lifecycle.
|
|
1200
|
+
*
|
|
1201
|
+
* **CRUD inherited** from mongokit (via {@link RevenueRepositoryBase}):
|
|
1202
|
+
* `getById`, `getByQuery`, `getAll`, `create`, `update`, `delete`,
|
|
1203
|
+
* `findOneAndUpdate`, `count`, `exists`, `claim`, `cursor`, `updateMany`,
|
|
1204
|
+
* `deleteMany`. All participate in `multiTenantPlugin` scope filtering
|
|
1205
|
+
* when wired.
|
|
1008
1206
|
*
|
|
1009
|
-
*
|
|
1207
|
+
* **Domain verbs (state transitions):** `activate`, `cancel`, `pause`,
|
|
1208
|
+
* `resume`. Each runs the state-machine guard (`SUBSCRIPTION_STATE_MACHINE`
|
|
1209
|
+
* — invalid transitions throw, never silently no-op), persists the
|
|
1210
|
+
* resulting writes through {@link RevenueRepositoryBase.optsFromCtx} so
|
|
1211
|
+
* tenant scope is preserved end-to-end, then dispatches its
|
|
1212
|
+
* `revenue:subscription.*` event via {@link RevenueRepositoryBase.dispatch}.
|
|
1010
1213
|
*
|
|
1011
|
-
*
|
|
1012
|
-
*
|
|
1013
|
-
*
|
|
1014
|
-
*
|
|
1214
|
+
* **Multi-tenant correctness.** Every internal `getById`/`update` call
|
|
1215
|
+
* threads `ctx.organizationId` through `optsFromCtx(ctx)`. Without this
|
|
1216
|
+
* threading the inner read would either throw
|
|
1217
|
+
* `Missing 'organizationId' in context` (when `multiTenantPlugin` is
|
|
1218
|
+
* required) or — worse — return another tenant's subscription matching
|
|
1219
|
+
* the same `_id` shape (when `required: false`). 2.1.0 had this bug; 2.1.1+
|
|
1220
|
+
* is correct.
|
|
1221
|
+
*
|
|
1222
|
+
* @example Activate a pending sub
|
|
1223
|
+
* ```ts
|
|
1224
|
+
* const ctx = { organizationId: 'org_42', actorId: 'user_99' };
|
|
1225
|
+
* const sub = await subRepo.create(
|
|
1226
|
+
* { customerId: 'cust_1', planKey: 'monthly', amount: 999, currency: 'USD',
|
|
1227
|
+
* status: SUBSCRIPTION_STATUS.PENDING, isActive: false },
|
|
1228
|
+
* ctx,
|
|
1229
|
+
* );
|
|
1230
|
+
* await subRepo.activate(String(sub._id), {}, ctx);
|
|
1231
|
+
* ```
|
|
1015
1232
|
*/
|
|
1016
|
-
var SubscriptionRepository = class extends
|
|
1017
|
-
deps;
|
|
1233
|
+
var SubscriptionRepository = class extends RevenueRepositoryBase {
|
|
1018
1234
|
constructor(model, plugins = []) {
|
|
1019
1235
|
super(model, plugins);
|
|
1020
1236
|
}
|
|
1021
|
-
inject(deps) {
|
|
1022
|
-
this.deps = deps;
|
|
1023
|
-
}
|
|
1024
|
-
/**
|
|
1025
|
-
* Host-owned outbox save → in-process transport publish (PACKAGE_RULES P8).
|
|
1026
|
-
* Session-bound when `ctx.session` is present (atomic outbox row write).
|
|
1027
|
-
*/
|
|
1028
|
-
async dispatch(event, ctx = {}) {
|
|
1029
|
-
if (this.deps.outbox) try {
|
|
1030
|
-
await this.deps.outbox.save(event, ctx.session !== void 0 ? { session: ctx.session } : {});
|
|
1031
|
-
} catch (err) {
|
|
1032
|
-
this.deps.logger?.error("[revenue] outbox.save failed for", event.type, err);
|
|
1033
|
-
}
|
|
1034
|
-
try {
|
|
1035
|
-
await this.deps.events.publish(event);
|
|
1036
|
-
} catch (err) {
|
|
1037
|
-
this.deps.logger?.error("[revenue] events.publish failed for", event.type, err);
|
|
1038
|
-
}
|
|
1039
|
-
}
|
|
1040
1237
|
async activate(subscriptionId, options = {}, ctx = {}) {
|
|
1041
|
-
const
|
|
1238
|
+
const opts = this.optsFromCtx(ctx);
|
|
1239
|
+
const sub = await this.getById(subscriptionId, opts);
|
|
1042
1240
|
if (!sub) throw new SubscriptionNotFoundError(subscriptionId);
|
|
1043
1241
|
SUBSCRIPTION_STATE_MACHINE.validate(sub.status, SUBSCRIPTION_STATUS.ACTIVE, subscriptionId);
|
|
1044
1242
|
const now = options.timestamp ?? /* @__PURE__ */ new Date();
|
|
@@ -1052,7 +1250,7 @@ var SubscriptionRepository = class extends Repository {
|
|
|
1052
1250
|
isActive: true,
|
|
1053
1251
|
activatedAt: now,
|
|
1054
1252
|
endDate
|
|
1055
|
-
}, { throwOnNotFound: true });
|
|
1253
|
+
}, this.optsFromCtx(ctx, { throwOnNotFound: true }));
|
|
1056
1254
|
if (!updated) throw new SubscriptionNotFoundError(subscriptionId);
|
|
1057
1255
|
await this.dispatch(createEvent(REVENUE_EVENTS.SUBSCRIPTION_ACTIVATED, {
|
|
1058
1256
|
subscription: updated,
|
|
@@ -1064,7 +1262,8 @@ var SubscriptionRepository = class extends Repository {
|
|
|
1064
1262
|
return updated;
|
|
1065
1263
|
}
|
|
1066
1264
|
async cancel(subscriptionId, options = {}, ctx = {}) {
|
|
1067
|
-
const
|
|
1265
|
+
const opts = this.optsFromCtx(ctx);
|
|
1266
|
+
const sub = await this.getById(subscriptionId, opts);
|
|
1068
1267
|
if (!sub) throw new SubscriptionNotFoundError(subscriptionId);
|
|
1069
1268
|
SUBSCRIPTION_STATE_MACHINE.validate(sub.status, SUBSCRIPTION_STATUS.CANCELLED, subscriptionId);
|
|
1070
1269
|
const updates = {
|
|
@@ -1079,7 +1278,7 @@ var SubscriptionRepository = class extends Repository {
|
|
|
1079
1278
|
updates.isActive = sub.isActive;
|
|
1080
1279
|
delete updates.canceledAt;
|
|
1081
1280
|
}
|
|
1082
|
-
const updated = await this.update(subscriptionId, updates, { throwOnNotFound: true });
|
|
1281
|
+
const updated = await this.update(subscriptionId, updates, this.optsFromCtx(ctx, { throwOnNotFound: true }));
|
|
1083
1282
|
if (!updated) throw new SubscriptionNotFoundError(subscriptionId);
|
|
1084
1283
|
await this.dispatch(createEvent(REVENUE_EVENTS.SUBSCRIPTION_CANCELLED, {
|
|
1085
1284
|
subscription: updated,
|
|
@@ -1092,7 +1291,8 @@ var SubscriptionRepository = class extends Repository {
|
|
|
1092
1291
|
return updated;
|
|
1093
1292
|
}
|
|
1094
1293
|
async pause(subscriptionId, options = {}, ctx = {}) {
|
|
1095
|
-
const
|
|
1294
|
+
const opts = this.optsFromCtx(ctx);
|
|
1295
|
+
const sub = await this.getById(subscriptionId, opts);
|
|
1096
1296
|
if (!sub) throw new SubscriptionNotFoundError(subscriptionId);
|
|
1097
1297
|
SUBSCRIPTION_STATE_MACHINE.validate(sub.status, SUBSCRIPTION_STATUS.PAUSED, subscriptionId);
|
|
1098
1298
|
const updated = await this.update(subscriptionId, {
|
|
@@ -1100,7 +1300,7 @@ var SubscriptionRepository = class extends Repository {
|
|
|
1100
1300
|
isActive: false,
|
|
1101
1301
|
pausedAt: /* @__PURE__ */ new Date(),
|
|
1102
1302
|
pauseReason: options.reason
|
|
1103
|
-
}, { throwOnNotFound: true });
|
|
1303
|
+
}, this.optsFromCtx(ctx, { throwOnNotFound: true }));
|
|
1104
1304
|
if (!updated) throw new SubscriptionNotFoundError(subscriptionId);
|
|
1105
1305
|
await this.dispatch(createEvent(REVENUE_EVENTS.SUBSCRIPTION_PAUSED, {
|
|
1106
1306
|
subscription: updated,
|
|
@@ -1112,7 +1312,8 @@ var SubscriptionRepository = class extends Repository {
|
|
|
1112
1312
|
return updated;
|
|
1113
1313
|
}
|
|
1114
1314
|
async resume(subscriptionId, options = {}, ctx = {}) {
|
|
1115
|
-
const
|
|
1315
|
+
const opts = this.optsFromCtx(ctx);
|
|
1316
|
+
const sub = await this.getById(subscriptionId, opts);
|
|
1116
1317
|
if (!sub) throw new SubscriptionNotFoundError(subscriptionId);
|
|
1117
1318
|
SUBSCRIPTION_STATE_MACHINE.validate(sub.status, SUBSCRIPTION_STATUS.ACTIVE, subscriptionId);
|
|
1118
1319
|
const updates = {
|
|
@@ -1123,7 +1324,7 @@ var SubscriptionRepository = class extends Repository {
|
|
|
1123
1324
|
const pauseDuration = Date.now() - sub.pausedAt.getTime();
|
|
1124
1325
|
updates.endDate = new Date(sub.endDate.getTime() + pauseDuration);
|
|
1125
1326
|
}
|
|
1126
|
-
const updated = await this.update(subscriptionId, updates, { throwOnNotFound: true });
|
|
1327
|
+
const updated = await this.update(subscriptionId, updates, this.optsFromCtx(ctx, { throwOnNotFound: true }));
|
|
1127
1328
|
if (!updated) throw new SubscriptionNotFoundError(subscriptionId);
|
|
1128
1329
|
await this.dispatch(createEvent(REVENUE_EVENTS.SUBSCRIPTION_RESUMED, {
|
|
1129
1330
|
subscription: updated,
|
|
@@ -1139,45 +1340,34 @@ var SubscriptionRepository = class extends Repository {
|
|
|
1139
1340
|
//#endregion
|
|
1140
1341
|
//#region src/repositories/settlement.repository.ts
|
|
1141
1342
|
/**
|
|
1142
|
-
* SettlementRepository —
|
|
1343
|
+
* SettlementRepository — payouts to recipients (organizations, vendors,
|
|
1344
|
+
* affiliates).
|
|
1143
1345
|
*
|
|
1144
|
-
* CRUD inherited
|
|
1346
|
+
* **CRUD inherited** via {@link RevenueRepositoryBase}. **Domain verbs:**
|
|
1347
|
+
* `schedule`, `processPending`, `complete`, `fail`. State machine:
|
|
1348
|
+
* `pending → processing → completed | failed`; `failed` with
|
|
1349
|
+
* `retry: true` resets to `pending` with a delayed `scheduledAt`.
|
|
1145
1350
|
*
|
|
1146
|
-
*
|
|
1147
|
-
*
|
|
1148
|
-
*
|
|
1351
|
+
* **Multi-tenant correctness.** Every read/write threads `ctx` through
|
|
1352
|
+
* {@link RevenueRepositoryBase.optsFromCtx} so `multiTenantPlugin`
|
|
1353
|
+
* scope filters apply. 2.1.0 had several `getById`/`update` calls that
|
|
1354
|
+
* dropped ctx — fixed in 2.1.1+.
|
|
1355
|
+
*
|
|
1356
|
+
* Bridges (`ledger`, `notification`) fire on `complete()` so a host
|
|
1357
|
+
* can pin double-entry book-keeping or push a "you got paid" email
|
|
1358
|
+
* without the repo knowing about either subsystem.
|
|
1149
1359
|
*/
|
|
1150
|
-
var SettlementRepository = class extends
|
|
1151
|
-
deps;
|
|
1360
|
+
var SettlementRepository = class extends RevenueRepositoryBase {
|
|
1152
1361
|
constructor(model, plugins = []) {
|
|
1153
1362
|
super(model, plugins);
|
|
1154
1363
|
}
|
|
1155
|
-
inject(deps) {
|
|
1156
|
-
this.deps = deps;
|
|
1157
|
-
}
|
|
1158
|
-
/**
|
|
1159
|
-
* Host-owned outbox save → in-process transport publish (PACKAGE_RULES P8).
|
|
1160
|
-
* Session-bound when `ctx.session` is present (atomic outbox row write).
|
|
1161
|
-
*/
|
|
1162
|
-
async dispatch(event, ctx = {}) {
|
|
1163
|
-
if (this.deps.outbox) try {
|
|
1164
|
-
await this.deps.outbox.save(event, ctx.session !== void 0 ? { session: ctx.session } : {});
|
|
1165
|
-
} catch (err) {
|
|
1166
|
-
this.deps.logger?.error("[revenue] outbox.save failed for", event.type, err);
|
|
1167
|
-
}
|
|
1168
|
-
try {
|
|
1169
|
-
await this.deps.events.publish(event);
|
|
1170
|
-
} catch (err) {
|
|
1171
|
-
this.deps.logger?.error("[revenue] events.publish failed for", event.type, err);
|
|
1172
|
-
}
|
|
1173
|
-
}
|
|
1174
1364
|
async schedule(params, ctx = {}) {
|
|
1175
1365
|
const settlement = await this.create({
|
|
1176
1366
|
...params,
|
|
1177
1367
|
status: SETTLEMENT_STATUS.PENDING,
|
|
1178
1368
|
scheduledAt: params.scheduledAt ?? /* @__PURE__ */ new Date(),
|
|
1179
1369
|
retryCount: 0
|
|
1180
|
-
});
|
|
1370
|
+
}, this.optsFromCtx(ctx));
|
|
1181
1371
|
await this.dispatch(createEvent(REVENUE_EVENTS.SETTLEMENT_SCHEDULED, {
|
|
1182
1372
|
settlement,
|
|
1183
1373
|
scheduledAt: settlement.scheduledAt
|
|
@@ -1198,7 +1388,7 @@ var SettlementRepository = class extends Repository {
|
|
|
1198
1388
|
filters: query,
|
|
1199
1389
|
limit: options.limit ?? 50,
|
|
1200
1390
|
sort: { scheduledAt: 1 }
|
|
1201
|
-
})).data ?? [];
|
|
1391
|
+
}, this.optsFromCtx(ctx))).data ?? [];
|
|
1202
1392
|
if (options.dryRun) return {
|
|
1203
1393
|
processed: pending.length,
|
|
1204
1394
|
succeeded: 0,
|
|
@@ -1206,7 +1396,7 @@ var SettlementRepository = class extends Repository {
|
|
|
1206
1396
|
settlements: pending,
|
|
1207
1397
|
errors: []
|
|
1208
1398
|
};
|
|
1209
|
-
const
|
|
1399
|
+
const out = {
|
|
1210
1400
|
processed: 0,
|
|
1211
1401
|
succeeded: 0,
|
|
1212
1402
|
failed: 0,
|
|
@@ -1219,9 +1409,9 @@ var SettlementRepository = class extends Repository {
|
|
|
1219
1409
|
await this.update(settlement._id, {
|
|
1220
1410
|
status: SETTLEMENT_STATUS.PROCESSING,
|
|
1221
1411
|
processedAt: /* @__PURE__ */ new Date()
|
|
1222
|
-
});
|
|
1223
|
-
|
|
1224
|
-
|
|
1412
|
+
}, this.optsFromCtx(ctx));
|
|
1413
|
+
out.succeeded++;
|
|
1414
|
+
out.settlements.push(settlement);
|
|
1225
1415
|
await this.dispatch(createEvent(REVENUE_EVENTS.SETTLEMENT_PROCESSING, {
|
|
1226
1416
|
settlement,
|
|
1227
1417
|
processedAt: /* @__PURE__ */ new Date()
|
|
@@ -1230,18 +1420,19 @@ var SettlementRepository = class extends Repository {
|
|
|
1230
1420
|
resourceId: settlement.publicId
|
|
1231
1421
|
}), ctx);
|
|
1232
1422
|
} catch (err) {
|
|
1233
|
-
|
|
1234
|
-
|
|
1423
|
+
out.failed++;
|
|
1424
|
+
out.errors.push({
|
|
1235
1425
|
settlementId: settlement._id,
|
|
1236
1426
|
error: err
|
|
1237
1427
|
});
|
|
1238
1428
|
}
|
|
1239
|
-
|
|
1429
|
+
out.processed++;
|
|
1240
1430
|
}
|
|
1241
|
-
return
|
|
1431
|
+
return out;
|
|
1242
1432
|
}
|
|
1243
1433
|
async complete(settlementId, details = {}, ctx = {}) {
|
|
1244
|
-
const
|
|
1434
|
+
const opts = this.optsFromCtx(ctx);
|
|
1435
|
+
const settlement = await this.getById(settlementId, opts);
|
|
1245
1436
|
if (!settlement) throw new SettlementNotFoundError(settlementId);
|
|
1246
1437
|
SETTLEMENT_STATE_MACHINE.validate(settlement.status, SETTLEMENT_STATUS.COMPLETED, settlementId);
|
|
1247
1438
|
const updates = {
|
|
@@ -1263,7 +1454,7 @@ var SettlementRepository = class extends Repository {
|
|
|
1263
1454
|
transactionHash: details.transactionHash,
|
|
1264
1455
|
transferredAt: details.transferredAt ?? /* @__PURE__ */ new Date()
|
|
1265
1456
|
};
|
|
1266
|
-
const updated = await this.update(settlementId, updates, { throwOnNotFound: true });
|
|
1457
|
+
const updated = await this.update(settlementId, updates, this.optsFromCtx(ctx, { throwOnNotFound: true }));
|
|
1267
1458
|
if (!updated) throw new SettlementNotFoundError(settlementId);
|
|
1268
1459
|
await this.deps.bridges.ledger?.onSettlementCompleted?.(updated, ctx);
|
|
1269
1460
|
await this.deps.bridges.notification?.onSettlementCompleted?.(updated, ctx);
|
|
@@ -1277,7 +1468,8 @@ var SettlementRepository = class extends Repository {
|
|
|
1277
1468
|
return updated;
|
|
1278
1469
|
}
|
|
1279
1470
|
async fail(settlementId, reason, options = {}, ctx = {}) {
|
|
1280
|
-
const
|
|
1471
|
+
const opts = this.optsFromCtx(ctx);
|
|
1472
|
+
const settlement = await this.getById(settlementId, opts);
|
|
1281
1473
|
if (!settlement) throw new SettlementNotFoundError(settlementId);
|
|
1282
1474
|
if (options.retry) await this.update(settlementId, {
|
|
1283
1475
|
status: SETTLEMENT_STATUS.PENDING,
|
|
@@ -1285,7 +1477,7 @@ var SettlementRepository = class extends Repository {
|
|
|
1285
1477
|
failureReason: reason,
|
|
1286
1478
|
failureCode: options.code,
|
|
1287
1479
|
scheduledAt: new Date(Date.now() + 36e5)
|
|
1288
|
-
});
|
|
1480
|
+
}, opts);
|
|
1289
1481
|
else {
|
|
1290
1482
|
SETTLEMENT_STATE_MACHINE.validate(settlement.status, SETTLEMENT_STATUS.FAILED, settlementId);
|
|
1291
1483
|
await this.update(settlementId, {
|
|
@@ -1293,9 +1485,9 @@ var SettlementRepository = class extends Repository {
|
|
|
1293
1485
|
failedAt: /* @__PURE__ */ new Date(),
|
|
1294
1486
|
failureReason: reason,
|
|
1295
1487
|
failureCode: options.code
|
|
1296
|
-
});
|
|
1488
|
+
}, opts);
|
|
1297
1489
|
}
|
|
1298
|
-
const updated = await this.getById(settlementId);
|
|
1490
|
+
const updated = await this.getById(settlementId, opts);
|
|
1299
1491
|
await this.dispatch(createEvent(REVENUE_EVENTS.SETTLEMENT_FAILED, {
|
|
1300
1492
|
settlement: updated,
|
|
1301
1493
|
reason,
|