@classytic/revenue 2.0.1 → 2.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/CHANGELOG.md +66 -0
  2. package/README.md +33 -10
  3. package/dist/bank-feed-BlQeq2rK.mjs +133 -0
  4. package/dist/bank-feed.enums-BadqNJTC.d.mts +118 -0
  5. package/dist/bank-feed.enums-kYTLTTbe.mjs +165 -0
  6. package/dist/bridges/index.d.mts +1 -1
  7. package/dist/core/state-machines.d.mts +25 -2
  8. package/dist/core/state-machines.mjs +43 -3
  9. package/dist/engine-types-Jctrbasz.d.mts +1160 -0
  10. package/dist/enums/index.d.mts +4 -3
  11. package/dist/enums/index.mjs +4 -3
  12. package/dist/{errors-DHa8JVQ-.mjs → errors-LYYg9wcs.mjs} +23 -1
  13. package/dist/{escrow.schema-D5X32LwX.d.mts → escrow.schema-YuBgjL-I.d.mts} +27 -27
  14. package/dist/{event-constants-CEMitnIV.mjs → event-constants-Dn1TKahe.mjs} +6 -0
  15. package/dist/events/index.d.mts +2 -2
  16. package/dist/events/index.mjs +3 -3
  17. package/dist/index.d.mts +32 -13
  18. package/dist/index.mjs +142 -19
  19. package/dist/providers/index.d.mts +2 -2
  20. package/dist/providers/index.mjs +2 -2
  21. package/dist/registry-h8sasoLh.d.mts +145 -0
  22. package/dist/repositories/create-repositories.d.mts +1 -1
  23. package/dist/repositories/create-repositories.mjs +1 -1
  24. package/dist/{revenue-bridges-sdlrR85c.d.mts → revenue-bridges-BtkWFsJu.d.mts} +107 -1
  25. package/dist/{revenue-event-catalog-LqxPnsU_.mjs → revenue-event-catalog-BvjNVnPd.mjs} +77 -3
  26. package/dist/{revenue-event-catalog-BX3g7RUi.d.mts → revenue-event-catalog-JpJcyK1E.d.mts} +198 -2
  27. package/dist/settlement.repository-BAdc9qGl.mjs +1444 -0
  28. package/dist/shared/index.d.mts +1 -1
  29. package/dist/shared/index.mjs +2 -2
  30. package/dist/{subscription.enums-tfoAgsTv.mjs → subscription.enums-95othr0i.mjs} +1 -40
  31. package/dist/{transaction.enums-u4MshXcL.d.mts → subscription.enums-k24kLpF7.d.mts} +1 -36
  32. package/dist/validators/index.d.mts +158 -2
  33. package/dist/validators/index.mjs +95 -2
  34. package/package.json +7 -7
  35. package/dist/engine-types-CcjIb4Fy.d.mts +0 -611
  36. package/dist/registry-DhFMsSn5.mjs +0 -150
  37. package/dist/registry-SvIGPAx_.d.mts +0 -143
  38. package/dist/settlement.repository-DHIPx5S4.mjs +0 -771
  39. /package/dist/{audit-B39B0Sdq.mjs → audit-Ba2XB2C4.mjs} +0 -0
  40. /package/dist/{audit-DZ0eTr9g.d.mts → audit-DRKuLBFO.d.mts} +0 -0
  41. /package/dist/{context-DRqSeTPM.d.mts → context-pjP1QeE3.d.mts} +0 -0
  42. /package/dist/{escrow.schema-BBv9oVEW.mjs → escrow.schema-C-b41z_G.mjs} +0 -0
  43. /package/dist/{monetization.enums-BtiU3t8o.mjs → monetization.enums-B9HBOecd.mjs} +0 -0
  44. /package/dist/{monetization.enums-D2xbxXJM.d.mts → monetization.enums-DzAI4sT7.d.mts} +0 -0
  45. /package/dist/{splits-BAfY-a9P.mjs → splits-CNfQj92L.mjs} +0 -0
package/dist/index.mjs CHANGED
@@ -1,23 +1,29 @@
1
- import { n as createEvent, t as REVENUE_EVENTS } from "./event-constants-CEMitnIV.mjs";
2
- import { A as isReleaseReason, C as HOLD_REASON_VALUES, D as RELEASE_REASON_VALUES, E as RELEASE_REASON, F as TRANSACTION_STATUS, I as TRANSACTION_STATUS_VALUES, L as isLibraryCategory, M as LIBRARY_CATEGORY_VALUES, N as TRANSACTION_FLOW, O as isHoldReason, P as TRANSACTION_FLOW_VALUES, R as isTransactionFlow, 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, j as LIBRARY_CATEGORIES, 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, z as isTransactionStatus } from "./subscription.enums-tfoAgsTv.mjs";
3
- import { a as PaymentVerificationError, c as RefundNotSupportedError, d as SubscriptionNotFoundError, f as TransactionNotFoundError, i as PaymentIntentCreationError, l as RevenueError, n as ConfigurationError, o as ProviderCapabilityError, p as ValidationError, r as InvalidStateTransitionError, s as ProviderNotFoundError, t as AlreadyVerifiedError, u as SettlementNotFoundError } from "./errors-DHa8JVQ-.mjs";
4
- import { HOLD_STATE_MACHINE, SETTLEMENT_STATE_MACHINE, SPLIT_STATE_MACHINE, SUBSCRIPTION_STATE_MACHINE, StateMachine, TRANSACTION_STATE_MACHINE } from "./core/state-machines.mjs";
5
- import { a as reverseTax, c as reverseCommission, i as getTaxType, n as calculateSplits, o as validateTaxCalculation, r as calculateTax, s as calculateCommission, t as calculateOrganizationPayout } from "./splits-BAfY-a9P.mjs";
6
- import { n as SubscriptionRepository, r as TransactionRepository, t as SettlementRepository } from "./settlement.repository-DHIPx5S4.mjs";
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 { n as SubscriptionRepository, r as TransactionRepository, t as SettlementRepository } from "./settlement.repository-BAdc9qGl.mjs";
3
+ import { n as createEvent, t as REVENUE_EVENTS } from "./event-constants-Dn1TKahe.mjs";
4
+ 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";
5
+ import { a as InvalidStateTransitionError, c as ProviderCapabilityError, d as RevenueError, f as SettlementNotFoundError, g as WrongTransactionKindError, h as ValidationError, i as ConfigurationError, l as ProviderNotFoundError, m as TransactionNotFoundError, n as BankFeedImportError, o as PaymentIntentCreationError, p as SubscriptionNotFoundError, r as BankFeedProviderNotFoundError, s as PaymentVerificationError, t as AlreadyVerifiedError, u as RefundNotSupportedError } from "./errors-LYYg9wcs.mjs";
6
+ import { 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 } from "./core/state-machines.mjs";
7
+ import { a as reverseTax, c as reverseCommission, i as getTaxType, n as calculateSplits, o as validateTaxCalculation, r as calculateTax, s as calculateCommission, t as calculateOrganizationPayout } from "./splits-CNfQj92L.mjs";
7
8
  import { createRevenueRepositories } from "./repositories/create-repositories.mjs";
