@wopr-network/platform-core 1.42.3 → 1.43.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/.github/workflows/key-server-image.yml +35 -0
- package/Dockerfile.key-server +20 -0
- package/GATEWAY_BILLING_RESEARCH.md +430 -0
- package/biome.json +2 -9
- package/dist/billing/crypto/__tests__/key-server.test.d.ts +1 -0
- package/dist/billing/crypto/__tests__/key-server.test.js +225 -0
- package/dist/billing/crypto/client.d.ts +84 -20
- package/dist/billing/crypto/client.js +76 -46
- package/dist/billing/crypto/client.test.js +76 -83
- package/dist/billing/crypto/index.d.ts +4 -2
- package/dist/billing/crypto/index.js +2 -1
- package/dist/billing/crypto/key-server-entry.d.ts +1 -0
- package/dist/billing/crypto/key-server-entry.js +43 -0
- package/dist/billing/crypto/key-server.d.ts +18 -0
- package/dist/billing/crypto/key-server.js +239 -0
- package/dist/db/schema/crypto.d.ts +247 -0
- package/dist/db/schema/crypto.js +35 -4
- package/drizzle/migrations/0014_crypto_key_server.sql +60 -0
- package/drizzle/migrations/meta/_journal.json +21 -0
- package/package.json +2 -1
- package/src/billing/crypto/__tests__/key-server.test.ts +247 -0
- package/src/billing/crypto/client.test.ts +80 -96
- package/src/billing/crypto/client.ts +138 -58
- package/src/billing/crypto/index.ts +11 -2
- package/src/billing/crypto/key-server-entry.ts +52 -0
- package/src/billing/crypto/key-server.ts +315 -0
- package/src/db/schema/crypto.ts +45 -4
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { createKeyServerApp } from "../key-server.js";
|
|
3
|
+
/** Create a mock db that supports transaction() by passing itself to the callback. */
|
|
4
|
+
function createMockDb() {
|
|
5
|
+
const mockMethod = {
|
|
6
|
+
id: "btc",
|
|
7
|
+
type: "native",
|
|
8
|
+
token: "BTC",
|
|
9
|
+
chain: "bitcoin",
|
|
10
|
+
xpub: "xpub6CUGRUonZSQ4TWtTMmzXdrXDtypWKiKrhko4egpiMZbpiaQL2jkwSB1icqYh2cfDfVxdx4df189oLKnC5fSwqPfgyP3hooxujYzAu3fDVmz",
|
|
11
|
+
nextIndex: 1,
|
|
12
|
+
decimals: 8,
|
|
13
|
+
confirmations: 6,
|
|
14
|
+
};
|
|
15
|
+
const db = {
|
|
16
|
+
update: vi.fn().mockReturnValue({
|
|
17
|
+
set: vi.fn().mockReturnValue({
|
|
18
|
+
where: vi.fn().mockReturnValue({
|
|
19
|
+
returning: vi.fn().mockResolvedValue([mockMethod]),
|
|
20
|
+
}),
|
|
21
|
+
}),
|
|
22
|
+
}),
|
|
23
|
+
insert: vi.fn().mockReturnValue({
|
|
24
|
+
values: vi.fn().mockReturnValue({
|
|
25
|
+
onConflictDoNothing: vi.fn().mockResolvedValue({ rowCount: 1 }),
|
|
26
|
+
}),
|
|
27
|
+
}),
|
|
28
|
+
select: vi.fn().mockReturnValue({
|
|
29
|
+
from: vi.fn().mockReturnValue({
|
|
30
|
+
where: vi.fn().mockResolvedValue([]),
|
|
31
|
+
}),
|
|
32
|
+
}),
|
|
33
|
+
// transaction() passes itself as tx — mocks work the same way
|
|
34
|
+
transaction: vi.fn().mockImplementation(async (fn) => fn(db)),
|
|
35
|
+
};
|
|
36
|
+
return db;
|
|
37
|
+
}
|
|
38
|
+
/** Minimal mock deps for key server tests. */
|
|
39
|
+
function mockDeps() {
|
|
40
|
+
const chargeStore = {
|
|
41
|
+
getByReferenceId: vi.fn().mockResolvedValue({
|
|
42
|
+
referenceId: "btc:bc1q...",
|
|
43
|
+
status: "New",
|
|
44
|
+
depositAddress: "bc1q...",
|
|
45
|
+
chain: "bitcoin",
|
|
46
|
+
token: "BTC",
|
|
47
|
+
amountUsdCents: 5000,
|
|
48
|
+
creditedAt: null,
|
|
49
|
+
}),
|
|
50
|
+
createStablecoinCharge: vi.fn().mockResolvedValue(undefined),
|
|
51
|
+
create: vi.fn(),
|
|
52
|
+
updateStatus: vi.fn(),
|
|
53
|
+
markCredited: vi.fn(),
|
|
54
|
+
isCredited: vi.fn(),
|
|
55
|
+
getByDepositAddress: vi.fn(),
|
|
56
|
+
getNextDerivationIndex: vi.fn(),
|
|
57
|
+
listActiveDepositAddresses: vi.fn(),
|
|
58
|
+
};
|
|
59
|
+
const methodStore = {
|
|
60
|
+
listEnabled: vi.fn().mockResolvedValue([
|
|
61
|
+
{
|
|
62
|
+
id: "btc",
|
|
63
|
+
token: "BTC",
|
|
64
|
+
chain: "bitcoin",
|
|
65
|
+
decimals: 8,
|
|
66
|
+
displayName: "Bitcoin",
|
|
67
|
+
contractAddress: null,
|
|
68
|
+
confirmations: 6,
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
id: "base-usdc",
|
|
72
|
+
token: "USDC",
|
|
73
|
+
chain: "base",
|
|
74
|
+
decimals: 6,
|
|
75
|
+
displayName: "USDC on Base",
|
|
76
|
+
contractAddress: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
77
|
+
confirmations: 12,
|
|
78
|
+
},
|
|
79
|
+
]),
|
|
80
|
+
listAll: vi.fn(),
|
|
81
|
+
getById: vi.fn(),
|
|
82
|
+
listByType: vi.fn(),
|
|
83
|
+
upsert: vi.fn().mockResolvedValue(undefined),
|
|
84
|
+
setEnabled: vi.fn().mockResolvedValue(undefined),
|
|
85
|
+
};
|
|
86
|
+
return {
|
|
87
|
+
db: createMockDb(),
|
|
88
|
+
chargeStore: chargeStore,
|
|
89
|
+
methodStore: methodStore,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
describe("key-server routes", () => {
|
|
93
|
+
it("GET /chains returns enabled payment methods", async () => {
|
|
94
|
+
const app = createKeyServerApp(mockDeps());
|
|
95
|
+
const res = await app.request("/chains");
|
|
96
|
+
expect(res.status).toBe(200);
|
|
97
|
+
const body = await res.json();
|
|
98
|
+
expect(body).toHaveLength(2);
|
|
99
|
+
expect(body[0].token).toBe("BTC");
|
|
100
|
+
expect(body[1].token).toBe("USDC");
|
|
101
|
+
});
|
|
102
|
+
it("POST /address requires chain", async () => {
|
|
103
|
+
const app = createKeyServerApp(mockDeps());
|
|
104
|
+
const res = await app.request("/address", {
|
|
105
|
+
method: "POST",
|
|
106
|
+
headers: { "Content-Type": "application/json" },
|
|
107
|
+
body: JSON.stringify({}),
|
|
108
|
+
});
|
|
109
|
+
expect(res.status).toBe(400);
|
|
110
|
+
});
|
|
111
|
+
it("POST /address derives BTC address", async () => {
|
|
112
|
+
const app = createKeyServerApp(mockDeps());
|
|
113
|
+
const res = await app.request("/address", {
|
|
114
|
+
method: "POST",
|
|
115
|
+
headers: { "Content-Type": "application/json" },
|
|
116
|
+
body: JSON.stringify({ chain: "btc" }),
|
|
117
|
+
});
|
|
118
|
+
expect(res.status).toBe(201);
|
|
119
|
+
const body = await res.json();
|
|
120
|
+
expect(body.address).toMatch(/^bc1q/);
|
|
121
|
+
expect(body.index).toBe(0);
|
|
122
|
+
expect(body.chain).toBe("bitcoin");
|
|
123
|
+
expect(body.token).toBe("BTC");
|
|
124
|
+
});
|
|
125
|
+
it("GET /charges/:id returns charge status", async () => {
|
|
126
|
+
const app = createKeyServerApp(mockDeps());
|
|
127
|
+
const res = await app.request("/charges/btc:bc1q...");
|
|
128
|
+
expect(res.status).toBe(200);
|
|
129
|
+
const body = await res.json();
|
|
130
|
+
expect(body.chargeId).toBe("btc:bc1q...");
|
|
131
|
+
expect(body.status).toBe("New");
|
|
132
|
+
});
|
|
133
|
+
it("GET /charges/:id returns 404 for missing charge", async () => {
|
|
134
|
+
const deps = mockDeps();
|
|
135
|
+
deps.chargeStore.getByReferenceId.mockResolvedValue(null);
|
|
136
|
+
const app = createKeyServerApp(deps);
|
|
137
|
+
const res = await app.request("/charges/nonexistent");
|
|
138
|
+
expect(res.status).toBe(404);
|
|
139
|
+
});
|
|
140
|
+
it("POST /charges validates amountUsd", async () => {
|
|
141
|
+
const app = createKeyServerApp(mockDeps());
|
|
142
|
+
const res = await app.request("/charges", {
|
|
143
|
+
method: "POST",
|
|
144
|
+
headers: { "Content-Type": "application/json" },
|
|
145
|
+
body: JSON.stringify({ chain: "btc", amountUsd: -10 }),
|
|
146
|
+
});
|
|
147
|
+
expect(res.status).toBe(400);
|
|
148
|
+
});
|
|
149
|
+
it("POST /charges creates a charge", async () => {
|
|
150
|
+
const app = createKeyServerApp(mockDeps());
|
|
151
|
+
const res = await app.request("/charges", {
|
|
152
|
+
method: "POST",
|
|
153
|
+
headers: { "Content-Type": "application/json" },
|
|
154
|
+
body: JSON.stringify({ chain: "btc", amountUsd: 50 }),
|
|
155
|
+
});
|
|
156
|
+
expect(res.status).toBe(201);
|
|
157
|
+
const body = await res.json();
|
|
158
|
+
expect(body.address).toMatch(/^bc1q/);
|
|
159
|
+
expect(body.amountUsd).toBe(50);
|
|
160
|
+
expect(body.expiresAt).toBeTruthy();
|
|
161
|
+
});
|
|
162
|
+
it("GET /admin/next-path returns available path", async () => {
|
|
163
|
+
const deps = mockDeps();
|
|
164
|
+
deps.adminToken = "test-admin";
|
|
165
|
+
const app = createKeyServerApp(deps);
|
|
166
|
+
const res = await app.request("/admin/next-path?coin_type=0", {
|
|
167
|
+
headers: { Authorization: "Bearer test-admin" },
|
|
168
|
+
});
|
|
169
|
+
expect(res.status).toBe(200);
|
|
170
|
+
const body = await res.json();
|
|
171
|
+
expect(body.path).toBe("m/44'/0'/0'");
|
|
172
|
+
expect(body.status).toBe("available");
|
|
173
|
+
});
|
|
174
|
+
it("DELETE /admin/chains/:id disables chain", async () => {
|
|
175
|
+
const deps = mockDeps();
|
|
176
|
+
deps.adminToken = "test-admin";
|
|
177
|
+
const app = createKeyServerApp(deps);
|
|
178
|
+
const res = await app.request("/admin/chains/doge", {
|
|
179
|
+
method: "DELETE",
|
|
180
|
+
headers: { Authorization: "Bearer test-admin" },
|
|
181
|
+
});
|
|
182
|
+
expect(res.status).toBe(204);
|
|
183
|
+
expect(deps.methodStore.setEnabled).toHaveBeenCalledWith("doge", false);
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
describe("key-server auth", () => {
|
|
187
|
+
it("rejects unauthenticated request when serviceKey is set", async () => {
|
|
188
|
+
const deps = mockDeps();
|
|
189
|
+
deps.serviceKey = "sk-test-secret";
|
|
190
|
+
const app = createKeyServerApp(deps);
|
|
191
|
+
const res = await app.request("/address", {
|
|
192
|
+
method: "POST",
|
|
193
|
+
headers: { "Content-Type": "application/json" },
|
|
194
|
+
body: JSON.stringify({ chain: "btc" }),
|
|
195
|
+
});
|
|
196
|
+
expect(res.status).toBe(401);
|
|
197
|
+
});
|
|
198
|
+
it("allows authenticated request with correct serviceKey", async () => {
|
|
199
|
+
const deps = mockDeps();
|
|
200
|
+
deps.serviceKey = "sk-test-secret";
|
|
201
|
+
const app = createKeyServerApp(deps);
|
|
202
|
+
const res = await app.request("/address", {
|
|
203
|
+
method: "POST",
|
|
204
|
+
headers: { "Content-Type": "application/json", Authorization: "Bearer sk-test-secret" },
|
|
205
|
+
body: JSON.stringify({ chain: "btc" }),
|
|
206
|
+
});
|
|
207
|
+
expect(res.status).toBe(201);
|
|
208
|
+
});
|
|
209
|
+
it("rejects admin route without adminToken", async () => {
|
|
210
|
+
const deps = mockDeps();
|
|
211
|
+
// no adminToken set — admin routes disabled
|
|
212
|
+
const app = createKeyServerApp(deps);
|
|
213
|
+
const res = await app.request("/admin/next-path?coin_type=0");
|
|
214
|
+
expect(res.status).toBe(403);
|
|
215
|
+
});
|
|
216
|
+
it("allows admin route with correct adminToken", async () => {
|
|
217
|
+
const deps = mockDeps();
|
|
218
|
+
deps.adminToken = "admin-secret";
|
|
219
|
+
const app = createKeyServerApp(deps);
|
|
220
|
+
const res = await app.request("/admin/next-path?coin_type=0", {
|
|
221
|
+
headers: { Authorization: "Bearer admin-secret" },
|
|
222
|
+
});
|
|
223
|
+
expect(res.status).toBe(200);
|
|
224
|
+
});
|
|
225
|
+
});
|
|
@@ -1,21 +1,91 @@
|
|
|
1
|
-
import type { CryptoBillingConfig } from "./types.js";
|
|
2
|
-
export type { CryptoBillingConfig as CryptoConfig };
|
|
3
1
|
/**
|
|
4
|
-
*
|
|
2
|
+
* Crypto Key Server client — for products to call the shared service.
|
|
5
3
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* Replaces BTCPayClient. Products set CRYPTO_SERVICE_URL instead of
|
|
5
|
+
* BTCPAY_API_KEY + BTCPAY_BASE_URL + BTCPAY_STORE_ID.
|
|
8
6
|
*/
|
|
9
|
-
export
|
|
7
|
+
export interface CryptoServiceConfig {
|
|
8
|
+
/** Base URL of the crypto key server (e.g. http://10.120.0.5:3100) */
|
|
9
|
+
baseUrl: string;
|
|
10
|
+
/** Service key for auth (reuses gateway service key) */
|
|
11
|
+
serviceKey?: string;
|
|
12
|
+
/** Tenant ID header */
|
|
13
|
+
tenantId?: string;
|
|
14
|
+
}
|
|
15
|
+
export interface DeriveAddressResult {
|
|
16
|
+
address: string;
|
|
17
|
+
index: number;
|
|
18
|
+
chain: string;
|
|
19
|
+
token: string;
|
|
20
|
+
}
|
|
21
|
+
export interface CreateChargeResult {
|
|
22
|
+
chargeId: string;
|
|
23
|
+
address: string;
|
|
24
|
+
chain: string;
|
|
25
|
+
token: string;
|
|
26
|
+
amountUsd: number;
|
|
27
|
+
derivationIndex: number;
|
|
28
|
+
expiresAt: string;
|
|
29
|
+
}
|
|
30
|
+
export interface ChargeStatus {
|
|
31
|
+
chargeId: string;
|
|
32
|
+
status: string;
|
|
33
|
+
address: string | null;
|
|
34
|
+
chain: string | null;
|
|
35
|
+
token: string | null;
|
|
36
|
+
amountUsdCents: number;
|
|
37
|
+
creditedAt: string | null;
|
|
38
|
+
}
|
|
39
|
+
export interface ChainInfo {
|
|
40
|
+
id: string;
|
|
41
|
+
token: string;
|
|
42
|
+
chain: string;
|
|
43
|
+
decimals: number;
|
|
44
|
+
displayName: string;
|
|
45
|
+
contractAddress: string | null;
|
|
46
|
+
confirmations: number;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Client for the shared crypto key server.
|
|
50
|
+
* Products use this instead of running local watchers + holding xpubs.
|
|
51
|
+
*/
|
|
52
|
+
export declare class CryptoServiceClient {
|
|
10
53
|
private readonly config;
|
|
11
|
-
constructor(config:
|
|
54
|
+
constructor(config: CryptoServiceConfig);
|
|
12
55
|
private headers;
|
|
13
|
-
/**
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
56
|
+
/** Derive the next unused address for a chain. */
|
|
57
|
+
deriveAddress(chain: string): Promise<DeriveAddressResult>;
|
|
58
|
+
/** Create a payment charge — derives address, sets expiry, starts watching. */
|
|
59
|
+
createCharge(opts: {
|
|
60
|
+
chain: string;
|
|
61
|
+
amountUsd: number;
|
|
62
|
+
callbackUrl?: string;
|
|
63
|
+
metadata?: Record<string, unknown>;
|
|
64
|
+
}): Promise<CreateChargeResult>;
|
|
65
|
+
/** Check charge status. */
|
|
66
|
+
getCharge(chargeId: string): Promise<ChargeStatus>;
|
|
67
|
+
/** List all enabled payment methods (for checkout UI). */
|
|
68
|
+
listChains(): Promise<ChainInfo[]>;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Load crypto service config from environment.
|
|
72
|
+
* Returns null if CRYPTO_SERVICE_URL is not set.
|
|
73
|
+
*
|
|
74
|
+
* Also supports legacy BTCPay env vars for backwards compat during migration.
|
|
75
|
+
*/
|
|
76
|
+
export declare function loadCryptoConfig(): CryptoServiceConfig | null;
|
|
77
|
+
export type CryptoConfig = CryptoServiceConfig;
|
|
78
|
+
/**
|
|
79
|
+
* @deprecated Use CryptoServiceClient instead. BTCPay is replaced by the crypto key server.
|
|
80
|
+
* Kept for backwards compat — products still import BTCPayClient during migration.
|
|
81
|
+
*/
|
|
82
|
+
export declare class BTCPayClient {
|
|
83
|
+
constructor(_config: {
|
|
84
|
+
apiKey: string;
|
|
85
|
+
baseUrl: string;
|
|
86
|
+
storeId: string;
|
|
87
|
+
});
|
|
88
|
+
createInvoice(_opts: {
|
|
19
89
|
amountUsd: number;
|
|
20
90
|
orderId: string;
|
|
21
91
|
buyerEmail?: string;
|
|
@@ -24,16 +94,10 @@ export declare class BTCPayClient {
|
|
|
24
94
|
id: string;
|
|
25
95
|
checkoutLink: string;
|
|
26
96
|
}>;
|
|
27
|
-
|
|
28
|
-
getInvoice(invoiceId: string): Promise<{
|
|
97
|
+
getInvoice(_invoiceId: string): Promise<{
|
|
29
98
|
id: string;
|
|
30
99
|
status: string;
|
|
31
100
|
amount: string;
|
|
32
101
|
currency: string;
|
|
33
102
|
}>;
|
|
34
103
|
}
|
|
35
|
-
/**
|
|
36
|
-
* Load BTCPay config from environment variables.
|
|
37
|
-
* Returns null if any required var is missing.
|
|
38
|
-
*/
|
|
39
|
-
export declare function loadCryptoConfig(): CryptoBillingConfig | null;
|
|
@@ -1,72 +1,102 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Crypto Key Server client — for products to call the shared service.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Replaces BTCPayClient. Products set CRYPTO_SERVICE_URL instead of
|
|
5
|
+
* BTCPAY_API_KEY + BTCPAY_BASE_URL + BTCPAY_STORE_ID.
|
|
6
6
|
*/
|
|
7
|
-
|
|
7
|
+
/**
|
|
8
|
+
* Client for the shared crypto key server.
|
|
9
|
+
* Products use this instead of running local watchers + holding xpubs.
|
|
10
|
+
*/
|
|
11
|
+
export class CryptoServiceClient {
|
|
8
12
|
config;
|
|
9
13
|
constructor(config) {
|
|
10
14
|
this.config = config;
|
|
11
15
|
}
|
|
12
16
|
headers() {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
Authorization
|
|
16
|
-
|
|
17
|
+
const h = { "Content-Type": "application/json" };
|
|
18
|
+
if (this.config.serviceKey)
|
|
19
|
+
h.Authorization = `Bearer ${this.config.serviceKey}`;
|
|
20
|
+
if (this.config.tenantId)
|
|
21
|
+
h["X-Tenant-Id"] = this.config.tenantId;
|
|
22
|
+
return h;
|
|
17
23
|
}
|
|
18
|
-
/**
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
* Returns the invoice ID and checkout link (URL to redirect the user).
|
|
22
|
-
*/
|
|
23
|
-
async createInvoice(opts) {
|
|
24
|
-
const url = `${this.config.baseUrl}/api/v1/stores/${this.config.storeId}/invoices`;
|
|
25
|
-
const body = {
|
|
26
|
-
amount: String(opts.amountUsd),
|
|
27
|
-
currency: "USD",
|
|
28
|
-
metadata: {
|
|
29
|
-
orderId: opts.orderId,
|
|
30
|
-
buyerEmail: opts.buyerEmail,
|
|
31
|
-
},
|
|
32
|
-
checkout: {
|
|
33
|
-
speedPolicy: "MediumSpeed",
|
|
34
|
-
expirationMinutes: 30,
|
|
35
|
-
...(opts.redirectURL ? { redirectURL: opts.redirectURL } : {}),
|
|
36
|
-
},
|
|
37
|
-
};
|
|
38
|
-
const res = await fetch(url, {
|
|
24
|
+
/** Derive the next unused address for a chain. */
|
|
25
|
+
async deriveAddress(chain) {
|
|
26
|
+
const res = await fetch(`${this.config.baseUrl}/address`, {
|
|
39
27
|
method: "POST",
|
|
40
28
|
headers: this.headers(),
|
|
41
|
-
body: JSON.stringify(
|
|
29
|
+
body: JSON.stringify({ chain }),
|
|
30
|
+
});
|
|
31
|
+
if (!res.ok) {
|
|
32
|
+
const text = await res.text().catch(() => "");
|
|
33
|
+
throw new Error(`CryptoService deriveAddress failed (${res.status}): ${text}`);
|
|
34
|
+
}
|
|
35
|
+
return (await res.json());
|
|
36
|
+
}
|
|
37
|
+
/** Create a payment charge — derives address, sets expiry, starts watching. */
|
|
38
|
+
async createCharge(opts) {
|
|
39
|
+
const res = await fetch(`${this.config.baseUrl}/charges`, {
|
|
40
|
+
method: "POST",
|
|
41
|
+
headers: this.headers(),
|
|
42
|
+
body: JSON.stringify(opts),
|
|
43
|
+
});
|
|
44
|
+
if (!res.ok) {
|
|
45
|
+
const text = await res.text().catch(() => "");
|
|
46
|
+
throw new Error(`CryptoService createCharge failed (${res.status}): ${text}`);
|
|
47
|
+
}
|
|
48
|
+
return (await res.json());
|
|
49
|
+
}
|
|
50
|
+
/** Check charge status. */
|
|
51
|
+
async getCharge(chargeId) {
|
|
52
|
+
const res = await fetch(`${this.config.baseUrl}/charges/${encodeURIComponent(chargeId)}`, {
|
|
53
|
+
headers: this.headers(),
|
|
42
54
|
});
|
|
43
55
|
if (!res.ok) {
|
|
44
56
|
const text = await res.text().catch(() => "");
|
|
45
|
-
throw new Error(`
|
|
57
|
+
throw new Error(`CryptoService getCharge failed (${res.status}): ${text}`);
|
|
46
58
|
}
|
|
47
|
-
|
|
48
|
-
return { id: data.id, checkoutLink: data.checkoutLink };
|
|
59
|
+
return (await res.json());
|
|
49
60
|
}
|
|
50
|
-
/**
|
|
51
|
-
async
|
|
52
|
-
const
|
|
53
|
-
|
|
61
|
+
/** List all enabled payment methods (for checkout UI). */
|
|
62
|
+
async listChains() {
|
|
63
|
+
const res = await fetch(`${this.config.baseUrl}/chains`, {
|
|
64
|
+
headers: this.headers(),
|
|
65
|
+
});
|
|
54
66
|
if (!res.ok) {
|
|
55
67
|
const text = await res.text().catch(() => "");
|
|
56
|
-
throw new Error(`
|
|
68
|
+
throw new Error(`CryptoService listChains failed (${res.status}): ${text}`);
|
|
57
69
|
}
|
|
58
70
|
return (await res.json());
|
|
59
71
|
}
|
|
60
72
|
}
|
|
61
73
|
/**
|
|
62
|
-
* Load
|
|
63
|
-
* Returns null if
|
|
74
|
+
* Load crypto service config from environment.
|
|
75
|
+
* Returns null if CRYPTO_SERVICE_URL is not set.
|
|
76
|
+
*
|
|
77
|
+
* Also supports legacy BTCPay env vars for backwards compat during migration.
|
|
64
78
|
*/
|
|
65
79
|
export function loadCryptoConfig() {
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
80
|
+
const baseUrl = process.env.CRYPTO_SERVICE_URL;
|
|
81
|
+
if (baseUrl) {
|
|
82
|
+
return {
|
|
83
|
+
baseUrl,
|
|
84
|
+
serviceKey: process.env.CRYPTO_SERVICE_KEY,
|
|
85
|
+
tenantId: process.env.TENANT_ID,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* @deprecated Use CryptoServiceClient instead. BTCPay is replaced by the crypto key server.
|
|
92
|
+
* Kept for backwards compat — products still import BTCPayClient during migration.
|
|
93
|
+
*/
|
|
94
|
+
export class BTCPayClient {
|
|
95
|
+
constructor(_config) { }
|
|
96
|
+
async createInvoice(_opts) {
|
|
97
|
+
throw new Error("BTCPayClient is deprecated — migrate to CryptoServiceClient");
|
|
98
|
+
}
|
|
99
|
+
async getInvoice(_invoiceId) {
|
|
100
|
+
throw new Error("BTCPayClient is deprecated — migrate to CryptoServiceClient");
|
|
101
|
+
}
|
|
72
102
|
}
|