@wopr-network/platform-core 1.13.3 → 1.14.1

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 (220) hide show
  1. package/.github/workflows/dependabot-auto-merge.yml +1 -2
  2. package/dist/api/routes/admin-credits.d.ts +2 -2
  3. package/dist/api/routes/admin-credits.js +9 -4
  4. package/dist/api/routes/quota.d.ts +2 -2
  5. package/dist/api/routes/verify-email.d.ts +3 -3
  6. package/dist/backup/on-demand-snapshot-service.d.ts +2 -2
  7. package/dist/billing/payram/webhook.d.ts +3 -3
  8. package/dist/billing/payram/webhook.js +5 -1
  9. package/dist/billing/payram/webhook.test.js +5 -4
  10. package/dist/billing/stripe/stripe-payment-processor.d.ts +2 -2
  11. package/dist/billing/stripe/stripe-payment-processor.test.js +7 -0
  12. package/dist/billing/stripe/tenant-store.d.ts +1 -1
  13. package/dist/billing/stripe/tenant-store.js +1 -1
  14. package/dist/credits/auto-topup-charge.d.ts +2 -2
  15. package/dist/credits/auto-topup-charge.js +5 -1
  16. package/dist/credits/auto-topup-charge.test.js +5 -4
  17. package/dist/credits/auto-topup-usage.d.ts +2 -2
  18. package/dist/credits/auto-topup-usage.test.js +53 -12
  19. package/dist/credits/credit-expiry-cron.d.ts +2 -2
  20. package/dist/credits/credit-expiry-cron.js +7 -4
  21. package/dist/credits/credit-expiry-cron.test.js +25 -8
  22. package/dist/credits/credit-ledger.d.ts +2 -2
  23. package/dist/credits/credit-ledger.js +1 -1
  24. package/dist/credits/dividend-cron.d.ts +4 -6
  25. package/dist/credits/dividend-cron.js +10 -16
  26. package/dist/credits/dividend-cron.test.js +31 -44
  27. package/dist/credits/dividend-repository.js +19 -22
  28. package/dist/credits/dividend-repository.test.js +4 -3
  29. package/dist/credits/index.d.ts +4 -2
  30. package/dist/credits/index.js +2 -1
  31. package/dist/credits/ledger.d.ts +195 -0
  32. package/dist/credits/ledger.js +561 -0
  33. package/dist/credits/ledger.test.js +418 -0
  34. package/dist/credits/signup-grant.d.ts +2 -2
  35. package/dist/credits/signup-grant.js +4 -4
  36. package/dist/credits/signup-grant.test.js +5 -3
  37. package/dist/credits/trial-balance-cron.d.ts +19 -0
  38. package/dist/credits/trial-balance-cron.js +30 -0
  39. package/dist/credits/trial-balance-cron.test.js +55 -0
  40. package/dist/db/schema/index.d.ts +1 -0
  41. package/dist/db/schema/index.js +1 -0
  42. package/dist/db/schema/ledger.d.ts +442 -0
  43. package/dist/db/schema/ledger.js +76 -0
  44. package/dist/gateway/credit-gate.d.ts +2 -2
  45. package/dist/gateway/credit-gate.js +5 -1
  46. package/dist/gateway/credit-gate.test.js +35 -33
  47. package/dist/gateway/protocol/deps.d.ts +2 -2
  48. package/dist/gateway/protocol/handlers.test.js +461 -0
  49. package/dist/gateway/proxy.d.ts +2 -2
  50. package/dist/gateway/types.d.ts +2 -2
  51. package/dist/metering/reconciliation-cron.test.js +9 -8
  52. package/dist/metering/reconciliation-repository.js +12 -10
  53. package/dist/metering/reconciliation-repository.test.js +9 -8
  54. package/dist/monetization/affiliate/affiliate-admin-repository.js +10 -8
  55. package/dist/monetization/affiliate/affiliate-admin-repository.test.js +32 -13
  56. package/dist/monetization/affiliate/credit-match.d.ts +2 -2
  57. package/dist/monetization/affiliate/credit-match.js +4 -1
  58. package/dist/monetization/affiliate/credit-match.test.js +58 -13
  59. package/dist/monetization/affiliate/new-user-bonus.d.ts +2 -2
  60. package/dist/monetization/affiliate/new-user-bonus.js +4 -1
  61. package/dist/monetization/affiliate/new-user-bonus.test.js +4 -3
  62. package/dist/monetization/credits/auto-topup-charge.d.ts +2 -2
  63. package/dist/monetization/credits/auto-topup-charge.js +5 -1
  64. package/dist/monetization/credits/auto-topup-charge.test.js +5 -4
  65. package/dist/monetization/credits/auto-topup-usage.d.ts +2 -2
  66. package/dist/monetization/credits/auto-topup-usage.test.js +53 -12
  67. package/dist/monetization/credits/bot-billing.d.ts +3 -3
  68. package/dist/monetization/credits/bot-billing.test.js +18 -5
  69. package/dist/monetization/credits/credit-expiry-cron.test.js +25 -8
  70. package/dist/monetization/credits/dividend-cron.d.ts +2 -4
  71. package/dist/monetization/credits/dividend-cron.js +7 -4
  72. package/dist/monetization/credits/dividend-cron.test.js +26 -46
  73. package/dist/monetization/credits/dividend-repository.js +15 -24
  74. package/dist/monetization/credits/dividend-repository.test.js +4 -3
  75. package/dist/monetization/credits/index.d.ts +2 -2
  76. package/dist/monetization/credits/index.js +1 -1
  77. package/dist/monetization/credits/member-usage.test.js +23 -10
  78. package/dist/monetization/credits/phone-billing.d.ts +2 -2
  79. package/dist/monetization/credits/phone-billing.js +5 -1
  80. package/dist/monetization/credits/phone-billing.test.js +9 -12
  81. package/dist/monetization/credits/runtime-cron.d.ts +2 -2
  82. package/dist/monetization/credits/runtime-cron.js +32 -8
  83. package/dist/monetization/credits/runtime-cron.test.js +28 -27
  84. package/dist/monetization/credits/runtime-scheduler.d.ts +2 -2
  85. package/dist/monetization/credits/runtime-scheduler.test.js +1 -1
  86. package/dist/monetization/credits/signup-grant.test.js +5 -3
  87. package/dist/monetization/credits/storage-tier-cron.test.js +3 -2
  88. package/dist/monetization/credits/trial-balance-cron.test.js +42 -0
  89. package/dist/monetization/feature-gate.d.ts +3 -3
  90. package/dist/monetization/index.d.ts +3 -3
  91. package/dist/monetization/index.js +1 -1
  92. package/dist/monetization/metering/reconciliation-cron.test.js +9 -8
  93. package/dist/monetization/metering/reconciliation-repository.js +11 -10
  94. package/dist/monetization/metering/reconciliation-repository.test.js +9 -8
  95. package/dist/monetization/payram/webhook.d.ts +2 -2
  96. package/dist/monetization/payram/webhook.js +5 -1
  97. package/dist/monetization/payram/webhook.test.js +5 -4
  98. package/dist/monetization/promotions/engine.d.ts +2 -2
  99. package/dist/monetization/promotions/engine.js +4 -1
  100. package/dist/monetization/promotions/engine.test.js +3 -1
  101. package/dist/monetization/repository-types.d.ts +1 -1
  102. package/dist/monetization/stripe/stripe-payment-processor.d.ts +2 -2
  103. package/dist/monetization/stripe/stripe-payment-processor.test.js +7 -0
  104. package/dist/monetization/stripe/webhook.d.ts +2 -2
  105. package/dist/monetization/stripe/webhook.js +70 -6
  106. package/dist/monetization/stripe/webhook.test.js +20 -15
  107. package/dist/onboarding/onboarding-service.d.ts +2 -2
  108. package/dist/onboarding/onboarding-service.js +6 -2
  109. package/drizzle/migrations/0003_double_entry_ledger.sql +82 -0
  110. package/drizzle/migrations/meta/_journal.json +7 -0
  111. package/package.json +1 -1
  112. package/src/api/routes/admin-credits.ts +11 -14
  113. package/src/api/routes/quota.ts +2 -2
  114. package/src/api/routes/verify-email.ts +4 -4
  115. package/src/backup/on-demand-snapshot-service.test.ts +3 -3
  116. package/src/backup/on-demand-snapshot-service.ts +3 -3
  117. package/src/billing/payram/webhook.test.ts +7 -5
  118. package/src/billing/payram/webhook.ts +8 -11
  119. package/src/billing/stripe/stripe-payment-processor.test.ts +10 -3
  120. package/src/billing/stripe/stripe-payment-processor.ts +3 -3
  121. package/src/billing/stripe/tenant-store.ts +1 -1
  122. package/src/credits/auto-topup-charge.test.ts +7 -5
  123. package/src/credits/auto-topup-charge.ts +7 -10
  124. package/src/credits/auto-topup-usage.test.ts +55 -13
  125. package/src/credits/auto-topup-usage.ts +2 -2
  126. package/src/credits/credit-expiry-cron.test.ts +26 -45
  127. package/src/credits/credit-expiry-cron.ts +9 -12
  128. package/src/credits/credit-ledger.ts +3 -3
  129. package/src/credits/dividend-cron.test.ts +38 -45
  130. package/src/credits/dividend-cron.ts +12 -26
  131. package/src/credits/dividend-repository.test.ts +4 -3
  132. package/src/credits/dividend-repository.ts +21 -23
  133. package/src/credits/index.ts +23 -4
  134. package/src/credits/ledger.test.ts +514 -0
  135. package/src/credits/ledger.ts +851 -0
  136. package/src/credits/signup-grant.test.ts +7 -4
  137. package/src/credits/signup-grant.ts +6 -12
  138. package/src/credits/trial-balance-cron.test.ts +68 -0
  139. package/src/credits/trial-balance-cron.ts +46 -0
  140. package/src/db/schema/index.ts +1 -0
  141. package/src/db/schema/ledger.ts +94 -0
  142. package/src/gateway/credit-gate-wiring.test.ts +3 -3
  143. package/src/gateway/credit-gate.test.ts +35 -33
  144. package/src/gateway/credit-gate.ts +6 -10
  145. package/src/gateway/gateway-routes.test.ts +5 -5
  146. package/src/gateway/protocol/deps.ts +2 -2
  147. package/src/gateway/protocol/handlers.test.ts +549 -1
  148. package/src/gateway/proxy.ts +2 -2
  149. package/src/gateway/route-mounting.test.ts +2 -2
  150. package/src/gateway/types.ts +2 -2
  151. package/src/metering/reconciliation-cron.test.ts +10 -9
  152. package/src/metering/reconciliation-repository.test.ts +10 -9
  153. package/src/metering/reconciliation-repository.ts +14 -11
  154. package/src/monetization/affiliate/affiliate-admin-repository.test.ts +32 -19
  155. package/src/monetization/affiliate/affiliate-admin-repository.ts +16 -8
  156. package/src/monetization/affiliate/credit-match.test.ts +60 -14
  157. package/src/monetization/affiliate/credit-match.ts +6 -9
  158. package/src/monetization/affiliate/new-user-bonus.test.ts +6 -4
  159. package/src/monetization/affiliate/new-user-bonus.ts +6 -9
  160. package/src/monetization/credits/auto-topup-charge.test.ts +7 -5
  161. package/src/monetization/credits/auto-topup-charge.ts +7 -10
  162. package/src/monetization/credits/auto-topup-usage.test.ts +55 -13
  163. package/src/monetization/credits/auto-topup-usage.ts +2 -2
  164. package/src/monetization/credits/bot-billing.test.ts +20 -6
  165. package/src/monetization/credits/bot-billing.ts +3 -3
  166. package/src/monetization/credits/credit-expiry-cron.test.ts +26 -45
  167. package/src/monetization/credits/dividend-cron.test.ts +34 -48
  168. package/src/monetization/credits/dividend-cron.ts +9 -14
  169. package/src/monetization/credits/dividend-repository.test.ts +4 -3
  170. package/src/monetization/credits/dividend-repository.ts +19 -25
  171. package/src/monetization/credits/index.ts +4 -4
  172. package/src/monetization/credits/member-usage.test.ts +25 -11
  173. package/src/monetization/credits/phone-billing.test.ts +18 -26
  174. package/src/monetization/credits/phone-billing.ts +7 -10
  175. package/src/monetization/credits/runtime-cron.test.ts +29 -28
  176. package/src/monetization/credits/runtime-cron.ts +34 -58
  177. package/src/monetization/credits/runtime-scheduler.test.ts +1 -1
  178. package/src/monetization/credits/runtime-scheduler.ts +2 -2
  179. package/src/monetization/credits/signup-grant.test.ts +7 -4
  180. package/src/monetization/credits/storage-tier-cron.test.ts +5 -3
  181. package/src/monetization/credits/trial-balance-cron.test.ts +52 -0
  182. package/src/monetization/feature-gate.ts +3 -3
  183. package/src/monetization/index.ts +4 -4
  184. package/src/monetization/metering/reconciliation-cron.test.ts +10 -9
  185. package/src/monetization/metering/reconciliation-repository.test.ts +11 -9
  186. package/src/monetization/metering/reconciliation-repository.ts +13 -11
  187. package/src/monetization/payram/webhook.test.ts +7 -5
  188. package/src/monetization/payram/webhook.ts +7 -10
  189. package/src/monetization/promotions/engine.test.ts +6 -5
  190. package/src/monetization/promotions/engine.ts +6 -3
  191. package/src/monetization/repository-types.ts +1 -1
  192. package/src/monetization/stripe/stripe-payment-processor.test.ts +10 -3
  193. package/src/monetization/stripe/stripe-payment-processor.ts +3 -3
  194. package/src/monetization/stripe/webhook.test.ts +22 -16
  195. package/src/monetization/stripe/webhook.ts +75 -50
  196. package/src/onboarding/onboarding-service.ts +8 -11
  197. package/dist/credits/credit-ledger-extra.test.js +0 -40
  198. package/dist/credits/credit-ledger.bench.js +0 -33
  199. package/dist/credits/credit-ledger.test.d.ts +0 -4
  200. package/dist/credits/credit-ledger.test.js +0 -203
  201. package/dist/credits/credit-transaction-repository.test.js +0 -232
  202. package/dist/monetization/credits/credit-ledger-extra.test.d.ts +0 -1
  203. package/dist/monetization/credits/credit-ledger-extra.test.js +0 -39
  204. package/dist/monetization/credits/credit-ledger.bench.d.ts +0 -1
  205. package/dist/monetization/credits/credit-ledger.bench.js +0 -32
  206. package/dist/monetization/credits/credit-ledger.test.d.ts +0 -4
  207. package/dist/monetization/credits/credit-ledger.test.js +0 -202
  208. package/dist/monetization/credits/credit-transaction-repository.test.d.ts +0 -1
  209. package/dist/monetization/credits/credit-transaction-repository.test.js +0 -232
  210. package/src/credits/credit-ledger-extra.test.ts +0 -57
  211. package/src/credits/credit-ledger.bench.ts +0 -56
  212. package/src/credits/credit-ledger.test.ts +0 -276
  213. package/src/credits/credit-transaction-repository.test.ts +0 -274
  214. package/src/monetization/credits/credit-ledger-extra.test.ts +0 -56
  215. package/src/monetization/credits/credit-ledger.bench.ts +0 -55
  216. package/src/monetization/credits/credit-ledger.test.ts +0 -275
  217. package/src/monetization/credits/credit-transaction-repository.test.ts +0 -274
  218. /package/dist/credits/{credit-ledger-extra.test.d.ts → ledger.test.d.ts} +0 -0
  219. /package/dist/credits/{credit-ledger.bench.d.ts → trial-balance-cron.test.d.ts} +0 -0
  220. /package/dist/{credits/credit-transaction-repository.test.d.ts → monetization/credits/trial-balance-cron.test.d.ts} +0 -0
