@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.
- package/.github/workflows/dependabot-auto-merge.yml +1 -2
- package/dist/api/routes/admin-credits.d.ts +2 -2
- package/dist/api/routes/admin-credits.js +9 -4
- package/dist/api/routes/quota.d.ts +2 -2
- package/dist/api/routes/verify-email.d.ts +3 -3
- package/dist/backup/on-demand-snapshot-service.d.ts +2 -2
- package/dist/billing/payram/webhook.d.ts +3 -3
- package/dist/billing/payram/webhook.js +5 -1
- package/dist/billing/payram/webhook.test.js +5 -4
- package/dist/billing/stripe/stripe-payment-processor.d.ts +2 -2
- package/dist/billing/stripe/stripe-payment-processor.test.js +7 -0
- package/dist/billing/stripe/tenant-store.d.ts +1 -1
- package/dist/billing/stripe/tenant-store.js +1 -1
- package/dist/credits/auto-topup-charge.d.ts +2 -2
- package/dist/credits/auto-topup-charge.js +5 -1
- package/dist/credits/auto-topup-charge.test.js +5 -4
- package/dist/credits/auto-topup-usage.d.ts +2 -2
- package/dist/credits/auto-topup-usage.test.js +53 -12
- package/dist/credits/credit-expiry-cron.d.ts +2 -2
- package/dist/credits/credit-expiry-cron.js +7 -4
- package/dist/credits/credit-expiry-cron.test.js +25 -8
- package/dist/credits/credit-ledger.d.ts +2 -2
- package/dist/credits/credit-ledger.js +1 -1
- package/dist/credits/dividend-cron.d.ts +4 -6
- package/dist/credits/dividend-cron.js +10 -16
- package/dist/credits/dividend-cron.test.js +31 -44
- package/dist/credits/dividend-repository.js +19 -22
- package/dist/credits/dividend-repository.test.js +4 -3
- package/dist/credits/index.d.ts +4 -2
- package/dist/credits/index.js +2 -1
- package/dist/credits/ledger.d.ts +195 -0
- package/dist/credits/ledger.js +561 -0
- package/dist/credits/ledger.test.js +418 -0
- package/dist/credits/signup-grant.d.ts +2 -2
- package/dist/credits/signup-grant.js +4 -4
- package/dist/credits/signup-grant.test.js +5 -3
- package/dist/credits/trial-balance-cron.d.ts +19 -0
- package/dist/credits/trial-balance-cron.js +30 -0
- package/dist/credits/trial-balance-cron.test.js +55 -0
- package/dist/db/schema/index.d.ts +1 -0
- package/dist/db/schema/index.js +1 -0
- package/dist/db/schema/ledger.d.ts +442 -0
- package/dist/db/schema/ledger.js +76 -0
- package/dist/gateway/credit-gate.d.ts +2 -2
- package/dist/gateway/credit-gate.js +5 -1
- package/dist/gateway/credit-gate.test.js +35 -33
- package/dist/gateway/protocol/deps.d.ts +2 -2
- package/dist/gateway/protocol/handlers.test.js +461 -0
- package/dist/gateway/proxy.d.ts +2 -2
- package/dist/gateway/types.d.ts +2 -2
- package/dist/metering/reconciliation-cron.test.js +9 -8
- package/dist/metering/reconciliation-repository.js +12 -10
- package/dist/metering/reconciliation-repository.test.js +9 -8
- package/dist/monetization/affiliate/affiliate-admin-repository.js +10 -8
- package/dist/monetization/affiliate/affiliate-admin-repository.test.js +32 -13
- package/dist/monetization/affiliate/credit-match.d.ts +2 -2
- package/dist/monetization/affiliate/credit-match.js +4 -1
- package/dist/monetization/affiliate/credit-match.test.js +58 -13
- package/dist/monetization/affiliate/new-user-bonus.d.ts +2 -2
- package/dist/monetization/affiliate/new-user-bonus.js +4 -1
- package/dist/monetization/affiliate/new-user-bonus.test.js +4 -3
- package/dist/monetization/credits/auto-topup-charge.d.ts +2 -2
- package/dist/monetization/credits/auto-topup-charge.js +5 -1
- package/dist/monetization/credits/auto-topup-charge.test.js +5 -4
- package/dist/monetization/credits/auto-topup-usage.d.ts +2 -2
- package/dist/monetization/credits/auto-topup-usage.test.js +53 -12
- package/dist/monetization/credits/bot-billing.d.ts +3 -3
- package/dist/monetization/credits/bot-billing.test.js +18 -5
- package/dist/monetization/credits/credit-expiry-cron.test.js +25 -8
- package/dist/monetization/credits/dividend-cron.d.ts +2 -4
- package/dist/monetization/credits/dividend-cron.js +7 -4
- package/dist/monetization/credits/dividend-cron.test.js +26 -46
- package/dist/monetization/credits/dividend-repository.js +15 -24
- package/dist/monetization/credits/dividend-repository.test.js +4 -3
- package/dist/monetization/credits/index.d.ts +2 -2
- package/dist/monetization/credits/index.js +1 -1
- package/dist/monetization/credits/member-usage.test.js +23 -10
- package/dist/monetization/credits/phone-billing.d.ts +2 -2
- package/dist/monetization/credits/phone-billing.js +5 -1
- package/dist/monetization/credits/phone-billing.test.js +9 -12
- package/dist/monetization/credits/runtime-cron.d.ts +2 -2
- package/dist/monetization/credits/runtime-cron.js +32 -8
- package/dist/monetization/credits/runtime-cron.test.js +28 -27
- package/dist/monetization/credits/runtime-scheduler.d.ts +2 -2
- package/dist/monetization/credits/runtime-scheduler.test.js +1 -1
- package/dist/monetization/credits/signup-grant.test.js +5 -3
- package/dist/monetization/credits/storage-tier-cron.test.js +3 -2
- package/dist/monetization/credits/trial-balance-cron.test.js +42 -0
- package/dist/monetization/feature-gate.d.ts +3 -3
- package/dist/monetization/index.d.ts +3 -3
- package/dist/monetization/index.js +1 -1
- package/dist/monetization/metering/reconciliation-cron.test.js +9 -8
- package/dist/monetization/metering/reconciliation-repository.js +11 -10
- package/dist/monetization/metering/reconciliation-repository.test.js +9 -8
- package/dist/monetization/payram/webhook.d.ts +2 -2
- package/dist/monetization/payram/webhook.js +5 -1
- package/dist/monetization/payram/webhook.test.js +5 -4
- package/dist/monetization/promotions/engine.d.ts +2 -2
- package/dist/monetization/promotions/engine.js +4 -1
- package/dist/monetization/promotions/engine.test.js +3 -1
- package/dist/monetization/repository-types.d.ts +1 -1
- package/dist/monetization/stripe/stripe-payment-processor.d.ts +2 -2
- package/dist/monetization/stripe/stripe-payment-processor.test.js +7 -0
- package/dist/monetization/stripe/webhook.d.ts +2 -2
- package/dist/monetization/stripe/webhook.js +70 -6
- package/dist/monetization/stripe/webhook.test.js +20 -15
- package/dist/onboarding/onboarding-service.d.ts +2 -2
- package/dist/onboarding/onboarding-service.js +6 -2
- package/drizzle/migrations/0003_double_entry_ledger.sql +82 -0
- package/drizzle/migrations/meta/_journal.json +7 -0
- package/package.json +1 -1
- package/src/api/routes/admin-credits.ts +11 -14
- package/src/api/routes/quota.ts +2 -2
- package/src/api/routes/verify-email.ts +4 -4
- package/src/backup/on-demand-snapshot-service.test.ts +3 -3
- package/src/backup/on-demand-snapshot-service.ts +3 -3
- package/src/billing/payram/webhook.test.ts +7 -5
- package/src/billing/payram/webhook.ts +8 -11
- package/src/billing/stripe/stripe-payment-processor.test.ts +10 -3
- package/src/billing/stripe/stripe-payment-processor.ts +3 -3
- package/src/billing/stripe/tenant-store.ts +1 -1
- package/src/credits/auto-topup-charge.test.ts +7 -5
- package/src/credits/auto-topup-charge.ts +7 -10
- package/src/credits/auto-topup-usage.test.ts +55 -13
- package/src/credits/auto-topup-usage.ts +2 -2
- package/src/credits/credit-expiry-cron.test.ts +26 -45
- package/src/credits/credit-expiry-cron.ts +9 -12
- package/src/credits/credit-ledger.ts +3 -3
- package/src/credits/dividend-cron.test.ts +38 -45
- package/src/credits/dividend-cron.ts +12 -26
- package/src/credits/dividend-repository.test.ts +4 -3
- package/src/credits/dividend-repository.ts +21 -23
- package/src/credits/index.ts +23 -4
- package/src/credits/ledger.test.ts +514 -0
- package/src/credits/ledger.ts +851 -0
- package/src/credits/signup-grant.test.ts +7 -4
- package/src/credits/signup-grant.ts +6 -12
- package/src/credits/trial-balance-cron.test.ts +68 -0
- package/src/credits/trial-balance-cron.ts +46 -0
- package/src/db/schema/index.ts +1 -0
- package/src/db/schema/ledger.ts +94 -0
- package/src/gateway/credit-gate-wiring.test.ts +3 -3
- package/src/gateway/credit-gate.test.ts +35 -33
- package/src/gateway/credit-gate.ts +6 -10
- package/src/gateway/gateway-routes.test.ts +5 -5
- package/src/gateway/protocol/deps.ts +2 -2
- package/src/gateway/protocol/handlers.test.ts +549 -1
- package/src/gateway/proxy.ts +2 -2
- package/src/gateway/route-mounting.test.ts +2 -2
- package/src/gateway/types.ts +2 -2
- package/src/metering/reconciliation-cron.test.ts +10 -9
- package/src/metering/reconciliation-repository.test.ts +10 -9
- package/src/metering/reconciliation-repository.ts +14 -11
- package/src/monetization/affiliate/affiliate-admin-repository.test.ts +32 -19
- package/src/monetization/affiliate/affiliate-admin-repository.ts +16 -8
- package/src/monetization/affiliate/credit-match.test.ts +60 -14
- package/src/monetization/affiliate/credit-match.ts +6 -9
- package/src/monetization/affiliate/new-user-bonus.test.ts +6 -4
- package/src/monetization/affiliate/new-user-bonus.ts +6 -9
- package/src/monetization/credits/auto-topup-charge.test.ts +7 -5
- package/src/monetization/credits/auto-topup-charge.ts +7 -10
- package/src/monetization/credits/auto-topup-usage.test.ts +55 -13
- package/src/monetization/credits/auto-topup-usage.ts +2 -2
- package/src/monetization/credits/bot-billing.test.ts +20 -6
- package/src/monetization/credits/bot-billing.ts +3 -3
- package/src/monetization/credits/credit-expiry-cron.test.ts +26 -45
- package/src/monetization/credits/dividend-cron.test.ts +34 -48
- package/src/monetization/credits/dividend-cron.ts +9 -14
- package/src/monetization/credits/dividend-repository.test.ts +4 -3
- package/src/monetization/credits/dividend-repository.ts +19 -25
- package/src/monetization/credits/index.ts +4 -4
- package/src/monetization/credits/member-usage.test.ts +25 -11
- package/src/monetization/credits/phone-billing.test.ts +18 -26
- package/src/monetization/credits/phone-billing.ts +7 -10
- package/src/monetization/credits/runtime-cron.test.ts +29 -28
- package/src/monetization/credits/runtime-cron.ts +34 -58
- package/src/monetization/credits/runtime-scheduler.test.ts +1 -1
- package/src/monetization/credits/runtime-scheduler.ts +2 -2
- package/src/monetization/credits/signup-grant.test.ts +7 -4
- package/src/monetization/credits/storage-tier-cron.test.ts +5 -3
- package/src/monetization/credits/trial-balance-cron.test.ts +52 -0
- package/src/monetization/feature-gate.ts +3 -3
- package/src/monetization/index.ts +4 -4
- package/src/monetization/metering/reconciliation-cron.test.ts +10 -9
- package/src/monetization/metering/reconciliation-repository.test.ts +11 -9
- package/src/monetization/metering/reconciliation-repository.ts +13 -11
- package/src/monetization/payram/webhook.test.ts +7 -5
- package/src/monetization/payram/webhook.ts +7 -10
- package/src/monetization/promotions/engine.test.ts +6 -5
- package/src/monetization/promotions/engine.ts +6 -3
- package/src/monetization/repository-types.ts +1 -1
- package/src/monetization/stripe/stripe-payment-processor.test.ts +10 -3
- package/src/monetization/stripe/stripe-payment-processor.ts +3 -3
- package/src/monetization/stripe/webhook.test.ts +22 -16
- package/src/monetization/stripe/webhook.ts +75 -50
- package/src/onboarding/onboarding-service.ts +8 -11
- package/dist/credits/credit-ledger-extra.test.js +0 -40
- package/dist/credits/credit-ledger.bench.js +0 -33
- package/dist/credits/credit-ledger.test.d.ts +0 -4
- package/dist/credits/credit-ledger.test.js +0 -203
- package/dist/credits/credit-transaction-repository.test.js +0 -232
- package/dist/monetization/credits/credit-ledger-extra.test.d.ts +0 -1
- package/dist/monetization/credits/credit-ledger-extra.test.js +0 -39
- package/dist/monetization/credits/credit-ledger.bench.d.ts +0 -1
- package/dist/monetization/credits/credit-ledger.bench.js +0 -32
- package/dist/monetization/credits/credit-ledger.test.d.ts +0 -4
- package/dist/monetization/credits/credit-ledger.test.js +0 -202
- package/dist/monetization/credits/credit-transaction-repository.test.d.ts +0 -1
- package/dist/monetization/credits/credit-transaction-repository.test.js +0 -232
- package/src/credits/credit-ledger-extra.test.ts +0 -57
- package/src/credits/credit-ledger.bench.ts +0 -56
- package/src/credits/credit-ledger.test.ts +0 -276
- package/src/credits/credit-transaction-repository.test.ts +0 -274
- package/src/monetization/credits/credit-ledger-extra.test.ts +0 -56
- package/src/monetization/credits/credit-ledger.bench.ts +0 -55
- package/src/monetization/credits/credit-ledger.test.ts +0 -275
- package/src/monetization/credits/credit-transaction-repository.test.ts +0 -274
- /package/dist/credits/{credit-ledger-extra.test.d.ts → ledger.test.d.ts} +0 -0
- /package/dist/credits/{credit-ledger.bench.d.ts → trial-balance-cron.test.d.ts} +0 -0
- /package/dist/{credits/credit-transaction-repository.test.d.ts → monetization/credits/trial-balance-cron.test.d.ts} +0 -0
|
@@ -2,13 +2,13 @@ import type { PGlite } from "@electric-sql/pglite";
|
|
|
2
2
|
import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
|
3
3
|
import type { PlatformDb } from "../db/index.js";
|
|
4
4
|
import { createTestDb, truncateAllTables } from "../test/db.js";
|
|
5
|
-
import {
|
|
5
|
+
import { DrizzleLedger } from "./ledger.js";
|
|
6
6
|
import { grantSignupCredits, SIGNUP_GRANT } from "./signup-grant.js";
|
|
7
7
|
|
|
8
8
|
describe("grantSignupCredits", () => {
|
|
9
9
|
let pool: PGlite;
|
|
10
10
|
let db: PlatformDb;
|
|
11
|
-
let ledger:
|
|
11
|
+
let ledger: DrizzleLedger;
|
|
12
12
|
|
|
13
13
|
beforeAll(async () => {
|
|
14
14
|
({ db, pool } = await createTestDb());
|
|
@@ -20,7 +20,9 @@ describe("grantSignupCredits", () => {
|
|
|
20
20
|
|
|
21
21
|
beforeEach(async () => {
|
|
22
22
|
await truncateAllTables(pool);
|
|
23
|
-
ledger = new
|
|
23
|
+
ledger = new DrizzleLedger(db);
|
|
24
|
+
|
|
25
|
+
await ledger.seedSystemAccounts();
|
|
24
26
|
});
|
|
25
27
|
|
|
26
28
|
it("grants credits to a new tenant and returns true", async () => {
|
|
@@ -53,7 +55,8 @@ describe("grantSignupCredits", () => {
|
|
|
53
55
|
const uniqueErr = Object.assign(new Error("duplicate key value violates unique constraint"), {
|
|
54
56
|
code: "23505",
|
|
55
57
|
});
|
|
56
|
-
const racingLedger = new
|
|
58
|
+
const racingLedger = new DrizzleLedger(db);
|
|
59
|
+
await racingLedger.seedSystemAccounts();
|
|
57
60
|
vi.spyOn(racingLedger, "hasReferenceId").mockResolvedValue(false);
|
|
58
61
|
vi.spyOn(racingLedger, "credit").mockRejectedValue(uniqueErr);
|
|
59
62
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Credit } from "./credit.js";
|
|
2
|
-
import type {
|
|
2
|
+
import type { ILedger } from "./ledger.js";
|
|
3
3
|
|
|
4
4
|
/** Signup grant amount: $5.00 */
|
|
5
5
|
export const SIGNUP_GRANT = Credit.fromDollars(5);
|
|
@@ -11,25 +11,19 @@ export const SIGNUP_GRANT = Credit.fromDollars(5);
|
|
|
11
11
|
*
|
|
12
12
|
* @returns true if the grant was applied, false if already granted.
|
|
13
13
|
*/
|
|
14
|
-
export async function grantSignupCredits(ledger:
|
|
14
|
+
export async function grantSignupCredits(ledger: ILedger, tenantId: string): Promise<boolean> {
|
|
15
15
|
const refId = `signup:${tenantId}`;
|
|
16
16
|
|
|
17
|
-
// Idempotency check
|
|
18
17
|
if (await ledger.hasReferenceId(refId)) {
|
|
19
18
|
return false;
|
|
20
19
|
}
|
|
21
20
|
|
|
22
21
|
try {
|
|
23
|
-
await ledger.credit(
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
"Welcome bonus — $5.00 credit on email verification",
|
|
28
|
-
refId,
|
|
29
|
-
);
|
|
22
|
+
await ledger.credit(tenantId, SIGNUP_GRANT, "signup_grant", {
|
|
23
|
+
description: "Welcome bonus — $5.00 credit on email verification",
|
|
24
|
+
referenceId: refId,
|
|
25
|
+
});
|
|
30
26
|
} catch (err) {
|
|
31
|
-
// Concurrent verify-email request won the race and already inserted the same referenceId.
|
|
32
|
-
// Treat unique constraint violation as a no-op (idempotent).
|
|
33
27
|
if (isUniqueConstraintViolation(err)) return false;
|
|
34
28
|
throw err;
|
|
35
29
|
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { PGlite } from "@electric-sql/pglite";
|
|
2
|
+
import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
|
3
|
+
import { createTestDb, truncateAllTables } from "../test/db.js";
|
|
4
|
+
import { Credit } from "./credit.js";
|
|
5
|
+
import { DrizzleLedger } from "./ledger.js";
|
|
6
|
+
import { runTrialBalanceCron } from "./trial-balance-cron.js";
|
|
7
|
+
|
|
8
|
+
describe("runTrialBalanceCron", () => {
|
|
9
|
+
let pool: PGlite;
|
|
10
|
+
let ledger: DrizzleLedger;
|
|
11
|
+
|
|
12
|
+
beforeAll(async () => {
|
|
13
|
+
const { db, pool: p } = await createTestDb();
|
|
14
|
+
pool = p;
|
|
15
|
+
ledger = new DrizzleLedger(db);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
afterAll(async () => {
|
|
19
|
+
await pool.close();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
beforeEach(async () => {
|
|
23
|
+
await truncateAllTables(pool);
|
|
24
|
+
await ledger.seedSystemAccounts();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("returns balanced when no entries exist", async () => {
|
|
28
|
+
const result = await runTrialBalanceCron({ ledger });
|
|
29
|
+
expect(result.balanced).toBe(true);
|
|
30
|
+
expect(result.differenceRaw).toBe(0);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("returns balanced after normal credit and debit", async () => {
|
|
34
|
+
await ledger.credit("t1", Credit.fromCents(500), "purchase");
|
|
35
|
+
await ledger.debit("t1", Credit.fromCents(200), "bot_runtime");
|
|
36
|
+
|
|
37
|
+
const result = await runTrialBalanceCron({ ledger });
|
|
38
|
+
expect(result.balanced).toBe(true);
|
|
39
|
+
expect(result.differenceRaw).toBe(0);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("logs an error on imbalance", async () => {
|
|
43
|
+
// Inject an imbalance by mocking trialBalance to return unbalanced data
|
|
44
|
+
const errorSpy = vi.spyOn(ledger, "trialBalance").mockResolvedValueOnce({
|
|
45
|
+
totalDebits: Credit.fromCents(1000),
|
|
46
|
+
totalCredits: Credit.fromCents(900),
|
|
47
|
+
balanced: false,
|
|
48
|
+
difference: Credit.fromCents(100),
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const result = await runTrialBalanceCron({ ledger });
|
|
52
|
+
expect(result.balanced).toBe(false);
|
|
53
|
+
expect(result.differenceRaw).toBe(Credit.fromCents(100).toRaw());
|
|
54
|
+
|
|
55
|
+
errorSpy.mockRestore();
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("does not throw on imbalance", async () => {
|
|
59
|
+
vi.spyOn(ledger, "trialBalance").mockResolvedValueOnce({
|
|
60
|
+
totalDebits: Credit.fromCents(500),
|
|
61
|
+
totalCredits: Credit.fromCents(400),
|
|
62
|
+
balanced: false,
|
|
63
|
+
difference: Credit.fromCents(100),
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
await expect(runTrialBalanceCron({ ledger })).resolves.not.toThrow();
|
|
67
|
+
});
|
|
68
|
+
});
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { logger } from "../config/logger.js";
|
|
2
|
+
import type { ILedger } from "./ledger.js";
|
|
3
|
+
|
|
4
|
+
export interface TrialBalanceCronConfig {
|
|
5
|
+
ledger: ILedger;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface TrialBalanceCronResult {
|
|
9
|
+
balanced: boolean;
|
|
10
|
+
totalDebits: number;
|
|
11
|
+
totalCredits: number;
|
|
12
|
+
/** Absolute difference in raw units (nanodollars). Zero when balanced. */
|
|
13
|
+
differenceRaw: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Run a trial balance check: assert that sum(debit lines) === sum(credit lines)
|
|
18
|
+
* across all journal entries.
|
|
19
|
+
*
|
|
20
|
+
* Designed to run hourly. Logs an error on imbalance but never throws —
|
|
21
|
+
* an imbalance is historical and requires human investigation, not automated action.
|
|
22
|
+
*/
|
|
23
|
+
export async function runTrialBalanceCron(cfg: TrialBalanceCronConfig): Promise<TrialBalanceCronResult> {
|
|
24
|
+
const tb = await cfg.ledger.trialBalance();
|
|
25
|
+
|
|
26
|
+
const result: TrialBalanceCronResult = {
|
|
27
|
+
balanced: tb.balanced,
|
|
28
|
+
totalDebits: tb.totalDebits.toRaw(),
|
|
29
|
+
totalCredits: tb.totalCredits.toRaw(),
|
|
30
|
+
differenceRaw: tb.difference.toRaw(),
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
if (!tb.balanced) {
|
|
34
|
+
logger.error("LEDGER IMBALANCE DETECTED — books do not balance", {
|
|
35
|
+
totalDebits: tb.totalDebits.toDisplayString(),
|
|
36
|
+
totalCredits: tb.totalCredits.toDisplayString(),
|
|
37
|
+
difference: tb.difference.toDisplayString(),
|
|
38
|
+
});
|
|
39
|
+
} else {
|
|
40
|
+
logger.info("Trial balance check passed", {
|
|
41
|
+
totalDebits: tb.totalDebits.toDisplayString(),
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return result;
|
|
46
|
+
}
|
package/src/db/schema/index.ts
CHANGED
|
@@ -25,6 +25,7 @@ export * from "./gateway-service-keys.js";
|
|
|
25
25
|
export * from "./gpu-allocations.js";
|
|
26
26
|
export * from "./gpu-configurations.js";
|
|
27
27
|
export * from "./gpu-nodes.js";
|
|
28
|
+
export * from "./ledger.js";
|
|
28
29
|
export * from "./marketplace-plugins.js";
|
|
29
30
|
export * from "./meter-events.js";
|
|
30
31
|
export * from "./node-registration-tokens.js";
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { sql } from "drizzle-orm";
|
|
2
|
+
import { bigint, index, jsonb, pgEnum, pgTable, text, uniqueIndex } from "drizzle-orm/pg-core";
|
|
3
|
+
|
|
4
|
+
export const accountTypeEnum = pgEnum("account_type", ["asset", "liability", "equity", "revenue", "expense"]);
|
|
5
|
+
|
|
6
|
+
export const entrySideEnum = pgEnum("entry_side", ["debit", "credit"]);
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Chart of accounts — every account that can appear in a journal line.
|
|
10
|
+
*
|
|
11
|
+
* System accounts (tenant_id IS NULL) are seeded at migration time.
|
|
12
|
+
* Per-tenant liability accounts are created lazily on first transaction.
|
|
13
|
+
*/
|
|
14
|
+
export const accounts = pgTable(
|
|
15
|
+
"accounts",
|
|
16
|
+
{
|
|
17
|
+
id: text("id").primaryKey(),
|
|
18
|
+
code: text("code").notNull(),
|
|
19
|
+
name: text("name").notNull(),
|
|
20
|
+
type: accountTypeEnum("type").notNull(),
|
|
21
|
+
normalSide: entrySideEnum("normal_side").notNull(),
|
|
22
|
+
tenantId: text("tenant_id"), // NULL = system account
|
|
23
|
+
createdAt: text("created_at").notNull().default(sql`(now())`),
|
|
24
|
+
},
|
|
25
|
+
(table) => [
|
|
26
|
+
uniqueIndex("idx_accounts_code").on(table.code),
|
|
27
|
+
index("idx_accounts_tenant").on(table.tenantId).where(sql`${table.tenantId} IS NOT NULL`),
|
|
28
|
+
index("idx_accounts_type").on(table.type),
|
|
29
|
+
],
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Journal entries — the header for each balanced transaction.
|
|
34
|
+
* One business event = one journal entry = two or more journal lines that sum to zero.
|
|
35
|
+
*/
|
|
36
|
+
export const journalEntries = pgTable(
|
|
37
|
+
"journal_entries",
|
|
38
|
+
{
|
|
39
|
+
id: text("id").primaryKey(),
|
|
40
|
+
postedAt: text("posted_at").notNull().default(sql`(now())`),
|
|
41
|
+
entryType: text("entry_type").notNull(), // purchase, usage, grant, refund, dividend, expiry, correction
|
|
42
|
+
description: text("description"),
|
|
43
|
+
referenceId: text("reference_id"),
|
|
44
|
+
tenantId: text("tenant_id").notNull(),
|
|
45
|
+
metadata: jsonb("metadata"), // funding_source, attributed_user_id, stripe_fingerprint, etc.
|
|
46
|
+
createdBy: text("created_by"), // system, admin:<id>, cron:expiry, etc.
|
|
47
|
+
},
|
|
48
|
+
(table) => [
|
|
49
|
+
uniqueIndex("idx_je_reference").on(table.referenceId).where(sql`${table.referenceId} IS NOT NULL`),
|
|
50
|
+
index("idx_je_tenant").on(table.tenantId),
|
|
51
|
+
index("idx_je_type").on(table.entryType),
|
|
52
|
+
index("idx_je_posted").on(table.postedAt),
|
|
53
|
+
index("idx_je_tenant_posted").on(table.tenantId, table.postedAt),
|
|
54
|
+
],
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Journal lines — the individual debits and credits within a journal entry.
|
|
59
|
+
* Invariant: for every journal_entry, SUM(debit amounts) = SUM(credit amounts).
|
|
60
|
+
* Amount is always positive; `side` determines the direction.
|
|
61
|
+
* Stored in nanodollars (Credit.toRaw()).
|
|
62
|
+
*/
|
|
63
|
+
export const journalLines = pgTable(
|
|
64
|
+
"journal_lines",
|
|
65
|
+
{
|
|
66
|
+
id: text("id").primaryKey(),
|
|
67
|
+
journalEntryId: text("journal_entry_id")
|
|
68
|
+
.notNull()
|
|
69
|
+
.references(() => journalEntries.id),
|
|
70
|
+
accountId: text("account_id")
|
|
71
|
+
.notNull()
|
|
72
|
+
.references(() => accounts.id),
|
|
73
|
+
amount: bigint("amount", { mode: "number" }).notNull(), // nanodollars, always positive
|
|
74
|
+
side: entrySideEnum("side").notNull(),
|
|
75
|
+
},
|
|
76
|
+
(table) => [
|
|
77
|
+
index("idx_jl_entry").on(table.journalEntryId),
|
|
78
|
+
index("idx_jl_account").on(table.accountId),
|
|
79
|
+
index("idx_jl_account_side").on(table.accountId, table.side),
|
|
80
|
+
],
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Materialized account balances — cache derived from journal_lines.
|
|
85
|
+
* Updated atomically within the same transaction as the journal line insert.
|
|
86
|
+
* Can always be reconstructed from journal_lines if corrupted.
|
|
87
|
+
*/
|
|
88
|
+
export const accountBalances = pgTable("account_balances", {
|
|
89
|
+
accountId: text("account_id")
|
|
90
|
+
.primaryKey()
|
|
91
|
+
.references(() => accounts.id),
|
|
92
|
+
balance: bigint("balance", { mode: "number" }).notNull().default(0), // net balance in nanodollars
|
|
93
|
+
lastUpdated: text("last_updated").notNull().default(sql`(now())`),
|
|
94
|
+
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ILedger } from "@wopr-network/platform-core/credits";
|
|
2
2
|
import { Credit } from "@wopr-network/platform-core/credits";
|
|
3
3
|
import { describe, expect, it, vi } from "vitest";
|
|
4
4
|
import { type CreditGateDeps, debitCredits } from "./credit-gate.js";
|
|
@@ -9,7 +9,7 @@ describe("onDebitComplete wiring", () => {
|
|
|
9
9
|
const balance = vi.fn().mockResolvedValue(Credit.fromCents(500));
|
|
10
10
|
const debit = vi.fn().mockResolvedValue(undefined);
|
|
11
11
|
const deps: CreditGateDeps = {
|
|
12
|
-
creditLedger: { balance, debit } as unknown as
|
|
12
|
+
creditLedger: { balance, debit } as unknown as ILedger,
|
|
13
13
|
topUpUrl: "/billing",
|
|
14
14
|
onDebitComplete,
|
|
15
15
|
};
|
|
@@ -25,7 +25,7 @@ describe("onDebitComplete wiring", () => {
|
|
|
25
25
|
const { InsufficientBalanceError } = await import("@wopr-network/platform-core/credits");
|
|
26
26
|
const debit = vi.fn().mockRejectedValue(new InsufficientBalanceError(Credit.fromCents(0), Credit.fromCents(100)));
|
|
27
27
|
const deps: CreditGateDeps = {
|
|
28
|
-
creditLedger: { debit } as unknown as
|
|
28
|
+
creditLedger: { debit } as unknown as ILedger,
|
|
29
29
|
topUpUrl: "/billing",
|
|
30
30
|
onDebitComplete,
|
|
31
31
|
};
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import type { PGlite } from "@electric-sql/pglite";
|
|
6
|
-
import { Credit,
|
|
6
|
+
import { Credit, DrizzleLedger } from "@wopr-network/platform-core/credits";
|
|
7
7
|
import { Hono } from "hono";
|
|
8
8
|
import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
|
9
9
|
import type { DrizzleDb } from "../db/index.js";
|
|
@@ -51,11 +51,12 @@ afterAll(async () => {
|
|
|
51
51
|
describe("creditBalanceCheck grace buffer", () => {
|
|
52
52
|
beforeEach(async () => {
|
|
53
53
|
await truncateAllTables(pool);
|
|
54
|
+
await new DrizzleLedger(db).seedSystemAccounts();
|
|
54
55
|
});
|
|
55
56
|
|
|
56
57
|
it("returns null when balance is above estimated cost (passes)", async () => {
|
|
57
|
-
const ledger = new
|
|
58
|
-
await ledger.credit("t1", Credit.fromCents(500), "purchase", "setup");
|
|
58
|
+
const ledger = new DrizzleLedger(db);
|
|
59
|
+
await ledger.credit("t1", Credit.fromCents(500), "purchase", { description: "setup" });
|
|
59
60
|
const c = await buildHonoContext("t1");
|
|
60
61
|
const deps: CreditGateDeps = { creditLedger: ledger, topUpUrl: "/billing" };
|
|
61
62
|
expect(await creditBalanceCheck(c, deps, 1)).toBeNull();
|
|
@@ -63,27 +64,27 @@ describe("creditBalanceCheck grace buffer", () => {
|
|
|
63
64
|
|
|
64
65
|
it("returns null when balance is zero but within default grace buffer (passes)", async () => {
|
|
65
66
|
// Balance at exactly 0 — within the -50 grace buffer
|
|
66
|
-
const ledger = new
|
|
67
|
-
await ledger.credit("t1", Credit.fromCents(10), "purchase", "setup");
|
|
68
|
-
await ledger.debit("t1", Credit.fromCents(10), "adapter_usage", "drain");
|
|
67
|
+
const ledger = new DrizzleLedger(db);
|
|
68
|
+
await ledger.credit("t1", Credit.fromCents(10), "purchase", { description: "setup" });
|
|
69
|
+
await ledger.debit("t1", Credit.fromCents(10), "adapter_usage", { description: "drain" });
|
|
69
70
|
const c = await buildHonoContext("t1");
|
|
70
71
|
const deps: CreditGateDeps = { creditLedger: ledger, topUpUrl: "/billing" };
|
|
71
72
|
expect(await creditBalanceCheck(c, deps, 0)).toBeNull();
|
|
72
73
|
});
|
|
73
74
|
|
|
74
75
|
it("returns null when balance is -49 (within 50-cent grace buffer)", async () => {
|
|
75
|
-
const ledger = new
|
|
76
|
-
await ledger.credit("t1", Credit.fromCents(1), "purchase", "setup");
|
|
77
|
-
await ledger.debit("t1", Credit.fromCents(50), "adapter_usage", "drain",
|
|
76
|
+
const ledger = new DrizzleLedger(db);
|
|
77
|
+
await ledger.credit("t1", Credit.fromCents(1), "purchase", { description: "setup" });
|
|
78
|
+
await ledger.debit("t1", Credit.fromCents(50), "adapter_usage", { description: "drain", allowNegative: true }); // balance = -49
|
|
78
79
|
const c = await buildHonoContext("t1");
|
|
79
80
|
const deps: CreditGateDeps = { creditLedger: ledger, topUpUrl: "/billing" };
|
|
80
81
|
expect(await creditBalanceCheck(c, deps, 0)).toBeNull();
|
|
81
82
|
});
|
|
82
83
|
|
|
83
84
|
it("returns credits_exhausted when balance is at -50 (at grace buffer limit)", async () => {
|
|
84
|
-
const ledger = new
|
|
85
|
-
await ledger.credit("t1", Credit.fromCents(1), "purchase", "setup");
|
|
86
|
-
await ledger.debit("t1", Credit.fromCents(51), "adapter_usage", "drain",
|
|
85
|
+
const ledger = new DrizzleLedger(db);
|
|
86
|
+
await ledger.credit("t1", Credit.fromCents(1), "purchase", { description: "setup" });
|
|
87
|
+
await ledger.debit("t1", Credit.fromCents(51), "adapter_usage", { description: "drain", allowNegative: true }); // balance = -50
|
|
87
88
|
const c = await buildHonoContext("t1");
|
|
88
89
|
const deps: CreditGateDeps = { creditLedger: ledger, topUpUrl: "/billing" };
|
|
89
90
|
const result = await creditBalanceCheck(c, deps, 0);
|
|
@@ -92,9 +93,9 @@ describe("creditBalanceCheck grace buffer", () => {
|
|
|
92
93
|
});
|
|
93
94
|
|
|
94
95
|
it("returns credits_exhausted when balance is at -51 (beyond grace buffer)", async () => {
|
|
95
|
-
const ledger = new
|
|
96
|
-
await ledger.credit("t1", Credit.fromCents(1), "purchase", "setup");
|
|
97
|
-
await ledger.debit("t1", Credit.fromCents(52), "adapter_usage", "drain",
|
|
96
|
+
const ledger = new DrizzleLedger(db);
|
|
97
|
+
await ledger.credit("t1", Credit.fromCents(1), "purchase", { description: "setup" });
|
|
98
|
+
await ledger.debit("t1", Credit.fromCents(52), "adapter_usage", { description: "drain", allowNegative: true }); // balance = -51
|
|
98
99
|
const c = await buildHonoContext("t1");
|
|
99
100
|
const deps: CreditGateDeps = { creditLedger: ledger, topUpUrl: "/billing" };
|
|
100
101
|
const result = await creditBalanceCheck(c, deps, 0);
|
|
@@ -103,9 +104,9 @@ describe("creditBalanceCheck grace buffer", () => {
|
|
|
103
104
|
});
|
|
104
105
|
|
|
105
106
|
it("returns credits_exhausted when custom graceBufferCents=0 and balance is 0", async () => {
|
|
106
|
-
const ledger = new
|
|
107
|
-
await ledger.credit("t1", Credit.fromCents(10), "purchase", "setup");
|
|
108
|
-
await ledger.debit("t1", Credit.fromCents(10), "adapter_usage", "drain"); // balance = 0
|
|
107
|
+
const ledger = new DrizzleLedger(db);
|
|
108
|
+
await ledger.credit("t1", Credit.fromCents(10), "purchase", { description: "setup" });
|
|
109
|
+
await ledger.debit("t1", Credit.fromCents(10), "adapter_usage", { description: "drain" }); // balance = 0
|
|
109
110
|
const c = await buildHonoContext("t1");
|
|
110
111
|
const deps: CreditGateDeps = { creditLedger: ledger, topUpUrl: "/billing", graceBufferCents: 0 };
|
|
111
112
|
const result = await creditBalanceCheck(c, deps, 0);
|
|
@@ -114,8 +115,8 @@ describe("creditBalanceCheck grace buffer", () => {
|
|
|
114
115
|
});
|
|
115
116
|
|
|
116
117
|
it("returns insufficient_credits when balance positive but below estimated cost", async () => {
|
|
117
|
-
const ledger = new
|
|
118
|
-
await ledger.credit("t1", Credit.fromCents(5), "purchase", "setup");
|
|
118
|
+
const ledger = new DrizzleLedger(db);
|
|
119
|
+
await ledger.credit("t1", Credit.fromCents(5), "purchase", { description: "setup" });
|
|
119
120
|
const c = await buildHonoContext("t1");
|
|
120
121
|
const deps: CreditGateDeps = { creditLedger: ledger, topUpUrl: "/billing" };
|
|
121
122
|
const result = await creditBalanceCheck(c, deps, 10);
|
|
@@ -131,11 +132,12 @@ describe("creditBalanceCheck grace buffer", () => {
|
|
|
131
132
|
describe("debitCredits with allowNegative and onBalanceExhausted", () => {
|
|
132
133
|
beforeEach(async () => {
|
|
133
134
|
await truncateAllTables(pool);
|
|
135
|
+
await new DrizzleLedger(db).seedSystemAccounts();
|
|
134
136
|
});
|
|
135
137
|
|
|
136
138
|
it("debit with cost that would exceed balance succeeds (allowNegative=true)", async () => {
|
|
137
|
-
const ledger = new
|
|
138
|
-
await ledger.credit("t1", Credit.fromCents(5), "purchase", "setup"); // balance = 5 cents
|
|
139
|
+
const ledger = new DrizzleLedger(db);
|
|
140
|
+
await ledger.credit("t1", Credit.fromCents(5), "purchase", { description: "setup" }); // balance = 5 cents
|
|
139
141
|
|
|
140
142
|
// costUsd = $0.10 = 10 cents, margin = 1.0
|
|
141
143
|
// This should push balance negative without throwing
|
|
@@ -147,8 +149,8 @@ describe("debitCredits with allowNegative and onBalanceExhausted", () => {
|
|
|
147
149
|
});
|
|
148
150
|
|
|
149
151
|
it("fires onBalanceExhausted when debit causes balance to cross zero", async () => {
|
|
150
|
-
const ledger = new
|
|
151
|
-
await ledger.credit("t1", Credit.fromCents(5), "purchase", "setup"); // balance = 5 cents
|
|
152
|
+
const ledger = new DrizzleLedger(db);
|
|
153
|
+
await ledger.credit("t1", Credit.fromCents(5), "purchase", { description: "setup" }); // balance = 5 cents
|
|
152
154
|
|
|
153
155
|
const onBalanceExhausted = vi.fn();
|
|
154
156
|
// costUsd = $0.10 = 10 cents with margin 1.0 → chargeCents = 10, pushes balance to -5
|
|
@@ -165,8 +167,8 @@ describe("debitCredits with allowNegative and onBalanceExhausted", () => {
|
|
|
165
167
|
});
|
|
166
168
|
|
|
167
169
|
it("does NOT fire onBalanceExhausted when balance stays positive after debit", async () => {
|
|
168
|
-
const ledger = new
|
|
169
|
-
await ledger.credit("t1", Credit.fromCents(500), "purchase", "setup"); // balance = 500 cents
|
|
170
|
+
const ledger = new DrizzleLedger(db);
|
|
171
|
+
await ledger.credit("t1", Credit.fromCents(500), "purchase", { description: "setup" }); // balance = 500 cents
|
|
170
172
|
|
|
171
173
|
const onBalanceExhausted = vi.fn();
|
|
172
174
|
// costUsd = $0.01 = 1 cent → balance stays at 499
|
|
@@ -184,8 +186,8 @@ describe("debitCredits with allowNegative and onBalanceExhausted", () => {
|
|
|
184
186
|
});
|
|
185
187
|
|
|
186
188
|
it("onBalanceExhausted callback receives correct tenantId and negative balance", async () => {
|
|
187
|
-
const ledger = new
|
|
188
|
-
await ledger.credit("t1", Credit.fromCents(3), "purchase", "setup"); // balance = 3 cents
|
|
189
|
+
const ledger = new DrizzleLedger(db);
|
|
190
|
+
await ledger.credit("t1", Credit.fromCents(3), "purchase", { description: "setup" }); // balance = 3 cents
|
|
189
191
|
|
|
190
192
|
const onBalanceExhausted = vi.fn();
|
|
191
193
|
// costUsd = $0.05 = 5 cents with margin 1.0 → pushes balance to -2
|
|
@@ -203,8 +205,8 @@ describe("debitCredits with allowNegative and onBalanceExhausted", () => {
|
|
|
203
205
|
});
|
|
204
206
|
|
|
205
207
|
it("calls onSpendAlertCrossed after successful debit", async () => {
|
|
206
|
-
const ledger = new
|
|
207
|
-
await ledger.credit("t1", Credit.fromCents(500), "purchase", "setup");
|
|
208
|
+
const ledger = new DrizzleLedger(db);
|
|
209
|
+
await ledger.credit("t1", Credit.fromCents(500), "purchase", { description: "setup" });
|
|
208
210
|
|
|
209
211
|
const onSpendAlertCrossed = vi.fn();
|
|
210
212
|
await debitCredits(
|
|
@@ -219,10 +221,10 @@ describe("debitCredits with allowNegative and onBalanceExhausted", () => {
|
|
|
219
221
|
});
|
|
220
222
|
|
|
221
223
|
it("does NOT fire onBalanceExhausted when balance was already negative before debit", async () => {
|
|
222
|
-
const ledger = new
|
|
224
|
+
const ledger = new DrizzleLedger(db);
|
|
223
225
|
// Start with negative balance: credit 5, debit 10 → balance = -5
|
|
224
|
-
await ledger.credit("t1", Credit.fromCents(5), "purchase", "setup");
|
|
225
|
-
await ledger.debit("t1", Credit.fromCents(10), "adapter_usage", "drain",
|
|
226
|
+
await ledger.credit("t1", Credit.fromCents(5), "purchase", { description: "setup" });
|
|
227
|
+
await ledger.debit("t1", Credit.fromCents(10), "adapter_usage", { description: "drain", allowNegative: true });
|
|
226
228
|
|
|
227
229
|
const onBalanceExhausted = vi.fn();
|
|
228
230
|
// Another debit of 1 cent — balance goes from -5 to -6, but was already negative
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* gateway endpoints.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import type {
|
|
9
|
+
import type { ILedger } from "@wopr-network/platform-core/credits";
|
|
10
10
|
import { Credit, InsufficientBalanceError } from "@wopr-network/platform-core/credits";
|
|
11
11
|
import type { Context } from "hono";
|
|
12
12
|
import { logger } from "../config/logger.js";
|
|
@@ -14,7 +14,7 @@ import { withMargin } from "../monetization/adapters/types.js";
|
|
|
14
14
|
import type { GatewayAuthEnv } from "./service-key-auth.js";
|
|
15
15
|
|
|
16
16
|
export interface CreditGateDeps {
|
|
17
|
-
creditLedger?:
|
|
17
|
+
creditLedger?: ILedger;
|
|
18
18
|
topUpUrl: string;
|
|
19
19
|
/** Maximum negative balance allowed before hard-stop, in cents. Default: 50 (-$0.50). */
|
|
20
20
|
graceBufferCents?: number;
|
|
@@ -118,15 +118,11 @@ export async function debitCredits(
|
|
|
118
118
|
}
|
|
119
119
|
|
|
120
120
|
try {
|
|
121
|
-
await deps.creditLedger.debit(
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
"adapter_usage",
|
|
125
|
-
`Gateway ${capability} via ${provider}`,
|
|
126
|
-
undefined,
|
|
127
|
-
true,
|
|
121
|
+
await deps.creditLedger.debit(tenantId, chargeCredit, "adapter_usage", {
|
|
122
|
+
description: `Gateway ${capability} via ${provider}`,
|
|
123
|
+
allowNegative: true,
|
|
128
124
|
attributedUserId,
|
|
129
|
-
);
|
|
125
|
+
});
|
|
130
126
|
|
|
131
127
|
// Only fire on first zero-crossing (balance was positive before, now ≤ 0)
|
|
132
128
|
if (deps.onBalanceExhausted) {
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* Uses mocked deps — no PGlite required.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import type {
|
|
10
|
+
import type { ILedger } from "@wopr-network/platform-core/credits";
|
|
11
11
|
import { Credit } from "@wopr-network/platform-core/credits";
|
|
12
12
|
import { Hono } from "hono";
|
|
13
13
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
@@ -51,7 +51,7 @@ function buildTestConfig(overrides: Partial<GatewayConfig> = {}): GatewayConfig
|
|
|
51
51
|
debit: vi.fn().mockResolvedValue(undefined),
|
|
52
52
|
credit: vi.fn(),
|
|
53
53
|
history: vi.fn(),
|
|
54
|
-
} as unknown as
|
|
54
|
+
} as unknown as ILedger;
|
|
55
55
|
const fetchFn = vi.fn().mockResolvedValue(
|
|
56
56
|
new Response(
|
|
57
57
|
JSON.stringify({
|
|
@@ -163,7 +163,7 @@ describe("gateway routes — credit check", () => {
|
|
|
163
163
|
debit: vi.fn(),
|
|
164
164
|
credit: vi.fn(),
|
|
165
165
|
history: vi.fn(),
|
|
166
|
-
} as unknown as
|
|
166
|
+
} as unknown as ILedger,
|
|
167
167
|
});
|
|
168
168
|
const app = buildApp(config);
|
|
169
169
|
const res = await chatRequest(app, VALID_KEY);
|
|
@@ -183,7 +183,7 @@ describe("gateway routes — credit check", () => {
|
|
|
183
183
|
debit: vi.fn(),
|
|
184
184
|
credit: vi.fn(),
|
|
185
185
|
history: vi.fn(),
|
|
186
|
-
} as unknown as
|
|
186
|
+
} as unknown as ILedger,
|
|
187
187
|
});
|
|
188
188
|
const app = buildApp(config);
|
|
189
189
|
const res = await chatRequest(app, VALID_KEY);
|
|
@@ -200,7 +200,7 @@ describe("gateway routes — credit check", () => {
|
|
|
200
200
|
debit: vi.fn(),
|
|
201
201
|
credit: vi.fn(),
|
|
202
202
|
history: vi.fn(),
|
|
203
|
-
} as unknown as
|
|
203
|
+
} as unknown as ILedger,
|
|
204
204
|
fetchFn,
|
|
205
205
|
});
|
|
206
206
|
const app = buildApp(config);
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* budget checking, metering, provider configs, fetch, and service key resolution.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import type { Credit,
|
|
8
|
+
import type { Credit, ILedger } from "@wopr-network/platform-core/credits";
|
|
9
9
|
import type { MeterEmitter } from "@wopr-network/platform-core/metering";
|
|
10
10
|
import type { IRateLimitRepository } from "../../api/rate-limit-repository.js";
|
|
11
11
|
import type { IBudgetChecker } from "../../monetization/budget/budget-checker.js";
|
|
@@ -18,7 +18,7 @@ import type { FetchFn, GatewayTenant, ProviderConfig } from "../types.js";
|
|
|
18
18
|
export interface ProtocolDeps {
|
|
19
19
|
meter: MeterEmitter;
|
|
20
20
|
budgetChecker: IBudgetChecker;
|
|
21
|
-
creditLedger?:
|
|
21
|
+
creditLedger?: ILedger;
|
|
22
22
|
topUpUrl: string;
|
|
23
23
|
graceBufferCents?: number;
|
|
24
24
|
providers: ProviderConfig;
|