@wopr-network/platform-core 1.13.3 → 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 (217) 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/index.d.ts +1 -0
  40. package/dist/db/schema/index.js +1 -0
  41. package/dist/db/schema/ledger.d.ts +442 -0
  42. package/dist/db/schema/ledger.js +76 -0
  43. package/dist/gateway/credit-gate.d.ts +2 -2
  44. package/dist/gateway/credit-gate.js +5 -1
  45. package/dist/gateway/credit-gate.test.js +35 -33
  46. package/dist/gateway/protocol/deps.d.ts +2 -2
  47. package/dist/gateway/proxy.d.ts +2 -2
  48. package/dist/gateway/types.d.ts +2 -2
  49. package/dist/metering/reconciliation-cron.test.js +9 -8
  50. package/dist/metering/reconciliation-repository.js +12 -10
  51. package/dist/metering/reconciliation-repository.test.js +9 -8
  52. package/dist/monetization/affiliate/affiliate-admin-repository.js +10 -8
  53. package/dist/monetization/affiliate/affiliate-admin-repository.test.js +32 -13
  54. package/dist/monetization/affiliate/credit-match.d.ts +2 -2
  55. package/dist/monetization/affiliate/credit-match.js +4 -1
  56. package/dist/monetization/affiliate/credit-match.test.js +58 -13
  57. package/dist/monetization/affiliate/new-user-bonus.d.ts +2 -2
  58. package/dist/monetization/affiliate/new-user-bonus.js +4 -1
  59. package/dist/monetization/affiliate/new-user-bonus.test.js +4 -3
  60. package/dist/monetization/credits/auto-topup-charge.d.ts +2 -2
  61. package/dist/monetization/credits/auto-topup-charge.js +5 -1
  62. package/dist/monetization/credits/auto-topup-charge.test.js +5 -4
  63. package/dist/monetization/credits/auto-topup-usage.d.ts +2 -2
  64. package/dist/monetization/credits/auto-topup-usage.test.js +53 -12
  65. package/dist/monetization/credits/bot-billing.d.ts +3 -3
  66. package/dist/monetization/credits/bot-billing.test.js +18 -5
  67. package/dist/monetization/credits/credit-expiry-cron.test.js +25 -8
  68. package/dist/monetization/credits/dividend-cron.d.ts +2 -4
  69. package/dist/monetization/credits/dividend-cron.js +7 -4
  70. package/dist/monetization/credits/dividend-cron.test.js +26 -46
  71. package/dist/monetization/credits/dividend-repository.js +15 -24
  72. package/dist/monetization/credits/dividend-repository.test.js +4 -3
  73. package/dist/monetization/credits/index.d.ts +2 -2
  74. package/dist/monetization/credits/index.js +1 -1
  75. package/dist/monetization/credits/member-usage.test.js +23 -10
  76. package/dist/monetization/credits/phone-billing.d.ts +2 -2
  77. package/dist/monetization/credits/phone-billing.js +5 -1
  78. package/dist/monetization/credits/phone-billing.test.js +9 -12
  79. package/dist/monetization/credits/runtime-cron.d.ts +2 -2
  80. package/dist/monetization/credits/runtime-cron.js +32 -8
  81. package/dist/monetization/credits/runtime-cron.test.js +28 -27
  82. package/dist/monetization/credits/runtime-scheduler.d.ts +2 -2
  83. package/dist/monetization/credits/runtime-scheduler.test.js +1 -1
  84. package/dist/monetization/credits/signup-grant.test.js +5 -3
  85. package/dist/monetization/credits/storage-tier-cron.test.js +3 -2
  86. package/dist/monetization/credits/trial-balance-cron.test.js +42 -0
  87. package/dist/monetization/feature-gate.d.ts +3 -3
  88. package/dist/monetization/index.d.ts +3 -3
  89. package/dist/monetization/index.js +1 -1
  90. package/dist/monetization/metering/reconciliation-cron.test.js +9 -8
  91. package/dist/monetization/metering/reconciliation-repository.js +11 -10
  92. package/dist/monetization/metering/reconciliation-repository.test.js +9 -8
  93. package/dist/monetization/payram/webhook.d.ts +2 -2
  94. package/dist/monetization/payram/webhook.js +5 -1
  95. package/dist/monetization/payram/webhook.test.js +5 -4
  96. package/dist/monetization/promotions/engine.d.ts +2 -2
  97. package/dist/monetization/promotions/engine.js +4 -1
  98. package/dist/monetization/promotions/engine.test.js +3 -1
  99. package/dist/monetization/repository-types.d.ts +1 -1
  100. package/dist/monetization/stripe/stripe-payment-processor.d.ts +2 -2
  101. package/dist/monetization/stripe/stripe-payment-processor.test.js +7 -0
  102. package/dist/monetization/stripe/webhook.d.ts +2 -2
  103. package/dist/monetization/stripe/webhook.js +70 -6
  104. package/dist/monetization/stripe/webhook.test.js +20 -15
  105. package/dist/onboarding/onboarding-service.d.ts +2 -2
  106. package/dist/onboarding/onboarding-service.js +6 -2
  107. package/drizzle/migrations/0003_double_entry_ledger.sql +82 -0
  108. package/drizzle/migrations/meta/_journal.json +7 -0
  109. package/package.json +1 -1
  110. package/src/api/routes/admin-credits.ts +11 -14
  111. package/src/api/routes/quota.ts +2 -2
  112. package/src/api/routes/verify-email.ts +4 -4
  113. package/src/backup/on-demand-snapshot-service.test.ts +3 -3
  114. package/src/backup/on-demand-snapshot-service.ts +3 -3
  115. package/src/billing/payram/webhook.test.ts +7 -5
  116. package/src/billing/payram/webhook.ts +8 -11
  117. package/src/billing/stripe/stripe-payment-processor.test.ts +10 -3
  118. package/src/billing/stripe/stripe-payment-processor.ts +3 -3
  119. package/src/billing/stripe/tenant-store.ts +1 -1
  120. package/src/credits/auto-topup-charge.test.ts +7 -5
  121. package/src/credits/auto-topup-charge.ts +7 -10
  122. package/src/credits/auto-topup-usage.test.ts +55 -13
  123. package/src/credits/auto-topup-usage.ts +2 -2
  124. package/src/credits/credit-expiry-cron.test.ts +26 -45
  125. package/src/credits/credit-expiry-cron.ts +9 -12
  126. package/src/credits/credit-ledger.ts +3 -3
  127. package/src/credits/dividend-cron.test.ts +38 -45
  128. package/src/credits/dividend-cron.ts +12 -26
  129. package/src/credits/dividend-repository.test.ts +4 -3
  130. package/src/credits/dividend-repository.ts +21 -23
  131. package/src/credits/index.ts +23 -4
  132. package/src/credits/ledger.test.ts +514 -0
  133. package/src/credits/ledger.ts +851 -0
  134. package/src/credits/signup-grant.test.ts +7 -4
  135. package/src/credits/signup-grant.ts +6 -12
  136. package/src/credits/trial-balance-cron.test.ts +68 -0
  137. package/src/credits/trial-balance-cron.ts +46 -0
  138. package/src/db/schema/index.ts +1 -0
  139. package/src/db/schema/ledger.ts +94 -0
  140. package/src/gateway/credit-gate-wiring.test.ts +3 -3
  141. package/src/gateway/credit-gate.test.ts +35 -33
  142. package/src/gateway/credit-gate.ts +6 -10
  143. package/src/gateway/gateway-routes.test.ts +5 -5
  144. package/src/gateway/protocol/deps.ts +2 -2
  145. package/src/gateway/proxy.ts +2 -2
  146. package/src/gateway/route-mounting.test.ts +2 -2
  147. package/src/gateway/types.ts +2 -2
  148. package/src/metering/reconciliation-cron.test.ts +10 -9
  149. package/src/metering/reconciliation-repository.test.ts +10 -9
  150. package/src/metering/reconciliation-repository.ts +14 -11
  151. package/src/monetization/affiliate/affiliate-admin-repository.test.ts +32 -19
  152. package/src/monetization/affiliate/affiliate-admin-repository.ts +16 -8
  153. package/src/monetization/affiliate/credit-match.test.ts +60 -14
  154. package/src/monetization/affiliate/credit-match.ts +6 -9
  155. package/src/monetization/affiliate/new-user-bonus.test.ts +6 -4
  156. package/src/monetization/affiliate/new-user-bonus.ts +6 -9
  157. package/src/monetization/credits/auto-topup-charge.test.ts +7 -5
  158. package/src/monetization/credits/auto-topup-charge.ts +7 -10
  159. package/src/monetization/credits/auto-topup-usage.test.ts +55 -13
  160. package/src/monetization/credits/auto-topup-usage.ts +2 -2
  161. package/src/monetization/credits/bot-billing.test.ts +20 -6
  162. package/src/monetization/credits/bot-billing.ts +3 -3
  163. package/src/monetization/credits/credit-expiry-cron.test.ts +26 -45
  164. package/src/monetization/credits/dividend-cron.test.ts +34 -48
  165. package/src/monetization/credits/dividend-cron.ts +9 -14
  166. package/src/monetization/credits/dividend-repository.test.ts +4 -3
  167. package/src/monetization/credits/dividend-repository.ts +19 -25
  168. package/src/monetization/credits/index.ts +4 -4
  169. package/src/monetization/credits/member-usage.test.ts +25 -11
  170. package/src/monetization/credits/phone-billing.test.ts +18 -26
  171. package/src/monetization/credits/phone-billing.ts +7 -10
  172. package/src/monetization/credits/runtime-cron.test.ts +29 -28
  173. package/src/monetization/credits/runtime-cron.ts +34 -58
  174. package/src/monetization/credits/runtime-scheduler.test.ts +1 -1
  175. package/src/monetization/credits/runtime-scheduler.ts +2 -2
  176. package/src/monetization/credits/signup-grant.test.ts +7 -4
  177. package/src/monetization/credits/storage-tier-cron.test.ts +5 -3
  178. package/src/monetization/credits/trial-balance-cron.test.ts +52 -0
  179. package/src/monetization/feature-gate.ts +3 -3
  180. package/src/monetization/index.ts +4 -4
  181. package/src/monetization/metering/reconciliation-cron.test.ts +10 -9
  182. package/src/monetization/metering/reconciliation-repository.test.ts +11 -9
  183. package/src/monetization/metering/reconciliation-repository.ts +13 -11
  184. package/src/monetization/payram/webhook.test.ts +7 -5
  185. package/src/monetization/payram/webhook.ts +7 -10
  186. package/src/monetization/promotions/engine.test.ts +6 -5
  187. package/src/monetization/promotions/engine.ts +6 -3
  188. package/src/monetization/repository-types.ts +1 -1
  189. package/src/monetization/stripe/stripe-payment-processor.test.ts +10 -3
  190. package/src/monetization/stripe/stripe-payment-processor.ts +3 -3
  191. package/src/monetization/stripe/webhook.test.ts +22 -16
  192. package/src/monetization/stripe/webhook.ts +75 -50
  193. package/src/onboarding/onboarding-service.ts +8 -11
  194. package/dist/credits/credit-ledger-extra.test.js +0 -40
  195. package/dist/credits/credit-ledger.bench.js +0 -33
  196. package/dist/credits/credit-ledger.test.d.ts +0 -4
  197. package/dist/credits/credit-ledger.test.js +0 -203
  198. package/dist/credits/credit-transaction-repository.test.js +0 -232
  199. package/dist/monetization/credits/credit-ledger-extra.test.d.ts +0 -1
  200. package/dist/monetization/credits/credit-ledger-extra.test.js +0 -39
  201. package/dist/monetization/credits/credit-ledger.bench.d.ts +0 -1
  202. package/dist/monetization/credits/credit-ledger.bench.js +0 -32
  203. package/dist/monetization/credits/credit-ledger.test.d.ts +0 -4
  204. package/dist/monetization/credits/credit-ledger.test.js +0 -202
  205. package/dist/monetization/credits/credit-transaction-repository.test.d.ts +0 -1
  206. package/dist/monetization/credits/credit-transaction-repository.test.js +0 -232
  207. package/src/credits/credit-ledger-extra.test.ts +0 -57
  208. package/src/credits/credit-ledger.bench.ts +0 -56
  209. package/src/credits/credit-ledger.test.ts +0 -276
  210. package/src/credits/credit-transaction-repository.test.ts +0 -274
  211. package/src/monetization/credits/credit-ledger-extra.test.ts +0 -56
  212. package/src/monetization/credits/credit-ledger.bench.ts +0 -55
  213. package/src/monetization/credits/credit-ledger.test.ts +0 -275
  214. package/src/monetization/credits/credit-transaction-repository.test.ts +0 -274
  215. /package/dist/credits/{credit-ledger-extra.test.d.ts → ledger.test.d.ts} +0 -0
  216. /package/dist/credits/{credit-ledger.bench.d.ts → trial-balance-cron.test.d.ts} +0 -0
  217. /package/dist/{credits/credit-transaction-repository.test.d.ts → monetization/credits/trial-balance-cron.test.d.ts} +0 -0