@@ -1,5 +1,5 @@
1
1
  import { and, eq, gte, lt, ne, sql } from "drizzle-orm";
2
- import { creditTransactions } from "../db/schema/credits.js";
2
+ import { journalEntries, journalLines } from "../db/schema/ledger.js";
3
3
  import { usageSummaries } from "../db/schema/meter-events.js";
4
4
  export class DrizzleUsageSummaryRepository {
5
5
  db;
@@ -25,19 +25,21 @@ export class DrizzleAdapterUsageRepository {
25
25
  this.db = db;
26
26
  }
27
27
  async getAggregatedAdapterUsageDebits(startIso, endIso) {
28
+ // Sum the debit-side journal line amounts for adapter_usage entries.
29
+ // In double-entry: DR tenant liability (2000:<tenantId>), CR revenue:adapter_usage (4010).
30
+ // The debit line on the tenant account represents the charge amount.
28
31
  const rows = await this.db
29
32
  .select({
30
- tenantId: creditTransactions.tenantId,
31
- // amount_credits stores negative values for debits; ABS gives the raw positive debit amount.
32
- // Use the raw column name in sql to bypass the custom creditColumn type serializer.
33
- // raw SQL: Drizzle cannot express ABS with COALESCE and SUM
34
- totalDebitRaw: sql `COALESCE(SUM(ABS(amount_credits)), 0)`,
33
+ tenantId: journalEntries.tenantId,
34
+ // raw SQL: Drizzle cannot express COALESCE with SUM aggregation
35
+ totalDebitRaw: sql `COALESCE(SUM(${journalLines.amount}), 0)`,
35
36
  })
36
- .from(creditTransactions)
37
- .where(and(eq(creditTransactions.type, "adapter_usage"),
37
+ .from(journalLines)
38
+ .innerJoin(journalEntries, eq(journalEntries.id, journalLines.journalEntryId))
39
+ .where(and(eq(journalEntries.entryType, "adapter_usage"), eq(journalLines.side, "debit"),
38
40
  // raw SQL: Drizzle cannot express timestamptz cast for text column date comparison
39
- sql `${creditTransactions.createdAt}::timestamptz >= ${startIso}::timestamptz`, sql `${creditTransactions.createdAt}::timestamptz < ${endIso}::timestamptz`))
40
- .groupBy(creditTransactions.tenantId);
41
+ sql `${journalEntries.postedAt}::timestamptz >= ${startIso}::timestamptz`, sql `${journalEntries.postedAt}::timestamptz < ${endIso}::timestamptz`))
42
+ .groupBy(journalEntries.tenantId);
41
43
  return rows.map((r) => ({ tenantId: r.tenantId, totalDebitRaw: Number(r.totalDebitRaw) }));
42
44
  }
43
45
  }