8
- import { a as PaymentResult, i as PaymentProvider, n as createProviderRegistry, o as RefundResult, r as PaymentIntent, s as WebhookEvent, t as ProviderRegistry } from "./registry-DhFMsSn5.mjs";
9
- import { D as InProcessRevenueBus, E as revenueEventDefinitions } from "./revenue-event-catalog-LqxPnsU_.mjs";
10
- 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-BtiU3t8o.mjs";
11
- import { _ as transactionListFilterSchema, a as paymentVerifySchema, c as settlementCreateSchema, d as subscriptionBaseSchema, f as subscriptionCreateSchema, g as transactionCreateSchema, h as transactionBaseSchema, i as paymentIntentSchema, l as settlementListFilterSchema, m as subscriptionUpdateSchema, n as escrowReleaseSchema, o as refundSchema, p as subscriptionListFilterSchema, r as splitRuleSchema, s as settlementBaseSchema, t as escrowHoldSchema, u as settlementUpdateSchema, v as transactionUpdateSchema } from "./escrow.schema-BBv9oVEW.mjs";
12
- import { C as sumMoney, E as toSmallestUnit, S as subtractMoney, T as toMajor, _ as isZeroMoney, a as CurrencyMismatchError, b as multiplyMoney, c as addMoney, d as fromMajor, f as fromSmallestUnit, g as isPositiveMoney, h as isNegativeMoney, i as CURRENCIES, l as compareMoney, m as isMoney, n as getAuditTrail, o as MINOR_UNIT_FACTOR, p as isCurrencyCode, r as getLastStateChange, s as absMoney, t as appendAuditEvent, u as equalsMoney, v as minorUnitFactor, w as toCurrencyCode, x as negateMoney, y as money } from "./audit-B39B0Sdq.mjs";
9
+ import { a as createProviderRegistry, i as ProviderRegistry, n as BankFeedProviderRegistry, o as PaymentProvider, r as createBankFeedProviderRegistry, t as BankFeedProvider } from "./bank-feed-BlQeq2rK.mjs";
10
+ import { M as revenueEventDefinitions, N as InProcessRevenueBus } from "./revenue-event-catalog-BvjNVnPd.mjs";
11
+ 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";
12
+ import { _ as transactionListFilterSchema, a as paymentVerifySchema, c as settlementCreateSchema, d as subscriptionBaseSchema, f as subscriptionCreateSchema, g as transactionCreateSchema, h as transactionBaseSchema, i as paymentIntentSchema, l as settlementListFilterSchema, m as subscriptionUpdateSchema, n as escrowReleaseSchema, o as refundSchema, p as subscriptionListFilterSchema, r as splitRuleSchema, s as settlementBaseSchema, t as escrowHoldSchema, u as settlementUpdateSchema, v as transactionUpdateSchema } from "./escrow.schema-C-b41z_G.mjs";
13
+ import { C as sumMoney, E as toSmallestUnit, S as subtractMoney, T as toMajor, _ as isZeroMoney, a as CurrencyMismatchError, b as multiplyMoney, c as addMoney, d as fromMajor, f as fromSmallestUnit, g as isPositiveMoney, h as isNegativeMoney, i as CURRENCIES, l as compareMoney, m as isMoney, n as getAuditTrail, o as MINOR_UNIT_FACTOR, p as isCurrencyCode, r as getLastStateChange, s as absMoney, t as appendAuditEvent, u as equalsMoney, v as minorUnitFactor, w as toCurrencyCode, x as negateMoney, y as money } from "./audit-Ba2XB2C4.mjs";
13
14
  import { PluginManager } from "./plugins/plugin.interface.mjs";
14
- import { customIdPlugin, multiTenantPlugin, prefixedId, softDeletePlugin } from "@classytic/mongokit";
15
- import { resolveTenantConfig } from "@classytic/primitives/tenant";
15
+ import { batchOperationsPlugin, customIdPlugin, methodRegistryPlugin, multiTenantPlugin, prefixedId, softDeletePlugin } from "@classytic/mongokit";
16
+ import { resolveTenantConfig } from "@classytic/repo-core/tenant";
16
17
  import mongoose, { Schema } from "mongoose";
17
18
  import { InvalidOutboxEventError, OutboxOwnershipError } from "@classytic/primitives/outbox";
18
19
  import { err, isErr, isOk, ok } from "@classytic/primitives/result";
19
20
 
20
21
  //#region src/models/transaction.schema.ts
