@wopr-network/platform-core 1.14.7 → 1.15.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.
- package/.env.example +10 -0
- package/dist/account/deletion-executor-repository.d.ts +2 -2
- package/dist/account/deletion-executor-repository.js +5 -5
- package/dist/{monetization/payram → billing/crypto}/cents-credits-boundary.test.js +14 -17
- package/dist/billing/{payram → crypto}/charge-store.d.ts +17 -13
- package/dist/billing/{payram → crypto}/charge-store.js +21 -18
- package/dist/billing/crypto/charge-store.test.js +64 -0
- package/dist/billing/crypto/checkout.d.ts +18 -0
- package/dist/billing/crypto/checkout.js +35 -0
- package/dist/billing/crypto/checkout.test.js +71 -0
- package/dist/billing/crypto/client.d.ts +39 -0
- package/dist/billing/crypto/client.js +72 -0
- package/dist/billing/crypto/client.test.js +100 -0
- package/dist/billing/crypto/index.d.ts +9 -0
- package/dist/billing/crypto/index.js +5 -0
- package/dist/billing/crypto/types.d.ts +61 -0
- package/dist/billing/crypto/types.js +24 -0
- package/dist/billing/crypto/webhook.d.ts +34 -0
- package/dist/billing/crypto/webhook.js +107 -0
- package/dist/billing/crypto/webhook.test.js +266 -0
- package/dist/billing/index.d.ts +1 -1
- package/dist/billing/index.js +2 -2
- package/dist/billing/payment-processor.d.ts +3 -3
- package/dist/config/provider-endpoints.d.ts +4 -0
- package/dist/config/provider-endpoints.js +10 -6
- package/dist/credits/credit-ledger.d.ts +3 -3
- package/dist/credits/credit-ledger.js +3 -3
- package/dist/db/index.d.ts +1 -1
- package/dist/db/index.js +1 -1
- package/dist/db/schema/credits.js +1 -1
- package/dist/db/schema/{payram.d.ts → crypto.d.ts} +17 -13
- package/dist/db/schema/crypto.js +25 -0
- package/dist/db/schema/index.d.ts +1 -1
- package/dist/db/schema/index.js +1 -1
- package/dist/fleet/node-repository.d.ts +1 -3
- package/dist/monetization/crypto/__tests__/webhook.test.js +249 -0
- package/dist/monetization/crypto/index.d.ts +4 -0
- package/dist/monetization/crypto/index.js +2 -0
- package/dist/monetization/crypto/webhook.d.ts +24 -0
- package/dist/monetization/crypto/webhook.js +88 -0
- package/dist/monetization/index.d.ts +3 -3
- package/dist/monetization/index.js +1 -1
- package/dist/monetization/repository-types.d.ts +1 -1
- package/dist/observability/pagerduty.test.js +1 -0
- package/dist/security/key-validation.test.js +65 -8
- package/drizzle/migrations/0004_crypto_charges.sql +25 -0
- package/drizzle/migrations/meta/_journal.json +7 -0
- package/package.json +1 -3
- package/src/account/deletion-executor-repository.ts +6 -6
- package/src/billing/{payram → crypto}/cents-credits-boundary.test.ts +14 -17
- package/src/billing/crypto/charge-store.test.ts +81 -0
- package/src/billing/{payram → crypto}/charge-store.ts +28 -25
- package/src/billing/crypto/checkout.test.ts +93 -0
- package/src/billing/crypto/checkout.ts +48 -0
- package/src/billing/crypto/client.test.ts +132 -0
- package/src/billing/crypto/client.ts +86 -0
- package/src/billing/crypto/index.ts +15 -0
- package/src/billing/crypto/types.ts +83 -0
- package/src/billing/crypto/webhook.test.ts +340 -0
- package/src/billing/crypto/webhook.ts +136 -0
- package/src/billing/index.ts +2 -2
- package/src/billing/payment-processor.ts +3 -3
- package/src/config/provider-endpoints.ts +10 -6
- package/src/credits/credit-ledger.ts +3 -3
- package/src/db/index.ts +1 -2
- package/src/db/schema/credits.ts +1 -1
- package/src/db/schema/crypto.ts +30 -0
- package/src/db/schema/index.ts +1 -1
- package/src/fleet/node-repository.ts +8 -3
- package/src/monetization/crypto/__tests__/webhook.test.ts +327 -0
- package/src/monetization/crypto/index.ts +23 -0
- package/src/monetization/crypto/webhook.ts +115 -0
- package/src/monetization/index.ts +23 -21
- package/src/monetization/repository-types.ts +2 -2
- package/src/observability/pagerduty.test.ts +1 -0
- package/src/security/key-validation.test.ts +74 -8
- package/dist/billing/payram/cents-credits-boundary.test.js +0 -75
- package/dist/billing/payram/charge-store.test.js +0 -64
- package/dist/billing/payram/checkout.d.ts +0 -15
- package/dist/billing/payram/checkout.js +0 -24
- package/dist/billing/payram/checkout.test.js +0 -74
- package/dist/billing/payram/client.d.ts +0 -7
- package/dist/billing/payram/client.js +0 -15
- package/dist/billing/payram/client.test.js +0 -52
- package/dist/billing/payram/index.d.ts +0 -8
- package/dist/billing/payram/index.js +0 -4
- package/dist/billing/payram/types.d.ts +0 -40
- package/dist/billing/payram/webhook.d.ts +0 -19
- package/dist/billing/payram/webhook.js +0 -71
- package/dist/billing/payram/webhook.test.d.ts +0 -7
- package/dist/billing/payram/webhook.test.js +0 -249
- package/dist/db/schema/payram.js +0 -21
- package/dist/monetization/payram/charge-store.test.d.ts +0 -1
- package/dist/monetization/payram/charge-store.test.js +0 -64
- package/dist/monetization/payram/checkout.test.d.ts +0 -1
- package/dist/monetization/payram/checkout.test.js +0 -73
- package/dist/monetization/payram/client.test.d.ts +0 -1
- package/dist/monetization/payram/client.test.js +0 -52
- package/dist/monetization/payram/index.d.ts +0 -4
- package/dist/monetization/payram/index.js +0 -2
- package/dist/monetization/payram/webhook.d.ts +0 -17
- package/dist/monetization/payram/webhook.js +0 -71
- package/dist/monetization/payram/webhook.test.d.ts +0 -7
- package/dist/monetization/payram/webhook.test.js +0 -247
- package/src/billing/payram/charge-store.test.ts +0 -84
- package/src/billing/payram/checkout.test.ts +0 -99
- package/src/billing/payram/checkout.ts +0 -40
- package/src/billing/payram/client.test.ts +0 -62
- package/src/billing/payram/client.ts +0 -21
- package/src/billing/payram/index.ts +0 -14
- package/src/billing/payram/types.ts +0 -44
- package/src/billing/payram/webhook.test.ts +0 -320
- package/src/billing/payram/webhook.ts +0 -94
- package/src/db/schema/payram.ts +0 -26
- package/src/monetization/payram/cents-credits-boundary.test.ts +0 -84
- package/src/monetization/payram/charge-store.test.ts +0 -84
- package/src/monetization/payram/checkout.test.ts +0 -98
- package/src/monetization/payram/client.test.ts +0 -62
- package/src/monetization/payram/index.ts +0 -20
- package/src/monetization/payram/webhook.test.ts +0 -327
- package/src/monetization/payram/webhook.ts +0 -97
- /package/dist/billing/{payram → crypto}/cents-credits-boundary.test.d.ts +0 -0
- /package/dist/billing/{payram → crypto}/charge-store.test.d.ts +0 -0
- /package/dist/billing/{payram → crypto}/checkout.test.d.ts +0 -0
- /package/dist/billing/{payram → crypto}/client.test.d.ts +0 -0
- /package/dist/billing/{payram/types.js → crypto/webhook.test.d.ts} +0 -0
- /package/dist/monetization/{payram/cents-credits-boundary.test.d.ts → crypto/__tests__/webhook.test.d.ts} +0 -0
|
@@ -2,5 +2,9 @@ import type { Provider } from "../security/types.js";
|
|
|
2
2
|
/**
|
|
3
3
|
* Base API URLs used to validate provider keys.
|
|
4
4
|
* Centralised here so every consumer references one source of truth.
|
|
5
|
+
*
|
|
6
|
+
* Each URL can be overridden via env var for proxied / air-gapped / self-hosted deployments:
|
|
7
|
+
* ANTHROPIC_API_URL, OPENAI_API_URL, GOOGLE_API_URL,
|
|
8
|
+
* DISCORD_API_URL, ELEVENLABS_API_URL, DEEPGRAM_API_URL
|
|
5
9
|
*/
|
|
6
10
|
export declare const PROVIDER_API_URLS: Record<Provider, string>;
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Base API URLs used to validate provider keys.
|
|
3
3
|
* Centralised here so every consumer references one source of truth.
|
|
4
|
+
*
|
|
5
|
+
* Each URL can be overridden via env var for proxied / air-gapped / self-hosted deployments:
|
|
6
|
+
* ANTHROPIC_API_URL, OPENAI_API_URL, GOOGLE_API_URL,
|
|
7
|
+
* DISCORD_API_URL, ELEVENLABS_API_URL, DEEPGRAM_API_URL
|
|
4
8
|
*/
|
|
5
9
|
export const PROVIDER_API_URLS = {
|
|
6
|
-
anthropic: "https://api.anthropic.com/v1/models",
|
|
7
|
-
openai: "https://api.openai.com/v1/models",
|
|
8
|
-
google: "https://generativelanguage.googleapis.com/v1/models",
|
|
9
|
-
discord: "https://discord.com/api/v10/users/@me",
|
|
10
|
-
elevenlabs: "https://api.elevenlabs.io/v1/user",
|
|
11
|
-
deepgram: "https://api.deepgram.com/v1/projects",
|
|
10
|
+
anthropic: process.env.ANTHROPIC_API_URL || "https://api.anthropic.com/v1/models",
|
|
11
|
+
openai: process.env.OPENAI_API_URL || "https://api.openai.com/v1/models",
|
|
12
|
+
google: process.env.GOOGLE_API_URL || "https://generativelanguage.googleapis.com/v1/models",
|
|
13
|
+
discord: process.env.DISCORD_API_URL || "https://discord.com/api/v10/users/@me",
|
|
14
|
+
elevenlabs: process.env.ELEVENLABS_API_URL || "https://api.elevenlabs.io/v1/user",
|
|
15
|
+
deepgram: process.env.DEEPGRAM_API_URL || "https://api.deepgram.com/v1/projects",
|
|
12
16
|
};
|
|
@@ -2,16 +2,16 @@
|
|
|
2
2
|
* NAMING CONVENTION — cents vs credits (WOP-1058)
|
|
3
3
|
*
|
|
4
4
|
* - `_cents` suffix = value denominated in USD cents.
|
|
5
|
-
* Used for: Stripe amounts,
|
|
5
|
+
* Used for: Stripe amounts, BTCPay amounts, ledger balances, ledger transactions.
|
|
6
6
|
* The platform credit unit IS the USD cent (1 credit = 1 cent = $0.01).
|
|
7
7
|
*
|
|
8
8
|
* - `_credits` suffix = platform credit count (used in DB columns and raw storage).
|
|
9
9
|
* Semantically identical to cents but signals "this is a platform concept stored
|
|
10
10
|
* as a Credit.toRaw() nanodollar value in the database."
|
|
11
11
|
*
|
|
12
|
-
* - NEVER rename a Stripe/
|
|
12
|
+
* - NEVER rename a Stripe/BTCPay-facing `_cents` field to `_credits`.
|
|
13
13
|
* Stripe's `amount` parameter and `amount_total` response are always USD cents.
|
|
14
|
-
*
|
|
14
|
+
* BTCPay's `amountUsdCents` is always USD cents.
|
|
15
15
|
*
|
|
16
16
|
* - When adding new fields: if it touches a payment processor API, use `_cents`.
|
|
17
17
|
* If it's a DB column storing Credit.toRaw() values, use `_credits`.
|
|
@@ -2,16 +2,16 @@
|
|
|
2
2
|
* NAMING CONVENTION — cents vs credits (WOP-1058)
|
|
3
3
|
*
|
|
4
4
|
* - `_cents` suffix = value denominated in USD cents.
|
|
5
|
-
* Used for: Stripe amounts,
|
|
5
|
+
* Used for: Stripe amounts, BTCPay amounts, ledger balances, ledger transactions.
|
|
6
6
|
* The platform credit unit IS the USD cent (1 credit = 1 cent = $0.01).
|
|
7
7
|
*
|
|
8
8
|
* - `_credits` suffix = platform credit count (used in DB columns and raw storage).
|
|
9
9
|
* Semantically identical to cents but signals "this is a platform concept stored
|
|
10
10
|
* as a Credit.toRaw() nanodollar value in the database."
|
|
11
11
|
*
|
|
12
|
-
* - NEVER rename a Stripe/
|
|
12
|
+
* - NEVER rename a Stripe/BTCPay-facing `_cents` field to `_credits`.
|
|
13
13
|
* Stripe's `amount` parameter and `amount_total` response are always USD cents.
|
|
14
|
-
*
|
|
14
|
+
* BTCPay's `amountUsdCents` is always USD cents.
|
|
15
15
|
*
|
|
16
16
|
* - When adding new fields: if it touches a payment processor API, use `_cents`.
|
|
17
17
|
* If it's a DB column storing Credit.toRaw() values, use `_credits`.
|
package/dist/db/index.d.ts
CHANGED
|
@@ -14,10 +14,10 @@ export type DrizzleDb = PgDatabase<PgQueryResultHKT, PlatformSchema>;
|
|
|
14
14
|
export type PlatformDb = DrizzleDb;
|
|
15
15
|
/** Create a Drizzle database instance wrapping the given pg.Pool. */
|
|
16
16
|
export declare function createDb(pool: Pool): PlatformDb;
|
|
17
|
-
export { schema };
|
|
18
17
|
export type { SQL } from "drizzle-orm";
|
|
19
18
|
export { and, asc, count, desc, eq, gt, gte, ilike, inArray, isNull, like, lt, lte, ne, or, sql } from "drizzle-orm";
|
|
20
19
|
export { pgTable, text } from "drizzle-orm/pg-core";
|
|
21
20
|
export type { AuthUser, IAuthUserRepository } from "./auth-user-repository.js";
|
|
22
21
|
export { BetterAuthUserRepository } from "./auth-user-repository.js";
|
|
23
22
|
export { creditColumn } from "./credit-column.js";
|
|
23
|
+
export { schema };
|
package/dist/db/index.js
CHANGED
|
@@ -4,7 +4,6 @@ import * as schema from "./schema/index.js";
|
|
|
4
4
|
export function createDb(pool) {
|
|
5
5
|
return drizzle(pool, { schema });
|
|
6
6
|
}
|
|
7
|
-
export { schema };
|
|
8
7
|
// Re-export commonly used drizzle-orm operators so consumers using pnpm link
|
|
9
8
|
// resolve them from the same drizzle-orm instance as the schema tables.
|
|
10
9
|
export { and, asc, count, desc, eq, gt, gte, ilike, inArray, isNull, like, lt, lte, ne, or, sql } from "drizzle-orm";
|
|
@@ -12,3 +11,4 @@ export { and, asc, count, desc, eq, gt, gte, ilike, inArray, isNull, like, lt, l
|
|
|
12
11
|
export { pgTable, text } from "drizzle-orm/pg-core";
|
|
13
12
|
export { BetterAuthUserRepository } from "./auth-user-repository.js";
|
|
14
13
|
export { creditColumn } from "./credit-column.js";
|
|
14
|
+
export { schema };
|
|
@@ -13,7 +13,7 @@ export const creditTransactions = pgTable("credit_transactions", {
|
|
|
13
13
|
type: text("type").notNull(), // signup_grant | purchase | bounty | referral | promo | community_dividend | bot_runtime | adapter_usage | addon | refund | correction
|
|
14
14
|
description: text("description"),
|
|
15
15
|
referenceId: text("reference_id").unique(),
|
|
16
|
-
fundingSource: text("funding_source"), // "stripe" | "
|
|
16
|
+
fundingSource: text("funding_source"), // "stripe" | "crypto" | null (null = legacy/signup)
|
|
17
17
|
attributedUserId: text("attributed_user_id"), // nullable — null for system/bot charges
|
|
18
18
|
createdAt: text("created_at").notNull().default(sql `(now())`),
|
|
19
19
|
expiresAt: text("expires_at"), // nullable — null means never expires
|
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* reference_id is the
|
|
2
|
+
* Crypto payment charges — tracks the lifecycle of each BTCPay invoice.
|
|
3
|
+
* reference_id is the BTCPay invoice ID.
|
|
4
|
+
*
|
|
5
|
+
* amountUsdCents stores the requested amount in USD cents (integer).
|
|
6
|
+
* This is NOT nanodollars — Credit.fromCents() handles the conversion
|
|
7
|
+
* when crediting the ledger in the webhook handler.
|
|
4
8
|
*/
|
|
5
|
-
export declare const
|
|
6
|
-
name: "
|
|
9
|
+
export declare const cryptoCharges: import("drizzle-orm/pg-core").PgTableWithColumns<{
|
|
10
|
+
name: "crypto_charges";
|
|
7
11
|
schema: undefined;
|
|
8
12
|
columns: {
|
|
9
13
|
referenceId: import("drizzle-orm/pg-core").PgColumn<{
|
|
10
14
|
name: "reference_id";
|
|
11
|
-
tableName: "
|
|
15
|
+
tableName: "crypto_charges";
|
|
12
16
|
dataType: "string";
|
|
13
17
|
columnType: "PgText";
|
|
14
18
|
data: string;
|
|
@@ -25,7 +29,7 @@ export declare const payramCharges: import("drizzle-orm/pg-core").PgTableWithCol
|
|
|
25
29
|
}, {}, {}>;
|
|
26
30
|
tenantId: import("drizzle-orm/pg-core").PgColumn<{
|
|
27
31
|
name: "tenant_id";
|
|
28
|
-
tableName: "
|
|
32
|
+
tableName: "crypto_charges";
|
|
29
33
|
dataType: "string";
|
|
30
34
|
columnType: "PgText";
|
|
31
35
|
data: string;
|
|
@@ -42,7 +46,7 @@ export declare const payramCharges: import("drizzle-orm/pg-core").PgTableWithCol
|
|
|
42
46
|
}, {}, {}>;
|
|
43
47
|
amountUsdCents: import("drizzle-orm/pg-core").PgColumn<{
|
|
44
48
|
name: "amount_usd_cents";
|
|
45
|
-
tableName: "
|
|
49
|
+
tableName: "crypto_charges";
|
|
46
50
|
dataType: "number";
|
|
47
51
|
columnType: "PgInteger";
|
|
48
52
|
data: number;
|
|
@@ -59,7 +63,7 @@ export declare const payramCharges: import("drizzle-orm/pg-core").PgTableWithCol
|
|
|
59
63
|
}, {}, {}>;
|
|
60
64
|
status: import("drizzle-orm/pg-core").PgColumn<{
|
|
61
65
|
name: "status";
|
|
62
|
-
tableName: "
|
|
66
|
+
tableName: "crypto_charges";
|
|
63
67
|
dataType: "string";
|
|
64
68
|
columnType: "PgText";
|
|
65
69
|
data: string;
|
|
@@ -76,7 +80,7 @@ export declare const payramCharges: import("drizzle-orm/pg-core").PgTableWithCol
|
|
|
76
80
|
}, {}, {}>;
|
|
77
81
|
currency: import("drizzle-orm/pg-core").PgColumn<{
|
|
78
82
|
name: "currency";
|
|
79
|
-
tableName: "
|
|
83
|
+
tableName: "crypto_charges";
|
|
80
84
|
dataType: "string";
|
|
81
85
|
columnType: "PgText";
|
|
82
86
|
data: string;
|
|
@@ -93,7 +97,7 @@ export declare const payramCharges: import("drizzle-orm/pg-core").PgTableWithCol
|
|
|
93
97
|
}, {}, {}>;
|
|
94
98
|
filledAmount: import("drizzle-orm/pg-core").PgColumn<{
|
|
95
99
|
name: "filled_amount";
|
|
96
|
-
tableName: "
|
|
100
|
+
tableName: "crypto_charges";
|
|
97
101
|
dataType: "string";
|
|
98
102
|
columnType: "PgText";
|
|
99
103
|
data: string;
|
|
@@ -110,7 +114,7 @@ export declare const payramCharges: import("drizzle-orm/pg-core").PgTableWithCol
|
|
|
110
114
|
}, {}, {}>;
|
|
111
115
|
createdAt: import("drizzle-orm/pg-core").PgColumn<{
|
|
112
116
|
name: "created_at";
|
|
113
|
-
tableName: "
|
|
117
|
+
tableName: "crypto_charges";
|
|
114
118
|
dataType: "string";
|
|
115
119
|
columnType: "PgText";
|
|
116
120
|
data: string;
|
|
@@ -127,7 +131,7 @@ export declare const payramCharges: import("drizzle-orm/pg-core").PgTableWithCol
|
|
|
127
131
|
}, {}, {}>;
|
|
128
132
|
updatedAt: import("drizzle-orm/pg-core").PgColumn<{
|
|
129
133
|
name: "updated_at";
|
|
130
|
-
tableName: "
|
|
134
|
+
tableName: "crypto_charges";
|
|
131
135
|
dataType: "string";
|
|
132
136
|
columnType: "PgText";
|
|
133
137
|
data: string;
|
|
@@ -144,7 +148,7 @@ export declare const payramCharges: import("drizzle-orm/pg-core").PgTableWithCol
|
|
|
144
148
|
}, {}, {}>;
|
|
145
149
|
creditedAt: import("drizzle-orm/pg-core").PgColumn<{
|
|
146
150
|
name: "credited_at";
|
|
147
|
-
tableName: "
|
|
151
|
+
tableName: "crypto_charges";
|
|
148
152
|
dataType: "string";
|
|
149
153
|
columnType: "PgText";
|
|
150
154
|
data: string;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { sql } from "drizzle-orm";
|
|
2
|
+
import { index, integer, pgTable, text } from "drizzle-orm/pg-core";
|
|
3
|
+
/**
|
|
4
|
+
* Crypto payment charges — tracks the lifecycle of each BTCPay invoice.
|
|
5
|
+
* reference_id is the BTCPay invoice ID.
|
|
6
|
+
*
|
|
7
|
+
* amountUsdCents stores the requested amount in USD cents (integer).
|
|
8
|
+
* This is NOT nanodollars — Credit.fromCents() handles the conversion
|
|
9
|
+
* when crediting the ledger in the webhook handler.
|
|
10
|
+
*/
|
|
11
|
+
export const cryptoCharges = pgTable("crypto_charges", {
|
|
12
|
+
referenceId: text("reference_id").primaryKey(),
|
|
13
|
+
tenantId: text("tenant_id").notNull(),
|
|
14
|
+
amountUsdCents: integer("amount_usd_cents").notNull(),
|
|
15
|
+
status: text("status").notNull().default("New"),
|
|
16
|
+
currency: text("currency"),
|
|
17
|
+
filledAmount: text("filled_amount"),
|
|
18
|
+
createdAt: text("created_at").notNull().default(sql `(now())`),
|
|
19
|
+
updatedAt: text("updated_at").notNull().default(sql `(now())`),
|
|
20
|
+
creditedAt: text("credited_at"),
|
|
21
|
+
}, (table) => [
|
|
22
|
+
index("idx_crypto_charges_tenant").on(table.tenantId),
|
|
23
|
+
index("idx_crypto_charges_status").on(table.status),
|
|
24
|
+
index("idx_crypto_charges_created").on(table.createdAt),
|
|
25
|
+
]);
|
|
@@ -16,6 +16,7 @@ export * from "./coupon-codes.js";
|
|
|
16
16
|
export * from "./credit-auto-topup.js";
|
|
17
17
|
export * from "./credit-auto-topup-settings.js";
|
|
18
18
|
export * from "./credits.js";
|
|
19
|
+
export * from "./crypto.js";
|
|
19
20
|
export * from "./dividend-distributions.js";
|
|
20
21
|
export * from "./email-notifications.js";
|
|
21
22
|
export * from "./fleet-event-history.js";
|
|
@@ -39,7 +40,6 @@ export * from "./onboarding-sessions.js";
|
|
|
39
40
|
export * from "./org-memberships.js";
|
|
40
41
|
export * from "./organization-members.js";
|
|
41
42
|
export * from "./page-contexts.js";
|
|
42
|
-
export * from "./payram.js";
|
|
43
43
|
export * from "./platform-api-keys.js";
|
|
44
44
|
export * from "./plugin-configs.js";
|
|
45
45
|
export * from "./plugin-marketplace-content.js";
|
package/dist/db/schema/index.js
CHANGED
|
@@ -16,6 +16,7 @@ export * from "./coupon-codes.js";
|
|
|
16
16
|
export * from "./credit-auto-topup.js";
|
|
17
17
|
export * from "./credit-auto-topup-settings.js";
|
|
18
18
|
export * from "./credits.js";
|
|
19
|
+
export * from "./crypto.js";
|
|
19
20
|
export * from "./dividend-distributions.js";
|
|
20
21
|
export * from "./email-notifications.js";
|
|
21
22
|
export * from "./fleet-event-history.js";
|
|
@@ -39,7 +40,6 @@ export * from "./onboarding-sessions.js";
|
|
|
39
40
|
export * from "./org-memberships.js";
|
|
40
41
|
export * from "./organization-members.js";
|
|
41
42
|
export * from "./page-contexts.js";
|
|
42
|
-
export * from "./payram.js";
|
|
43
43
|
export * from "./platform-api-keys.js";
|
|
44
44
|
export * from "./plugin-configs.js";
|
|
45
45
|
export * from "./plugin-marketplace-content.js";
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import type { NodeStatus } from "./node-state-machine.js";
|
|
2
2
|
import type { NewProvisioningNode, Node, NodeRegistration, NodeTransition, ProvisionDataUpdate, SelfHostedNodeRegistration } from "./repository-types.js";
|
|
3
|
-
export type { Node, NodeTransition };
|
|
4
|
-
export type { NodeRegistration, SelfHostedNodeRegistration };
|
|
5
|
-
export type { NewProvisioningNode, ProvisionDataUpdate };
|
|
3
|
+
export type { NewProvisioningNode, Node, NodeRegistration, NodeTransition, ProvisionDataUpdate, SelfHostedNodeRegistration, };
|
|
6
4
|
export interface INodeRepository {
|
|
7
5
|
getById(id: string): Promise<Node | null>;
|
|
8
6
|
getBySecret(secret: string): Promise<Node | null>;
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { CryptoChargeRepository } from "../../../billing/crypto/charge-store.js";
|
|
3
|
+
import { DrizzleWebhookSeenRepository } from "../../../billing/drizzle-webhook-seen-repository.js";
|
|
4
|
+
import { noOpReplayGuard } from "../../../billing/webhook-seen-repository.js";
|
|
5
|
+
import { DrizzleLedger } from "../../../credits/ledger.js";
|
|
6
|
+
import { createTestDb, truncateAllTables } from "../../../test/db.js";
|
|
7
|
+
import { handleCryptoWebhook } from "../webhook.js";
|
|
8
|
+
function makePayload(overrides = {}) {
|
|
9
|
+
return {
|
|
10
|
+
deliveryId: "del-001",
|
|
11
|
+
webhookId: "whk-001",
|
|
12
|
+
originalDeliveryId: "del-001",
|
|
13
|
+
isRedelivery: false,
|
|
14
|
+
type: "InvoiceSettled",
|
|
15
|
+
timestamp: Date.now(),
|
|
16
|
+
storeId: "store-test",
|
|
17
|
+
invoiceId: "inv-test-001",
|
|
18
|
+
metadata: { orderId: "order-001" },
|
|
19
|
+
...overrides,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
let pool;
|
|
23
|
+
let db;
|
|
24
|
+
beforeAll(async () => {
|
|
25
|
+
({ db, pool } = await createTestDb());
|
|
26
|
+
});
|
|
27
|
+
afterAll(async () => {
|
|
28
|
+
await pool.close();
|
|
29
|
+
});
|
|
30
|
+
describe("handleCryptoWebhook (monetization layer)", () => {
|
|
31
|
+
let chargeStore;
|
|
32
|
+
let creditLedger;
|
|
33
|
+
let deps;
|
|
34
|
+
beforeEach(async () => {
|
|
35
|
+
await truncateAllTables(pool);
|
|
36
|
+
chargeStore = new CryptoChargeRepository(db);
|
|
37
|
+
creditLedger = new DrizzleLedger(db);
|
|
38
|
+
await creditLedger.seedSystemAccounts();
|
|
39
|
+
deps = { chargeStore, creditLedger, replayGuard: noOpReplayGuard };
|
|
40
|
+
// Default test charge: $25 = 2500 cents
|
|
41
|
+
await chargeStore.create("inv-test-001", "tenant-a", 2500);
|
|
42
|
+
});
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
// InvoiceSettled — credits ledger
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
describe("InvoiceSettled", () => {
|
|
47
|
+
it("credits the ledger with the USD amount in cents", async () => {
|
|
48
|
+
const result = await handleCryptoWebhook(deps, makePayload({ type: "InvoiceSettled" }));
|
|
49
|
+
expect(result.handled).toBe(true);
|
|
50
|
+
expect(result.status).toBe("Settled");
|
|
51
|
+
expect(result.tenant).toBe("tenant-a");
|
|
52
|
+
expect(result.creditedCents).toBe(2500);
|
|
53
|
+
const balance = await creditLedger.balance("tenant-a");
|
|
54
|
+
expect(balance.toCents()).toBe(2500);
|
|
55
|
+
});
|
|
56
|
+
it("marks the charge as credited after settlement", async () => {
|
|
57
|
+
await handleCryptoWebhook(deps, makePayload({ type: "InvoiceSettled" }));
|
|
58
|
+
expect(await chargeStore.isCredited("inv-test-001")).toBe(true);
|
|
59
|
+
});
|
|
60
|
+
it("uses crypto: prefix on reference ID in ledger entry", async () => {
|
|
61
|
+
await handleCryptoWebhook(deps, makePayload({ type: "InvoiceSettled" }));
|
|
62
|
+
const history = await creditLedger.history("tenant-a");
|
|
63
|
+
expect(history).toHaveLength(1);
|
|
64
|
+
expect(history[0].referenceId).toBe("crypto:inv-test-001");
|
|
65
|
+
expect(history[0].entryType).toBe("purchase");
|
|
66
|
+
});
|
|
67
|
+
it("records fundingSource as crypto", async () => {
|
|
68
|
+
await handleCryptoWebhook(deps, makePayload({ type: "InvoiceSettled" }));
|
|
69
|
+
const history = await creditLedger.history("tenant-a");
|
|
70
|
+
expect(history[0].metadata?.fundingSource).toBe("crypto");
|
|
71
|
+
});
|
|
72
|
+
it("is idempotent — second InvoiceSettled does not double-credit", async () => {
|
|
73
|
+
await handleCryptoWebhook(deps, makePayload({ type: "InvoiceSettled" }));
|
|
74
|
+
const result2 = await handleCryptoWebhook(deps, makePayload({ type: "InvoiceSettled" }));
|
|
75
|
+
expect(result2.handled).toBe(true);
|
|
76
|
+
expect(result2.creditedCents).toBe(0);
|
|
77
|
+
// Balance is still $25, not $50
|
|
78
|
+
const balance = await creditLedger.balance("tenant-a");
|
|
79
|
+
expect(balance.toCents()).toBe(2500);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
// ---------------------------------------------------------------------------
|
|
83
|
+
// Non-settlement event types — no ledger credit
|
|
84
|
+
// ---------------------------------------------------------------------------
|
|
85
|
+
describe("InvoiceProcessing", () => {
|
|
86
|
+
it("does NOT credit the ledger", async () => {
|
|
87
|
+
const result = await handleCryptoWebhook(deps, makePayload({ type: "InvoiceProcessing" }));
|
|
88
|
+
expect(result.handled).toBe(true);
|
|
89
|
+
expect(result.tenant).toBe("tenant-a");
|
|
90
|
+
expect(result.creditedCents).toBeUndefined();
|
|
91
|
+
expect((await creditLedger.balance("tenant-a")).toCents()).toBe(0);
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
describe("InvoiceCreated", () => {
|
|
95
|
+
it("does NOT credit the ledger", async () => {
|
|
96
|
+
const result = await handleCryptoWebhook(deps, makePayload({ type: "InvoiceCreated" }));
|
|
97
|
+
expect(result.handled).toBe(true);
|
|
98
|
+
expect(result.creditedCents).toBeUndefined();
|
|
99
|
+
expect((await creditLedger.balance("tenant-a")).toCents()).toBe(0);
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
describe("InvoiceExpired", () => {
|
|
103
|
+
it("does NOT credit the ledger", async () => {
|
|
104
|
+
const result = await handleCryptoWebhook(deps, makePayload({ type: "InvoiceExpired" }));
|
|
105
|
+
expect(result.handled).toBe(true);
|
|
106
|
+
expect(result.creditedCents).toBeUndefined();
|
|
107
|
+
expect((await creditLedger.balance("tenant-a")).toCents()).toBe(0);
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
describe("InvoiceInvalid", () => {
|
|
111
|
+
it("does NOT credit the ledger", async () => {
|
|
112
|
+
const result = await handleCryptoWebhook(deps, makePayload({ type: "InvoiceInvalid" }));
|
|
113
|
+
expect(result.handled).toBe(true);
|
|
114
|
+
expect(result.creditedCents).toBeUndefined();
|
|
115
|
+
expect((await creditLedger.balance("tenant-a")).toCents()).toBe(0);
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
// ---------------------------------------------------------------------------
|
|
119
|
+
// Unknown invoiceId — returns handled:false
|
|
120
|
+
// ---------------------------------------------------------------------------
|
|
121
|
+
describe("missing charge", () => {
|
|
122
|
+
it("returns handled:false when invoiceId is not in the charge store", async () => {
|
|
123
|
+
const result = await handleCryptoWebhook(deps, makePayload({ invoiceId: "inv-unknown-999" }));
|
|
124
|
+
expect(result.handled).toBe(false);
|
|
125
|
+
expect(result.tenant).toBeUndefined();
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
// ---------------------------------------------------------------------------
|
|
129
|
+
// Status mapping for all known event types
|
|
130
|
+
// ---------------------------------------------------------------------------
|
|
131
|
+
describe("status mapping", () => {
|
|
132
|
+
it.each([
|
|
133
|
+
["InvoiceCreated", "New"],
|
|
134
|
+
["InvoiceProcessing", "Processing"],
|
|
135
|
+
["InvoiceReceivedPayment", "Processing"],
|
|
136
|
+
["InvoiceSettled", "Settled"],
|
|
137
|
+
["InvoicePaymentSettled", "Settled"],
|
|
138
|
+
["InvoiceExpired", "Expired"],
|
|
139
|
+
["InvoiceInvalid", "Invalid"],
|
|
140
|
+
])("maps %s event to %s status", async (eventType, expectedStatus) => {
|
|
141
|
+
const result = await handleCryptoWebhook(deps, makePayload({ type: eventType }));
|
|
142
|
+
expect(result.status).toBe(expectedStatus);
|
|
143
|
+
});
|
|
144
|
+
it("throws on unknown event types", async () => {
|
|
145
|
+
await expect(handleCryptoWebhook(deps, makePayload({ type: "InvoiceSomeUnknownEvent" }))).rejects.toThrow("Unknown BTCPay event type: InvoiceSomeUnknownEvent");
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
// ---------------------------------------------------------------------------
|
|
149
|
+
// Charge store status updates
|
|
150
|
+
// ---------------------------------------------------------------------------
|
|
151
|
+
describe("charge store updates", () => {
|
|
152
|
+
it("updates charge status on every webhook call", async () => {
|
|
153
|
+
await handleCryptoWebhook(deps, makePayload({ type: "InvoiceProcessing" }));
|
|
154
|
+
const charge = await chargeStore.getByReferenceId("inv-test-001");
|
|
155
|
+
expect(charge?.status).toBe("Processing");
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
// ---------------------------------------------------------------------------
|
|
159
|
+
// Replay guard / idempotency
|
|
160
|
+
// ---------------------------------------------------------------------------
|
|
161
|
+
describe("replay guard", () => {
|
|
162
|
+
it("blocks duplicate invoiceId + event type combinations", async () => {
|
|
163
|
+
const replayGuard = new DrizzleWebhookSeenRepository(db);
|
|
164
|
+
const depsWithGuard = { ...deps, replayGuard };
|
|
165
|
+
const first = await handleCryptoWebhook(depsWithGuard, makePayload({ type: "InvoiceSettled" }));
|
|
166
|
+
expect(first.handled).toBe(true);
|
|
167
|
+
expect(first.creditedCents).toBe(2500);
|
|
168
|
+
expect(first.duplicate).toBeUndefined();
|
|
169
|
+
const second = await handleCryptoWebhook(depsWithGuard, makePayload({ type: "InvoiceSettled" }));
|
|
170
|
+
expect(second.handled).toBe(true);
|
|
171
|
+
expect(second.duplicate).toBe(true);
|
|
172
|
+
expect(second.creditedCents).toBeUndefined();
|
|
173
|
+
// Balance is still $25, not $50
|
|
174
|
+
expect((await creditLedger.balance("tenant-a")).toCents()).toBe(2500);
|
|
175
|
+
});
|
|
176
|
+
it("same invoice with a different event type is not blocked", async () => {
|
|
177
|
+
const replayGuard = new DrizzleWebhookSeenRepository(db);
|
|
178
|
+
const depsWithGuard = { ...deps, replayGuard };
|
|
179
|
+
await handleCryptoWebhook(depsWithGuard, makePayload({ type: "InvoiceProcessing" }));
|
|
180
|
+
const result = await handleCryptoWebhook(depsWithGuard, makePayload({ type: "InvoiceSettled" }));
|
|
181
|
+
expect(result.duplicate).toBeUndefined();
|
|
182
|
+
expect(result.creditedCents).toBe(2500);
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
// ---------------------------------------------------------------------------
|
|
186
|
+
// BotBilling reactivation — WOPR-specific behaviour
|
|
187
|
+
// ---------------------------------------------------------------------------
|
|
188
|
+
describe("BotBilling reactivation", () => {
|
|
189
|
+
it("calls botBilling.checkReactivation on InvoiceSettled and returns reactivatedBots", async () => {
|
|
190
|
+
const mockBotBilling = {
|
|
191
|
+
checkReactivation: vi.fn().mockResolvedValue(["bot-1", "bot-2"]),
|
|
192
|
+
};
|
|
193
|
+
const depsWithBots = { ...deps, botBilling: mockBotBilling };
|
|
194
|
+
const result = await handleCryptoWebhook(depsWithBots, makePayload({ type: "InvoiceSettled" }));
|
|
195
|
+
expect(mockBotBilling.checkReactivation).toHaveBeenCalledWith("tenant-a", creditLedger);
|
|
196
|
+
expect(result.reactivatedBots).toEqual(["bot-1", "bot-2"]);
|
|
197
|
+
});
|
|
198
|
+
it("omits reactivatedBots when no bots are reactivated", async () => {
|
|
199
|
+
const mockBotBilling = {
|
|
200
|
+
checkReactivation: vi.fn().mockResolvedValue([]),
|
|
201
|
+
};
|
|
202
|
+
const depsWithBots = { ...deps, botBilling: mockBotBilling };
|
|
203
|
+
const result = await handleCryptoWebhook(depsWithBots, makePayload({ type: "InvoiceSettled" }));
|
|
204
|
+
expect(result.reactivatedBots).toBeUndefined();
|
|
205
|
+
});
|
|
206
|
+
it("does NOT call botBilling on non-settled events", async () => {
|
|
207
|
+
const mockBotBilling = {
|
|
208
|
+
checkReactivation: vi.fn().mockResolvedValue(["bot-1"]),
|
|
209
|
+
};
|
|
210
|
+
const depsWithBots = { ...deps, botBilling: mockBotBilling };
|
|
211
|
+
await handleCryptoWebhook(depsWithBots, makePayload({ type: "InvoiceProcessing" }));
|
|
212
|
+
expect(mockBotBilling.checkReactivation).not.toHaveBeenCalled();
|
|
213
|
+
});
|
|
214
|
+
it("does NOT call botBilling when charge is already credited (idempotency path)", async () => {
|
|
215
|
+
const mockBotBilling = {
|
|
216
|
+
checkReactivation: vi.fn().mockResolvedValue(["bot-1"]),
|
|
217
|
+
};
|
|
218
|
+
const depsWithBots = { ...deps, botBilling: mockBotBilling };
|
|
219
|
+
// First settlement — should call reactivation
|
|
220
|
+
await handleCryptoWebhook(depsWithBots, makePayload({ type: "InvoiceSettled" }));
|
|
221
|
+
expect(mockBotBilling.checkReactivation).toHaveBeenCalledTimes(1);
|
|
222
|
+
// Second settlement — charge already credited, should NOT call reactivation again
|
|
223
|
+
await handleCryptoWebhook(depsWithBots, makePayload({ type: "InvoiceSettled" }));
|
|
224
|
+
expect(mockBotBilling.checkReactivation).toHaveBeenCalledTimes(1);
|
|
225
|
+
});
|
|
226
|
+
it("operates correctly when botBilling is not provided", async () => {
|
|
227
|
+
// No botBilling dependency — should complete without error
|
|
228
|
+
const result = await handleCryptoWebhook(deps, makePayload({ type: "InvoiceSettled" }));
|
|
229
|
+
expect(result.handled).toBe(true);
|
|
230
|
+
expect(result.creditedCents).toBe(2500);
|
|
231
|
+
expect(result.reactivatedBots).toBeUndefined();
|
|
232
|
+
});
|
|
233
|
+
});
|
|
234
|
+
// ---------------------------------------------------------------------------
|
|
235
|
+
// Multiple tenants — independent processing
|
|
236
|
+
// ---------------------------------------------------------------------------
|
|
237
|
+
describe("multiple tenants", () => {
|
|
238
|
+
it("processes invoices for different tenants independently", async () => {
|
|
239
|
+
await chargeStore.create("inv-b-001", "tenant-b", 5000);
|
|
240
|
+
await chargeStore.create("inv-c-001", "tenant-c", 1500);
|
|
241
|
+
await handleCryptoWebhook(deps, makePayload({ invoiceId: "inv-b-001", type: "InvoiceSettled" }));
|
|
242
|
+
await handleCryptoWebhook(deps, makePayload({ invoiceId: "inv-c-001", type: "InvoiceSettled" }));
|
|
243
|
+
expect((await creditLedger.balance("tenant-b")).toCents()).toBe(5000);
|
|
244
|
+
expect((await creditLedger.balance("tenant-c")).toCents()).toBe(1500);
|
|
245
|
+
// Original tenant-a was not settled in this test
|
|
246
|
+
expect((await creditLedger.balance("tenant-a")).toCents()).toBe(0);
|
|
247
|
+
});
|
|
248
|
+
});
|
|
249
|
+
});
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export type { CryptoBillingConfig, CryptoChargeRecord, CryptoCheckoutOpts, CryptoConfig, CryptoPaymentState, CryptoWebhookPayload, CryptoWebhookResult, ICryptoChargeRepository, } from "@wopr-network/platform-core/billing";
|
|
2
|
+
export { BTCPayClient, CryptoChargeRepository, createCryptoCheckout, DrizzleCryptoChargeRepository, loadCryptoConfig, MIN_PAYMENT_USD, mapBtcPayEventToStatus, verifyCryptoWebhookSignature, } from "@wopr-network/platform-core/billing";
|
|
3
|
+
export type { CryptoWebhookDeps } from "./webhook.js";
|
|
4
|
+
export { handleCryptoWebhook } from "./webhook.js";
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export { BTCPayClient, CryptoChargeRepository, createCryptoCheckout, DrizzleCryptoChargeRepository, loadCryptoConfig, MIN_PAYMENT_USD, mapBtcPayEventToStatus, verifyCryptoWebhookSignature, } from "@wopr-network/platform-core/billing";
|
|
2
|
+
export { handleCryptoWebhook } from "./webhook.js";
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { CryptoWebhookPayload, CryptoWebhookResult, ICryptoChargeRepository, IWebhookSeenRepository } from "@wopr-network/platform-core/billing";
|
|
2
|
+
import type { ILedger } from "@wopr-network/platform-core/credits";
|
|
3
|
+
import type { BotBilling } from "../credits/bot-billing.js";
|
|
4
|
+
export interface CryptoWebhookDeps {
|
|
5
|
+
chargeStore: ICryptoChargeRepository;
|
|
6
|
+
creditLedger: ILedger;
|
|
7
|
+
botBilling?: BotBilling;
|
|
8
|
+
replayGuard: IWebhookSeenRepository;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Process a BTCPay Server webhook event (WOPR-specific version).
|
|
12
|
+
*
|
|
13
|
+
* Only credits the ledger on InvoiceSettled.
|
|
14
|
+
* Uses botBilling.checkReactivation for WOPR bot suspension recovery.
|
|
15
|
+
*
|
|
16
|
+
* Idempotency strategy (matches Stripe webhook pattern):
|
|
17
|
+
* Primary: `creditLedger.hasReferenceId("crypto:<invoiceId>")` — atomic,
|
|
18
|
+
* checked inside the ledger's serialized transaction.
|
|
19
|
+
* Secondary: `chargeStore.markCredited()` — advisory flag for queries.
|
|
20
|
+
*
|
|
21
|
+
* CRITICAL: charge.amountUsdCents is in USD cents (integer).
|
|
22
|
+
* Credit.fromCents() converts cents → nanodollars for the ledger.
|
|
23
|
+
*/
|
|
24
|
+
export declare function handleCryptoWebhook(deps: CryptoWebhookDeps, payload: CryptoWebhookPayload): Promise<CryptoWebhookResult>;
|