@@ -2,7 +2,7 @@ import crypto from "node:crypto";
2
2
  import type { PGlite } from "@electric-sql/pglite";
3
3
  import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
4
4
  import { Credit } from "../credits/credit.js";
5
- import { CreditLedger } from "../credits/credit-ledger.js";
5
+ import { DrizzleLedger } from "../credits/ledger.js";
6
6
  import type { PlatformDb } from "../db/index.js";
7
7
  import { usageSummaries } from "../db/schema/meter-events.js";
8
8
  import { createTestDb, truncateAllTables } from "../test/db.js";
@@ -18,7 +18,7 @@ const DAY_END = DAY_START + 24 * 60 * 60 * 1000;
18
18
  describe("runReconciliation", () => {
19
19
  let pool: PGlite;
20
20
  let db: PlatformDb;
21
- let ledger: CreditLedger;
21
+ let ledger: DrizzleLedger;
22
22
  let usageSummaryRepo: DrizzleUsageSummaryRepository;
23
23
  let adapterUsageRepo: DrizzleAdapterUsageRepository;
24
24
 
@@ -26,7 +26,7 @@ describe("runReconciliation", () => {
26
26
  const t = await createTestDb();
27
27
  pool = t.pool;
28
28
  db = t.db;
29
- ledger = new CreditLedger(db);
29
+ ledger = new DrizzleLedger(db);
30
30
  usageSummaryRepo = new DrizzleUsageSummaryRepository(db);
31
31
  adapterUsageRepo = new DrizzleAdapterUsageRepository(db);
32
32
  });
@@ -37,6 +37,7 @@ describe("runReconciliation", () => {
37
37
 
38
38
  beforeEach(async () => {
39
39
  await truncateAllTables(pool);
40
+ await ledger.seedSystemAccounts();
40
41
  });
41
42
 
42
43
  /** Insert a usage_summaries row directly. */
@@ -75,7 +76,7 @@ describe("runReconciliation", () => {
75
76
  await insertSummary({ tenant: "t1", totalCharge: charge.toRaw() });
76
77
 
77
78
  await ledger.credit("t1", Credit.fromCents(500), "purchase");
78
- await ledger.debit("t1", charge, "adapter_usage", "chat usage");
79
+ await ledger.debit("t1", charge, "adapter_usage", { description: "chat usage" });
79
80
 
80
81
  const result = await runReconciliation({ usageSummaryRepo, adapterUsageRepo, targetDate: TODAY });
81
82
  expect(result.tenantsChecked).toBe(1);
@@ -86,7 +87,7 @@ describe("runReconciliation", () => {
86
87
  await insertSummary({ tenant: "t1", totalCharge: Credit.fromCents(100).toRaw() });
87
88
 
88
89
  await ledger.credit("t1", Credit.fromCents(500), "purchase");
89
- await ledger.debit("t1", Credit.fromCents(80), "adapter_usage", "chat usage");
90
+ await ledger.debit("t1", Credit.fromCents(80), "adapter_usage", { description: "chat usage" });
90
91
 
91
92
  const result = await runReconciliation({ usageSummaryRepo, adapterUsageRepo, targetDate: TODAY });
92
93
  expect(result.tenantsChecked).toBe(1);
@@ -118,7 +119,7 @@ describe("runReconciliation", () => {
118
119
 
119
120
  await ledger.credit("t1", Credit.fromCents(500), "purchase");
120
121
  // Debit as bot_runtime — should NOT count toward reconciliation
121
- await ledger.debit("t1", Credit.fromCents(20), "bot_runtime", "daily runtime");
122
+ await ledger.debit("t1", Credit.fromCents(20), "bot_runtime", { description: "daily runtime" });
122
123
 
123
124
  const result = await runReconciliation({ usageSummaryRepo, adapterUsageRepo, targetDate: TODAY });
124
125
  // Metered 20c, ledger adapter_usage = 0 => drift = 20c
@@ -150,12 +151,12 @@ describe("runReconciliation", () => {
150
151
  // t1: balanced
151
152
  await insertSummary({ tenant: "t1", totalCharge: Credit.fromCents(50).toRaw() });
152
153
  await ledger.credit("t1", Credit.fromCents(500), "purchase");
153
- await ledger.debit("t1", Credit.fromCents(50), "adapter_usage", "chat");
154
+ await ledger.debit("t1", Credit.fromCents(50), "adapter_usage", { description: "chat" });
154
155
 
155
156
  // t2: drifted
156
157
  await insertSummary({ tenant: "t2", totalCharge: Credit.fromCents(100).toRaw() });
157
158
  await ledger.credit("t2", Credit.fromCents(500), "purchase");
158
- await ledger.debit("t2", Credit.fromCents(60), "adapter_usage", "chat");
159
+ await ledger.debit("t2", Credit.fromCents(60), "adapter_usage", { description: "chat" });
159
160
 
160
161
  const result = await runReconciliation({ usageSummaryRepo, adapterUsageRepo, targetDate: TODAY });
161
162
  expect(result.tenantsChecked).toBe(2);
@@ -193,7 +194,7 @@ describe("runReconciliation", () => {
193
194
  await insertSummary({ tenant: "t1", totalCharge: Credit.fromCents(50).toRaw() });
194
195
 
195
196
  await ledger.credit("t1", Credit.fromCents(500), "purchase");
196
- await ledger.debit("t1", Credit.fromCents(80), "adapter_usage", "chat usage");
197
+ await ledger.debit("t1", Credit.fromCents(80), "adapter_usage", { description: "chat usage" });
197
198
 
198
199
  const result = await runReconciliation({ usageSummaryRepo, adapterUsageRepo, targetDate: TODAY });
199
200
  expect(result.discrepancies).toHaveLength(1);
@@ -2,7 +2,7 @@ import crypto from "node:crypto";
2
2
  import type { PGlite } from "@electric-sql/pglite";
3
3
  import { afterAll, beforeAll, beforeEach, describe, expect, it } from "vitest";
4
4
  import { Credit } from "../credits/credit.js";
5
- import { CreditLedger } from "../credits/credit-ledger.js";
5
+ import { DrizzleLedger } from "../credits/ledger.js";
6
6
  import type { PlatformDb } from "../db/index.js";
7
7
  import { createTestDb, seedUsageSummary, truncateAllTables } from "../test/db.js";
8
8
  import { DrizzleAdapterUsageRepository, DrizzleUsageSummaryRepository } from "./reconciliation-repository.js";
@@ -126,12 +126,13 @@ describe("DrizzleUsageSummaryRepository", () => {
126
126
 
127
127
  describe("DrizzleAdapterUsageRepository", () => {
128
128
  let repo: DrizzleAdapterUsageRepository;
129
- let ledger: CreditLedger;
129
+ let ledger: DrizzleLedger;
130
130
 
131
131
  beforeEach(async () => {
132
132
  await truncateAllTables(pool);
133
133
  repo = new DrizzleAdapterUsageRepository(db);
134
- ledger = new CreditLedger(db);
134
+ ledger = new DrizzleLedger(db);
135
+ await ledger.seedSystemAccounts();
135
136
  });
136
137
 
137
138
  it("returns empty array when no adapter_usage debits exist", async () => {
@@ -147,9 +148,9 @@ describe("DrizzleAdapterUsageRepository", () => {
147
148
  await ledger.credit("t1", Credit.fromCents(1000), "purchase");
148
149
  await ledger.credit("t2", Credit.fromCents(1000), "purchase");
149
150
 
150
- await ledger.debit("t1", Credit.fromCents(30), "adapter_usage", "t1-debit-1");
151
- await ledger.debit("t1", Credit.fromCents(20), "adapter_usage", "t1-debit-2");
152
- 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" });
153
154
 
154
155
  // Query window covering today
155
156
  const today = new Date().toISOString().slice(0, 10);
@@ -168,8 +169,8 @@ describe("DrizzleAdapterUsageRepository", () => {
168
169
 
169
170
  it("excludes non-adapter_usage debit types", async () => {
170
171
  await ledger.credit("t1", Credit.fromCents(1000), "purchase");
171
- await ledger.debit("t1", Credit.fromCents(30), "adapter_usage", "adapter debit");
172
- 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" });
173
174
 
174
175
  const today = new Date().toISOString().slice(0, 10);
175
176
  const startIso = `${today}T00:00:00Z`;
@@ -182,7 +183,7 @@ describe("DrizzleAdapterUsageRepository", () => {
182
183
 
183
184
  it("excludes credit transactions (positive amounts are not debits)", async () => {
184
185
  await ledger.credit("t1", Credit.fromCents(1000), "purchase");
185
- await ledger.debit("t1", Credit.fromCents(10), "adapter_usage", "real debit");
186
+ await ledger.debit("t1", Credit.fromCents(10), "adapter_usage", { description: "real debit" });
186
187
 
187
188
  const today = new Date().toISOString().slice(0, 10);
188
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 { PlatformDb } 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,27 @@ export class DrizzleAdapterUsageRepository implements IAdapterUsageRepository {
59
59
  constructor(private readonly db: PlatformDb) {}
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).
64
+ // The debit line on the tenant account represents the charge amount.
62
65
  const rows = await this.db
63
66
  .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)`,
67
+ tenantId: journalEntries.tenantId,
68
+ // raw SQL: Drizzle cannot express COALESCE with SUM aggregation
69
+ totalDebitRaw: sql<number>`COALESCE(SUM(${journalLines.amount}), 0)`,
69
70
  })
70
- .from(creditTransactions)
71
+ .from(journalLines)
72
+ .innerJoin(journalEntries, eq(journalEntries.id, journalLines.journalEntryId))
71
73
  .where(
72
74
  and(
73
- eq(creditTransactions.type, "adapter_usage"),
75
+ eq(journalEntries.entryType, "adapter_usage"),
76
+ eq(journalLines.side, "debit"),
74
77
  // 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`,
78
+ sql`${journalEntries.postedAt}::timestamptz >= ${startIso}::timestamptz`,
79
+ sql`${journalEntries.postedAt}::timestamptz < ${endIso}::timestamptz`,
77
80
  ),
78
81
  )
79
- .groupBy(creditTransactions.tenantId);
82
+ .groupBy(journalEntries.tenantId);
80
83
 
81
84
  return rows.map((r) => ({ tenantId: r.tenantId, totalDebitRaw: Number(r.totalDebitRaw) }));
82
85
  }
@@ -1,10 +1,10 @@
1
1
  import type { PGlite } from "@electric-sql/pglite";
2
- import { sql } from "drizzle-orm";
2
+ import { Credit, DrizzleLedger } from "@wopr-network/platform-core/credits";
3
3
  import { afterAll, beforeAll, beforeEach, describe, expect, it } from "vitest";
4
4
  import type { DrizzleDb } from "../../db/index.js";
5
5
  import { affiliateReferrals } from "../../db/schema/affiliate.js";
6
6
  import { affiliateFraudEvents } from "../../db/schema/affiliate-fraud.js";
7
- import { beginTestTransaction, createTestDb, endTestTransaction, rollbackTestTransaction } from "../../test/db.js";
7
+ import { createTestDb, truncateAllTables } from "../../test/db.js";
8
8
  import { ADMIN_BLOCK_SENTINEL, DrizzleAffiliateFraudAdminRepository } from "./affiliate-admin-repository.js";
9
9
 
10
10
  describe("DrizzleAffiliateFraudAdminRepository", () => {
@@ -14,16 +14,15 @@ describe("DrizzleAffiliateFraudAdminRepository", () => {
14
14
 
15
15
  beforeAll(async () => {
16
16
  ({ db, pool } = await createTestDb());
17
- await beginTestTransaction(pool);
18
17
  });
19
18
 
20
19
  afterAll(async () => {
21
- await endTestTransaction(pool);
22
20
  await pool.close();
23
21
  });
24
22
 
25
23
  beforeEach(async () => {
26
- await rollbackTestTransaction(pool);
24
+ await truncateAllTables(pool);
25
+ await new DrizzleLedger(db).seedSystemAccounts();
27
26
  repo = new DrizzleAffiliateFraudAdminRepository(db);
28
27
  });
29
28
 
@@ -162,11 +161,17 @@ describe("DrizzleAffiliateFraudAdminRepository", () => {
162
161
 
163
162
  describe("blockFingerprint", () => {
164
163
  it("should insert fraud events with ADMIN_BLOCK as referredTenantId", async () => {
165
- await db.execute(
166
- sql`INSERT INTO credit_transactions (id, tenant_id, amount_credits, balance_after_credits, type, created_at, stripe_fingerprint)
167
- VALUES ('ct-1', 't-alice', 0, 0, 'purchase', now(), 'fp_abc123'),
168
- ('ct-2', 't-bob', 0, 0, 'purchase', now(), 'fp_abc123')`,
169
- );
164
+ const ledger = new DrizzleLedger(db);
165
+ await ledger.credit("t-alice", Credit.fromCents(1), "purchase", {
166
+ description: "test purchase",
167
+ referenceId: "ref-alice-fp_abc123",
168
+ stripeFingerprint: "fp_abc123",
169
+ });
170
+ await ledger.credit("t-bob", Credit.fromCents(1), "purchase", {
171
+ description: "test purchase",
172
+ referenceId: "ref-bob-fp_abc123",
173
+ stripeFingerprint: "fp_abc123",
174
+ });
170
175
 
171
176
  await repo.blockFingerprint("fp_abc123", "admin-user-1");
172
177
 
@@ -185,11 +190,17 @@ describe("DrizzleAffiliateFraudAdminRepository", () => {
185
190
  });
186
191
 
187
192
  it("should use unique referralId per tenant to avoid unique constraint conflicts", async () => {
188
- await db.execute(
189
- sql`INSERT INTO credit_transactions (id, tenant_id, amount_credits, balance_after_credits, type, created_at, stripe_fingerprint)
190
- VALUES ('ct-3', 't-carol', 0, 0, 'purchase', now(), 'fp_def456'),
191
- ('ct-4', 't-dave', 0, 0, 'purchase', now(), 'fp_def456')`,
192
- );
193
+ const ledger = new DrizzleLedger(db);
194
+ await ledger.credit("t-carol", Credit.fromCents(1), "purchase", {
195
+ description: "test purchase",
196
+ referenceId: "ref-carol-fp_def456",
197
+ stripeFingerprint: "fp_def456",
198
+ });
199
+ await ledger.credit("t-dave", Credit.fromCents(1), "purchase", {
200
+ description: "test purchase",
201
+ referenceId: "ref-dave-fp_def456",
202
+ stripeFingerprint: "fp_def456",
203
+ });
193
204
 
194
205
  await repo.blockFingerprint("fp_def456", "admin-user-2");
195
206
 
@@ -204,10 +215,12 @@ describe("DrizzleAffiliateFraudAdminRepository", () => {
204
215
  });
205
216
 
206
217
  it("should be idempotent via onConflictDoNothing", async () => {
207
- await db.execute(
208
- sql`INSERT INTO credit_transactions (id, tenant_id, amount_credits, balance_after_credits, type, created_at, stripe_fingerprint)
209
- VALUES ('ct-5', 't-eve', 0, 0, 'purchase', now(), 'fp_ghi789')`,
210
- );
218
+ const ledger = new DrizzleLedger(db);
219
+ await ledger.credit("t-eve", Credit.fromCents(1), "purchase", {
220
+ description: "test purchase",
221
+ referenceId: "ref-eve-fp_ghi789",
222
+ stripeFingerprint: "fp_ghi789",
223
+ });
211
224
 
212
225
  await repo.blockFingerprint("fp_ghi789", "admin-user-3");
213
226
  await repo.blockFingerprint("fp_ghi789", "admin-user-3");
@@ -4,7 +4,7 @@ import { logger } from "../../config/logger.js";
4
4
  import type { DrizzleDb } from "../../db/index.js";
5
5
  import { affiliateReferrals } from "../../db/schema/affiliate.js";
6
6
  import { affiliateFraudEvents } from "../../db/schema/affiliate-fraud.js";
7
- import { creditTransactions } from "../../db/schema/credits.js";
7
+ import { journalEntries } from "../../db/schema/ledger.js";
8
8
 
9
9
  function parseSignals(raw: string): string[] {
10
10
  try {
@@ -119,13 +119,16 @@ export class DrizzleAffiliateFraudAdminRepository implements IAffiliateFraudAdmi
119
119
  }
120
120
 
121
121
  async listFingerprintClusters(): Promise<FingerprintCluster[]> {
122
+ // Query journal_entries.metadata->>'stripeFingerprint' for purchase entries
122
123
  // raw SQL: Drizzle cannot express HAVING COUNT(DISTINCT ...) with array_agg in a single query
123
124
  type ClusterRow = { stripe_fingerprint: string; tenant_ids: string[] };
124
125
  const rows = (await this.db.execute(sql`
125
- SELECT stripe_fingerprint, array_agg(DISTINCT tenant_id ORDER BY tenant_id) AS tenant_ids
126
- FROM credit_transactions
127
- WHERE stripe_fingerprint IS NOT NULL
128
- GROUP BY stripe_fingerprint
126
+ SELECT metadata->>'stripeFingerprint' AS stripe_fingerprint,
127
+ array_agg(DISTINCT tenant_id ORDER BY tenant_id) AS tenant_ids
128
+ FROM journal_entries
129
+ WHERE metadata->>'stripeFingerprint' IS NOT NULL
130
+ AND entry_type = 'purchase'
131
+ GROUP BY metadata->>'stripeFingerprint'
129
132
  HAVING COUNT(DISTINCT tenant_id) > 1
130
133
  ORDER BY COUNT(DISTINCT tenant_id) DESC
131
134
  `)) as unknown as { rows: ClusterRow[] };
@@ -138,9 +141,14 @@ export class DrizzleAffiliateFraudAdminRepository implements IAffiliateFraudAdmi
138
141
 
139
142
  async blockFingerprint(fingerprint: string, adminUserId: string): Promise<void> {
140
143
  const rows = await this.db
141
- .selectDistinct({ tenantId: creditTransactions.tenantId })
142
- .from(creditTransactions)
143
- .where(eq(creditTransactions.stripeFingerprint, fingerprint));
144
+ .selectDistinct({ tenantId: journalEntries.tenantId })
145
+ .from(journalEntries)
146
+ .where(
147
+ and(
148
+ eq(journalEntries.entryType, "purchase"),
149
+ sql`${journalEntries.metadata}->>'stripeFingerprint' = ${fingerprint}`,
150
+ ),
151
+ );
144
152
  const tenantIds = rows.map((r) => r.tenantId);
145
153
 
146
154
  const now = new Date().toISOString();
@@ -1,5 +1,5 @@
1
1
  import type { PGlite } from "@electric-sql/pglite";
2
- import { Credit, CreditLedger } from "@wopr-network/platform-core/credits";
2
+ import { Credit, DrizzleLedger } from "@wopr-network/platform-core/credits";
3
3
  import { afterAll, beforeAll, beforeEach, describe, expect, it } from "vitest";
4
4
  import type { DrizzleDb } from "../../db/index.js";
5
5
  import { createTestDb, truncateAllTables } from "../../test/db.js";
@@ -10,7 +10,7 @@ import { DrizzleAffiliateRepository } from "./drizzle-affiliate-repository.js";
10
10
  describe("processAffiliateCreditMatch", () => {
11
11
  let pool: PGlite;
12
12
  let db: DrizzleDb;
13
- let ledger: CreditLedger;
13
+ let ledger: DrizzleLedger;
14
14
  let affiliateRepo: DrizzleAffiliateRepository;
15
15
  let fraudRepo: DrizzleAffiliateFraudRepository;
16
16
 
@@ -24,13 +24,19 @@ describe("processAffiliateCreditMatch", () => {
24
24
 
25
25
  beforeEach(async () => {
26
26
  await truncateAllTables(pool);
27
- ledger = new CreditLedger(db);
27
+ ledger = new DrizzleLedger(db);
28
+
29
+ await ledger.seedSystemAccounts();
28
30
  affiliateRepo = new DrizzleAffiliateRepository(db);
29
31
  fraudRepo = new DrizzleAffiliateFraudRepository(db);
30
32
  });
31
33
 
32
34
  it("does nothing when tenant has no referral", async () => {
33
- await ledger.credit("buyer", Credit.fromCents(1000), "purchase", "first buy", "session-1", "stripe");
35
+ await ledger.credit("buyer", Credit.fromCents(1000), "purchase", {
36
+ description: "first buy",
37
+ referenceId: "session-1",
38
+ fundingSource: "stripe",
39
+ });
34
40
 
35
41
  const result = await processAffiliateCreditMatch({
36
42
  tenantId: "buyer",
@@ -45,8 +51,16 @@ describe("processAffiliateCreditMatch", () => {
45
51
  it("does nothing when tenant already has prior purchases", async () => {
46
52
  await affiliateRepo.recordReferral("referrer", "buyer", "abc123");
47
53
 
48
- await ledger.credit("buyer", Credit.fromCents(500), "purchase", "old buy", "session-0", "stripe");
49
- await ledger.credit("buyer", Credit.fromCents(1000), "purchase", "new buy", "session-1", "stripe");
54
+ await ledger.credit("buyer", Credit.fromCents(500), "purchase", {
55
+ description: "old buy",
56
+ referenceId: "session-0",
57
+ fundingSource: "stripe",
58
+ });
59
+ await ledger.credit("buyer", Credit.fromCents(1000), "purchase", {
60
+ description: "new buy",
61
+ referenceId: "session-1",
62
+ fundingSource: "stripe",
63
+ });
50
64
 
51
65
  const result = await processAffiliateCreditMatch({
52
66
  tenantId: "buyer",
@@ -60,7 +74,11 @@ describe("processAffiliateCreditMatch", () => {
60
74
 
61
75
  it("credits referrer on first purchase with 100% match", async () => {
62
76
  await affiliateRepo.recordReferral("referrer", "buyer", "abc123");
63
- await ledger.credit("buyer", Credit.fromCents(2000), "purchase", "first buy", "session-1", "stripe");
77
+ await ledger.credit("buyer", Credit.fromCents(2000), "purchase", {
78
+ description: "first buy",
79
+ referenceId: "session-1",
80
+ fundingSource: "stripe",
81
+ });
64
82
 
65
83
  const result = await processAffiliateCreditMatch({
66
84
  tenantId: "buyer",
@@ -83,7 +101,11 @@ describe("processAffiliateCreditMatch", () => {
83
101
 
84
102
  it("respects custom match rate", async () => {
85
103
  await affiliateRepo.recordReferral("referrer", "buyer", "abc123");
86
- await ledger.credit("buyer", Credit.fromCents(2000), "purchase", "first buy", "session-1", "stripe");
104
+ await ledger.credit("buyer", Credit.fromCents(2000), "purchase", {
105
+ description: "first buy",
106
+ referenceId: "session-1",
107
+ fundingSource: "stripe",
108
+ });
87
109
 
88
110
  const result = await processAffiliateCreditMatch({
89
111
  tenantId: "buyer",
@@ -99,7 +121,11 @@ describe("processAffiliateCreditMatch", () => {
99
121
 
100
122
  it("is idempotent — second call returns null", async () => {
101
123
  await affiliateRepo.recordReferral("referrer", "buyer", "abc123");
102
- await ledger.credit("buyer", Credit.fromCents(1000), "purchase", "first buy", "session-1", "stripe");
124
+ await ledger.credit("buyer", Credit.fromCents(1000), "purchase", {
125
+ description: "first buy",
126
+ referenceId: "session-1",
127
+ fundingSource: "stripe",
128
+ });
103
129
 
104
130
  const first = await processAffiliateCreditMatch({
105
131
  tenantId: "buyer",
@@ -123,7 +149,11 @@ describe("processAffiliateCreditMatch", () => {
123
149
  signupIp: "1.2.3.4",
124
150
  signupEmail: "alice+ref@gmail.com",
125
151
  });
126
- await ledger.credit("buyer", Credit.fromCents(2000), "purchase", "first buy", "session-1", "stripe");
152
+ await ledger.credit("buyer", Credit.fromCents(2000), "purchase", {
153
+ description: "first buy",
154
+ referenceId: "session-1",
155
+ fundingSource: "stripe",
156
+ });
127
157
 
128
158
  const result = await processAffiliateCreditMatch({
129
159
  tenantId: "buyer",
@@ -156,7 +186,11 @@ describe("processAffiliateCreditMatch", () => {
156
186
 
157
187
  // New referral
158
188
  await affiliateRepo.recordReferral("referrer", "buyer", "abc123");
159
- await ledger.credit("buyer", Credit.fromCents(1000), "purchase", "first buy", "session-1", "stripe");
189
+ await ledger.credit("buyer", Credit.fromCents(1000), "purchase", {
190
+ description: "first buy",
191
+ referenceId: "session-1",
192
+ fundingSource: "stripe",
193
+ });
160
194
 
161
195
  const result = await processAffiliateCreditMatch({
162
196
  tenantId: "buyer",
@@ -184,7 +218,11 @@ describe("processAffiliateCreditMatch", () => {
184
218
 
185
219
  // New referral
186
220
  await affiliateRepo.recordReferral("referrer", "buyer", "abc123");
187
- await ledger.credit("buyer", Credit.fromCents(1000), "purchase", "first buy", "session-1", "stripe");
221
+ await ledger.credit("buyer", Credit.fromCents(1000), "purchase", {
222
+ description: "first buy",
223
+ referenceId: "session-1",
224
+ fundingSource: "stripe",
225
+ });
188
226
 
189
227
  const result = await processAffiliateCreditMatch({
190
228
  tenantId: "buyer",
@@ -203,7 +241,11 @@ describe("processAffiliateCreditMatch", () => {
203
241
 
204
242
  it("allows payout when under both caps", async () => {
205
243
  await affiliateRepo.recordReferral("referrer", "buyer", "abc123");
206
- await ledger.credit("buyer", Credit.fromCents(2000), "purchase", "first buy", "session-1", "stripe");
244
+ await ledger.credit("buyer", Credit.fromCents(2000), "purchase", {
245
+ description: "first buy",
246
+ referenceId: "session-1",
247
+ fundingSource: "stripe",
248
+ });
207
249
 
208
250
  const result = await processAffiliateCreditMatch({
209
251
  tenantId: "buyer",
@@ -223,7 +265,11 @@ describe("processAffiliateCreditMatch", () => {
223
265
  await affiliateRepo.recordReferral("referrer", "buyer", "abc123", {
224
266
  signupIp: "1.2.3.4",
225
267
  });
226
- await ledger.credit("buyer", Credit.fromCents(2000), "purchase", "first buy", "session-1", "stripe");
268
+ await ledger.credit("buyer", Credit.fromCents(2000), "purchase", {
269
+ description: "first buy",
270
+ referenceId: "session-1",
271
+ fundingSource: "stripe",
272
+ });
227
273
 
228
274
  const result = await processAffiliateCreditMatch({
229
275
  tenantId: "buyer",
@@ -1,4 +1,4 @@
1
- import type { Credit, ICreditLedger } from "@wopr-network/platform-core/credits";
1
+ import type { Credit, ILedger } from "@wopr-network/platform-core/credits";
2
2
  import { config } from "../../config/index.js";
3
3
  import type { IAffiliateFraudRepository } from "./affiliate-fraud-repository.js";
4
4
  import type { IAffiliateRepository } from "./drizzle-affiliate-repository.js";
@@ -11,7 +11,7 @@ const DEFAULT_MAX_MATCH_CREDITS_30D = config.billing.affiliateMaxMatchCredits30d
11
11
  export interface AffiliateCreditMatchDeps {
12
12
  tenantId: string;
13
13
  purchaseAmount: Credit;
14
- ledger: ICreditLedger;
14
+ ledger: ILedger;
15
15
  affiliateRepo: IAffiliateRepository;
16
16
  matchRate?: number;
17
17
  fraudRepo?: IAffiliateFraudRepository;
@@ -122,13 +122,10 @@ export async function processAffiliateCreditMatch(
122
122
  if (matchAmount.isZero() || matchAmount.isNegative()) return null;
123
123
 
124
124
  // 6. Credit the referrer
125
- await ledger.credit(
126
- referral.referrerTenantId,
127
- matchAmount,
128
- "affiliate_match",
129
- `Affiliate match for referred tenant ${tenantId}`,
130
- refId,
131
- );
125
+ await ledger.credit(referral.referrerTenantId, matchAmount, "affiliate_match", {
126
+ description: `Affiliate match for referred tenant ${tenantId}`,
127
+ referenceId: refId,
128
+ });
132
129
 
133
130
  // 7. Update referral record
134
131
  await affiliateRepo.markFirstPurchase(tenantId);
@@ -1,5 +1,5 @@
1
1
  import type { PGlite } from "@electric-sql/pglite";
2
- import { Credit, CreditLedger } from "@wopr-network/platform-core/credits";
2
+ import { Credit, DrizzleLedger } from "@wopr-network/platform-core/credits";
3
3
  import { afterAll, beforeAll, beforeEach, describe, expect, it } from "vitest";
4
4
  import type { DrizzleDb } from "../../db/index.js";
5
5
  import { createTestDb, truncateAllTables } from "../../test/db.js";
@@ -9,7 +9,7 @@ import { DEFAULT_BONUS_RATE, grantNewUserBonus } from "./new-user-bonus.js";
9
9
  describe("grantNewUserBonus", () => {
10
10
  let pool: PGlite;
11
11
  let db: DrizzleDb;
12
- let ledger: CreditLedger;
12
+ let ledger: DrizzleLedger;
13
13
  let affiliateRepo: DrizzleAffiliateRepository;
14
14
 
15
15
  beforeAll(async () => {
@@ -22,7 +22,9 @@ describe("grantNewUserBonus", () => {
22
22
 
23
23
  beforeEach(async () => {
24
24
  await truncateAllTables(pool);
25
- ledger = new CreditLedger(db);
25
+ ledger = new DrizzleLedger(db);
26
+
27
+ await ledger.seedSystemAccounts();
26
28
  affiliateRepo = new DrizzleAffiliateRepository(db);
27
29
  });
28
30
 
@@ -48,7 +50,7 @@ describe("grantNewUserBonus", () => {
48
50
 
49
51
  const txns = await ledger.history("referred-1");
50
52
  expect(txns).toHaveLength(1);
51
- expect(txns[0].type).toBe("affiliate_bonus");
53
+ expect(txns[0].entryType).toBe("affiliate_bonus");
52
54
  expect(txns[0].referenceId).toBe("affiliate-bonus:referred-1");
53
55
  expect(txns[0].description).toContain("first-purchase bonus");
54
56
  });
@@ -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 { config } from "../../config/index.js";
4
4
  import type { IAffiliateRepository } from "./drizzle-affiliate-repository.js";
@@ -7,7 +7,7 @@ import type { IAffiliateRepository } from "./drizzle-affiliate-repository.js";
7
7
  export const DEFAULT_BONUS_RATE = config.billing.affiliateNewUserBonusRate;
8
8
 
9
9
  export interface NewUserBonusParams {
10
- ledger: ICreditLedger;
10
+ ledger: ILedger;
11
11
  affiliateRepo: IAffiliateRepository;
12
12
  referredTenantId: string;
13
13
  purchaseAmount: Credit;
@@ -57,13 +57,10 @@ export async function grantNewUserBonus(params: NewUserBonusParams): Promise<New
57
57
  await affiliateRepo.markFirstPurchase(referredTenantId);
58
58
 
59
59
  // 6. Credit the bonus
60
- await ledger.credit(
61
- referredTenantId,
62
- bonus,
63
- "affiliate_bonus",
64
- `New user first-purchase bonus (${Math.round(rate * 100)}%)`,
65
- refId,
66
- );
60
+ await ledger.credit(referredTenantId, bonus, "affiliate_bonus", {
61
+ description: `New user first-purchase bonus (${Math.round(rate * 100)}%)`,
62
+ referenceId: refId,
63
+ });
67
64
 
68
65
  return { granted: true, bonus };
69
66
  }
@@ -1,7 +1,7 @@
1
1
  import crypto from "node:crypto";
2
2
  import type { PGlite } from "@electric-sql/pglite";
3
3
  import type { ITenantCustomerRepository } from "@wopr-network/platform-core/billing";
4
- import { Credit, CreditLedger } from "@wopr-network/platform-core/credits";
4
+ import { Credit, DrizzleLedger } from "@wopr-network/platform-core/credits";
5
5
  import Stripe from "stripe";
6
6
  import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
7
7
  import type { DrizzleDb } from "../../db/index.js";
@@ -48,7 +48,7 @@ function mockTenantStore(stripeCustomerId = "cus_123") {
48
48
  describe("chargeAutoTopup", () => {
49
49
  let pool: PGlite;
50
50
  let db: DrizzleDb;
51
- let ledger: CreditLedger;
51
+ let ledger: DrizzleLedger;
52
52
 
53
53
  beforeAll(async () => {
54
54
  ({ db, pool } = await createTestDb());
@@ -60,7 +60,9 @@ describe("chargeAutoTopup", () => {
60
60
 
61
61
  beforeEach(async () => {
62
62
  await truncateAllTables(pool);
63
- ledger = new CreditLedger(db);
63
+ ledger = new DrizzleLedger(db);
64
+
65
+ await ledger.seedSystemAccounts();
64
66
  });
65
67
 
66
68
  it("charges Stripe and credits ledger on success", async () => {
@@ -79,8 +81,8 @@ describe("chargeAutoTopup", () => {
79
81
  expect(result.paymentReference).toEqual(expect.any(String));
80
82
  expect((await ledger.balance("t1")).toCents()).toBe(500);
81
83
  const history = await ledger.history("t1");
82
- expect(history[0].type).toBe("purchase");
83
- expect(history[0].fundingSource).toBe("stripe");
84
+ expect(history[0].entryType).toBe("purchase");
85
+ expect(history[0].metadata?.fundingSource).toBe("stripe");
84
86
  });
85
87
 
86
88
  it("writes success event to credit_auto_topup log", async () => {