@wopr-network/platform-core 1.13.2 → 1.14.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 (238) hide show
  1. package/dist/api/routes/admin-credits.d.ts +2 -2
  2. package/dist/api/routes/admin-credits.js +9 -4
  3. package/dist/api/routes/quota.d.ts +2 -2
  4. package/dist/api/routes/verify-email.d.ts +3 -3
  5. package/dist/backup/on-demand-snapshot-service.d.ts +2 -2
  6. package/dist/billing/payram/webhook.d.ts +3 -3
  7. package/dist/billing/payram/webhook.js +5 -1
  8. package/dist/billing/payram/webhook.test.js +5 -4
  9. package/dist/billing/stripe/stripe-payment-processor.d.ts +2 -2
  10. package/dist/billing/stripe/stripe-payment-processor.test.js +7 -0
  11. package/dist/billing/stripe/tenant-store.d.ts +1 -1
  12. package/dist/billing/stripe/tenant-store.js +1 -1
  13. package/dist/credits/auto-topup-charge.d.ts +2 -2
  14. package/dist/credits/auto-topup-charge.js +5 -1
  15. package/dist/credits/auto-topup-charge.test.js +5 -4
  16. package/dist/credits/auto-topup-usage.d.ts +2 -2
  17. package/dist/credits/auto-topup-usage.test.js +53 -12
  18. package/dist/credits/credit-expiry-cron.d.ts +2 -2
  19. package/dist/credits/credit-expiry-cron.js +7 -4
  20. package/dist/credits/credit-expiry-cron.test.js +25 -8
  21. package/dist/credits/credit-ledger.d.ts +2 -2
  22. package/dist/credits/credit-ledger.js +1 -1
  23. package/dist/credits/dividend-cron.d.ts +4 -6
  24. package/dist/credits/dividend-cron.js +10 -16
  25. package/dist/credits/dividend-cron.test.js +31 -44
  26. package/dist/credits/dividend-repository.js +19 -22
  27. package/dist/credits/dividend-repository.test.js +4 -3
  28. package/dist/credits/index.d.ts +4 -2
  29. package/dist/credits/index.js +2 -1
  30. package/dist/credits/ledger.d.ts +195 -0
  31. package/dist/credits/ledger.js +561 -0
  32. package/dist/credits/ledger.test.js +418 -0
  33. package/dist/credits/signup-grant.d.ts +2 -2
  34. package/dist/credits/signup-grant.js +4 -4
  35. package/dist/credits/signup-grant.test.js +5 -3
  36. package/dist/credits/trial-balance-cron.d.ts +19 -0
  37. package/dist/credits/trial-balance-cron.js +30 -0
  38. package/dist/credits/trial-balance-cron.test.js +55 -0
  39. package/dist/db/schema/gateway-service-keys.d.ts +109 -0
  40. package/dist/db/schema/gateway-service-keys.js +18 -0
  41. package/dist/db/schema/index.d.ts +2 -0
  42. package/dist/db/schema/index.js +2 -0
  43. package/dist/db/schema/ledger.d.ts +442 -0
  44. package/dist/db/schema/ledger.js +76 -0
  45. package/dist/gateway/credit-gate.d.ts +2 -2
  46. package/dist/gateway/credit-gate.js +5 -1
  47. package/dist/gateway/credit-gate.test.js +35 -33
  48. package/dist/gateway/gateway-routes.test.js +1 -1
  49. package/dist/gateway/index.d.ts +2 -0
  50. package/dist/gateway/index.js +1 -0
  51. package/dist/gateway/protocol/anthropic.js +1 -1
  52. package/dist/gateway/protocol/deps.d.ts +5 -5
  53. package/dist/gateway/protocol/openai.js +1 -1
  54. package/dist/gateway/proxy.d.ts +4 -4
  55. package/dist/gateway/route-mounting.test.js +1 -1
  56. package/dist/gateway/service-key-auth.d.ts +1 -1
  57. package/dist/gateway/service-key-auth.js +1 -1
  58. package/dist/gateway/service-key-repository.d.ts +27 -0
  59. package/dist/gateway/service-key-repository.js +64 -0
  60. package/dist/gateway/types.d.ts +5 -5
  61. package/dist/metering/reconciliation-cron.test.js +9 -8
  62. package/dist/metering/reconciliation-repository.js +12 -10
  63. package/dist/metering/reconciliation-repository.test.js +9 -8
  64. package/dist/monetization/affiliate/affiliate-admin-repository.js +10 -8
  65. package/dist/monetization/affiliate/affiliate-admin-repository.test.js +32 -13
  66. package/dist/monetization/affiliate/credit-match.d.ts +2 -2
  67. package/dist/monetization/affiliate/credit-match.js +4 -1
  68. package/dist/monetization/affiliate/credit-match.test.js +58 -13
  69. package/dist/monetization/affiliate/new-user-bonus.d.ts +2 -2
  70. package/dist/monetization/affiliate/new-user-bonus.js +4 -1
  71. package/dist/monetization/affiliate/new-user-bonus.test.js +4 -3
  72. package/dist/monetization/credits/auto-topup-charge.d.ts +2 -2
  73. package/dist/monetization/credits/auto-topup-charge.js +5 -1
  74. package/dist/monetization/credits/auto-topup-charge.test.js +5 -4
  75. package/dist/monetization/credits/auto-topup-usage.d.ts +2 -2
  76. package/dist/monetization/credits/auto-topup-usage.test.js +53 -12
  77. package/dist/monetization/credits/bot-billing.d.ts +3 -3
  78. package/dist/monetization/credits/bot-billing.test.js +18 -5
  79. package/dist/monetization/credits/credit-expiry-cron.test.js +25 -8
  80. package/dist/monetization/credits/dividend-cron.d.ts +2 -4
  81. package/dist/monetization/credits/dividend-cron.js +7 -4
  82. package/dist/monetization/credits/dividend-cron.test.js +26 -46
  83. package/dist/monetization/credits/dividend-repository.js +15 -24
  84. package/dist/monetization/credits/dividend-repository.test.js +4 -3
  85. package/dist/monetization/credits/index.d.ts +2 -2
  86. package/dist/monetization/credits/index.js +1 -1
  87. package/dist/monetization/credits/member-usage.test.js +23 -10
  88. package/dist/monetization/credits/phone-billing.d.ts +2 -2
  89. package/dist/monetization/credits/phone-billing.js +5 -1
  90. package/dist/monetization/credits/phone-billing.test.js +9 -12
  91. package/dist/monetization/credits/runtime-cron.d.ts +2 -2
  92. package/dist/monetization/credits/runtime-cron.js +32 -8
  93. package/dist/monetization/credits/runtime-cron.test.js +28 -27
  94. package/dist/monetization/credits/runtime-scheduler.d.ts +2 -2
  95. package/dist/monetization/credits/runtime-scheduler.test.js +1 -1
  96. package/dist/monetization/credits/signup-grant.test.js +5 -3
  97. package/dist/monetization/credits/storage-tier-cron.test.js +3 -2
  98. package/dist/monetization/credits/trial-balance-cron.test.js +42 -0
  99. package/dist/monetization/feature-gate.d.ts +3 -3
  100. package/dist/monetization/index.d.ts +3 -3
  101. package/dist/monetization/index.js +1 -1
  102. package/dist/monetization/metering/reconciliation-cron.test.js +9 -8
  103. package/dist/monetization/metering/reconciliation-repository.js +11 -10
  104. package/dist/monetization/metering/reconciliation-repository.test.js +9 -8
  105. package/dist/monetization/payram/webhook.d.ts +2 -2
  106. package/dist/monetization/payram/webhook.js +5 -1
  107. package/dist/monetization/payram/webhook.test.js +5 -4
  108. package/dist/monetization/promotions/engine.d.ts +2 -2
  109. package/dist/monetization/promotions/engine.js +4 -1
  110. package/dist/monetization/promotions/engine.test.js +3 -1
  111. package/dist/monetization/repository-types.d.ts +1 -1
  112. package/dist/monetization/socket/socket.d.ts +3 -3
  113. package/dist/monetization/stripe/stripe-payment-processor.d.ts +2 -2
  114. package/dist/monetization/stripe/stripe-payment-processor.test.js +7 -0
  115. package/dist/monetization/stripe/webhook.d.ts +2 -2
  116. package/dist/monetization/stripe/webhook.js +70 -6
  117. package/dist/monetization/stripe/webhook.test.js +20 -15
  118. package/dist/onboarding/onboarding-service.d.ts +2 -2
  119. package/dist/onboarding/onboarding-service.js +6 -2
  120. package/drizzle/migrations/0002_gateway_service_keys.sql +14 -0
  121. package/drizzle/migrations/0003_double_entry_ledger.sql +82 -0
  122. package/drizzle/migrations/meta/_journal.json +14 -0
  123. package/package.json +1 -1
  124. package/src/api/routes/admin-credits.ts +11 -14
  125. package/src/api/routes/quota.ts +2 -2
  126. package/src/api/routes/verify-email.ts +4 -4
  127. package/src/backup/on-demand-snapshot-service.test.ts +3 -3
  128. package/src/backup/on-demand-snapshot-service.ts +3 -3
  129. package/src/billing/payram/webhook.test.ts +7 -5
  130. package/src/billing/payram/webhook.ts +8 -11
  131. package/src/billing/stripe/stripe-payment-processor.test.ts +10 -3
  132. package/src/billing/stripe/stripe-payment-processor.ts +3 -3
  133. package/src/billing/stripe/tenant-store.ts +1 -1
  134. package/src/credits/auto-topup-charge.test.ts +7 -5
  135. package/src/credits/auto-topup-charge.ts +7 -10
  136. package/src/credits/auto-topup-usage.test.ts +55 -13
  137. package/src/credits/auto-topup-usage.ts +2 -2
  138. package/src/credits/credit-expiry-cron.test.ts +26 -45
  139. package/src/credits/credit-expiry-cron.ts +9 -12
  140. package/src/credits/credit-ledger.ts +3 -3
  141. package/src/credits/dividend-cron.test.ts +38 -45
  142. package/src/credits/dividend-cron.ts +12 -26
  143. package/src/credits/dividend-repository.test.ts +4 -3
  144. package/src/credits/dividend-repository.ts +21 -23
  145. package/src/credits/index.ts +23 -4
  146. package/src/credits/ledger.test.ts +514 -0
  147. package/src/credits/ledger.ts +851 -0
  148. package/src/credits/signup-grant.test.ts +7 -4
  149. package/src/credits/signup-grant.ts +6 -12
  150. package/src/credits/trial-balance-cron.test.ts +68 -0
  151. package/src/credits/trial-balance-cron.ts +46 -0
  152. package/src/db/schema/gateway-service-keys.ts +23 -0
  153. package/src/db/schema/index.ts +2 -0
  154. package/src/db/schema/ledger.ts +94 -0
  155. package/src/gateway/credit-gate-wiring.test.ts +3 -3
  156. package/src/gateway/credit-gate.test.ts +35 -33
  157. package/src/gateway/credit-gate.ts +6 -10
  158. package/src/gateway/gateway-routes.test.ts +6 -6
  159. package/src/gateway/index.ts +2 -0
  160. package/src/gateway/protocol/anthropic.ts +2 -2
  161. package/src/gateway/protocol/deps.ts +5 -5
  162. package/src/gateway/protocol/openai.ts +2 -2
  163. package/src/gateway/proxy.ts +4 -4
  164. package/src/gateway/route-mounting.test.ts +3 -3
  165. package/src/gateway/service-key-auth.ts +4 -2
  166. package/src/gateway/service-key-repository.ts +87 -0
  167. package/src/gateway/types.ts +5 -5
  168. package/src/metering/reconciliation-cron.test.ts +10 -9
  169. package/src/metering/reconciliation-repository.test.ts +10 -9
  170. package/src/metering/reconciliation-repository.ts +14 -11
  171. package/src/monetization/affiliate/affiliate-admin-repository.test.ts +32 -19
  172. package/src/monetization/affiliate/affiliate-admin-repository.ts +16 -8
  173. package/src/monetization/affiliate/credit-match.test.ts +60 -14
  174. package/src/monetization/affiliate/credit-match.ts +6 -9
  175. package/src/monetization/affiliate/new-user-bonus.test.ts +6 -4
  176. package/src/monetization/affiliate/new-user-bonus.ts +6 -9
  177. package/src/monetization/credits/auto-topup-charge.test.ts +7 -5
  178. package/src/monetization/credits/auto-topup-charge.ts +7 -10
  179. package/src/monetization/credits/auto-topup-usage.test.ts +55 -13
  180. package/src/monetization/credits/auto-topup-usage.ts +2 -2
  181. package/src/monetization/credits/bot-billing.test.ts +20 -6
  182. package/src/monetization/credits/bot-billing.ts +3 -3
  183. package/src/monetization/credits/credit-expiry-cron.test.ts +26 -45
  184. package/src/monetization/credits/dividend-cron.test.ts +34 -48
  185. package/src/monetization/credits/dividend-cron.ts +9 -14
  186. package/src/monetization/credits/dividend-repository.test.ts +4 -3
  187. package/src/monetization/credits/dividend-repository.ts +19 -25
  188. package/src/monetization/credits/index.ts +4 -4
  189. package/src/monetization/credits/member-usage.test.ts +25 -11
  190. package/src/monetization/credits/phone-billing.test.ts +18 -26
  191. package/src/monetization/credits/phone-billing.ts +7 -10
  192. package/src/monetization/credits/runtime-cron.test.ts +29 -28
  193. package/src/monetization/credits/runtime-cron.ts +34 -58
  194. package/src/monetization/credits/runtime-scheduler.test.ts +1 -1
  195. package/src/monetization/credits/runtime-scheduler.ts +2 -2
  196. package/src/monetization/credits/signup-grant.test.ts +7 -4
  197. package/src/monetization/credits/storage-tier-cron.test.ts +5 -3
  198. package/src/monetization/credits/trial-balance-cron.test.ts +52 -0
  199. package/src/monetization/feature-gate.ts +3 -3
  200. package/src/monetization/index.ts +4 -4
  201. package/src/monetization/metering/reconciliation-cron.test.ts +10 -9
  202. package/src/monetization/metering/reconciliation-repository.test.ts +11 -9
  203. package/src/monetization/metering/reconciliation-repository.ts +13 -11
  204. package/src/monetization/payram/webhook.test.ts +7 -5
  205. package/src/monetization/payram/webhook.ts +7 -10
  206. package/src/monetization/promotions/engine.test.ts +6 -5
  207. package/src/monetization/promotions/engine.ts +6 -3
  208. package/src/monetization/repository-types.ts +1 -1
  209. package/src/monetization/socket/socket.ts +4 -4
  210. package/src/monetization/stripe/stripe-payment-processor.test.ts +10 -3
  211. package/src/monetization/stripe/stripe-payment-processor.ts +3 -3
  212. package/src/monetization/stripe/webhook.test.ts +22 -16
  213. package/src/monetization/stripe/webhook.ts +75 -50
  214. package/src/onboarding/onboarding-service.ts +8 -11
  215. package/dist/credits/credit-ledger-extra.test.js +0 -40
  216. package/dist/credits/credit-ledger.bench.js +0 -33
  217. package/dist/credits/credit-ledger.test.d.ts +0 -4
  218. package/dist/credits/credit-ledger.test.js +0 -203
  219. package/dist/credits/credit-transaction-repository.test.js +0 -232
  220. package/dist/monetization/credits/credit-ledger-extra.test.d.ts +0 -1
  221. package/dist/monetization/credits/credit-ledger-extra.test.js +0 -39
  222. package/dist/monetization/credits/credit-ledger.bench.d.ts +0 -1
  223. package/dist/monetization/credits/credit-ledger.bench.js +0 -32
  224. package/dist/monetization/credits/credit-ledger.test.d.ts +0 -4
  225. package/dist/monetization/credits/credit-ledger.test.js +0 -202
  226. package/dist/monetization/credits/credit-transaction-repository.test.d.ts +0 -1
  227. package/dist/monetization/credits/credit-transaction-repository.test.js +0 -232
  228. package/src/credits/credit-ledger-extra.test.ts +0 -57
  229. package/src/credits/credit-ledger.bench.ts +0 -56
  230. package/src/credits/credit-ledger.test.ts +0 -276
  231. package/src/credits/credit-transaction-repository.test.ts +0 -274
  232. package/src/monetization/credits/credit-ledger-extra.test.ts +0 -56
  233. package/src/monetization/credits/credit-ledger.bench.ts +0 -55
  234. package/src/monetization/credits/credit-ledger.test.ts +0 -275
  235. package/src/monetization/credits/credit-transaction-repository.test.ts +0 -274
  236. /package/dist/credits/{credit-ledger-extra.test.d.ts → ledger.test.d.ts} +0 -0
  237. /package/dist/credits/{credit-ledger.bench.d.ts → trial-balance-cron.test.d.ts} +0 -0
  238. /package/dist/{credits/credit-transaction-repository.test.d.ts → monetization/credits/trial-balance-cron.test.d.ts} +0 -0
