@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
@@ -3,7 +3,7 @@ import { and, count, desc, eq, gte, isNotNull, sql } from "drizzle-orm";
3
3
  import { logger } from "../../config/logger.js";
4
4
  import { affiliateReferrals } from "../../db/schema/affiliate.js";
5
5
  import { affiliateFraudEvents } from "../../db/schema/affiliate-fraud.js";
6
- import { creditTransactions } from "../../db/schema/credits.js";
6
+ import { journalEntries } from "../../db/schema/ledger.js";
7
7
  function parseSignals(raw) {
8
8
  try {
9
9
  const parsed = JSON.parse(raw);
@@ -84,10 +84,12 @@ export class DrizzleAffiliateFraudAdminRepository {
84
84
  }
85
85
  async listFingerprintClusters() {
86
86
  const rows = (await this.db.execute(sql `
87
- SELECT stripe_fingerprint, array_agg(DISTINCT tenant_id ORDER BY tenant_id) AS tenant_ids
88
- FROM credit_transactions
89
- WHERE stripe_fingerprint IS NOT NULL
90
- GROUP BY stripe_fingerprint
87
+ SELECT metadata->>'stripeFingerprint' AS stripe_fingerprint,
88
+ array_agg(DISTINCT tenant_id ORDER BY tenant_id) AS tenant_ids
89
+ FROM journal_entries
90
+ WHERE metadata->>'stripeFingerprint' IS NOT NULL
91
+ AND entry_type = 'purchase'
92
+ GROUP BY metadata->>'stripeFingerprint'
91
93
  HAVING COUNT(DISTINCT tenant_id) > 1
92
94
  ORDER BY COUNT(DISTINCT tenant_id) DESC
93
95
  `));
@@ -98,9 +100,9 @@ export class DrizzleAffiliateFraudAdminRepository {
98
100
  }
99
101
  async blockFingerprint(fingerprint, adminUserId) {
100
102
  const rows = await this.db
101
- .selectDistinct({ tenantId: creditTransactions.tenantId })
102
- .from(creditTransactions)
103
- .where(eq(creditTransactions.stripeFingerprint, fingerprint));
103
+ .selectDistinct({ tenantId: journalEntries.tenantId })
104
+ .from(journalEntries)
105
+ .where(and(eq(journalEntries.entryType, "purchase"), sql `${journalEntries.metadata}->>'stripeFingerprint' = ${fingerprint}`));
104
106
  const tenantIds = rows.map((r) => r.tenantId);
105
107
  const now = new Date().toISOString();
106
108
  for (const tenantId of tenantIds) {
@@ -1,8 +1,8 @@
1
- import { sql } from "drizzle-orm";
1
+ import { Credit, DrizzleLedger } from "@wopr-network/platform-core/credits";
2
2
  import { afterAll, beforeAll, beforeEach, describe, expect, it } from "vitest";
3
3
  import { affiliateReferrals } from "../../db/schema/affiliate.js";
4
4
  import { affiliateFraudEvents } from "../../db/schema/affiliate-fraud.js";
5
- import { beginTestTransaction, createTestDb, endTestTransaction, rollbackTestTransaction } from "../../test/db.js";
5
+ import { createTestDb, truncateAllTables } from "../../test/db.js";
6
6
  import { ADMIN_BLOCK_SENTINEL, DrizzleAffiliateFraudAdminRepository } from "./affiliate-admin-repository.js";
7
7
  describe("DrizzleAffiliateFraudAdminRepository", () => {
8
8
  let pool;
@@ -10,14 +10,13 @@ describe("DrizzleAffiliateFraudAdminRepository", () => {
10
10
  let repo;
11
11
  beforeAll(async () => {
12
12
  ({ db, pool } = await createTestDb());
13
- await beginTestTransaction(pool);
14
13
  });
15
14
  afterAll(async () => {
16
- await endTestTransaction(pool);
17
15
  await pool.close();
18
16
  });
19
17
  beforeEach(async () => {
20
- await rollbackTestTransaction(pool);
18
+ await truncateAllTables(pool);
19
+ await new DrizzleLedger(db).seedSystemAccounts();
21
20
  repo = new DrizzleAffiliateFraudAdminRepository(db);
22
21
  });
23
22
  describe("listSuppressions", () => {
@@ -140,9 +139,17 @@ describe("DrizzleAffiliateFraudAdminRepository", () => {
140
139
  });
141
140
  describe("blockFingerprint", () => {
142
141
  it("should insert fraud events with ADMIN_BLOCK as referredTenantId", async () => {
143
- await db.execute(sql `INSERT INTO credit_transactions (id, tenant_id, amount_credits, balance_after_credits, type, created_at, stripe_fingerprint)
144
- VALUES ('ct-1', 't-alice', 0, 0, 'purchase', now(), 'fp_abc123'),
145
- ('ct-2', 't-bob', 0, 0, 'purchase', now(), 'fp_abc123')`);
142
+ const ledger = new DrizzleLedger(db);
143
+ await ledger.credit("t-alice", Credit.fromCents(1), "purchase", {
144
+ description: "test purchase",
145
+ referenceId: "ref-alice-fp_abc123",
146
+ stripeFingerprint: "fp_abc123",
147
+ });
148
+ await ledger.credit("t-bob", Credit.fromCents(1), "purchase", {
149
+ description: "test purchase",
150
+ referenceId: "ref-bob-fp_abc123",
151
+ stripeFingerprint: "fp_abc123",
152
+ });
146
153
  await repo.blockFingerprint("fp_abc123", "admin-user-1");
147
154
  const result = await repo.listSuppressions(50, 0);
148
155
  expect(result.events).toHaveLength(2);
@@ -156,9 +163,17 @@ describe("DrizzleAffiliateFraudAdminRepository", () => {
156
163
  expect(tenantIds).toEqual(["t-alice", "t-bob"]);
157
164
  });
158
165
  it("should use unique referralId per tenant to avoid unique constraint conflicts", async () => {
159
- await db.execute(sql `INSERT INTO credit_transactions (id, tenant_id, amount_credits, balance_after_credits, type, created_at, stripe_fingerprint)
160
- VALUES ('ct-3', 't-carol', 0, 0, 'purchase', now(), 'fp_def456'),
161
- ('ct-4', 't-dave', 0, 0, 'purchase', now(), 'fp_def456')`);
166
+ const ledger = new DrizzleLedger(db);
167
+ await ledger.credit("t-carol", Credit.fromCents(1), "purchase", {
168
+ description: "test purchase",
169
+ referenceId: "ref-carol-fp_def456",
170
+ stripeFingerprint: "fp_def456",
171
+ });
172
+ await ledger.credit("t-dave", Credit.fromCents(1), "purchase", {
173
+ description: "test purchase",
174
+ referenceId: "ref-dave-fp_def456",
175
+ stripeFingerprint: "fp_def456",
176
+ });
162
177
  await repo.blockFingerprint("fp_def456", "admin-user-2");
163
178
  const result = await repo.listSuppressions(50, 0);
164
179
  expect(result.events).toHaveLength(2);
@@ -169,8 +184,12 @@ describe("DrizzleAffiliateFraudAdminRepository", () => {
169
184
  expect(new Set(referralIds).size).toBe(2);
170
185
  });
171
186
  it("should be idempotent via onConflictDoNothing", async () => {
172
- await db.execute(sql `INSERT INTO credit_transactions (id, tenant_id, amount_credits, balance_after_credits, type, created_at, stripe_fingerprint)
173
- VALUES ('ct-5', 't-eve', 0, 0, 'purchase', now(), 'fp_ghi789')`);
187
+ const ledger = new DrizzleLedger(db);
188
+ await ledger.credit("t-eve", Credit.fromCents(1), "purchase", {
189
+ description: "test purchase",
190
+ referenceId: "ref-eve-fp_ghi789",
191
+ stripeFingerprint: "fp_ghi789",
192
+ });
174
193
  await repo.blockFingerprint("fp_ghi789", "admin-user-3");
175
194
  await repo.blockFingerprint("fp_ghi789", "admin-user-3");
176
195
  const result = await repo.listSuppressions(50, 0);
@@ -1,10 +1,10 @@
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 type { IAffiliateFraudRepository } from "./affiliate-fraud-repository.js";
3
3
  import type { IAffiliateRepository } from "./drizzle-affiliate-repository.js";
4
4
  export interface AffiliateCreditMatchDeps {
5
5
  tenantId: string;
6
6
  purchaseAmount: Credit;
7
- ledger: ICreditLedger;
7
+ ledger: ILedger;
8
8
  affiliateRepo: IAffiliateRepository;
9
9
  matchRate?: number;
10
10
  fraudRepo?: IAffiliateFraudRepository;
@@ -85,7 +85,10 @@ export async function processAffiliateCreditMatch(deps) {
85
85
  if (matchAmount.isZero() || matchAmount.isNegative())
86
86
  return null;
87
87
  // 6. Credit the referrer
88
- await ledger.credit(referral.referrerTenantId, matchAmount, "affiliate_match", `Affiliate match for referred tenant ${tenantId}`, refId);
88
+ await ledger.credit(referral.referrerTenantId, matchAmount, "affiliate_match", {
89
+ description: `Affiliate match for referred tenant ${tenantId}`,
90
+ referenceId: refId,
91
+ });
89
92
  // 7. Update referral record
90
93
  await affiliateRepo.markFirstPurchase(tenantId);
91
94
  await affiliateRepo.recordMatch(tenantId, matchAmount);
@@ -1,4 +1,4 @@
1
- import { Credit, CreditLedger } from "@wopr-network/platform-core/credits";
1
+ import { Credit, DrizzleLedger } from "@wopr-network/platform-core/credits";
2
2
  import { afterAll, beforeAll, beforeEach, describe, expect, it } from "vitest";
3
3
  import { createTestDb, truncateAllTables } from "../../test/db.js";
4
4
  import { DrizzleAffiliateFraudRepository } from "./affiliate-fraud-repository.js";
@@ -18,12 +18,17 @@ describe("processAffiliateCreditMatch", () => {
18
18
  });
19
19
  beforeEach(async () => {
20
20
  await truncateAllTables(pool);
21
- ledger = new CreditLedger(db);
21
+ ledger = new DrizzleLedger(db);
22
+ await ledger.seedSystemAccounts();
22
23
  affiliateRepo = new DrizzleAffiliateRepository(db);
23
24
  fraudRepo = new DrizzleAffiliateFraudRepository(db);
24
25
  });
25
26
  it("does nothing when tenant has no referral", async () => {
26
- await ledger.credit("buyer", Credit.fromCents(1000), "purchase", "first buy", "session-1", "stripe");
27
+ await ledger.credit("buyer", Credit.fromCents(1000), "purchase", {
28
+ description: "first buy",
29
+ referenceId: "session-1",
30
+ fundingSource: "stripe",
31
+ });
27
32
  const result = await processAffiliateCreditMatch({
28
33
  tenantId: "buyer",
29
34
  purchaseAmount: Credit.fromCents(1000),
@@ -34,8 +39,16 @@ describe("processAffiliateCreditMatch", () => {
34
39
  });
35
40
  it("does nothing when tenant already has prior purchases", async () => {
36
41
  await affiliateRepo.recordReferral("referrer", "buyer", "abc123");
37
- await ledger.credit("buyer", Credit.fromCents(500), "purchase", "old buy", "session-0", "stripe");
38
- await ledger.credit("buyer", Credit.fromCents(1000), "purchase", "new buy", "session-1", "stripe");
42
+ await ledger.credit("buyer", Credit.fromCents(500), "purchase", {
43
+ description: "old buy",
44
+ referenceId: "session-0",
45
+ fundingSource: "stripe",
46
+ });
47
+ await ledger.credit("buyer", Credit.fromCents(1000), "purchase", {
48
+ description: "new buy",
49
+ referenceId: "session-1",
50
+ fundingSource: "stripe",
51
+ });
39
52
  const result = await processAffiliateCreditMatch({
40
53
  tenantId: "buyer",
41
54
  purchaseAmount: Credit.fromCents(1000),
@@ -46,7 +59,11 @@ describe("processAffiliateCreditMatch", () => {
46
59
  });
47
60
  it("credits referrer on first purchase with 100% match", async () => {
48
61
  await affiliateRepo.recordReferral("referrer", "buyer", "abc123");
49
- await ledger.credit("buyer", Credit.fromCents(2000), "purchase", "first buy", "session-1", "stripe");
62
+ await ledger.credit("buyer", Credit.fromCents(2000), "purchase", {
63
+ description: "first buy",
64
+ referenceId: "session-1",
65
+ fundingSource: "stripe",
66
+ });
50
67
  const result = await processAffiliateCreditMatch({
51
68
  tenantId: "buyer",
52
69
  purchaseAmount: Credit.fromCents(2000),
@@ -65,7 +82,11 @@ describe("processAffiliateCreditMatch", () => {
65
82
  });
66
83
  it("respects custom match rate", async () => {
67
84
  await affiliateRepo.recordReferral("referrer", "buyer", "abc123");
68
- await ledger.credit("buyer", Credit.fromCents(2000), "purchase", "first buy", "session-1", "stripe");
85
+ await ledger.credit("buyer", Credit.fromCents(2000), "purchase", {
86
+ description: "first buy",
87
+ referenceId: "session-1",
88
+ fundingSource: "stripe",
89
+ });
69
90
  const result = await processAffiliateCreditMatch({
70
91
  tenantId: "buyer",
71
92
  purchaseAmount: Credit.fromCents(2000),
@@ -78,7 +99,11 @@ describe("processAffiliateCreditMatch", () => {
78
99
  });
79
100
  it("is idempotent — second call returns null", async () => {
80
101
  await affiliateRepo.recordReferral("referrer", "buyer", "abc123");
81
- await ledger.credit("buyer", Credit.fromCents(1000), "purchase", "first buy", "session-1", "stripe");
102
+ await ledger.credit("buyer", Credit.fromCents(1000), "purchase", {
103
+ description: "first buy",
104
+ referenceId: "session-1",
105
+ fundingSource: "stripe",
106
+ });
82
107
  const first = await processAffiliateCreditMatch({
83
108
  tenantId: "buyer",
84
109
  purchaseAmount: Credit.fromCents(1000),
@@ -99,7 +124,11 @@ describe("processAffiliateCreditMatch", () => {
99
124
  signupIp: "1.2.3.4",
100
125
  signupEmail: "alice+ref@gmail.com",
101
126
  });
102
- await ledger.credit("buyer", Credit.fromCents(2000), "purchase", "first buy", "session-1", "stripe");
127
+ await ledger.credit("buyer", Credit.fromCents(2000), "purchase", {
128
+ description: "first buy",
129
+ referenceId: "session-1",
130
+ fundingSource: "stripe",
131
+ });
103
132
  const result = await processAffiliateCreditMatch({
104
133
  tenantId: "buyer",
105
134
  purchaseAmount: Credit.fromCents(2000),
@@ -127,7 +156,11 @@ describe("processAffiliateCreditMatch", () => {
127
156
  }
128
157
  // New referral
129
158
  await affiliateRepo.recordReferral("referrer", "buyer", "abc123");
130
- await ledger.credit("buyer", Credit.fromCents(1000), "purchase", "first buy", "session-1", "stripe");
159
+ await ledger.credit("buyer", Credit.fromCents(1000), "purchase", {
160
+ description: "first buy",
161
+ referenceId: "session-1",
162
+ fundingSource: "stripe",
163
+ });
131
164
  const result = await processAffiliateCreditMatch({
132
165
  tenantId: "buyer",
133
166
  purchaseAmount: Credit.fromCents(1000),
@@ -151,7 +184,11 @@ describe("processAffiliateCreditMatch", () => {
151
184
  }
152
185
  // New referral
153
186
  await affiliateRepo.recordReferral("referrer", "buyer", "abc123");
154
- await ledger.credit("buyer", Credit.fromCents(1000), "purchase", "first buy", "session-1", "stripe");
187
+ await ledger.credit("buyer", Credit.fromCents(1000), "purchase", {
188
+ description: "first buy",
189
+ referenceId: "session-1",
190
+ fundingSource: "stripe",
191
+ });
155
192
  const result = await processAffiliateCreditMatch({
156
193
  tenantId: "buyer",
157
194
  purchaseAmount: Credit.fromCents(1000),
@@ -167,7 +204,11 @@ describe("processAffiliateCreditMatch", () => {
167
204
  });
168
205
  it("allows payout when under both caps", async () => {
169
206
  await affiliateRepo.recordReferral("referrer", "buyer", "abc123");
170
- await ledger.credit("buyer", Credit.fromCents(2000), "purchase", "first buy", "session-1", "stripe");
207
+ await ledger.credit("buyer", Credit.fromCents(2000), "purchase", {
208
+ description: "first buy",
209
+ referenceId: "session-1",
210
+ fundingSource: "stripe",
211
+ });
171
212
  const result = await processAffiliateCreditMatch({
172
213
  tenantId: "buyer",
173
214
  purchaseAmount: Credit.fromCents(2000),
@@ -184,7 +225,11 @@ describe("processAffiliateCreditMatch", () => {
184
225
  await affiliateRepo.recordReferral("referrer", "buyer", "abc123", {
185
226
  signupIp: "1.2.3.4",
186
227
  });
187
- await ledger.credit("buyer", Credit.fromCents(2000), "purchase", "first buy", "session-1", "stripe");
228
+ await ledger.credit("buyer", Credit.fromCents(2000), "purchase", {
229
+ description: "first buy",
230
+ referenceId: "session-1",
231
+ fundingSource: "stripe",
232
+ });
188
233
  const result = await processAffiliateCreditMatch({
189
234
  tenantId: "buyer",
190
235
  purchaseAmount: Credit.fromCents(2000),
@@ -1,10 +1,10 @@
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 { IAffiliateRepository } from "./drizzle-affiliate-repository.js";
4
4
  /** Default bonus rate: 20% of purchase amount. Override with AFFILIATE_NEW_USER_BONUS_RATE env var. */
5
5
  export declare const DEFAULT_BONUS_RATE: number;
6
6
  export interface NewUserBonusParams {
7
- ledger: ICreditLedger;
7
+ ledger: ILedger;
8
8
  affiliateRepo: IAffiliateRepository;
9
9
  referredTenantId: string;
10
10
  purchaseAmount: Credit;
@@ -34,6 +34,9 @@ export async function grantNewUserBonus(params) {
34
34
  // 5. Mark first purchase on the referral row (no-op if already set)
35
35
  await affiliateRepo.markFirstPurchase(referredTenantId);
36
36
  // 6. Credit the bonus
37
- await ledger.credit(referredTenantId, bonus, "affiliate_bonus", `New user first-purchase bonus (${Math.round(rate * 100)}%)`, refId);
37
+ await ledger.credit(referredTenantId, bonus, "affiliate_bonus", {
38
+ description: `New user first-purchase bonus (${Math.round(rate * 100)}%)`,
39
+ referenceId: refId,
40
+ });
38
41
  return { granted: true, bonus };
39
42
  }
@@ -1,4 +1,4 @@
1
- import { Credit, CreditLedger } from "@wopr-network/platform-core/credits";
1
+ import { Credit, DrizzleLedger } from "@wopr-network/platform-core/credits";
2
2
  import { afterAll, beforeAll, beforeEach, describe, expect, it } from "vitest";
3
3
  import { createTestDb, truncateAllTables } from "../../test/db.js";
4
4
  import { DrizzleAffiliateRepository } from "./drizzle-affiliate-repository.js";
@@ -16,7 +16,8 @@ describe("grantNewUserBonus", () => {
16
16
  });
17
17
  beforeEach(async () => {
18
18
  await truncateAllTables(pool);
19
- ledger = new CreditLedger(db);
19
+ ledger = new DrizzleLedger(db);
20
+ await ledger.seedSystemAccounts();
20
21
  affiliateRepo = new DrizzleAffiliateRepository(db);
21
22
  });
22
23
  it("DEFAULT_BONUS_RATE equals 0.20", () => {
@@ -37,7 +38,7 @@ describe("grantNewUserBonus", () => {
37
38
  expect((await ledger.balance("referred-1")).toCents()).toBe(1000);
38
39
  const txns = await ledger.history("referred-1");
39
40
  expect(txns).toHaveLength(1);
40
- expect(txns[0].type).toBe("affiliate_bonus");
41
+ expect(txns[0].entryType).toBe("affiliate_bonus");
41
42
  expect(txns[0].referenceId).toBe("affiliate-bonus:referred-1");
42
43
  expect(txns[0].description).toContain("first-purchase bonus");
43
44
  });
@@ -1,5 +1,5 @@
1
1
  import type { ITenantCustomerRepository } from "@wopr-network/platform-core/billing";
2
- import type { Credit, ICreditLedger } from "@wopr-network/platform-core/credits";
2
+ import type { Credit, ILedger } from "@wopr-network/platform-core/credits";
3
3
  import Stripe from "stripe";
4
4
  import type { IAutoTopupEventLogRepository } from "./auto-topup-event-log-repository.js";
5
5
  /** After this many consecutive Stripe failures, the auto-topup mode is disabled. */
@@ -7,7 +7,7 @@ export declare const MAX_CONSECUTIVE_FAILURES = 3;
7
7
  export interface AutoTopupChargeDeps {
8
8
  stripe: Stripe;
9
9
  tenantRepo: ITenantCustomerRepository;
10
- creditLedger: ICreditLedger;
10
+ creditLedger: ILedger;
11
11
  eventLogRepo: IAutoTopupEventLogRepository;
12
12
  }
13
13
  export interface AutoTopupChargeResult {
@@ -113,7 +113,11 @@ export async function chargeAutoTopup(deps, tenantId, amount, source) {
113
113
  // 5. Credit the ledger (idempotent via referenceId = PI ID)
114
114
  try {
115
115
  if (!(await deps.creditLedger.hasReferenceId(paymentIntent.id))) {
116
- await deps.creditLedger.credit(tenantId, amount, "purchase", `Auto-topup (${source})`, paymentIntent.id, "stripe");
116
+ await deps.creditLedger.credit(tenantId, amount, "purchase", {
117
+ description: `Auto-topup (${source})`,
118
+ referenceId: paymentIntent.id,
119
+ fundingSource: "stripe",
120
+ });
117
121
  }
118
122
  }
119
123
  catch (err) {
@@ -1,5 +1,5 @@
1
1
  import crypto from "node:crypto";
2
- import { Credit, CreditLedger } from "@wopr-network/platform-core/credits";
2
+ import { Credit, DrizzleLedger } from "@wopr-network/platform-core/credits";
3
3
  import Stripe from "stripe";
4
4
  import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
5
5
  import { creditAutoTopup } from "../../db/schema/credit-auto-topup.js";
@@ -46,7 +46,8 @@ describe("chargeAutoTopup", () => {
46
46
  });
47
47
  beforeEach(async () => {
48
48
  await truncateAllTables(pool);
49
- ledger = new CreditLedger(db);
49
+ ledger = new DrizzleLedger(db);
50
+ await ledger.seedSystemAccounts();
50
51
  });
51
52
  it("charges Stripe and credits ledger on success", async () => {
52
53
  const stripe = mockStripe();
@@ -62,8 +63,8 @@ describe("chargeAutoTopup", () => {
62
63
  expect(result.paymentReference).toEqual(expect.any(String));
63
64
  expect((await ledger.balance("t1")).toCents()).toBe(500);
64
65
  const history = await ledger.history("t1");
65
- expect(history[0].type).toBe("purchase");
66
- expect(history[0].fundingSource).toBe("stripe");
66
+ expect(history[0].entryType).toBe("purchase");
67
+ expect(history[0].metadata?.fundingSource).toBe("stripe");
67
68
  });
68
69
  it("writes success event to credit_auto_topup log", async () => {
69
70
  const stripe = mockStripe();
@@ -1,8 +1,8 @@
1
- import type { Credit, IAutoTopupSettingsRepository, ICreditLedger } from "@wopr-network/platform-core/credits";
1
+ import type { Credit, IAutoTopupSettingsRepository, ILedger } from "@wopr-network/platform-core/credits";
2
2
  import type { AutoTopupChargeResult } from "./auto-topup-charge.js";
3
3
  export interface UsageTopupDeps {
4
4
  settingsRepo: IAutoTopupSettingsRepository;
5
- creditLedger: ICreditLedger;
5
+ creditLedger: ILedger;
6
6
  /** Injected charge function (allows mocking in tests). */
7
7
  chargeAutoTopup: (tenantId: string, amount: Credit, source: string) => Promise<AutoTopupChargeResult>;
8
8
  /** Optional tenant status check. If provided and returns non-null, skip the charge. */
@@ -1,4 +1,4 @@
1
- import { Credit, CreditLedger, DrizzleAutoTopupSettingsRepository } from "@wopr-network/platform-core/credits";
1
+ import { Credit, DrizzleAutoTopupSettingsRepository, DrizzleLedger } from "@wopr-network/platform-core/credits";
2
2
  import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
3
3
  import { createTestDb, truncateAllTables } from "../../test/db.js";
4
4
  import { maybeTriggerUsageTopup } from "./auto-topup-usage.js";
@@ -15,7 +15,8 @@ describe("maybeTriggerUsageTopup", () => {
15
15
  });
16
16
  beforeEach(async () => {
17
17
  await truncateAllTables(pool);
18
- ledger = new CreditLedger(db);
18
+ ledger = new DrizzleLedger(db);
19
+ await ledger.seedSystemAccounts();
19
20
  settingsRepo = new DrizzleAutoTopupSettingsRepository(db);
20
21
  });
21
22
  it("does nothing when tenant has no auto-topup settings", async () => {
@@ -26,7 +27,11 @@ describe("maybeTriggerUsageTopup", () => {
26
27
  });
27
28
  it("does nothing when usage_enabled is false", async () => {
28
29
  await settingsRepo.upsert("t1", { usageEnabled: false });
29
- await ledger.credit("t1", Credit.fromCents(50), "purchase", "buy", "ref-1", "stripe");
30
+ await ledger.credit("t1", Credit.fromCents(50), "purchase", {
31
+ description: "buy",
32
+ referenceId: "ref-1",
33
+ fundingSource: "stripe",
34
+ });
30
35
  const mockCharge = vi.fn();
31
36
  const deps = { settingsRepo, creditLedger: ledger, chargeAutoTopup: mockCharge };
32
37
  await maybeTriggerUsageTopup(deps, "t1");
@@ -38,7 +43,11 @@ describe("maybeTriggerUsageTopup", () => {
38
43
  usageThreshold: Credit.fromCents(100),
39
44
  usageTopup: Credit.fromCents(500),
40
45
  });
41
- await ledger.credit("t1", Credit.fromCents(200), "purchase", "buy", "ref-1", "stripe");
46
+ await ledger.credit("t1", Credit.fromCents(200), "purchase", {
47
+ description: "buy",
48
+ referenceId: "ref-1",
49
+ fundingSource: "stripe",
50
+ });
42
51
  const mockCharge = vi.fn();
43
52
  const deps = { settingsRepo, creditLedger: ledger, chargeAutoTopup: mockCharge };
44
53
  await maybeTriggerUsageTopup(deps, "t1");
@@ -50,7 +59,11 @@ describe("maybeTriggerUsageTopup", () => {
50
59
  usageThreshold: Credit.fromCents(100),
51
60
  usageTopup: Credit.fromCents(500),
52
61
  });
53
- await ledger.credit("t1", Credit.fromCents(50), "purchase", "buy", "ref-1", "stripe");
62
+ await ledger.credit("t1", Credit.fromCents(50), "purchase", {
63
+ description: "buy",
64
+ referenceId: "ref-1",
65
+ fundingSource: "stripe",
66
+ });
54
67
  const mockCharge = vi.fn().mockResolvedValue({ success: true, paymentReference: "pi_123" });
55
68
  const deps = { settingsRepo, creditLedger: ledger, chargeAutoTopup: mockCharge };
56
69
  await maybeTriggerUsageTopup(deps, "t1");
@@ -59,7 +72,11 @@ describe("maybeTriggerUsageTopup", () => {
59
72
  it("skips when charge is already in-flight", async () => {
60
73
  await settingsRepo.upsert("t1", { usageEnabled: true, usageThreshold: Credit.fromCents(100) });
61
74
  await settingsRepo.setUsageChargeInFlight("t1", true);
62
- await ledger.credit("t1", Credit.fromCents(50), "purchase", "buy", "ref-1", "stripe");
75
+ await ledger.credit("t1", Credit.fromCents(50), "purchase", {
76
+ description: "buy",
77
+ referenceId: "ref-1",
78
+ fundingSource: "stripe",
79
+ });
63
80
  const mockCharge = vi.fn();
64
81
  const deps = { settingsRepo, creditLedger: ledger, chargeAutoTopup: mockCharge };
65
82
  await maybeTriggerUsageTopup(deps, "t1");
@@ -72,7 +89,11 @@ describe("maybeTriggerUsageTopup", () => {
72
89
  usageThreshold: Credit.fromCents(500),
73
90
  usageTopup: Credit.fromCents(2000),
74
91
  });
75
- await ledger.credit("t1", Credit.fromCents(100), "purchase", "buy", "ref-1", "stripe");
92
+ await ledger.credit("t1", Credit.fromCents(100), "purchase", {
93
+ description: "buy",
94
+ referenceId: "ref-1",
95
+ fundingSource: "stripe",
96
+ });
76
97
  const mockCharge = vi.fn().mockResolvedValue({ success: true, paymentReference: "pi_race" });
77
98
  const deps = { settingsRepo, creditLedger: ledger, chargeAutoTopup: mockCharge };
78
99
  // Fire two concurrent calls — both see balance < threshold,
@@ -91,7 +112,11 @@ describe("maybeTriggerUsageTopup", () => {
91
112
  usageThreshold: Credit.fromCents(100),
92
113
  usageTopup: Credit.fromCents(500),
93
114
  });
94
- await ledger.credit("t1", Credit.fromCents(50), "purchase", "buy", "ref-1", "stripe");
115
+ await ledger.credit("t1", Credit.fromCents(50), "purchase", {
116
+ description: "buy",
117
+ referenceId: "ref-1",
118
+ fundingSource: "stripe",
119
+ });
95
120
  const mockCharge = vi.fn().mockResolvedValue({ success: true, paymentReference: "pi_123" });
96
121
  const deps = { settingsRepo, creditLedger: ledger, chargeAutoTopup: mockCharge };
97
122
  // First call — triggers charge, flag set then cleared
@@ -109,7 +134,11 @@ describe("maybeTriggerUsageTopup", () => {
109
134
  usageThreshold: Credit.fromCents(100),
110
135
  usageTopup: Credit.fromCents(500),
111
136
  });
112
- await ledger.credit("t1", Credit.fromCents(50), "purchase", "buy", "ref-1", "stripe");
137
+ await ledger.credit("t1", Credit.fromCents(50), "purchase", {
138
+ description: "buy",
139
+ referenceId: "ref-1",
140
+ fundingSource: "stripe",
141
+ });
113
142
  const mockCharge = vi
114
143
  .fn()
115
144
  .mockRejectedValueOnce(new Error("Stripe network error"))
@@ -132,7 +161,11 @@ describe("maybeTriggerUsageTopup", () => {
132
161
  });
133
162
  await settingsRepo.incrementUsageFailures("t1");
134
163
  await settingsRepo.incrementUsageFailures("t1");
135
- await ledger.credit("t1", Credit.fromCents(50), "purchase", "buy", "ref-1", "stripe");
164
+ await ledger.credit("t1", Credit.fromCents(50), "purchase", {
165
+ description: "buy",
166
+ referenceId: "ref-1",
167
+ fundingSource: "stripe",
168
+ });
136
169
  const mockCharge = vi.fn().mockResolvedValue({ success: true });
137
170
  const deps = { settingsRepo, creditLedger: ledger, chargeAutoTopup: mockCharge };
138
171
  await maybeTriggerUsageTopup(deps, "t1");
@@ -144,7 +177,11 @@ describe("maybeTriggerUsageTopup", () => {
144
177
  usageThreshold: Credit.fromCents(100),
145
178
  usageTopup: Credit.fromCents(500),
146
179
  });
147
- await ledger.credit("t1", Credit.fromCents(50), "purchase", "buy", "ref-1", "stripe");
180
+ await ledger.credit("t1", Credit.fromCents(50), "purchase", {
181
+ description: "buy",
182
+ referenceId: "ref-1",
183
+ fundingSource: "stripe",
184
+ });
148
185
  const mockCharge = vi.fn().mockResolvedValue({ success: false, error: "declined" });
149
186
  const deps = { settingsRepo, creditLedger: ledger, chargeAutoTopup: mockCharge };
150
187
  await maybeTriggerUsageTopup(deps, "t1");
@@ -170,7 +207,11 @@ describe("maybeTriggerUsageTopup", () => {
170
207
  });
171
208
  await settingsRepo.incrementUsageFailures("t1");
172
209
  await settingsRepo.incrementUsageFailures("t1");
173
- await ledger.credit("t1", Credit.fromCents(50), "purchase", "buy", "ref-1", "stripe");
210
+ await ledger.credit("t1", Credit.fromCents(50), "purchase", {
211
+ description: "buy",
212
+ referenceId: "ref-1",
213
+ fundingSource: "stripe",
214
+ });
174
215
  const mockCharge = vi.fn().mockResolvedValue({ success: false, error: "declined" });
175
216
  const deps = { settingsRepo, creditLedger: ledger, chargeAutoTopup: mockCharge };
176
217
  await maybeTriggerUsageTopup(deps, "t1");
@@ -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 { IBotInstanceRepository } from "../../fleet/bot-instance-repository.js";
4
4
  import type { INodeCommandBus } from "../../fleet/node-command-bus.js";
@@ -11,7 +11,7 @@ export interface IBotBilling {
11
11
  suspendBot(botId: string): Promise<void>;
12
12
  suspendAllForTenant(tenantId: string): Promise<string[]>;
13
13
  reactivateBot(botId: string): Promise<void>;
14
- checkReactivation(tenantId: string, ledger: ICreditLedger): Promise<string[]>;
14
+ checkReactivation(tenantId: string, ledger: ILedger): Promise<string[]>;
15
15
  destroyBot(botId: string): Promise<void>;
16
16
  destroyExpiredBots(): Promise<string[]>;
17
17
  getBotBilling(botId: string): Promise<unknown>;
@@ -56,7 +56,7 @@ export declare class DrizzleBotBilling implements IBotBilling {
56
56
  *
57
57
  * @returns IDs of reactivated bots.
58
58
  */
59
- checkReactivation(tenantId: string, ledger: ICreditLedger): Promise<string[]>;
59
+ checkReactivation(tenantId: string, ledger: ILedger): Promise<string[]>;
60
60
  /**
61
61
  * Mark a bot as destroyed.
62
62
  * Sets billingState='destroyed'. Actual Docker cleanup is handled by the caller.
@@ -1,4 +1,4 @@
1
- import { Credit, CreditLedger } from "@wopr-network/platform-core/credits";
1
+ import { Credit, DrizzleLedger } from "@wopr-network/platform-core/credits";
2
2
  import { sql } from "drizzle-orm";
3
3
  import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
4
4
  import { botInstances } from "../../db/schema/bot-instances.js";
@@ -61,7 +61,8 @@ describe("BotBilling", () => {
61
61
  beforeEach(async () => {
62
62
  await truncateAllTables(pool);
63
63
  billing = new BotBilling(new DrizzleBotInstanceRepository(db));
64
- ledger = new CreditLedger(db);
64
+ ledger = new DrizzleLedger(db);
65
+ await ledger.seedSystemAccounts();
65
66
  });
66
67
  describe("registerBot", () => {
67
68
  it("registers a bot in active billing state", async () => {
@@ -178,7 +179,11 @@ describe("BotBilling", () => {
178
179
  await billing.registerBot("bot-2", "tenant-1", "bot-b");
179
180
  await billing.suspendBot("bot-1");
180
181
  await billing.suspendBot("bot-2");
181
- await ledger.credit("tenant-1", Credit.fromCents(500), "purchase", "test credit", "ref-1", "stripe");
182
+ await ledger.credit("tenant-1", Credit.fromCents(500), "purchase", {
183
+ description: "test credit",
184
+ referenceId: "ref-1",
185
+ fundingSource: "stripe",
186
+ });
182
187
  const reactivated = await billing.checkReactivation("tenant-1", ledger);
183
188
  expect(reactivated.sort()).toEqual(["bot-1", "bot-2"]);
184
189
  expect(await billing.getActiveBotCount("tenant-1")).toBe(2);
@@ -193,12 +198,20 @@ describe("BotBilling", () => {
193
198
  it("does not reactivate destroyed bots", async () => {
194
199
  await billing.registerBot("bot-1", "tenant-1", "bot-a");
195
200
  await billing.destroyBot("bot-1");
196
- await ledger.credit("tenant-1", Credit.fromCents(500), "purchase", "test credit", "ref-1", "stripe");
201
+ await ledger.credit("tenant-1", Credit.fromCents(500), "purchase", {
202
+ description: "test credit",
203
+ referenceId: "ref-1",
204
+ fundingSource: "stripe",
205
+ });
197
206
  const reactivated = await billing.checkReactivation("tenant-1", ledger);
198
207
  expect(reactivated).toEqual([]);
199
208
  });
200
209
  it("returns empty array for tenant with no bots", async () => {
201
- await ledger.credit("tenant-1", Credit.fromCents(500), "purchase", "test credit", "ref-1", "stripe");
210
+ await ledger.credit("tenant-1", Credit.fromCents(500), "purchase", {
211
+ description: "test credit",
212
+ referenceId: "ref-1",
213
+ fundingSource: "stripe",
214
+ });
202
215
  const reactivated = await billing.checkReactivation("tenant-1", ledger);
203
216
  expect(reactivated).toEqual([]);
204
217
  });