@@ -1,7 +1,7 @@
1
1
  import crypto from "node:crypto";
2
2
  import { afterAll, beforeAll, beforeEach, describe, expect, it } from "vitest";
3
3
  import { Credit } from "../credits/credit.js";
4
- import { CreditLedger } from "../credits/credit-ledger.js";
4
+ import { DrizzleLedger } from "../credits/ledger.js";
5
5
  import { createTestDb, seedUsageSummary, truncateAllTables } from "../test/db.js";
6
6
  import { DrizzleAdapterUsageRepository, DrizzleUsageSummaryRepository } from "./reconciliation-repository.js";
7
7
  let pool;
@@ -108,7 +108,8 @@ describe("DrizzleAdapterUsageRepository", () => {
108
108
  beforeEach(async () => {
109
109
  await truncateAllTables(pool);
110
110
  repo = new DrizzleAdapterUsageRepository(db);
111
- ledger = new CreditLedger(db);
111
+ ledger = new DrizzleLedger(db);
112
+ await ledger.seedSystemAccounts();
112
113
  });
113
114
  it("returns empty array when no adapter_usage debits exist", async () => {
114
115
  const today = new Date().toISOString().slice(0, 10);
@@ -121,9 +122,9 @@ describe("DrizzleAdapterUsageRepository", () => {
121
122
  // Fund tenants
122
123
  await ledger.credit("t1", Credit.fromCents(1000), "purchase");
123
124
  await ledger.credit("t2", Credit.fromCents(1000), "purchase");
124
- await ledger.debit("t1", Credit.fromCents(30), "adapter_usage", "t1-debit-1");
125
- await ledger.debit("t1", Credit.fromCents(20), "adapter_usage", "t1-debit-2");
126
- await ledger.debit("t2", Credit.fromCents(50), "adapter_usage", "t2-debit-1");
125
+ await ledger.debit("t1", Credit.fromCents(30), "adapter_usage", { description: "t1-debit-1" });
126
+ await ledger.debit("t1", Credit.fromCents(20), "adapter_usage", { description: "t1-debit-2" });
127
+ await ledger.debit("t2", Credit.fromCents(50), "adapter_usage", { description: "t2-debit-1" });
127
128
  // Query window covering today
128
129
  const today = new Date().toISOString().slice(0, 10);
129
130
  const startIso = `${today}T00:00:00Z`;
@@ -137,8 +138,8 @@ describe("DrizzleAdapterUsageRepository", () => {
137
138
  });
138
139
  it("excludes non-adapter_usage debit types", async () => {
139
140
  await ledger.credit("t1", Credit.fromCents(1000), "purchase");
140
- await ledger.debit("t1", Credit.fromCents(30), "adapter_usage", "adapter debit");
141
- await ledger.debit("t1", Credit.fromCents(20), "bot_runtime", "runtime debit");
141
+ await ledger.debit("t1", Credit.fromCents(30), "adapter_usage", { description: "adapter debit" });
142
+ await ledger.debit("t1", Credit.fromCents(20), "bot_runtime", { description: "runtime debit" });
142
143
  const today = new Date().toISOString().slice(0, 10);
143
144
  const startIso = `${today}T00:00:00Z`;
144
145
  const endIso = new Date(new Date(startIso).getTime() + 86400000).toISOString();
@@ -148,7 +149,7 @@ describe("DrizzleAdapterUsageRepository", () => {
148
149
  });
149
150
  it("excludes credit transactions (positive amounts are not debits)", async () => {
150
151
  await ledger.credit("t1", Credit.fromCents(1000), "purchase");
151
- await ledger.debit("t1", Credit.fromCents(10), "adapter_usage", "real debit");
152
+ await ledger.debit("t1", Credit.fromCents(10), "adapter_usage", { description: "real debit" });
152
153
  const today = new Date().toISOString().slice(0, 10);
153
154
  const startIso = `${today}T00:00:00Z`;
154
155
  const endIso = new Date(new Date(startIso).getTime() + 86400000).toISOString();
@@ -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. */