@@ -0,0 +1,52 @@
1
+ import type { PGlite } from "@electric-sql/pglite";
2
+ import { Credit, DrizzleLedger, runTrialBalanceCron } from "@wopr-network/platform-core/credits";
3
+ import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
4
+ import { createTestDb, truncateAllTables } from "../../test/db.js";
5
+
6
+ describe("runTrialBalanceCron", () => {
7
+ let pool: PGlite;
8
+ let ledger: DrizzleLedger;
9
+
10
+ beforeAll(async () => {
11
+ const { db, pool: p } = await createTestDb();
12
+ pool = p;
13
+ ledger = new DrizzleLedger(db);
14
+ });
15
+
16
+ afterAll(async () => {
17
+ await pool.close();
18
+ });
19
+
20
+ beforeEach(async () => {
21
+ await truncateAllTables(pool);
22
+ await ledger.seedSystemAccounts();
23
+ });
24
+
25
+ it("returns balanced when no entries exist", async () => {
26
+ const result = await runTrialBalanceCron({ ledger });
27
+ expect(result.balanced).toBe(true);
28
+ expect(result.differenceRaw).toBe(0);
29
+ });
30
+
31
+ it("returns balanced after normal credit and debit", async () => {
32
+ await ledger.credit("t1", Credit.fromCents(500), "purchase");
33
+ await ledger.debit("t1", Credit.fromCents(200), "bot_runtime");
34
+
35
+ const result = await runTrialBalanceCron({ ledger });
36
+ expect(result.balanced).toBe(true);
37
+ expect(result.differenceRaw).toBe(0);
38
+ });
39
+
40
+ it("logs an error on imbalance without throwing", async () => {
41
+ vi.spyOn(ledger, "trialBalance").mockResolvedValueOnce({
42
+ totalDebits: Credit.fromCents(1000),
43
+ totalCredits: Credit.fromCents(900),
44
+ balanced: false,
45
+ difference: Credit.fromCents(100),
46
+ });
47
+
48
+ const result = await runTrialBalanceCron({ ledger });
49
+ expect(result.balanced).toBe(false);
50
+ expect(result.differenceRaw).toBe(Credit.fromCents(100).toRaw());
51
+ });
52
+ });
@@ -1,4 +1,4 @@
1
- import type { CreditLedger } from "@wopr-network/platform-core/credits";
1
+ import type { ILedger } from "@wopr-network/platform-core/credits";
2
2
  import { Credit } from "@wopr-network/platform-core/credits";
