@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
@@ -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
  });
@@ -1,4 +1,4 @@
1
- import { Credit, DrizzleCreditLedger, runCreditExpiryCron } from "@wopr-network/platform-core/credits";
1
+ import { Credit, DrizzleLedger, runCreditExpiryCron } 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
  describe("runCreditExpiryCron", () => {
@@ -7,13 +7,14 @@ describe("runCreditExpiryCron", () => {
7
7
  beforeAll(async () => {
8
8
  const { db, pool: p } = await createTestDb();
9
9
  pool = p;
10
- ledger = new DrizzleCreditLedger(db);
10
+ ledger = new DrizzleLedger(db);
11
11
  });
12
12
  afterAll(async () => {
13
13
  await pool.close();
14
14
  });
15
15
  beforeEach(async () => {
16
16
  await truncateAllTables(pool);
17
+ await ledger.seedSystemAccounts();
17
18
  });
18
19
  // All tests pass an explicit `now` parameter — hardcoded dates are time-independent
19
20
  // because runCreditExpiryCron never reads the system clock.
@@ -24,7 +25,11 @@ describe("runCreditExpiryCron", () => {
24
25
  expect(result.errors).toEqual([]);
25
26
  });
26
27
  it("debits expired promotional credit grant", async () => {
27
- await ledger.credit("tenant-1", Credit.fromCents(500), "promo", "New user bonus", "promo:tenant-1", undefined, undefined, "2026-01-10T00:00:00Z");
28
+ await ledger.credit("tenant-1", Credit.fromCents(500), "promo", {
29
+ description: "New user bonus",
30
+ referenceId: "promo:tenant-1",
31
+ expiresAt: "2026-01-10T00:00:00Z",
32
+ });
28
33
  const result = await runCreditExpiryCron({ ledger, now: "2026-01-15T00:00:00Z" });
29
34
  expect(result.processed).toBe(1);
30
35
  expect(result.expired).toContain("tenant-1");
@@ -32,29 +37,41 @@ describe("runCreditExpiryCron", () => {
32
37
  expect(balance.toCents()).toBe(0);
33
38
  });
34
39
  it("does not debit non-expired credits", async () => {
35
- await ledger.credit("tenant-1", Credit.fromCents(500), "promo", "Future bonus", "promo:tenant-1-future", undefined, undefined, "2026-02-01T00:00:00Z");
40
+ await ledger.credit("tenant-1", Credit.fromCents(500), "promo", {
41
+ description: "Future bonus",
42
+ referenceId: "promo:tenant-1-future",
43
+ expiresAt: "2026-02-01T00:00:00Z",
44
+ });
36
45
  const result = await runCreditExpiryCron({ ledger, now: "2026-01-15T00:00:00Z" });
37
46
  expect(result.processed).toBe(0);
38
47
  const balance = await ledger.balance("tenant-1");
39
48
  expect(balance.toCents()).toBe(500);
40
49
  });
41
50
  it("does not debit credits without expires_at", async () => {
42
- await ledger.credit("tenant-1", Credit.fromCents(500), "purchase", "Top-up");
51
+ await ledger.credit("tenant-1", Credit.fromCents(500), "purchase", { description: "Top-up" });
43
52
  const result = await runCreditExpiryCron({ ledger, now: "2026-01-15T00:00:00Z" });
44
53
  expect(result.processed).toBe(0);
45
54
  const balance = await ledger.balance("tenant-1");
46
55
  expect(balance.toCents()).toBe(500);
47
56
  });
48
57
  it("only debits up to available balance when partially consumed", async () => {
49
- await ledger.credit("tenant-1", Credit.fromCents(500), "promo", "Promo", "promo:partial", undefined, undefined, "2026-01-10T00:00:00Z");
50
- await ledger.debit("tenant-1", Credit.fromCents(300), "bot_runtime", "Runtime");
58
+ await ledger.credit("tenant-1", Credit.fromCents(500), "promo", {
59
+ description: "Promo",
60
+ referenceId: "promo:partial",
61
+ expiresAt: "2026-01-10T00:00:00Z",
62
+ });
63
+ await ledger.debit("tenant-1", Credit.fromCents(300), "bot_runtime", { description: "Runtime" });
51
64
  const result = await runCreditExpiryCron({ ledger, now: "2026-01-15T00:00:00Z" });
52
65
  expect(result.processed).toBe(1);
53
66
  const balance = await ledger.balance("tenant-1");
54
67
  expect(balance.toCents()).toBe(0);
55
68
  });
56
69
  it("is idempotent -- does not double-debit on second run", async () => {
57
- await ledger.credit("tenant-1", Credit.fromCents(500), "promo", "Promo", "promo:idemp", undefined, undefined, "2026-01-10T00:00:00Z");
70
+ await ledger.credit("tenant-1", Credit.fromCents(500), "promo", {
71
+ description: "Promo",
72
+ referenceId: "promo:idemp",
73
+ expiresAt: "2026-01-10T00:00:00Z",
74
+ });
58
75
  await runCreditExpiryCron({ ledger, now: "2026-01-15T00:00:00Z" });
59
76
  const balanceAfterFirst = await ledger.balance("tenant-1");
60
77
  const result2 = await runCreditExpiryCron({ ledger, now: "2026-01-15T00:00:00Z" });
@@ -1,9 +1,7 @@
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
- import type { ICreditTransactionRepository } from "./credit-transaction-repository.js";
4
3
  export interface DividendCronConfig {
5
- creditTransactionRepo: ICreditTransactionRepository;
6
- ledger: ICreditLedger;
4
+ ledger: ILedger;
7
5
  /** Fraction of daily purchases matched as dividend pool. Default 1.0 (100%). */
8
6
  matchRate: number;
9
7
  /** The date to compute dividend for, as YYYY-MM-DD string. Typically yesterday. */
@@ -21,7 +21,7 @@ export async function runDividendCron(cfg) {
21
21
  // Idempotency: check if any per-tenant dividend was already distributed for this date.
22
22
  // We look for any referenceId matching "dividend:YYYY-MM-DD:*".
23
23
  const sentinelPrefix = `dividend:${cfg.targetDate}:`;
24
- const alreadyRan = await cfg.creditTransactionRepo.existsByReferenceIdLike(`${sentinelPrefix}%`);
24
+ const alreadyRan = await cfg.ledger.existsByReferenceIdLike(`${sentinelPrefix}%`);
25
25
  if (alreadyRan) {
26
26
  result.skippedAlreadyRun = true;
27
27
  logger.info("Dividend cron already ran for this date", { targetDate: cfg.targetDate });
@@ -30,14 +30,14 @@ export async function runDividendCron(cfg) {
30
30
  // Step 1: Sum all purchase amounts for the target date.
31
31
  const dayStart = `${cfg.targetDate} 00:00:00`;
32
32
  const dayEnd = `${cfg.targetDate} 24:00:00`;
33
- const dailyPurchaseTotalCredit = await cfg.creditTransactionRepo.sumPurchasesForPeriod(dayStart, dayEnd);
33
+ const dailyPurchaseTotalCredit = await cfg.ledger.sumPurchasesForPeriod(dayStart, dayEnd);
34
34
  result.pool = dailyPurchaseTotalCredit.multiply(cfg.matchRate);
35
35
  // Step 2: Find all active tenants (purchased in last 7 days from target date).
36
36
  // The 7-day window is: [targetDate - 6 days 00:00:00, targetDate 24:00:00)
37
37
  // This gives a full 7-day range ending at the end of targetDate.
38
38
  const windowStart = subtractDays(cfg.targetDate, 6);
39
39
  const windowStartTs = `${windowStart} 00:00:00`;
40
- const activeTenantIds = await cfg.creditTransactionRepo.getActiveTenantIdsInWindow(windowStartTs, dayEnd);
40
+ const activeTenantIds = await cfg.ledger.getActiveTenantIdsInWindow(windowStartTs, dayEnd);
41
41
  result.activeCount = activeTenantIds.length;
42
42
  // Step 3: Compute per-user share.
43
43
  if (result.pool.isZero() || result.activeCount <= 0) {
@@ -61,7 +61,10 @@ export async function runDividendCron(cfg) {
61
61
  for (const tenantId of activeTenantIds) {
62
62
  const perUserRef = `dividend:${cfg.targetDate}:${tenantId}`;
63
63
  try {
64
- await cfg.ledger.credit(tenantId, result.perUser, "community_dividend", `Community dividend for ${cfg.targetDate}: pool ${result.pool.toCents()}c / ${result.activeCount} users`, perUserRef);
64
+ await cfg.ledger.credit(tenantId, result.perUser, "community_dividend", {
65
+ description: `Community dividend for ${cfg.targetDate}: pool ${result.pool.toCents()}c / ${result.activeCount} users`,
66
+ referenceId: perUserRef,
67
+ });
65
68
  result.distributed++;
66
69
  }
67
70
  catch (err) {