22
+ const NO_BANK_FEED_INDEXES = {
23
+ idempotentImport: false,
24
+ byAccount: false,
25
+ matchCandidates: false
26
+ };
21
27
  function buildTransactionSchema(config) {
22
28
  const fields = {
23
29
  publicId: { type: String },
@@ -25,6 +31,13 @@ function buildTransactionSchema(config) {
25
31
  type: String,
26
32
  default: null
27
33
  },
34
+ kind: {
35
+ type: String,
36
+ enum: TRANSACTION_KIND_VALUES,
37
+ default: TRANSACTION_KIND.PAYMENT_FLOW,
38
+ required: true,
39
+ index: true
40
+ },
28
41
  type: {
29
42
  type: String,
30
43
  required: true
@@ -56,6 +69,9 @@ function buildTransactionSchema(config) {
56
69
  default: 0
57
70
  },
58
71
  taxDetails: { type: Schema.Types.Mixed },
72
+ fxRate: { type: Number },
73
+ originalAmount: { type: Number },
74
+ originalCurrency: { type: String },
59
75
  method: {
60
76
  type: String,
61
77
  required: true
@@ -64,11 +80,27 @@ function buildTransactionSchema(config) {
64
80
  type: String,
65
81
  default: "pending"
66
82
  },
83
+ approvals: {
84
+ type: Schema.Types.Mixed,
85
+ default: null
86
+ },
67
87
  gateway: { type: Schema.Types.Mixed },
68
88
  paymentDetails: { type: Schema.Types.Mixed },
69
89
  commission: { type: Schema.Types.Mixed },
70
90
  splits: [{ type: Schema.Types.Mixed }],
71
91
  hold: { type: Schema.Types.Mixed },
92
+ externalId: { type: String },
93
+ postedDate: { type: Date },
94
+ valueDate: { type: Date },
95
+ description: { type: String },
96
+ counterparty: { type: Schema.Types.Mixed },
97
+ reference: { type: String },
98
+ balanceAfter: { type: Number },
99
+ vendorCategory: { type: String },
100
+ bankAccountId: { type: String },
101
+ source: { type: String },
102
+ journalEntryRef: { type: Schema.Types.Mixed },
103
+ matching: { type: Schema.Types.Mixed },
72
104
  sourceId: { type: String },
73
105
  sourceModel: { type: String },
74
106
  relatedTransactionId: {
@@ -103,6 +135,32 @@ function buildTransactionSchema(config) {
103
135
  sourceId: 1,
104
136
  sourceModel: 1
105
137
  });
138
+ schema.index({
139
+ kind: 1,
140
+ status: 1,
141
+ createdAt: -1
142
+ });
143
+ schema.index({ relatedTransactionId: 1 }, { sparse: true });
144
+ const bfi = config.bankFeedIndexes ?? NO_BANK_FEED_INDEXES;
145
+ if (bfi.byAccount) schema.index({
146
+ bankAccountId: 1,
147
+ postedDate: -1
148
+ }, {
149
+ partialFilterExpression: { bankAccountId: { $type: "string" } },
150
+ name: "bank_feed_by_account"
151
+ });
152
+ if (bfi.matchCandidates) {
153
+ schema.index({
154
+ kind: 1,
155
+ amount: 1,
156
+ postedDate: -1
157
+ }, { name: "match_candidates_by_amount_date" });
158
+ schema.index({
159
+ kind: 1,
160
+ amount: 1,
161
+ createdAt: -1
162
+ }, { name: "match_candidates_by_amount_createdat" });
163
+ }
106
164
  if (config.extraIndexes) for (const idx of config.extraIndexes) schema.index(idx.fields, idx.options);
107
165
  return schema;
108
166
  }
@@ -151,6 +209,10 @@ function buildSubscriptionSchema(config) {
151
209
  canceledAt: { type: Date },
152
210
  cancelAt: { type: Date },
153
211
  cancellationReason: { type: String },
212
+ approvals: {
213
+ type: Schema.Types.Mixed,
214
+ default: null
215
+ },
154
216
  renewalTransactionId: {
155
217
  type: Schema.Types.ObjectId,
156
218
  ref: txnRef
@@ -207,6 +269,10 @@ function buildSettlementSchema(config) {
207
269
  type: String,
208
270
  default: "pending"
209
271
  },
272
+ approvals: {
273
+ type: Schema.Types.Mixed,
274
+ default: null
275
+ },
210
276
  payoutMethod: {
211
277
  type: String,
212
278
  required: true
@@ -282,7 +348,7 @@ function buildSettlementSchema(config) {
282
348
  * The field storage type follows `scope.fieldType` (`'objectId'` →
283
349
  * `Schema.Types.ObjectId` + `ref`, `'string'` → `String`). No hardcoding
284
350
  * here — callers must pass a `ResolvedTenantConfig` from
285
- * `@classytic/primitives/tenant` via `resolveTenantConfig(...)`.
351
+ * `@classytic/repo-core/tenant` via `resolveTenantConfig(...)`.
286
352
  */
287
353
  function injectTenantField(schema, scope) {
288
354
  schema.add({ [scope.tenantField]: {
@@ -325,7 +391,7 @@ var RevenueModelCollisionError = class extends Error {
325
391
  }
326
392
  };
327
393
  function createRevenueModels(options) {
328
- const { connection, scope, schemaOptions = {}, modules = {}, collectionPrefix, forceRecreate } = options;
394
+ const { connection, scope, schemaOptions = {}, modules = {}, collectionPrefix, forceRecreate, bankFeedIndexes } = options;
329
395
  const prefix = collectionPrefix ?? "";
330
396
  if (forceRecreate) {
331
397
  for (const name of REVENUE_MODEL_NAMES) if (connection.models[name]) connection.deleteModel(name);
@@ -333,7 +399,8 @@ function createRevenueModels(options) {
333
399
  const txnSchema = buildTransactionSchema({
334
400
  scoped: scope.enabled,
335
401
  extraFields: schemaOptions.transaction?.extraFields,
336
- extraIndexes: schemaOptions.transaction?.extraIndexes
402
+ extraIndexes: schemaOptions.transaction?.extraIndexes,
403
+ ...bankFeedIndexes ? { bankFeedIndexes } : {}
337
404
  });
338
405
  injectTenantField(txnSchema, scope);
339
406
  txnSchema.index({ "gateway.sessionId": 1 }, { sparse: true });
@@ -349,6 +416,23 @@ function createRevenueModels(options) {
349
416
  publicId: { $type: "string" }
350
417
  }
351
418
  });
419
+ if (bankFeedIndexes?.idempotentImport) if (scope.enabled && scope.strategy === "field") txnSchema.index({
420
+ [scope.tenantField]: 1,
421
+ bankAccountId: 1,
422
+ externalId: 1
423
+ }, {
424
+ unique: true,
425
+ partialFilterExpression: { externalId: { $type: "string" } },
426
+ name: "bank_feed_idempotent_import"
427
+ });
428
+ else txnSchema.index({
429
+ bankAccountId: 1,
430
+ externalId: 1
431
+ }, {
432
+ unique: true,
433
+ partialFilterExpression: { externalId: { $type: "string" } },
434
+ name: "bank_feed_idempotent_import"
435
+ });
352
436
  const models = { Transaction: connection.model("Transaction", txnSchema, prefix + DEFAULT_COLLECTIONS.Transaction) };
353
437
  if (modules.subscription !== false) {
354
438
  const subSchema = buildSubscriptionSchema({
@@ -388,10 +472,23 @@ function createRevenueModels(options) {
388
472
  * See PACKAGE_RULES §13–§14.
389
473
  */
390
474
  async function createRevenue(config) {
475
+ const bankFeedRaw = config.modules?.bankFeed;
476
+ const bankFeedEnabled = bankFeedRaw === false ? false : typeof bankFeedRaw === "object" && bankFeedRaw !== null ? bankFeedRaw.enabled !== false : true;
477
+ const userIndexCfg = typeof bankFeedRaw === "object" && bankFeedRaw !== null ? bankFeedRaw.indexes : void 0;
478
+ const bankFeedIndexes = bankFeedEnabled ? {
479
+ idempotentImport: userIndexCfg?.idempotentImport ?? true,
480
+ byAccount: userIndexCfg?.byAccount ?? true,
481
+ matchCandidates: userIndexCfg?.matchCandidates ?? false
482
+ } : {
483
+ idempotentImport: false,
484
+ byAccount: false,
485
+ matchCandidates: false
486
+ };
391
487
  const modules = {
392
488
  subscription: config.modules?.subscription !== false,
393
489
  escrow: config.modules?.escrow ?? false,
394
- settlement: config.modules?.settlement ?? false
490
+ settlement: config.modules?.settlement ?? false,
491
+ bankFeed: bankFeedEnabled
395
492
  };
396
493
  const scope = resolveTenantConfig(config.scope);
397
494
  const events = config.eventTransport ?? new InProcessRevenueBus({ logger: config.logger });
@@ -400,6 +497,7 @@ async function createRevenue(config) {
400
497
  scope,
401
498
  schemaOptions: config.schemaOptions,
402
499
  modules,
500
+ bankFeedIndexes,
403
501
  ...config.collectionPrefix !== void 0 ? { collectionPrefix: config.collectionPrefix } : {},
404
502
  ...config.forceRecreate !== void 0 ? { forceRecreate: config.forceRecreate } : {}
405
503
  });
@@ -424,16 +522,18 @@ async function createRevenue(config) {
424
522
  return plugins;
425
523
  };
426
524
  const repositories = createRevenueRepositories(models, {
427
- transaction: buildPlugins("txn"),
525
+ transaction: buildPlugins("txn", modules.bankFeed ? [methodRegistryPlugin(), batchOperationsPlugin()] : []),
428
526
  subscription: buildPlugins("sub"),
429
527
  settlement: buildPlugins("stl")
430
528
  }, config.repositoryPlugins);
431
529
  const providers = createProviderRegistry(config.providers ?? {}, config.defaultCurrency);
530
+ const bankFeedProviders = createBankFeedProviderRegistry(config.bankFeedProviders ?? {});
432
531
  const commission = typeof config.modules?.commission === "object" ? config.modules.commission : config.commission;
433
532
  repositories.transaction.inject({
434
533
  events,
435
534
  outbox: config.outbox,
436
535
  providers,
536
+ bankFeedProviders,
437
537
  bridges: config.bridges ?? {},
438
538
  commission,
439
539
  defaultCurrency: config.defaultCurrency,
@@ -460,6 +560,7 @@ async function createRevenue(config) {
460
560
  models,
461
561
  repositories,
462
562
  providers,
563
+ bankFeedProviders,
463
564
  events,
464
565
  async syncIndexes() {
465
566
  await Promise.all(Object.values(models).filter(Boolean).map((m) => m.createIndexes()));
@@ -471,4 +572,26 @@ async function createRevenue(config) {
471
572
  }
472
573
 
473
574
  //#endregion
474
- export { AlreadyVerifiedError, CURRENCIES, ConfigurationError, CurrencyMismatchError, HOLD_REASON, HOLD_REASON_VALUES, HOLD_STATE_MACHINE, HOLD_STATUS, HOLD_STATUS_VALUES, InProcessRevenueBus, InvalidOutboxEventError, InvalidStateTransitionError, LIBRARY_CATEGORIES, LIBRARY_CATEGORY_VALUES, MINOR_UNIT_FACTOR, MONETIZATION_TYPES, MONETIZATION_TYPE_VALUES, OutboxOwnershipError, PAYMENT_GATEWAY_TYPE, PAYMENT_GATEWAY_TYPE_VALUES, PAYMENT_STATUS, PAYMENT_STATUS_VALUES, PAYOUT_METHOD, PAYOUT_METHOD_VALUES, PLAN_KEYS, PLAN_KEY_VALUES, PaymentIntent, PaymentIntentCreationError, PaymentProvider, PaymentResult, PaymentVerificationError, PluginManager, ProviderCapabilityError, ProviderNotFoundError, ProviderRegistry, RELEASE_REASON, RELEASE_REASON_VALUES, REVENUE_EVENTS, RefundNotSupportedError, RefundResult, RevenueError, SETTLEMENT_STATE_MACHINE, SETTLEMENT_STATUS, SETTLEMENT_STATUS_VALUES, SETTLEMENT_TYPE, SETTLEMENT_TYPE_VALUES, SPLIT_STATE_MACHINE, SPLIT_STATUS, SPLIT_STATUS_VALUES, SPLIT_TYPE, SPLIT_TYPE_VALUES, SUBSCRIPTION_STATE_MACHINE, SUBSCRIPTION_STATUS, SUBSCRIPTION_STATUS_VALUES, SettlementNotFoundError, SettlementRepository, StateMachine, SubscriptionNotFoundError, SubscriptionRepository, TRANSACTION_FLOW, TRANSACTION_FLOW_VALUES, TRANSACTION_STATE_MACHINE, TRANSACTION_STATUS, TRANSACTION_STATUS_VALUES, TransactionNotFoundError, TransactionRepository, ValidationError, WebhookEvent, absMoney, addMoney, appendAuditEvent, calculateCommission, calculateOrganizationPayout, calculateSplits, calculateTax, compareMoney, createEvent, createProviderRegistry, createRevenue, equalsMoney, err, escrowHoldSchema, escrowReleaseSchema, fromMajor, fromSmallestUnit, getAuditTrail, getLastStateChange, getTaxType, isCurrencyCode, isErr, isHoldReason, isHoldStatus, isLibraryCategory, isMonetizationType, isMoney, isNegativeMoney, isOk, isPaymentGatewayType, isPaymentStatus, isPayoutMethod, isPlanKey, isPositiveMoney, isReleaseReason, isSettlementStatus, isSettlementType, isSplitStatus, isSplitType, isSubscriptionStatus, isTransactionFlow, isTransactionStatus, isZeroMoney, minorUnitFactor, money, multiplyMoney, negateMoney, ok, paymentIntentSchema, paymentVerifySchema, refundSchema, revenueEventDefinitions, reverseCommission, reverseTax, settlementBaseSchema, settlementCreateSchema, settlementListFilterSchema, settlementUpdateSchema, splitRuleSchema, subscriptionBaseSchema, subscriptionCreateSchema, subscriptionListFilterSchema, subscriptionUpdateSchema, subtractMoney, sumMoney, toCurrencyCode, toMajor, toSmallestUnit, transactionBaseSchema, transactionCreateSchema, transactionListFilterSchema, transactionUpdateSchema, validateTaxCalculation };
575
+ //#region src/events/outbox-store.ts
576
+ var MemoryOutboxStore = class {
577
+ events = [];
578
+ async save(event, _options) {
579
+ this.events.push({ event });
580
+ }
581
+ async getPending(limit) {
582
+ return this.events.filter((e) => !e.acknowledgedAt).slice(0, limit).map((e) => e.event);
583
+ }
584
+ async acknowledge(eventId, _options) {
585
+ const entry = this.events.find((e) => e.event.meta.id === eventId);
586
+ if (entry) entry.acknowledgedAt = /* @__PURE__ */ new Date();
587
+ }
588
+ async purge(olderThanMs) {
589
+ const cutoff = Date.now() - olderThanMs;
590
+ const before = this.events.length;
591
+ this.events = this.events.filter((e) => !e.acknowledgedAt || e.acknowledgedAt.getTime() >= cutoff);
592
+ return before - this.events.length;
593
+ }
594
+ };
595
+
596
+ //#endregion
597
+ export { AlreadyVerifiedError, BANK_FEED_SOURCE, BANK_FEED_SOURCE_VALUES, BANK_FEED_STATE_MACHINE, BANK_FEED_STATUS, BANK_FEED_STATUS_VALUES, BankFeedImportError, BankFeedProvider, BankFeedProviderNotFoundError, BankFeedProviderRegistry, CURRENCIES, ConfigurationError, CurrencyMismatchError, HOLD_REASON, HOLD_REASON_VALUES, HOLD_STATE_MACHINE, HOLD_STATUS, HOLD_STATUS_VALUES, InProcessRevenueBus, InvalidOutboxEventError, InvalidStateTransitionError, LIBRARY_CATEGORIES, LIBRARY_CATEGORY_VALUES, MANUAL_STATE_MACHINE, MINOR_UNIT_FACTOR, MONETIZATION_TYPES, MONETIZATION_TYPE_VALUES, MemoryOutboxStore, OutboxOwnershipError, PAYMENT_FLOW_STATE_MACHINE, PAYMENT_GATEWAY_TYPE, PAYMENT_GATEWAY_TYPE_VALUES, PAYMENT_STATUS, PAYMENT_STATUS_VALUES, PAYOUT_METHOD, PAYOUT_METHOD_VALUES, PLAN_KEYS, PLAN_KEY_VALUES, PaymentIntentCreationError, PaymentProvider, PaymentVerificationError, PluginManager, ProviderCapabilityError, ProviderNotFoundError, ProviderRegistry, RELEASE_REASON, RELEASE_REASON_VALUES, REVENUE_EVENTS, RefundNotSupportedError, RevenueError, SETTLEMENT_STATE_MACHINE, SETTLEMENT_STATUS, SETTLEMENT_STATUS_VALUES, SETTLEMENT_TYPE, SETTLEMENT_TYPE_VALUES, SPLIT_STATE_MACHINE, SPLIT_STATUS, SPLIT_STATUS_VALUES, SPLIT_TYPE, SPLIT_TYPE_VALUES, SUBSCRIPTION_STATE_MACHINE, SUBSCRIPTION_STATUS, SUBSCRIPTION_STATUS_VALUES, SettlementNotFoundError, SettlementRepository, StateMachine, SubscriptionNotFoundError, SubscriptionRepository, TRANSACTION_FLOW, TRANSACTION_FLOW_VALUES, TRANSACTION_KIND, TRANSACTION_KIND_VALUES, TRANSACTION_STATE_MACHINE, TRANSACTION_STATUS, TRANSACTION_STATUS_VALUES, TransactionNotFoundError, TransactionRepository, ValidationError, WrongTransactionKindError, absMoney, addMoney, appendAuditEvent, calculateCommission, calculateOrganizationPayout, calculateSplits, calculateTax, compareMoney, createBankFeedProviderRegistry, createEvent, createProviderRegistry, createRevenue, equalsMoney, err, escrowHoldSchema, escrowReleaseSchema, fromMajor, fromSmallestUnit, getAuditTrail, getLastStateChange, getTaxType, initialStatusFor, isBankFeedSource, isBankFeedStatus, isCurrencyCode, isErr, isHoldReason, isHoldStatus, isLibraryCategory, isMonetizationType, isMoney, isNegativeMoney, isOk, isPaymentGatewayType, isPaymentStatus, isPayoutMethod, isPlanKey, isPositiveMoney, isReleaseReason, isSettlementStatus, isSettlementType, isSplitStatus, isSplitType, isStatusValidForKind, isSubscriptionStatus, isTransactionFlow, isTransactionKind, isTransactionStatus, isZeroMoney, minorUnitFactor, money, multiplyMoney, negateMoney, ok, paymentIntentSchema, paymentVerifySchema, refundSchema, revenueEventDefinitions, reverseCommission, reverseTax, settlementBaseSchema, settlementCreateSchema, settlementListFilterSchema, settlementUpdateSchema, smFor, splitRuleSchema, statusesForKind, subscriptionBaseSchema, subscriptionCreateSchema, subscriptionListFilterSchema, subscriptionUpdateSchema, subtractMoney, sumMoney, toCurrencyCode, toMajor, toSmallestUnit, transactionBaseSchema, transactionCreateSchema, transactionListFilterSchema, transactionUpdateSchema, validateTaxCalculation };
@@ -1,2 +1,2 @@
1
- import { a as PaymentIntentData, c as PaymentResultData, d as RefundResultData, f as WebhookEvent, i as PaymentIntent, l as ProviderCapabilities, n as createProviderRegistry, o as PaymentProvider, p as WebhookEventData, r as CreateIntentParams, s as PaymentResult, t as ProviderRegistry, u as RefundResult } from "../registry-SvIGPAx_.mjs";
2
- export { type CreateIntentParams, PaymentIntent, type PaymentIntentData, PaymentProvider, PaymentResult, type PaymentResultData, type ProviderCapabilities, ProviderRegistry, RefundResult, type RefundResultData, WebhookEvent, type WebhookEventData, createProviderRegistry };
1
+ import { a as BankFeedProviderRegistry, c as ParseUploadParams, d as PaymentProvider, i as BankFeedProviderCapabilities, l as ParseUploadResult, n as createProviderRegistry, o as FetchTransactionsParams, r as BankFeedProvider, s as FetchTransactionsResult, t as ProviderRegistry, u as createBankFeedProviderRegistry } from "../registry-h8sasoLh.mjs";
2
+ export { BankFeedProvider, type BankFeedProviderCapabilities, BankFeedProviderRegistry, type FetchTransactionsParams, type FetchTransactionsResult, type ParseUploadParams, type ParseUploadResult, PaymentProvider, ProviderRegistry, createBankFeedProviderRegistry, createProviderRegistry };
@@ -1,3 +1,3 @@
1
- import { a as PaymentResult, i as PaymentProvider, n as createProviderRegistry, o as RefundResult, r as PaymentIntent, s as WebhookEvent, t as ProviderRegistry } from "../registry-DhFMsSn5.mjs";
1
+ import { a as createProviderRegistry, i as ProviderRegistry, n as BankFeedProviderRegistry, o as PaymentProvider, r as createBankFeedProviderRegistry, t as BankFeedProvider } from "../bank-feed-BlQeq2rK.mjs";
2
2
 
3
- export { PaymentIntent, PaymentProvider, PaymentResult, ProviderRegistry, RefundResult, WebhookEvent, createProviderRegistry };
3
+ export { BankFeedProvider, BankFeedProviderRegistry, PaymentProvider, ProviderRegistry, createBankFeedProviderRegistry, createProviderRegistry };
@@ -0,0 +1,145 @@
1
+ import { a as BankFeedSourceValue } from "./bank-feed.enums-BadqNJTC.mjs";
2
+ import { CreateIntentParams, PaymentIntent, PaymentResult, ProviderCapabilities, RefundResult, WebhookEvent } from "@classytic/primitives/payment-gateway";
3
+ import { BankStatement, BankTransaction } from "@classytic/primitives/bank-transaction";
4
+
5
+ //#region src/providers/base.d.ts
6
+ /**
7
+ * Abstract `PaymentProvider` — the contract revenue's repositories
8
+ * consume. Provider implementations may extend this for the default
9
+ * config plumbing, or just satisfy the structural shape.
10
+ */
11
+ declare abstract class PaymentProvider {
12
+ readonly config: Record<string, unknown>;
13
+ readonly name: string;
14
+ private _defaultCurrency;
15
+ constructor(config?: Record<string, unknown>);
16
+ get defaultCurrency(): string;
17
+ setDefaultCurrency(currency: string): void;
18
+ abstract createIntent(params: CreateIntentParams): Promise<PaymentIntent>;
19
+ abstract verifyPayment(intentId: string): Promise<PaymentResult>;
20
+ abstract getStatus(intentId: string): Promise<PaymentResult>;
21
+ abstract refund(paymentId: string, amount?: number | null, options?: {
22
+ reason?: string;
23
+ }): Promise<RefundResult>;
24
+ abstract handleWebhook(payload: unknown, headers?: Record<string, string>): Promise<WebhookEvent>;
25
+ /**
26
+ * Default: accept all signatures (manual / dev provider). Real
27
+ * gateways MUST override with HMAC / timing-safe verification.
28
+ */
29
+ verifyWebhookSignature(_payload: unknown, _signature: string): boolean;
30
+ getCapabilities(): ProviderCapabilities;
31
+ }
32
+ //#endregion
33
+ //#region src/providers/bank-feed.d.ts
34
+ interface BankFeedProviderCapabilities {
35
+ /** Supports continuous sync (Plaid, QBO CDC, Xero CDC). */
36
+ supportsSync: boolean;
37
+ /** Supports file upload parsing (OFX, CAMT.053, MT940, CSV, IIF). */
38
+ supportsUpload: boolean;
39
+ /** Provider may report retracted entries (Plaid `removed[]`, OFX correction). */
40
+ supportsRemovals: boolean;
41
+ /** Cursor-resumable — `fetchTransactions` returns a `nextCursor`. */
42
+ cursorBased: boolean;
43
+ /** Multi-account in a single sync call. */
44
+ multiAccount: boolean;
45
+ }
46
+ interface FetchTransactionsParams {
47
+ /** Resumption token from a previous call (Plaid cursor, QBO `LastUpdatedTime`, …). */
48
+ cursor?: string | undefined;
49
+ /** Account scope. May be undefined for providers that fetch all accounts. */
50
+ bankAccountId?: string | undefined;
51
+ /** Optional date floor — supplements cursor-based drains. */
52
+ from?: Date | undefined;
53
+ /** Optional date ceiling. */
54
+ to?: Date | undefined;
55
+ /** Provider-specific knobs. */
56
+ options?: Record<string, unknown> | undefined;
57
+ }
58
+ interface FetchTransactionsResult {
59
+ /** Newly added or updated rows. */
60
+ transactions: BankTransaction[];
61
+ /** Vendor-stable IDs of rows the upstream feed has retracted. */
62
+ removed?: Array<{
63
+ externalId: string;
64
+ bankAccountId?: string;
65
+ }>;
66
+ /** Resumption cursor for the next call. */
67
+ nextCursor?: string;
68
+ /** True when more pages are available — driver may stop calling when false. */
69
+ hasMore?: boolean;
70
+ /** Provider raw response for audit. Optional. */
71
+ raw?: unknown;
72
+ }
73
+ interface ParseUploadParams {
74
+ /** Raw upload bytes / string. */
75
+ buffer: Buffer | string | Uint8Array;
76
+ /** Format hint — providers MAY auto-detect when absent. */
77
+ format?: BankFeedSourceValue;
78
+ /** Account scope to stamp on every parsed row (when the file omits it). */
79
+ bankAccountId?: string;
80
+ /** Format-specific quirks (e.g. `'chase' | 'boa' | 'lenient'`). */
81
+ options?: Record<string, unknown>;
82
+ }
83
+ interface ParseUploadResult {
84
+ /** Statement-level metadata (account, period, balances). */
85
+ statements: BankStatement[];
86
+ /** Flat list of all rows across all statements — convenience for `import()`. */
87
+ transactions: BankTransaction[];
88
+ /** Per-row parse errors that didn't abort the file. */
89
+ warnings: Array<{
90
+ line?: number;
91
+ reason: string;
92
+ }>;
93
+ }
94
+ /**
95
+ * Bank-feed provider — implement one method or both depending on the
96
+ * upstream's capabilities. Mirrors the optional-method pattern that
97
+ * works well across PaymentProvider's gateway plurality.
98
+ */
99
+ declare abstract class BankFeedProvider {
100
+ readonly config: Record<string, unknown>;
101
+ readonly name: string;
102
+ constructor(name: string, config?: Record<string, unknown>);
103
+ /**
104
+ * Fetch a batch of transactions. Cursor-based providers (Plaid)
105
+ * return `nextCursor` for the next call; date-range providers
106
+ * (older QBO Reports API) ignore cursor and use `from` / `to`.
107
+ * Throws if the provider does not support sync.
108
+ */
109
+ fetchTransactions?(_params: FetchTransactionsParams): Promise<FetchTransactionsResult>;
110
+ /**
111
+ * Parse a file upload (OFX / CAMT.053 / MT940 / CSV / IIF). The
112
+ * canonical implementation delegates to `@classytic/fin-io`. Throws
113
+ * if the provider does not support uploads.
114
+ */
115
+ parseUpload?(_params: ParseUploadParams): Promise<ParseUploadResult>;
116
+ /**
117
+ * Async drain — yields one batch per call until the upstream is
118
+ * caught up. Default implementation pulls `fetchTransactions` in a
119
+ * loop; providers can override for more efficient pagination
120
+ * (e.g. SSE / long-poll) or to interleave `removed[]` correctly.
121
+ */
122
+ drain(params?: FetchTransactionsParams): AsyncGenerator<FetchTransactionsResult>;
123
+ abstract getCapabilities(): BankFeedProviderCapabilities;
124
+ }
125
+ declare class BankFeedProviderRegistry {
126
+ private providers;
127
+ register(name: string, provider: BankFeedProvider): void;
128
+ get(name: string): BankFeedProvider;
129
+ has(name: string): boolean;
130
+ list(): string[];
131
+ }
132
+ declare function createBankFeedProviderRegistry(providers?: Record<string, BankFeedProvider>): BankFeedProviderRegistry;
133
+ //#endregion
134
+ //#region src/providers/registry.d.ts
135
+ declare class ProviderRegistry {
136
+ private providers;
137
+ register(name: string, provider: PaymentProvider): void;
138
+ get(name: string): PaymentProvider;
139
+ has(name: string): boolean;
140
+ list(): string[];
141
+ setDefaultCurrency(currency: string): void;
142
+ }
143
+ declare function createProviderRegistry(providers?: Record<string, PaymentProvider>, defaultCurrency?: string): ProviderRegistry;
144
+ //#endregion
145
+ export { BankFeedProviderRegistry as a, ParseUploadParams as c, PaymentProvider as d, BankFeedProviderCapabilities as i, ParseUploadResult as l, createProviderRegistry as n, FetchTransactionsParams as o, BankFeedProvider as r, FetchTransactionsResult as s, ProviderRegistry as t, createBankFeedProviderRegistry as u };
@@ -1,4 +1,4 @@
1
- import { a as SettlementRepository, c as RevenueModels, o as SubscriptionRepository, s as TransactionRepository } from "../engine-types-CcjIb4Fy.mjs";
1
+ import { c as SubscriptionRepository, l as TransactionRepository, s as SettlementRepository, u as RevenueModels } from "../engine-types-Jctrbasz.mjs";
2
2
  import { PluginType } from "@classytic/mongokit";
3
3
 
4
4
  //#region src/repositories/create-repositories.d.ts
@@ -1,4 +1,4 @@
1
- import { n as SubscriptionRepository, r as TransactionRepository, t as SettlementRepository } from "../settlement.repository-DHIPx5S4.mjs";
1
+ import { n as SubscriptionRepository, r as TransactionRepository, t as SettlementRepository } from "../settlement.repository-BAdc9qGl.mjs";
2
2
 
3
3
  //#region src/repositories/create-repositories.ts
4
4
  function createRevenueRepositories(models, builtInPlugins, hostPlugins = {}) {
@@ -1,10 +1,116 @@
1
- import { t as RevenueContext } from "./context-DRqSeTPM.mjs";
1
+ import { t as RevenueContext } from "./context-pjP1QeE3.mjs";
2
2
 
3
3
  //#region src/bridges/ledger.bridge.d.ts
4
+ /**
5
+ * LedgerBridge — host-implemented contract for posting revenue events
6
+ * into a general-ledger / accounting system.
7
+ *
8
+ * Revenue does NOT import any ledger package directly (PACKAGE_RULES §23).
9
+ * The host wires this bridge once at engine creation time; revenue's repo
10
+ * verbs call the relevant hook after each state transition. Every method
11
+ * is optional — features degrade gracefully when omitted.
12
+ *
13
+ * Two phases of hooks:
14
+ *
15
+ * 1. **Payment-flow hooks** (`onPaymentVerified`, `onRefundProcessed`,
16
+ * `onSettlementCompleted`) — the original Stripe-style integration.
17
+ * Fired when a gateway transaction reaches a terminal accounting
18
+ * moment.
19
+ *
20
+ * 2. **Bank-feed hooks** (`onTransactionImported`, `onTransactionMatched`,
21
+ * `onTransactionJournalized`, `onTransactionRejected`,
22
+ * `onTransactionUnmatched`, `onTransactionRemovedByFeed`) — added
23
+ * in 3.0. Fired during the bank-feed lifecycle so the host can post
24
+ * JEs at match time, recall them on un-match, and chain
25
+ * `journalize()` after a successful JE post.
26
+ *
27
+ * The canonical wiring for `onTransactionMatched` is:
28
+ *
29
+ * ```ts
30
+ * const ledgerBridge: LedgerBridge = {
31
+ * async onTransactionMatched(txn, mapping, ctx) {
32
+ * const report = await wireImport({
33
+ * source: [{ txn, mapping }],
34
+ * mapper: bankToJournalEntryMapper(mapping),
35
+ * journalEntries: ledger.repositories.journalEntry,
36
+ * context: { organizationId: ctx.organizationId },
37
+ * }).run();
38
+ * if (report.ok && report.entries[0]) {
39
+ * await revenue.repositories.transaction.journalize(
40
+ * String(txn._id),
41
+ * { journalEntryRef: { type: 'JournalEntry', id: report.entries[0].id } },
42
+ * ctx,
43
+ * );
44
+ * }
45
+ * },
46
+ * };
47
+ * ```
48
+ */
4
49
  interface LedgerBridge {
50
+ /** Fired when a gateway transaction reaches `verified` status. */
5
51
  onPaymentVerified?(transaction: Record<string, unknown>, ctx: RevenueContext): Promise<void>;
52
+ /** Fired after a refund posts (the new outflow row + the updated original). */
6
53
  onRefundProcessed?(original: Record<string, unknown>, refund: Record<string, unknown>, ctx: RevenueContext): Promise<void>;
54
+ /** Fired when a settlement record reaches `completed`. */
7
55
  onSettlementCompleted?(settlement: Record<string, unknown>, ctx: RevenueContext): Promise<void>;
56
+ /**
57
+ * Fired once per row inserted by `import()`. Use this for live preview
58
+ * dashboards or downstream materialized views — most production hosts
59
+ * leave this unimplemented and act on the per-batch `events.publish`
60
+ * stream instead.
61
+ */
62
+ onTransactionImported?(transaction: Record<string, unknown>, ctx: RevenueContext): Promise<void>;
63
+ /**
64
+ * Fired after `match()` succeeds. The host's typical implementation
65
+ * posts a journal entry via the host's ledger package (e.g. arc's
66
+ * `wireImport` over `@classytic/ledger`'s `journalEntry` repository),
67
+ * then calls `revenue.repositories.transaction.journalize(id, …)` so
68
+ * the row transitions `matched → journalized`.
69
+ */
70
+ onTransactionMatched?(transaction: Record<string, unknown>, mapping: {
71
+ debitAccount?: string;
72
+ creditAccount?: string;
73
+ notes?: string;
74
+ }, ctx: RevenueContext): Promise<void>;
75
+ /**
76
+ * Fired after `unmatch()` succeeds. Hosts can void / reverse the
77
+ * journal entry created at match time. Pass the prior
78
+ * `journalEntryRef` so reversal is keyed correctly.
79
+ */
80
+ onTransactionUnmatched?(transaction: Record<string, unknown>, priorJournalEntryRef: {
81
+ type: string;
82
+ id: string;
83
+ } | undefined, ctx: RevenueContext): Promise<void>;
84
+ /**
85
+ * Fired after `journalize()` stamps `journalEntryRef` on the row.
86
+ * Most hosts are passive on this hook (the JE was created in
87
+ * `onTransactionMatched`); useful for audit logs and analytics.
88
+ */
89
+ onTransactionJournalized?(transaction: Record<string, unknown>, journalEntryRef: {
90
+ type: string;
91
+ id: string;
92
+ }, ctx: RevenueContext): Promise<void>;
93
+ /**
94
+ * Fired after `reject()` — operator skip on a duplicate / non-cash
95
+ * import. Hosts typically log for audit.
96
+ *
97
+ * **JE reversal contract.** `reject()` is legal from `'matched'` as
98
+ * well as `'imported'`. If your `onTransactionMatched` posted a JE
99
+ * synchronously (the typical bridge implementation), the JE must be
100
+ * reversed here — revenue itself never calls into ledger and has no
101
+ * way to know one exists. Idempotency is the bridge's responsibility:
102
+ * a reject on a never-journalized row should be a no-op for ledger,
103
+ * a reject after journalize should void the JE keyed off
104
+ * `transaction.matching` or whatever the host stamped at match time.
105
+ */
106
+ onTransactionRejected?(transaction: Record<string, unknown>, reason: string, ctx: RevenueContext): Promise<void>;
107
+ /**
108
+ * Fired when the upstream feed retracts a row (Plaid `removed[]`,
109
+ * OFX correction, QBO CDC delete). The transaction is soft-deleted
110
+ * before this hook runs. If a JE was already posted, the host
111
+ * should reverse it.
112
+ */
113
+ onTransactionRemovedByFeed?(transaction: Record<string, unknown>, ctx: RevenueContext): Promise<void>;
8
114
  }
9
115
  //#endregion
10
116
  //#region src/bridges/tax.bridge.d.ts