3
3
  import type { Context, Next } from "hono";
4
4
  import { DAILY_BOT_COST } from "./credits/runtime-cron.js";
@@ -74,7 +74,7 @@ export function createFeatureGate(cfg: FeatureGateConfig) {
74
74
  /**
75
75
  * Convenience factory that creates a requireBalance middleware from a CreditLedger instance.
76
76
  */
77
- export function createBalanceGate(ledger: CreditLedger, userKey?: string, userIdField?: string) {
77
+ export function createBalanceGate(ledger: ILedger, userKey?: string, userIdField?: string) {
78
78
  return createFeatureGate({
79
79
  getUserBalance: (tenantId) => ledger.balance(tenantId),
80
80
  userKey,
@@ -94,7 +94,7 @@ export type ResolveTenantId = (c: Context) => string | undefined | Promise<strin
94
94
 
95
95
  export interface CreditGateConfig {
96
96
  /** CreditLedger instance used to check balance. */
97
- ledger: CreditLedger;
97
+ ledger: ILedger;
98
98
  /** Resolve the tenant ID from the request context. */
99
99
  resolveTenantId: ResolveTenantId;
100
100
  }
@@ -134,11 +134,12 @@ export { BudgetChecker, DrizzleBudgetChecker } from "./budget/index.js";
134
134
  // Credit ledger (WOP-384)
135
135
  export type {
136
136
  BillingState,
137
- CreditTransaction,
138
137
  CreditType,
139
138
  DebitType,
140
139
  GetActiveBotCount,
141
140
  HistoryOptions,
141
+ ILedger,
142
+ JournalEntry,
142
143
  OnSuspend,
143
144
  RuntimeCronConfig,
144
145
  RuntimeCronResult,
@@ -147,12 +148,12 @@ export type {
147
148
  export {
148
149
  BotBilling,
149
150
  buildResourceTierCosts,
150
- CreditLedger,
151
151
  DAILY_BOT_COST,
152
152
  DrizzleBotBilling,
153
- DrizzleCreditLedger,
153
+ DrizzleLedger,
154
154
  grantSignupCredits,
155
155
  InsufficientBalanceError,
156
+ Ledger,
156
157
  runRuntimeDeductions,
157
158
  SIGNUP_GRANT,
158
159
  SUSPENSION_GRACE_DAYS,
@@ -215,7 +216,6 @@ export {
215
216
  export type {
216
217
  IBotBilling,
217
218
  IBudgetChecker,
218
- ICreditLedger,
219
219
  IMeterAggregator,
220
220
  IMeterEmitter,
221
221
  IPayRamChargeRepository,
@@ -1,6 +1,6 @@
1
1
  import crypto from "node:crypto";
2
2
  import type { PGlite } from "@electric-sql/pglite";
3
- import { Credit, CreditLedger } from "@wopr-network/platform-core/credits";
3
+ import { Credit, DrizzleLedger } from "@wopr-network/platform-core/credits";
4
4
  import { runReconciliation } from "@wopr-network/platform-core/metering";
5
5
  import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
6
6
  import type { DrizzleDb } from "../../db/index.js";
@@ -17,7 +17,7 @@ const DAY_END = DAY_START + 24 * 60 * 60 * 1000;
17
17
  describe("runReconciliation", () => {
18
18
  let pool: PGlite;
19
19
  let db: DrizzleDb;
20
- let ledger: CreditLedger;
20
+ let ledger: DrizzleLedger;
21
21
  let usageSummaryRepo: DrizzleUsageSummaryRepository;
22
22
  let adapterUsageRepo: DrizzleAdapterUsageRepository;
23
23
 
@@ -25,7 +25,7 @@ describe("runReconciliation", () => {
25
25
  const t = await createTestDb();
26
26
  pool = t.pool;
27
27
  db = t.db;
28
- ledger = new CreditLedger(db);
28
+ ledger = new DrizzleLedger(db);
29
29
  usageSummaryRepo = new DrizzleUsageSummaryRepository(db);
30
30
  adapterUsageRepo = new DrizzleAdapterUsageRepository(db);
31
31
  });
@@ -36,6 +36,7 @@ describe("runReconciliation", () => {
36
36
 
37
37
  beforeEach(async () => {
38
38
  await truncateAllTables(pool);
39
+ await ledger.seedSystemAccounts();
39
40
  });
40
41
 
41
42
  /** Insert a usage_summaries row directly. */
@@ -74,7 +75,7 @@ describe("runReconciliation", () => {
74
75
  await insertSummary({ tenant: "t1", totalCharge: charge.toRaw() });
75
76
 
76
77
  await ledger.credit("t1", Credit.fromCents(500), "purchase");
77
- await ledger.debit("t1", charge, "adapter_usage", "chat usage");
78
+ await ledger.debit("t1", charge, "adapter_usage", { description: "chat usage" });
78
79
 
79
80
  const result = await runReconciliation({ usageSummaryRepo, adapterUsageRepo, targetDate: TODAY });
80
81
  expect(result.tenantsChecked).toBe(1);
@@ -85,7 +86,7 @@ describe("runReconciliation", () => {
85
86
  await insertSummary({ tenant: "t1", totalCharge: Credit.fromCents(100).toRaw() });
86
87
 
87
88
  await ledger.credit("t1", Credit.fromCents(500), "purchase");
88
- await ledger.debit("t1", Credit.fromCents(80), "adapter_usage", "chat usage");
89
+ await ledger.debit("t1", Credit.fromCents(80), "adapter_usage", { description: "chat usage" });
89
90
 
90
91
  const result = await runReconciliation({ usageSummaryRepo, adapterUsageRepo, targetDate: TODAY });
91
92
  expect(result.tenantsChecked).toBe(1);
@@ -117,7 +118,7 @@ describe("runReconciliation", () => {
117
118
 
118
119
  await ledger.credit("t1", Credit.fromCents(500), "purchase");
119
120
  // Debit as bot_runtime — should NOT count toward reconciliation
120
- await ledger.debit("t1", Credit.fromCents(20), "bot_runtime", "daily runtime");
121
+ await ledger.debit("t1", Credit.fromCents(20), "bot_runtime", { description: "daily runtime" });
121
122
 
122
123
  const result = await runReconciliation({ usageSummaryRepo, adapterUsageRepo, targetDate: TODAY });
123
124
  // Metered 20c, ledger adapter_usage = 0 => drift = 20c
@@ -149,12 +150,12 @@ describe("runReconciliation", () => {
149
150
  // t1: balanced
150
151
  await insertSummary({ tenant: "t1", totalCharge: Credit.fromCents(50).toRaw() });
151
152
  await ledger.credit("t1", Credit.fromCents(500), "purchase");
152
- await ledger.debit("t1", Credit.fromCents(50), "adapter_usage", "chat");
153
+ await ledger.debit("t1", Credit.fromCents(50), "adapter_usage", { description: "chat" });
153
154
 
154
155
  // t2: drifted
155
156
  await insertSummary({ tenant: "t2", totalCharge: Credit.fromCents(100).toRaw() });
156
157
  await ledger.credit("t2", Credit.fromCents(500), "purchase");
157
- await ledger.debit("t2", Credit.fromCents(60), "adapter_usage", "chat");
158
+ await ledger.debit("t2", Credit.fromCents(60), "adapter_usage", { description: "chat" });
158
159
 
159
160
  const result = await runReconciliation({ usageSummaryRepo, adapterUsageRepo, targetDate: TODAY });
160
161
  expect(result.tenantsChecked).toBe(2);
@@ -192,7 +193,7 @@ describe("runReconciliation", () => {
192
193
  await insertSummary({ tenant: "t1", totalCharge: Credit.fromCents(50).toRaw() });
193
194
 
194
195
  await ledger.credit("t1", Credit.fromCents(500), "purchase");
195
- await ledger.debit("t1", Credit.fromCents(80), "adapter_usage", "chat usage");
196
+ await ledger.debit("t1", Credit.fromCents(80), "adapter_usage", { description: "chat usage" });
196
197
 
197
198
  const result = await runReconciliation({ usageSummaryRepo, adapterUsageRepo, targetDate: TODAY });
198
199
  expect(result.discrepancies).toHaveLength(1);
@@ -1,6 +1,6 @@
1
1
  import crypto from "node:crypto";
2
2
  import type { PGlite } from "@electric-sql/pglite";
3
- import { Credit, CreditLedger } from "@wopr-network/platform-core/credits";
3
+ import { Credit, DrizzleLedger } from "@wopr-network/platform-core/credits";
4
4
  import { afterAll, beforeAll, beforeEach, describe, expect, it } from "vitest";
5
5
  import type { DrizzleDb } from "../../db/index.js";
6
6
  import { createTestDb, seedUsageSummary, truncateAllTables } from "../../test/db.js";
@@ -125,12 +125,14 @@ describe("DrizzleUsageSummaryRepository", () => {
125
125
 
126
126
  describe("DrizzleAdapterUsageRepository", () => {
127
127
  let repo: DrizzleAdapterUsageRepository;
128
- let ledger: CreditLedger;
128
+ let ledger: DrizzleLedger;
129
129
 
130
130
  beforeEach(async () => {
131
131
  await truncateAllTables(pool);
132
132
  repo = new DrizzleAdapterUsageRepository(db);
133
- ledger = new CreditLedger(db);
133
+ ledger = new DrizzleLedger(db);
134
+
135
+ await ledger.seedSystemAccounts();
134
136
  });
135
137
 
136
138
  it("returns empty array when no adapter_usage debits exist", async () => {
@@ -146,9 +148,9 @@ describe("DrizzleAdapterUsageRepository", () => {
146
148
  await ledger.credit("t1", Credit.fromCents(1000), "purchase");
147
149
  await ledger.credit("t2", Credit.fromCents(1000), "purchase");
148
150
 
149
- await ledger.debit("t1", Credit.fromCents(30), "adapter_usage", "t1-debit-1");
150
- await ledger.debit("t1", Credit.fromCents(20), "adapter_usage", "t1-debit-2");
151
- await ledger.debit("t2", Credit.fromCents(50), "adapter_usage", "t2-debit-1");
151
+ await ledger.debit("t1", Credit.fromCents(30), "adapter_usage", { description: "t1-debit-1" });
152
+ await ledger.debit("t1", Credit.fromCents(20), "adapter_usage", { description: "t1-debit-2" });
153
+ await ledger.debit("t2", Credit.fromCents(50), "adapter_usage", { description: "t2-debit-1" });
152
154
 
153
155
  // Query window covering today
154
156
  const today = new Date().toISOString().slice(0, 10);
@@ -167,8 +169,8 @@ describe("DrizzleAdapterUsageRepository", () => {
167
169
 
168
170
  it("excludes non-adapter_usage debit types", async () => {
169
171
  await ledger.credit("t1", Credit.fromCents(1000), "purchase");
170
- await ledger.debit("t1", Credit.fromCents(30), "adapter_usage", "adapter debit");
171
- await ledger.debit("t1", Credit.fromCents(20), "bot_runtime", "runtime debit");
172
+ await ledger.debit("t1", Credit.fromCents(30), "adapter_usage", { description: "adapter debit" });
173
+ await ledger.debit("t1", Credit.fromCents(20), "bot_runtime", { description: "runtime debit" });
172
174
 
173
175
  const today = new Date().toISOString().slice(0, 10);
174
176
  const startIso = `${today}T00:00:00Z`;
@@ -181,7 +183,7 @@ describe("DrizzleAdapterUsageRepository", () => {
181
183
 
182
184
  it("excludes credit transactions (positive amounts are not debits)", async () => {
183
185
  await ledger.credit("t1", Credit.fromCents(1000), "purchase");
184
- await ledger.debit("t1", Credit.fromCents(10), "adapter_usage", "real debit");
186
+ await ledger.debit("t1", Credit.fromCents(10), "adapter_usage", { description: "real debit" });
185
187
 
186
188
  const today = new Date().toISOString().slice(0, 10);
187
189
  const startIso = `${today}T00:00:00Z`;
@@ -1,6 +1,6 @@
1
1
  import { and, eq, gte, lt, ne, sql } from "drizzle-orm";
2
2
  import type { DrizzleDb } from "../../db/index.js";
3
- import { creditTransactions } from "../../db/schema/credits.js";
3
+ import { journalEntries, journalLines } from "../../db/schema/ledger.js";
4
4
  import { usageSummaries } from "../../db/schema/meter-events.js";
5
5
 
6
6
  // ---------------------------------------------------------------------------
@@ -59,24 +59,26 @@ export class DrizzleAdapterUsageRepository implements IAdapterUsageRepository {
59
59
  constructor(private readonly db: DrizzleDb) {}
60
60
 
61
61
  async getAggregatedAdapterUsageDebits(startIso: string, endIso: string): Promise<AggregatedDebit[]> {
62
+ // Sum the debit-side journal line amounts for adapter_usage entries.
63
+ // In double-entry: DR tenant liability (2000:<tenantId>), CR revenue:adapter_usage (4010).
62
64
  const rows = await this.db
63
65
  .select({
64
- tenantId: creditTransactions.tenantId,
65
- // amount_credits stores negative values for debits; ABS gives the raw positive debit amount.
66
- // Use the raw column name in sql to bypass the custom creditColumn type serializer.
67
- // raw SQL: Drizzle cannot express ABS with COALESCE and SUM
68
- totalDebitRaw: sql<number>`COALESCE(SUM(ABS(amount_credits)), 0)`,
66
+ tenantId: journalEntries.tenantId,
67
+ // raw SQL: Drizzle cannot express COALESCE with SUM aggregation
68
+ totalDebitRaw: sql<number>`COALESCE(SUM(${journalLines.amount}), 0)`,
69
69
  })
70
- .from(creditTransactions)
70
+ .from(journalLines)
71
+ .innerJoin(journalEntries, eq(journalEntries.id, journalLines.journalEntryId))
71
72
  .where(
72
73
  and(
73
- eq(creditTransactions.type, "adapter_usage"),
74
+ eq(journalEntries.entryType, "adapter_usage"),
75
+ eq(journalLines.side, "debit"),
74
76
  // raw SQL: Drizzle cannot express timestamptz cast for text column date comparison
75
- sql`${creditTransactions.createdAt}::timestamptz >= ${startIso}::timestamptz`,
76
- sql`${creditTransactions.createdAt}::timestamptz < ${endIso}::timestamptz`,
77
+ sql`${journalEntries.postedAt}::timestamptz >= ${startIso}::timestamptz`,
78
+ sql`${journalEntries.postedAt}::timestamptz < ${endIso}::timestamptz`,
77
79
  ),
78
80
  )
79
- .groupBy(creditTransactions.tenantId);
81
+ .groupBy(journalEntries.tenantId);
80
82
 
81
83
  return rows.map((r) => ({ tenantId: r.tenantId, totalDebitRaw: Number(r.totalDebitRaw) }));
82
84
  }
@@ -12,7 +12,7 @@ import {
12
12
  noOpReplayGuard,
13
13
  PayRamChargeRepository,
14
14
  } from "@wopr-network/platform-core/billing";
15
- import { CreditLedger } from "@wopr-network/platform-core/credits";
15
+ import { DrizzleLedger } from "@wopr-network/platform-core/credits";
16
16
  import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
17
17
  import type { DrizzleDb } from "../../db/index.js";
18
18
  import { createTestDb, truncateAllTables } from "../../test/db.js";
@@ -44,13 +44,15 @@ afterAll(async () => {
44
44
 
45
45
  describe("handlePayRamWebhook", () => {
46
46
  let chargeStore: PayRamChargeRepository;
47
- let creditLedger: CreditLedger;
47
+ let creditLedger: DrizzleLedger;
48
48
  let deps: PayRamWebhookDeps;
49
49
 
50
50
  beforeEach(async () => {
51
51
  await truncateAllTables(pool);
52
52
  chargeStore = new PayRamChargeRepository(db);
53
- creditLedger = new CreditLedger(db);
53
+ creditLedger = new DrizzleLedger(db);
54
+
55
+ await creditLedger.seedSystemAccounts();
54
56
  deps = { chargeStore, creditLedger, replayGuard: noOpReplayGuard };
55
57
 
56
58
  // Create a default test charge
@@ -80,14 +82,14 @@ describe("handlePayRamWebhook", () => {
80
82
  const history = await creditLedger.history("tenant-a");
81
83
  expect(history).toHaveLength(1);
82
84
  expect(history[0].referenceId).toBe("payram:ref-test-001");
83
- expect(history[0].type).toBe("purchase");
85
+ expect(history[0].entryType).toBe("purchase");
84
86
  });
85
87
 
86
88
  it("records fundingSource as payram", async () => {
87
89
  await handlePayRamWebhook(deps, makePayload({ status: "FILLED" }));
88
90
 
89
91
  const history = await creditLedger.history("tenant-a");
90
- expect(history[0].fundingSource).toBe("payram");
92
+ expect(history[0].metadata?.fundingSource).toBe("payram");
91
93
  });
92
94
 
93
95
  it("marks the charge as credited after FILLED", async () => {
@@ -4,13 +4,13 @@ import type {
4
4
  PayRamWebhookPayload,
5
5
  PayRamWebhookResult,
6
6
  } from "@wopr-network/platform-core/billing";
7
- import type { ICreditLedger } from "@wopr-network/platform-core/credits";
7
+ import type { ILedger } from "@wopr-network/platform-core/credits";
8
8
  import { Credit } from "@wopr-network/platform-core/credits";
9
9
  import type { BotBilling } from "../credits/bot-billing.js";
10
10
 
11
11
  export interface PayRamWebhookDeps {
12
12
  chargeStore: PayRamChargeRepository;
13
- creditLedger: ICreditLedger;
13
+ creditLedger: ILedger;
14
14
  botBilling?: BotBilling;
15
15
  replayGuard: IWebhookSeenRepository;
16
16
  }
@@ -60,14 +60,11 @@ export async function handlePayRamWebhook(
60
60
  // overpayment stays in the PayRam wallet as a buffer.
61
61
  const creditCents = charge.amountUsdCents;
62
62
 
63
- await creditLedger.credit(
64
- charge.tenantId,
65
- Credit.fromCents(creditCents),
66
- "purchase",
67
- `Crypto credit purchase via PayRam (ref: ${payload.reference_id}, ${payload.currency ?? "crypto"})`,
68
- `payram:${payload.reference_id}`,
69
- "payram",
70
- );
63
+ await creditLedger.credit(charge.tenantId, Credit.fromCents(creditCents), "purchase", {
64
+ description: `Crypto credit purchase via PayRam (ref: ${payload.reference_id}, ${payload.currency ?? "crypto"})`,
65
+ referenceId: `payram:${payload.reference_id}`,
66
+ fundingSource: "payram",
67
+ });
71
68
 
72
69
  await chargeStore.markCredited(payload.reference_id);
73
70
 
@@ -1,4 +1,4 @@
1
- import type { ICreditLedger } from "@wopr-network/platform-core/credits";
1
+ import type { ILedger } from "@wopr-network/platform-core/credits";
2
2
  import { Credit } from "@wopr-network/platform-core/credits";
3
3
  import { beforeEach, describe, expect, it, vi } from "vitest";
4
4
  import type { ICouponRepository } from "./coupon-repository.js";
@@ -40,7 +40,7 @@ describe("PromotionEngine", () => {
40
40
  let promotionRepo: IPromotionRepository;
41
41
  let couponRepo: ICouponRepository;
42
42
  let redemptionRepo: IRedemptionRepository;
43
- let ledger: ICreditLedger;
43
+ let ledger: ILedger;
44
44
  let engine: PromotionEngine;
45
45
 
46
46
  beforeEach(() => {
@@ -95,7 +95,7 @@ describe("PromotionEngine", () => {
95
95
  createdAt: new Date().toISOString(),
96
96
  }),
97
97
  hasReferenceId: vi.fn().mockResolvedValue(false),
98
- } as unknown as ICreditLedger;
98
+ } as unknown as ILedger;
99
99
 
100
100
  engine = new PromotionEngine({ promotionRepo, couponRepo, redemptionRepo, ledger });
101
101
  });
@@ -111,8 +111,9 @@ describe("PromotionEngine", () => {
111
111
  "tenant-1",
112
112
  expect.any(Object), // Credit instance
113
113
  "promo",
114
- expect.any(String),
115
- "promo:promo-1:tenant-1:1",
114
+ expect.objectContaining({
115
+ referenceId: "promo:promo-1:tenant-1:1",
116
+ }),
116
117
  );
117
118
  });
118
119
 
@@ -1,4 +1,4 @@
1
- import type { ICreditLedger } from "@wopr-network/platform-core/credits";
1
+ import type { ILedger } from "@wopr-network/platform-core/credits";
2
2
  import { Credit } from "@wopr-network/platform-core/credits";
3
3
  import type { ICouponRepository } from "./coupon-repository.js";
4
4
  import type { IPromotionRepository, Promotion } from "./promotion-repository.js";
@@ -22,7 +22,7 @@ interface PromotionEngineDeps {
22
22
  promotionRepo: IPromotionRepository;
23
23
  couponRepo: ICouponRepository;
24
24
  redemptionRepo: IRedemptionRepository;
25
- ledger: ICreditLedger;
25
+ ledger: ILedger;
26
26
  }
27
27
 
28
28
  export class PromotionEngine {
@@ -113,7 +113,10 @@ export class PromotionEngine {
113
113
  if (!granted) return null;
114
114
 
115
115
  // Grant credits
116
- const tx = await ledger.credit(ctx.tenantId, grantAmount, "promo", `Promotion: ${promo.name}`, refId);
116
+ const tx = await ledger.credit(ctx.tenantId, grantAmount, "promo", {
117
+ description: `Promotion: ${promo.name}`,
118
+ referenceId: refId,
119
+ });
117
120
 
118
121
  // Record redemption
119
122
  await redemptionRepo.create({
@@ -5,7 +5,7 @@ export type {
5
5
  ITenantCustomerRepository,
6
6
  PayRamChargeRecord,
7
7
  } from "@wopr-network/platform-core/billing";
8
- export type { IAutoTopupSettingsRepository, ICreditLedger } from "@wopr-network/platform-core/credits";
8
+ export type { IAutoTopupSettingsRepository, ILedger } from "@wopr-network/platform-core/credits";
9
9
  export type { IMeterAggregator, IMeterEmitter } from "@wopr-network/platform-core/metering";
10
10
  export type { FraudEvent, FraudEventInput, IAffiliateFraudRepository } from "./affiliate/affiliate-fraud-repository.js";
11
11
  export type {
@@ -15,13 +15,13 @@ import type { MeterEmitter } from "@wopr-network/platform-core/metering";
15
15
  import type { AdapterCapability, AdapterResult, ProviderAdapter } from "../adapters/types.js";
16
16
  import { withMargin } from "../adapters/types.js";
17
17
  import type { ArbitrageRouter } from "../arbitrage/router.js";
18
- import type { BudgetChecker, SpendLimits } from "../budget/budget-checker.js";
18
+ import type { IBudgetChecker, SpendLimits } from "../budget/budget-checker.js";
19
19
 
20
20
  export interface SocketConfig {
21
21
  /** MeterEmitter instance for usage tracking */
22
22
  meter: MeterEmitter;
23
- /** BudgetChecker instance for pre-call budget validation */
24
- budgetChecker?: BudgetChecker;
23
+ /** IBudgetChecker instance for pre-call budget validation */
24
+ budgetChecker?: IBudgetChecker;
25
25
  /** Default margin multiplier (default: 1.3) */
26
26
  defaultMargin?: number;
27
27
  /** ArbitrageRouter for cost-optimized routing (GPU-first, cheapest, 5xx failover) */
@@ -65,7 +65,7 @@ const CAPABILITY_METHOD: Record<AdapterCapability, keyof ProviderAdapter> = {
65
65
  export class AdapterSocket {
66
66
  private readonly adapters = new Map<string, ProviderAdapter>();
67
67
  private readonly meter: MeterEmitter;
68
- private readonly budgetChecker?: BudgetChecker;
68
+ private readonly budgetChecker?: IBudgetChecker;
69
69
  private readonly defaultMargin: number;
70
70
  private readonly router?: ArbitrageRouter;
71
71
 
@@ -4,7 +4,7 @@ import type {
4
4
  IWebhookSeenRepository,
5
5
  } from "@wopr-network/platform-core/billing";
6
6
  import { PaymentMethodOwnershipError } from "@wopr-network/platform-core/billing";
7
- import type { CreditTransaction, ICreditLedger } from "@wopr-network/platform-core/credits";
7
+ import type { ILedger, JournalEntry } from "@wopr-network/platform-core/credits";
8
8
  import { Credit } from "@wopr-network/platform-core/credits";
9
9
  import type Stripe from "stripe";
10
10
  import { beforeEach, describe, expect, it, vi } from "vitest";
@@ -61,7 +61,8 @@ function createMocks() {
61
61
  buildCustomerIdMap: vi.fn(),
62
62
  };
63
63
 
64
- const creditLedger: ICreditLedger = {
64
+ const creditLedger: ILedger = {
65
+ post: vi.fn(),
65
66
  credit: vi.fn(),
66
67
  debit: vi.fn(),
67
68
  balance: vi.fn(),
@@ -72,6 +73,12 @@ function createMocks() {
72
73
  expiredCredits: vi.fn(),
73
74
  lifetimeSpend: vi.fn(),
74
75
  lifetimeSpendBatch: vi.fn().mockResolvedValue(new Map()),
76
+ trialBalance: vi.fn(),
77
+ accountBalance: vi.fn(),
78
+ seedSystemAccounts: vi.fn(),
79
+ existsByReferenceIdLike: vi.fn(),
80
+ sumPurchasesForPeriod: vi.fn(),
81
+ getActiveTenantIdsInWindow: vi.fn(),
75
82
  };
76
83
 
77
84
  const replayGuard: IWebhookSeenRepository = {
@@ -340,7 +347,7 @@ describe("StripePaymentProcessor", () => {
340
347
  status: "succeeded",
341
348
  } as unknown as Stripe.Response<Stripe.PaymentIntent>);
342
349
  vi.mocked(mocks.creditLedger.hasReferenceId).mockResolvedValue(false);
343
- vi.mocked(mocks.creditLedger.credit).mockResolvedValue({} as unknown as CreditTransaction);
350
+ vi.mocked(mocks.creditLedger.credit).mockResolvedValue({} as unknown as JournalEntry);
344
351
  vi.mocked(mocks.autoTopupEventLog.writeEvent).mockResolvedValue(undefined);
345
352
 
346
353
  const result = await processor.charge({
@@ -17,7 +17,7 @@ import {
17
17
  type SetupResult,
18
18
  type WebhookResult,
19
19
  } from "@wopr-network/platform-core/billing";
20
- import type { ICreditLedger } from "@wopr-network/platform-core/credits";
20
+ import type { ILedger } from "@wopr-network/platform-core/credits";
21
21
  import { Credit } from "@wopr-network/platform-core/credits";
22
22
  import type Stripe from "stripe";
23
23
  import { chargeAutoTopup } from "../credits/auto-topup-charge.js";
@@ -30,7 +30,7 @@ export interface StripePaymentProcessorDeps {
30
30
  tenantRepo: ITenantCustomerRepository;
31
31
  webhookSecret: string;
32
32
  priceMap?: CreditPriceMap;
33
- creditLedger: ICreditLedger;
33
+ creditLedger: ILedger;
34
34
  botBilling?: BotBilling;
35
35
  replayGuard: IWebhookSeenRepository;
36
36
  autoTopupEventLog?: IAutoTopupEventLogRepository;
@@ -43,7 +43,7 @@ export class StripePaymentProcessor implements IPaymentProcessor {
43
43
  private readonly tenantRepo: ITenantCustomerRepository;
44
44
  private readonly webhookSecret: string;
45
45
  private readonly priceMap: CreditPriceMap;
46
- private readonly creditLedger: ICreditLedger;
46
+ private readonly creditLedger: ILedger;
47
47
  private readonly botBilling?: BotBilling;
48
48
  private readonly replayGuard: IWebhookSeenRepository;
49
49
  private readonly autoTopupEventLog?: IAutoTopupEventLogRepository;