@classytic/revenue 2.5.0 → 2.7.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.
Files changed (28) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/dist/{audit-Ba2XB2C4.mjs → audit-C5c4LAf6.mjs} +1 -1
  3. package/dist/{audit-DRKuLBFO.d.mts → audit-Cug6g_31.d.mts} +3 -2
  4. package/dist/{bank-feed-ClxNob_I.mjs → bank-feed-yxrt4YpT.mjs} +1 -1
  5. package/dist/core/state-machines.mjs +2 -2
  6. package/dist/{engine-types-F3f1lWNG.d.mts → engine-types-D6ijamqA.d.mts} +29 -1
  7. package/dist/enums/index.mjs +1 -1
  8. package/dist/{errors-Bt5NRVMq.mjs → errors-8P5tRgV7.mjs} +11 -1
  9. package/dist/{escrow.schema-o4qhjOca.d.mts → escrow.schema-DSx7EywJ.d.mts} +18 -18
  10. package/dist/{escrow.schema-BcKdzrJ7.mjs → escrow.schema-gksK8F-W.mjs} +5 -4
  11. package/dist/events/index.d.mts +1 -1
  12. package/dist/events/index.mjs +2 -2
  13. package/dist/index.d.mts +13 -5
  14. package/dist/index.mjs +10 -10
  15. package/dist/providers/index.mjs +1 -1
  16. package/dist/repositories/create-repositories.d.mts +1 -1
  17. package/dist/repositories/create-repositories.mjs +1 -1
  18. package/dist/{revenue-event-catalog-j8Fh7Yag.d.mts → revenue-event-catalog-BU_KYN2-.d.mts} +126 -126
  19. package/dist/{revenue-event-catalog-B9aZmNpL.mjs → revenue-event-catalog-DQXCuS-l.mjs} +3 -2
  20. package/dist/{settlement.repository-BDrJQc27.mjs → settlement.repository-KDKH2jcq.mjs} +104 -44
  21. package/dist/shared/index.d.mts +2 -2
  22. package/dist/shared/index.mjs +3 -3
  23. package/dist/{splits-CNfQj92L.mjs → splits-rs6mkdGN.mjs} +22 -7
  24. package/dist/validators/index.d.mts +1 -1
  25. package/dist/validators/index.mjs +5 -4
  26. package/package.json +8 -7
  27. /package/dist/{event-constants-DM_-A57b.mjs → event-constants-CPmh2sIt.mjs} +0 -0
  28. /package/dist/{subscription.enums-95othr0i.mjs → subscription.enums-olW5dyly.mjs} +0 -0
@@ -1,6 +1,7 @@
1
- import { t as REVENUE_EVENTS } from "./event-constants-DM_-A57b.mjs";
1
+ import { t as REVENUE_EVENTS } from "./event-constants-CPmh2sIt.mjs";
2
2
  import { PAYMENT_METHOD_KIND } from "@classytic/primitives/payment-method-kind";
3
3
  import { createEvent, matchEventPattern } from "@classytic/primitives/events";
4
+ import { CURRENCY_PATTERN } from "@classytic/primitives/currency";
4
5
  import { z } from "zod";
5
6
 
6
7
  //#region src/events/in-process-bus.ts
@@ -91,7 +92,7 @@ function defineRevenueEvent(input) {
91
92
  }
92
93
  const money = z.object({
93
94
  amount: z.number().nonnegative(),
94
- currency: z.string().length(3)
95
+ currency: z.string().regex(CURRENCY_PATTERN, "ISO 4217 (3 uppercase letters)")
95
96
  });
96
97
  const transactionRef = z.object({
97
98
  _id: z.union([z.string(), z.any()]).optional(),
@@ -1,10 +1,11 @@
1
1
  import { _ as TRANSACTION_STATUS, a as TRANSACTION_KIND, s as initialStatusFor } from "./bank-feed.enums-ByS3mjX0.mjs";
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";
2
+ import { _ as ValidationError, g as UnmanagedSessionError, h as TransactionNotFoundError, m as SubscriptionNotFoundError, n as BankFeedImportError, o as MethodKindLockedError, p as SettlementNotFoundError, v as WrongTransactionKindError } from "./errors-8P5tRgV7.mjs";
3
+ import { n as createEvent, t as REVENUE_EVENTS } from "./event-constants-CPmh2sIt.mjs";
4
+ import { g as SETTLEMENT_STATUS, r as SUBSCRIPTION_STATUS, w as HOLD_STATUS } from "./subscription.enums-olW5dyly.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-CNfQj92L.mjs";
6
+ import { c as calculateCommission, l as reverseCommission, n as calculateSplits, o as reverseTax, t as calculateOrganizationPayout } from "./splits-rs6mkdGN.mjs";
7
7
  import { Repository, repoOptionsFromCtx, withTransaction } from "@classytic/mongokit";
8
+ import { addDays, addMonths, addYears } from "@classytic/primitives/calendar";
8
9
 
9
10
  //#region src/repositories/base.repository.ts
10
11
  /**
@@ -139,12 +140,14 @@ var RevenueRepositoryBase = class extends Repository {
139
140
  * @param ctx - The same context that produced the business write.
140
141
  */
141
142
  async dispatch(event, ctx = {}) {
143
+ if (ctx.session && !this.deps.outbox) throw new UnmanagedSessionError();
142
144
  if (this.deps.outbox) try {
143
145
  await this.deps.outbox.save(event, ctx.session !== void 0 ? { session: ctx.session } : {});
144
146
  } catch (err) {
145
147
  this.deps.logger?.error("[revenue] outbox.save failed for", event.type, err);
146
148
  throw err;
147
149
  }
150
+ if (ctx.session) return;
148
151
  try {
149
152
  await this.deps.events.publish(event);
150
153
  } catch (err) {
@@ -1303,11 +1306,7 @@ var SubscriptionRepository = class extends RevenueRepositoryBase {
1303
1306
  if (!sub) throw new SubscriptionNotFoundError(subscriptionId);
1304
1307
  SUBSCRIPTION_STATE_MACHINE.validate(sub.status, SUBSCRIPTION_STATUS.ACTIVE, subscriptionId);
1305
1308
  const now = options.timestamp ?? /* @__PURE__ */ new Date();
1306
- const endDate = new Date(now);
1307
- if (sub.planKey === "monthly") endDate.setMonth(endDate.getMonth() + 1);
1308
- else if (sub.planKey === "quarterly") endDate.setMonth(endDate.getMonth() + 3);
1309
- else if (sub.planKey === "yearly") endDate.setFullYear(endDate.getFullYear() + 1);
1310
- else endDate.setDate(endDate.getDate() + 30);
1309
+ const endDate = sub.planKey === "monthly" ? addMonths(now, 1) : sub.planKey === "quarterly" ? addMonths(now, 3) : sub.planKey === "yearly" ? addYears(now, 1) : addDays(now, 30);
1311
1310
  const updated = await this.update(subscriptionId, {
1312
1311
  status: SUBSCRIPTION_STATUS.ACTIVE,
1313
1312
  isActive: true,
@@ -1402,6 +1401,38 @@ var SubscriptionRepository = class extends RevenueRepositoryBase {
1402
1401
 
1403
1402
  //#endregion
1404
1403
  //#region src/repositories/settlement.repository.ts
1404
+ function emptySettlementSummary() {
1405
+ return {
1406
+ currency: null,
1407
+ pending: 0,
1408
+ held: 0,
1409
+ available: 0,
1410
+ processing: 0,
1411
+ paidOut: 0,
1412
+ failed: 0,
1413
+ lifetime: 0
1414
+ };
1415
+ }
1416
+ /** Bucket one aggregated (status, due, total) row into a running summary. */
1417
+ function foldSettlementStatus(s, status, due, total) {
1418
+ switch (status) {
1419
+ case SETTLEMENT_STATUS.PENDING:
1420
+ s.pending += total;
1421
+ if (due) s.available += total;
1422
+ else s.held += total;
1423
+ break;
1424
+ case SETTLEMENT_STATUS.PROCESSING:
1425
+ s.processing += total;
1426
+ break;
1427
+ case SETTLEMENT_STATUS.COMPLETED:
1428
+ s.paidOut += total;
1429
+ break;
1430
+ case SETTLEMENT_STATUS.FAILED:
1431
+ s.failed += total;
1432
+ break;
1433
+ default: break;
1434
+ }
1435
+ }
1405
1436
  /**
1406
1437
  * SettlementRepository — payouts to recipients (organizations, vendors,
1407
1438
  * affiliates).
@@ -1563,12 +1594,16 @@ var SettlementRepository = class extends RevenueRepositoryBase {
1563
1594
  }), ctx);
1564
1595
  return updated;
1565
1596
  }
1566
- async recipientBalance(recipientId, options = {}, ctx = {}) {
1567
- const match = { recipientId };
1568
- if (options.recipientType !== void 0) match.recipientType = options.recipientType;
1569
- if (options.currency !== void 0) match.currency = options.currency;
1597
+ /**
1598
+ * Settlement money-state for an ARBITRARY filter — a recipient, an org, or
1599
+ * the whole platform (pass `_bypassTenant` in ctx to span every org). One
1600
+ * tenant-scoped `aggregatePipeline` (the `{ recipientId, status }` index backs
1601
+ * the `$match`), never a raw `Model.aggregate`. The reusable rollup behind
1602
+ * `recipientBalance`, platform reconciliation, and cross-org earnings.
1603
+ */
1604
+ async summary(filter = {}, ctx = {}) {
1570
1605
  const now = /* @__PURE__ */ new Date();
1571
- const rows = await this.aggregatePipeline([{ $match: match }, { $group: {
1606
+ const rows = await this.aggregatePipeline([{ $match: filter }, { $group: {
1572
1607
  _id: {
1573
1608
  status: "$status",
1574
1609
  due: { $lte: ["$scheduledAt", now] }
@@ -1576,39 +1611,64 @@ var SettlementRepository = class extends RevenueRepositoryBase {
1576
1611
  total: { $sum: "$amount" },
1577
1612
  currency: { $first: "$currency" }
1578
1613
  } }], this.optsFromCtx(ctx));
1579
- const balance = {
1580
- recipientId,
1581
- currency: options.currency ?? null,
1582
- pending: 0,
1583
- held: 0,
1584
- available: 0,
1585
- processing: 0,
1586
- paidOut: 0,
1587
- failed: 0,
1588
- lifetime: 0
1589
- };
1614
+ const out = emptySettlementSummary();
1590
1615
  for (const row of rows) {
1591
- switch (row._id.status) {
1592
- case SETTLEMENT_STATUS.PENDING:
1593
- balance.pending += row.total;
1594
- if (row._id.due) balance.available += row.total;
1595
- else balance.held += row.total;
1596
- break;
1597
- case SETTLEMENT_STATUS.PROCESSING:
1598
- balance.processing += row.total;
1599
- break;
1600
- case SETTLEMENT_STATUS.COMPLETED:
1601
- balance.paidOut += row.total;
1602
- break;
1603
- case SETTLEMENT_STATUS.FAILED:
1604
- balance.failed += row.total;
1605
- break;
1606
- default: break;
1616
+ foldSettlementStatus(out, row._id.status, row._id.due, row.total);
1617
+ if (row.currency && !out.currency) out.currency = row.currency;
1618
+ }
1619
+ out.lifetime = out.pending + out.processing + out.paidOut;
1620
+ return out;
1621
+ }
1622
+ /**
1623
+ * Per-recipient settlement rollup — one {@link RecipientBreakdown} per
1624
+ * distinct (recipientId, recipientType) matching `filter`. The reusable list
1625
+ * behind a platform "who is owed what" view; pass `_bypassTenant` to span all
1626
+ * orgs.
1627
+ */
1628
+ async breakdownByRecipient(filter = {}, ctx = {}) {
1629
+ const now = /* @__PURE__ */ new Date();
1630
+ const rows = await this.aggregatePipeline([{ $match: filter }, { $group: {
1631
+ _id: {
1632
+ recipientId: "$recipientId",
1633
+ recipientType: "$recipientType",
1634
+ status: "$status",
1635
+ due: { $lte: ["$scheduledAt", now] }
1636
+ },
1637
+ total: { $sum: "$amount" },
1638
+ currency: { $first: "$currency" },
1639
+ role: { $first: "$metadata.role" }
1640
+ } }], this.optsFromCtx(ctx));
1641
+ const byKey = /* @__PURE__ */ new Map();
1642
+ for (const row of rows) {
1643
+ const key = `${row._id.recipientType}:${row._id.recipientId}`;
1644
+ let r = byKey.get(key);
1645
+ if (!r) {
1646
+ r = {
1647
+ recipientId: row._id.recipientId,
1648
+ recipientType: row._id.recipientType,
1649
+ role: row.role ?? null,
1650
+ ...emptySettlementSummary()
1651
+ };
1652
+ byKey.set(key, r);
1607
1653
  }
1608
- if (row.currency && !balance.currency) balance.currency = row.currency;
1654
+ foldSettlementStatus(r, row._id.status, row._id.due, row.total);
1655
+ if (row.role && !r.role) r.role = row.role;
1656
+ if (row.currency && !r.currency) r.currency = row.currency;
1609
1657
  }
1610
- balance.lifetime = balance.pending + balance.processing + balance.paidOut;
1611
- return balance;
1658
+ for (const r of byKey.values()) r.lifetime = r.pending + r.processing + r.paidOut;
1659
+ return [...byKey.values()];
1660
+ }
1661
+ /** One recipient's wallet — a thin `summary` over `{ recipientId, … }`. */
1662
+ async recipientBalance(recipientId, options = {}, ctx = {}) {
1663
+ const filter = { recipientId };
1664
+ if (options.recipientType !== void 0) filter.recipientType = options.recipientType;
1665
+ if (options.currency !== void 0) filter.currency = options.currency;
1666
+ const s = await this.summary(filter, ctx);
1667
+ return {
1668
+ recipientId,
1669
+ ...s,
1670
+ currency: options.currency ?? s.currency
1671
+ };
1612
1672
  }
1613
1673
  };
1614
1674
 
@@ -1,2 +1,2 @@
1
- import { A as SplitInfo, B as validateTaxCalculation, C as multiplyMoney, D as toCurrencyCode, E as sumMoney, F as TaxConfig, H as calculateCommission, I as TaxType, L as calculateTax, M as calculateOrganizationPayout, N as calculateSplits, O as toMajor, P as TaxCalculation, R as getTaxType, S as money, T as subtractMoney, U as reverseCommission, V as CommissionInfo, _ as isMoney, a as CurrencyCode, b as isZeroMoney, c as Money, d as addMoney, f as compareMoney, g as isCurrencyCode, h as fromSmallestUnit, i as CURRENCIES, j as SplitRule, k as toSmallestUnit, l as MoneyValue, m as fromMajor, n as getAuditTrail, o as CurrencyMismatchError, p as equalsMoney, r as getLastStateChange, s as MINOR_UNIT_FACTOR, t as appendAuditEvent, u as absMoney, v as isNegativeMoney, w as negateMoney, x as minorUnitFactor, y as isPositiveMoney, z as reverseTax } from "../audit-DRKuLBFO.mjs";
2
- export { CURRENCIES, CommissionInfo, type CurrencyCode, CurrencyMismatchError, MINOR_UNIT_FACTOR, type Money, type MoneyValue, SplitInfo, SplitRule, TaxCalculation, TaxConfig, TaxType, absMoney, addMoney, appendAuditEvent, calculateCommission, calculateOrganizationPayout, calculateSplits, calculateTax, compareMoney, equalsMoney, fromMajor, fromSmallestUnit, getAuditTrail, getLastStateChange, getTaxType, isCurrencyCode, isMoney, isNegativeMoney, isPositiveMoney, isZeroMoney, minorUnitFactor, money, multiplyMoney, negateMoney, reverseCommission, reverseTax, subtractMoney, sumMoney, toCurrencyCode, toMajor, toSmallestUnit, validateTaxCalculation };
1
+ import { A as SplitInfo, B as reverseTax, C as multiplyMoney, D as toCurrencyCode, E as sumMoney, F as TaxCalculation, H as CommissionInfo, I as TaxConfig, L as TaxType, M as calculateOrganizationPayout, N as calculateSplits, O as toMajor, P as reverseSplits, R as calculateTax, S as money, T as subtractMoney, U as calculateCommission, V as validateTaxCalculation, W as reverseCommission, _ as isMoney, a as CurrencyCode, b as isZeroMoney, c as Money, d as addMoney, f as compareMoney, g as isCurrencyCode, h as fromSmallestUnit, i as CURRENCIES, j as SplitRule, k as toSmallestUnit, l as MoneyValue, m as fromMajor, n as getAuditTrail, o as CurrencyMismatchError, p as equalsMoney, r as getLastStateChange, s as MINOR_UNIT_FACTOR, t as appendAuditEvent, u as absMoney, v as isNegativeMoney, w as negateMoney, x as minorUnitFactor, y as isPositiveMoney, z as getTaxType } from "../audit-Cug6g_31.mjs";
2
+ export { CURRENCIES, CommissionInfo, type CurrencyCode, CurrencyMismatchError, MINOR_UNIT_FACTOR, type Money, type MoneyValue, SplitInfo, SplitRule, TaxCalculation, TaxConfig, TaxType, absMoney, addMoney, appendAuditEvent, calculateCommission, calculateOrganizationPayout, calculateSplits, calculateTax, compareMoney, equalsMoney, fromMajor, fromSmallestUnit, getAuditTrail, getLastStateChange, getTaxType, isCurrencyCode, isMoney, isNegativeMoney, isPositiveMoney, isZeroMoney, minorUnitFactor, money, multiplyMoney, negateMoney, reverseCommission, reverseSplits, reverseTax, subtractMoney, sumMoney, toCurrencyCode, toMajor, toSmallestUnit, validateTaxCalculation };
@@ -1,4 +1,4 @@
1
- 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";
2
- 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";
1
+ import { a as getTaxType, c as calculateCommission, i as calculateTax, l as reverseCommission, n as calculateSplits, o as reverseTax, r as reverseSplits, s as validateTaxCalculation, t as calculateOrganizationPayout } from "../splits-rs6mkdGN.mjs";
2
+ 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-C5c4LAf6.mjs";
3
3
 
4
- export { CURRENCIES, CurrencyMismatchError, MINOR_UNIT_FACTOR, absMoney, addMoney, appendAuditEvent, calculateCommission, calculateOrganizationPayout, calculateSplits, calculateTax, compareMoney, equalsMoney, fromMajor, fromSmallestUnit, getAuditTrail, getLastStateChange, getTaxType, isCurrencyCode, isMoney, isNegativeMoney, isPositiveMoney, isZeroMoney, minorUnitFactor, money, multiplyMoney, negateMoney, reverseCommission, reverseTax, subtractMoney, sumMoney, toCurrencyCode, toMajor, toSmallestUnit, validateTaxCalculation };
4
+ export { CURRENCIES, CurrencyMismatchError, MINOR_UNIT_FACTOR, absMoney, addMoney, appendAuditEvent, calculateCommission, calculateOrganizationPayout, calculateSplits, calculateTax, compareMoney, equalsMoney, fromMajor, fromSmallestUnit, getAuditTrail, getLastStateChange, getTaxType, isCurrencyCode, isMoney, isNegativeMoney, isPositiveMoney, isZeroMoney, minorUnitFactor, money, multiplyMoney, negateMoney, reverseCommission, reverseSplits, reverseTax, subtractMoney, sumMoney, toCurrencyCode, toMajor, toSmallestUnit, validateTaxCalculation };
@@ -1,3 +1,5 @@
1
+ import { _ as ValidationError } from "./errors-8P5tRgV7.mjs";
2
+
1
3
  //#region src/shared/calculators/commission.ts
2
4
  function calculateCommission(amount, commissionRate, gatewayFeeRate = 0) {
3
5
  if (!commissionRate || commissionRate <= 0) return null;
@@ -17,9 +19,9 @@ function calculateCommission(amount, commissionRate, gatewayFeeRate = 0) {
17
19
  }
18
20
  function reverseCommission(originalCommission, originalAmount, refundAmount) {
19
21
  if (!originalCommission?.netAmount) return null;
20
- if (!originalAmount || originalAmount <= 0) throw new Error("Original amount must be greater than 0");
21
- if (refundAmount < 0) throw new Error("Refund amount cannot be negative");
22
- if (refundAmount > originalAmount) throw new Error("Refund amount exceeds original amount");
22
+ if (!originalAmount || originalAmount <= 0) throw new ValidationError("Original amount must be greater than 0");
23
+ if (refundAmount < 0) throw new ValidationError("Refund amount cannot be negative");
24
+ if (refundAmount > originalAmount) throw new ValidationError("Refund amount exceeds original amount");
23
25
  const refundRatio = refundAmount / originalAmount;
24
26
  return {
25
27
  rate: originalCommission.rate,
@@ -74,9 +76,9 @@ function reverseTax(originalTax, originalAmount, refundAmount) {
74
76
  totalAmount: refundAmount,
75
77
  pricesIncludeTax: false
76
78
  };
77
- if (!originalAmount || originalAmount <= 0) throw new Error("Original amount must be greater than 0");
78
- if (refundAmount < 0) throw new Error("Refund amount cannot be negative");
79
- if (refundAmount > originalAmount) throw new Error("Refund amount exceeds original amount");
79
+ if (!originalAmount || originalAmount <= 0) throw new ValidationError("Original amount must be greater than 0");
80
+ if (refundAmount < 0) throw new ValidationError("Refund amount cannot be negative");
81
+ if (refundAmount > originalAmount) throw new ValidationError("Refund amount exceeds original amount");
80
82
  const refundRatio = refundAmount / originalAmount;
81
83
  const reversedType = originalTax.type ? originalTax.type === "collected" ? "paid" : originalTax.type === "paid" ? "collected" : "exempt" : void 0;
82
84
  return {
@@ -118,6 +120,19 @@ function calculateSplits(amount, rules, gatewayFeeRate = 0) {
118
120
  function calculateOrganizationPayout(amount, splits) {
119
121
  return amount - splits.reduce((sum, s) => sum + s.grossAmount, 0);
120
122
  }
123
+ function reverseSplits(originalSplits, originalAmount, refundAmount) {
124
+ if (!originalAmount || originalAmount <= 0) throw new ValidationError("Original amount must be greater than 0");
125
+ if (refundAmount < 0) throw new ValidationError("Refund amount cannot be negative");
126
+ if (refundAmount > originalAmount) throw new ValidationError("Refund amount exceeds original amount");
127
+ const refundRatio = refundAmount / originalAmount;
128
+ return originalSplits.map((s) => ({
129
+ ...s,
130
+ grossAmount: Math.round(s.grossAmount * refundRatio),
131
+ gatewayFeeAmount: Math.round(s.gatewayFeeAmount * refundRatio),
132
+ netAmount: Math.round(s.netAmount * refundRatio),
133
+ status: "waived"
134
+ }));
135
+ }
121
136
 
122
137
  //#endregion
123
- export { reverseTax as a, reverseCommission as c, getTaxType as i, calculateSplits as n, validateTaxCalculation as o, calculateTax as r, calculateCommission as s, calculateOrganizationPayout as t };
138
+ export { getTaxType as a, calculateCommission as c, calculateTax as i, reverseCommission as l, calculateSplits as n, reverseTax as o, reverseSplits as r, validateTaxCalculation as s, calculateOrganizationPayout as t };
@@ -1,4 +1,4 @@
1
- import { A as transactionBaseSchema, C as subscriptionBaseSchema, D as TransactionCreateInput, E as subscriptionUpdateSchema, M as transactionListFilterSchema, N as transactionUpdateSchema, O as TransactionListFilter, S as SubscriptionUpdateInput, T as subscriptionListFilterSchema, _ as settlementCreateSchema, a as escrowReleaseSchema, b as SubscriptionCreateInput, c as PaymentVerifyInput, d as paymentVerifySchema, f as refundSchema, g as settlementBaseSchema, h as SettlementUpdateInput, i as escrowHoldSchema, j as transactionCreateSchema, k as TransactionUpdateInput, l as RefundInput, m as SettlementListFilter, n as EscrowReleaseInput, o as splitRuleSchema, p as SettlementCreateInput, r as SplitRuleInput, s as PaymentIntentInput, t as EscrowHoldInput, u as paymentIntentSchema, v as settlementListFilterSchema, w as subscriptionCreateSchema, x as SubscriptionListFilter, y as settlementUpdateSchema } from "../escrow.schema-o4qhjOca.mjs";
1
+ import { A as transactionBaseSchema, C as subscriptionBaseSchema, D as TransactionCreateInput, E as subscriptionUpdateSchema, M as transactionListFilterSchema, N as transactionUpdateSchema, O as TransactionListFilter, S as SubscriptionUpdateInput, T as subscriptionListFilterSchema, _ as settlementCreateSchema, a as escrowReleaseSchema, b as SubscriptionCreateInput, c as PaymentVerifyInput, d as paymentVerifySchema, f as refundSchema, g as settlementBaseSchema, h as SettlementUpdateInput, i as escrowHoldSchema, j as transactionCreateSchema, k as TransactionUpdateInput, l as RefundInput, m as SettlementListFilter, n as EscrowReleaseInput, o as splitRuleSchema, p as SettlementCreateInput, r as SplitRuleInput, s as PaymentIntentInput, t as EscrowHoldInput, u as paymentIntentSchema, v as settlementListFilterSchema, w as subscriptionCreateSchema, x as SubscriptionListFilter, y as settlementUpdateSchema } from "../escrow.schema-DSx7EywJ.mjs";
2
2
  import { z } from "zod";
3
3
 
4
4
  //#region src/validators/bank-feed.schema.d.ts
@@ -1,5 +1,6 @@
1
1
  import { n as BANK_FEED_SOURCE_VALUES, o as TRANSACTION_KIND_VALUES } from "../bank-feed.enums-ByS3mjX0.mjs";
2
- 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-BcKdzrJ7.mjs";
2
+ 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-gksK8F-W.mjs";
3
+ import { CURRENCY_PATTERN } from "@classytic/primitives/currency";
3
4
  import { z } from "zod";
4
5
 
5
6
  //#region src/validators/bank-feed.schema.ts
@@ -12,7 +13,7 @@ import { z } from "zod";
12
13
  */
13
14
  const moneySchema = z.object({
14
15
  amount: z.number().int(),
15
- currency: z.string().length(3)
16
+ currency: z.string().regex(CURRENCY_PATTERN, "ISO 4217 (3 uppercase letters)")
16
17
  });
17
18
  const counterpartySchema = z.object({
18
19
  name: z.string().optional(),
@@ -66,7 +67,7 @@ const unmatchBodySchema = z.object({ unmatchedBy: z.string().optional() });
66
67
  const manualEntrySchema = z.object({
67
68
  kind: z.literal("manual").default("manual"),
68
69
  amount: z.number().nonnegative(),
69
- currency: z.string().length(3),
70
+ currency: z.string().regex(CURRENCY_PATTERN, "ISO 4217 (3 uppercase letters)"),
70
71
  flow: z.enum(["inflow", "outflow"]),
71
72
  type: z.string().min(1),
72
73
  description: z.string().optional(),
@@ -79,7 +80,7 @@ const manualEntrySchema = z.object({
79
80
  });
80
81
  const findMatchCandidatesQuerySchema = z.object({
81
82
  amount: z.coerce.number().nonnegative(),
82
- currency: z.string().length(3).optional(),
83
+ currency: z.string().regex(CURRENCY_PATTERN, "ISO 4217 (3 uppercase letters)").optional(),
83
84
  postedDate: z.coerce.date(),
84
85
  toleranceDays: z.coerce.number().int().min(0).max(30).optional(),
85
86
  amountTolerancePct: z.coerce.number().min(0).max(.5).optional(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@classytic/revenue",
3
- "version": "2.5.0",
3
+ "version": "2.7.0",
4
4
  "description": "Payment lifecycle engine — transactions, subscriptions, escrow, settlements, commissions. MongoKit-powered, Arc-compatible, framework-agnostic.",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -100,9 +100,9 @@
100
100
  "LICENSE"
101
101
  ],
102
102
  "peerDependencies": {
103
- "@classytic/mongokit": ">=3.13.3",
104
- "@classytic/primitives": ">=0.7.1",
105
- "@classytic/repo-core": ">=0.4.2",
103
+ "@classytic/mongokit": ">=3.18.0",
104
+ "@classytic/primitives": ">=0.9.1",
105
+ "@classytic/repo-core": ">=0.7.0",
106
106
  "mongoose": ">=9.4.1",
107
107
  "zod": ">=4.0.0"
108
108
  },
@@ -110,9 +110,10 @@
110
110
  "node": ">=18.0.0"
111
111
  },
112
112
  "devDependencies": {
113
- "@classytic/mongokit": "^3.13.3",
114
- "@classytic/primitives": "^0.7.1",
115
- "@classytic/repo-core": "^0.4.2",
113
+ "@classytic/dev-tools": "^0.2.0",
114
+ "@classytic/mongokit": "^3.18.0",
115
+ "@classytic/primitives": "^0.9.0",
116
+ "@classytic/repo-core": "^0.7.0",
116
117
  "@types/node": "^22.8.7",
117
118
  "mongoose": "^9.4.1",
118
119
  "tsdown": "^0.20.3",