@solvapay/server 2.4.1 → 2.5.0-preview-4dcf60c628500a6210ba69edbf7ae56ee47fbb3a
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/dist/edge.d.ts +161 -39
- package/dist/edge.js +261 -255
- package/dist/fetch/index.cjs +250 -287
- package/dist/fetch/index.d.cts +23 -6
- package/dist/fetch/index.d.ts +23 -6
- package/dist/fetch/index.js +217 -254
- package/dist/index.cjs +306 -296
- package/dist/index.d.cts +161 -39
- package/dist/index.d.ts +161 -39
- package/dist/index.js +261 -255
- package/package.json +4 -4
package/dist/edge.js
CHANGED
|
@@ -3,6 +3,32 @@ import { SolvaPayError as SolvaPayError7 } from "@solvapay/core";
|
|
|
3
3
|
|
|
4
4
|
// src/client.ts
|
|
5
5
|
import { SolvaPayError } from "@solvapay/core";
|
|
6
|
+
var API_ERROR_BODY_MAX = 240;
|
|
7
|
+
function apiDebugLog(...args) {
|
|
8
|
+
if (process.env.SOLVAPAY_DEBUG === "true") {
|
|
9
|
+
console.log(...args);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
function isNonJsonResponseBody(body, contentType) {
|
|
13
|
+
const ct = contentType?.split(";")[0]?.trim().toLowerCase() ?? "";
|
|
14
|
+
if (ct.includes("json")) return false;
|
|
15
|
+
const trimmed = body.trimStart();
|
|
16
|
+
if (trimmed.startsWith("<")) return true;
|
|
17
|
+
if (ct && !ct.includes("json")) return true;
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
async function throwApiError(operation, res) {
|
|
21
|
+
const body = await res.text();
|
|
22
|
+
const contentType = res.headers.get("content-type");
|
|
23
|
+
const nonJson = isNonJsonResponseBody(body, contentType);
|
|
24
|
+
const snippet = body.length > API_ERROR_BODY_MAX ? `${body.slice(0, API_ERROR_BODY_MAX)}\u2026` : body;
|
|
25
|
+
const detail = nonJson ? `non-JSON response (${contentType ?? "unknown content-type"}) \u2014 the API may not have been reached. Body: ${snippet}` : snippet;
|
|
26
|
+
apiDebugLog(`\u274C API Error: ${res.status} - ${detail}`);
|
|
27
|
+
throw new SolvaPayError(`${operation} failed (${res.status}): ${detail}`, {
|
|
28
|
+
status: res.status,
|
|
29
|
+
code: nonJson ? "non_json_response" : void 0
|
|
30
|
+
});
|
|
31
|
+
}
|
|
6
32
|
function createSolvaPayClient(opts) {
|
|
7
33
|
const base = opts.apiBaseUrl ?? "https://api.solvapay.com";
|
|
8
34
|
if (!opts.apiKey) throw new SolvaPayError("Missing apiKey");
|
|
@@ -10,12 +36,6 @@ function createSolvaPayClient(opts) {
|
|
|
10
36
|
"Content-Type": "application/json",
|
|
11
37
|
Authorization: `Bearer ${opts.apiKey}`
|
|
12
38
|
};
|
|
13
|
-
const debug = process.env.SOLVAPAY_DEBUG === "true";
|
|
14
|
-
const log = (...args) => {
|
|
15
|
-
if (debug) {
|
|
16
|
-
console.log(...args);
|
|
17
|
-
}
|
|
18
|
-
};
|
|
19
39
|
return {
|
|
20
40
|
// POST: /v1/sdk/limits
|
|
21
41
|
async checkLimits(params) {
|
|
@@ -26,11 +46,7 @@ function createSolvaPayClient(opts) {
|
|
|
26
46
|
body: JSON.stringify(params)
|
|
27
47
|
});
|
|
28
48
|
if (!res.ok) {
|
|
29
|
-
|
|
30
|
-
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
31
|
-
throw new SolvaPayError(`Check limits failed (${res.status}): ${error}`, {
|
|
32
|
-
status: res.status
|
|
33
|
-
});
|
|
49
|
+
await throwApiError("Check limits", res);
|
|
34
50
|
}
|
|
35
51
|
const result = await res.json();
|
|
36
52
|
return result;
|
|
@@ -44,11 +60,7 @@ function createSolvaPayClient(opts) {
|
|
|
44
60
|
body: JSON.stringify(params)
|
|
45
61
|
});
|
|
46
62
|
if (!res.ok) {
|
|
47
|
-
|
|
48
|
-
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
49
|
-
throw new SolvaPayError(`Track usage failed (${res.status}): ${error}`, {
|
|
50
|
-
status: res.status
|
|
51
|
-
});
|
|
63
|
+
await throwApiError("Track usage", res);
|
|
52
64
|
}
|
|
53
65
|
return await res.json();
|
|
54
66
|
},
|
|
@@ -61,11 +73,7 @@ function createSolvaPayClient(opts) {
|
|
|
61
73
|
body: JSON.stringify(params)
|
|
62
74
|
});
|
|
63
75
|
if (!res.ok) {
|
|
64
|
-
|
|
65
|
-
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
66
|
-
throw new SolvaPayError(`Track usage bulk failed (${res.status}): ${error}`, {
|
|
67
|
-
status: res.status
|
|
68
|
-
});
|
|
76
|
+
await throwApiError("Track usage bulk", res);
|
|
69
77
|
}
|
|
70
78
|
return await res.json();
|
|
71
79
|
},
|
|
@@ -78,11 +86,7 @@ function createSolvaPayClient(opts) {
|
|
|
78
86
|
body: JSON.stringify(params)
|
|
79
87
|
});
|
|
80
88
|
if (!res.ok) {
|
|
81
|
-
|
|
82
|
-
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
83
|
-
throw new SolvaPayError(`Create customer failed (${res.status}): ${error}`, {
|
|
84
|
-
status: res.status
|
|
85
|
-
});
|
|
89
|
+
await throwApiError("Create customer", res);
|
|
86
90
|
}
|
|
87
91
|
const result = await res.json();
|
|
88
92
|
return {
|
|
@@ -98,11 +102,7 @@ function createSolvaPayClient(opts) {
|
|
|
98
102
|
body: JSON.stringify(params)
|
|
99
103
|
});
|
|
100
104
|
if (!res.ok) {
|
|
101
|
-
|
|
102
|
-
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
103
|
-
throw new SolvaPayError(`Update customer failed (${res.status}): ${error}`, {
|
|
104
|
-
status: res.status
|
|
105
|
-
});
|
|
105
|
+
await throwApiError("Update customer", res);
|
|
106
106
|
}
|
|
107
107
|
const result = await res.json();
|
|
108
108
|
return {
|
|
@@ -130,11 +130,7 @@ function createSolvaPayClient(opts) {
|
|
|
130
130
|
headers
|
|
131
131
|
});
|
|
132
132
|
if (!res.ok) {
|
|
133
|
-
|
|
134
|
-
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
135
|
-
throw new SolvaPayError(`Get customer failed (${res.status}): ${error}`, {
|
|
136
|
-
status: res.status
|
|
137
|
-
});
|
|
133
|
+
await throwApiError("Get customer", res);
|
|
138
134
|
}
|
|
139
135
|
const result = await res.json();
|
|
140
136
|
let customer = result;
|
|
@@ -168,11 +164,7 @@ function createSolvaPayClient(opts) {
|
|
|
168
164
|
body: JSON.stringify(body)
|
|
169
165
|
});
|
|
170
166
|
if (!res.ok) {
|
|
171
|
-
|
|
172
|
-
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
173
|
-
throw new SolvaPayError(`Assign credits failed (${res.status}): ${error}`, {
|
|
174
|
-
status: res.status
|
|
175
|
-
});
|
|
167
|
+
await throwApiError("Assign credits", res);
|
|
176
168
|
}
|
|
177
169
|
return await res.json();
|
|
178
170
|
},
|
|
@@ -184,11 +176,7 @@ function createSolvaPayClient(opts) {
|
|
|
184
176
|
headers
|
|
185
177
|
});
|
|
186
178
|
if (!res.ok) {
|
|
187
|
-
|
|
188
|
-
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
189
|
-
throw new SolvaPayError(`Get merchant failed (${res.status}): ${error}`, {
|
|
190
|
-
status: res.status
|
|
191
|
-
});
|
|
179
|
+
await throwApiError("Get merchant", res);
|
|
192
180
|
}
|
|
193
181
|
return res.json();
|
|
194
182
|
},
|
|
@@ -200,11 +188,7 @@ function createSolvaPayClient(opts) {
|
|
|
200
188
|
headers
|
|
201
189
|
});
|
|
202
190
|
if (!res.ok) {
|
|
203
|
-
|
|
204
|
-
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
205
|
-
throw new SolvaPayError(`Get platform config failed (${res.status}): ${error}`, {
|
|
206
|
-
status: res.status
|
|
207
|
-
});
|
|
191
|
+
await throwApiError("Get platform config", res);
|
|
208
192
|
}
|
|
209
193
|
return res.json();
|
|
210
194
|
},
|
|
@@ -216,11 +200,7 @@ function createSolvaPayClient(opts) {
|
|
|
216
200
|
headers
|
|
217
201
|
});
|
|
218
202
|
if (!res.ok) {
|
|
219
|
-
|
|
220
|
-
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
221
|
-
throw new SolvaPayError(`Get product failed (${res.status}): ${error}`, {
|
|
222
|
-
status: res.status
|
|
223
|
-
});
|
|
203
|
+
await throwApiError("Get product", res);
|
|
224
204
|
}
|
|
225
205
|
const result = await res.json();
|
|
226
206
|
const data = result.data || {};
|
|
@@ -235,11 +215,7 @@ function createSolvaPayClient(opts) {
|
|
|
235
215
|
headers
|
|
236
216
|
});
|
|
237
217
|
if (!res.ok) {
|
|
238
|
-
|
|
239
|
-
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
240
|
-
throw new SolvaPayError(`List products failed (${res.status}): ${error}`, {
|
|
241
|
-
status: res.status
|
|
242
|
-
});
|
|
218
|
+
await throwApiError("List products", res);
|
|
243
219
|
}
|
|
244
220
|
const result = await res.json();
|
|
245
221
|
const products = Array.isArray(result) ? result : result.products || [];
|
|
@@ -257,11 +233,7 @@ function createSolvaPayClient(opts) {
|
|
|
257
233
|
body: JSON.stringify(params)
|
|
258
234
|
});
|
|
259
235
|
if (!res.ok) {
|
|
260
|
-
|
|
261
|
-
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
262
|
-
throw new SolvaPayError(`Create product failed (${res.status}): ${error}`, {
|
|
263
|
-
status: res.status
|
|
264
|
-
});
|
|
236
|
+
await throwApiError("Create product", res);
|
|
265
237
|
}
|
|
266
238
|
const result = await res.json();
|
|
267
239
|
return result;
|
|
@@ -275,11 +247,7 @@ function createSolvaPayClient(opts) {
|
|
|
275
247
|
body: JSON.stringify(params)
|
|
276
248
|
});
|
|
277
249
|
if (!res.ok) {
|
|
278
|
-
|
|
279
|
-
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
280
|
-
throw new SolvaPayError(`Bootstrap MCP product failed (${res.status}): ${error}`, {
|
|
281
|
-
status: res.status
|
|
282
|
-
});
|
|
250
|
+
await throwApiError("Bootstrap MCP product", res);
|
|
283
251
|
}
|
|
284
252
|
return await res.json();
|
|
285
253
|
},
|
|
@@ -292,11 +260,7 @@ function createSolvaPayClient(opts) {
|
|
|
292
260
|
body: JSON.stringify(params)
|
|
293
261
|
});
|
|
294
262
|
if (!res.ok) {
|
|
295
|
-
|
|
296
|
-
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
297
|
-
throw new SolvaPayError(`Configure MCP plans failed (${res.status}): ${error}`, {
|
|
298
|
-
status: res.status
|
|
299
|
-
});
|
|
263
|
+
await throwApiError("Configure MCP plans", res);
|
|
300
264
|
}
|
|
301
265
|
return await res.json();
|
|
302
266
|
},
|
|
@@ -308,11 +272,7 @@ function createSolvaPayClient(opts) {
|
|
|
308
272
|
headers
|
|
309
273
|
});
|
|
310
274
|
if (!res.ok && res.status !== 404) {
|
|
311
|
-
|
|
312
|
-
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
313
|
-
throw new SolvaPayError(`Delete product failed (${res.status}): ${error}`, {
|
|
314
|
-
status: res.status
|
|
315
|
-
});
|
|
275
|
+
await throwApiError("Delete product", res);
|
|
316
276
|
}
|
|
317
277
|
},
|
|
318
278
|
// POST: /v1/sdk/products/{productRef}/clone
|
|
@@ -324,11 +284,7 @@ function createSolvaPayClient(opts) {
|
|
|
324
284
|
body: JSON.stringify(overrides || {})
|
|
325
285
|
});
|
|
326
286
|
if (!res.ok) {
|
|
327
|
-
|
|
328
|
-
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
329
|
-
throw new SolvaPayError(`Clone product failed (${res.status}): ${error}`, {
|
|
330
|
-
status: res.status
|
|
331
|
-
});
|
|
287
|
+
await throwApiError("Clone product", res);
|
|
332
288
|
}
|
|
333
289
|
return await res.json();
|
|
334
290
|
},
|
|
@@ -340,11 +296,7 @@ function createSolvaPayClient(opts) {
|
|
|
340
296
|
headers
|
|
341
297
|
});
|
|
342
298
|
if (!res.ok) {
|
|
343
|
-
|
|
344
|
-
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
345
|
-
throw new SolvaPayError(`List plans failed (${res.status}): ${error}`, {
|
|
346
|
-
status: res.status
|
|
347
|
-
});
|
|
299
|
+
await throwApiError("List plans", res);
|
|
348
300
|
}
|
|
349
301
|
const result = await res.json();
|
|
350
302
|
const plans = Array.isArray(result) ? result : result.plans || [];
|
|
@@ -369,11 +321,7 @@ function createSolvaPayClient(opts) {
|
|
|
369
321
|
body: JSON.stringify(params)
|
|
370
322
|
});
|
|
371
323
|
if (!res.ok) {
|
|
372
|
-
|
|
373
|
-
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
374
|
-
throw new SolvaPayError(`Create plan failed (${res.status}): ${error}`, {
|
|
375
|
-
status: res.status
|
|
376
|
-
});
|
|
324
|
+
await throwApiError("Create plan", res);
|
|
377
325
|
}
|
|
378
326
|
const result = await res.json();
|
|
379
327
|
return result;
|
|
@@ -387,11 +335,7 @@ function createSolvaPayClient(opts) {
|
|
|
387
335
|
body: JSON.stringify(params)
|
|
388
336
|
});
|
|
389
337
|
if (!res.ok) {
|
|
390
|
-
|
|
391
|
-
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
392
|
-
throw new SolvaPayError(`Update plan failed (${res.status}): ${error}`, {
|
|
393
|
-
status: res.status
|
|
394
|
-
});
|
|
338
|
+
await throwApiError("Update plan", res);
|
|
395
339
|
}
|
|
396
340
|
return await res.json();
|
|
397
341
|
},
|
|
@@ -403,11 +347,7 @@ function createSolvaPayClient(opts) {
|
|
|
403
347
|
headers
|
|
404
348
|
});
|
|
405
349
|
if (!res.ok && res.status !== 404) {
|
|
406
|
-
|
|
407
|
-
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
408
|
-
throw new SolvaPayError(`Delete plan failed (${res.status}): ${error}`, {
|
|
409
|
-
status: res.status
|
|
410
|
-
});
|
|
350
|
+
await throwApiError("Delete plan", res);
|
|
411
351
|
}
|
|
412
352
|
},
|
|
413
353
|
// POST: /payment-intents
|
|
@@ -428,11 +368,7 @@ function createSolvaPayClient(opts) {
|
|
|
428
368
|
})
|
|
429
369
|
});
|
|
430
370
|
if (!res.ok) {
|
|
431
|
-
|
|
432
|
-
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
433
|
-
throw new SolvaPayError(`Create payment intent failed (${res.status}): ${error}`, {
|
|
434
|
-
status: res.status
|
|
435
|
-
});
|
|
371
|
+
await throwApiError("Create payment intent", res);
|
|
436
372
|
}
|
|
437
373
|
return await res.json();
|
|
438
374
|
},
|
|
@@ -456,11 +392,7 @@ function createSolvaPayClient(opts) {
|
|
|
456
392
|
})
|
|
457
393
|
});
|
|
458
394
|
if (!res.ok) {
|
|
459
|
-
|
|
460
|
-
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
461
|
-
throw new SolvaPayError(`Create topup payment intent failed (${res.status}): ${error}`, {
|
|
462
|
-
status: res.status
|
|
463
|
-
});
|
|
395
|
+
await throwApiError("Create topup payment intent", res);
|
|
464
396
|
}
|
|
465
397
|
return await res.json();
|
|
466
398
|
},
|
|
@@ -480,11 +412,7 @@ function createSolvaPayClient(opts) {
|
|
|
480
412
|
})
|
|
481
413
|
});
|
|
482
414
|
if (!res.ok) {
|
|
483
|
-
|
|
484
|
-
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
485
|
-
throw new SolvaPayError(`Process payment failed (${res.status}): ${error}`, {
|
|
486
|
-
status: res.status
|
|
487
|
-
});
|
|
415
|
+
await throwApiError("Process payment", res);
|
|
488
416
|
}
|
|
489
417
|
const result = await res.json();
|
|
490
418
|
return result;
|
|
@@ -505,54 +433,20 @@ function createSolvaPayClient(opts) {
|
|
|
505
433
|
})
|
|
506
434
|
});
|
|
507
435
|
if (!res.ok) {
|
|
508
|
-
|
|
509
|
-
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
510
|
-
throw new SolvaPayError(
|
|
511
|
-
`Attach business details failed (${res.status}): ${error}`,
|
|
512
|
-
{ status: res.status }
|
|
513
|
-
);
|
|
514
|
-
}
|
|
515
|
-
return await res.json();
|
|
516
|
-
},
|
|
517
|
-
// POST: /v1/sdk/purchases/{purchaseRef}/cancel
|
|
518
|
-
async cancelPurchase(params) {
|
|
519
|
-
const url = `${base}/v1/sdk/purchases/${params.purchaseRef}/cancel`;
|
|
520
|
-
const requestOptions = {
|
|
521
|
-
method: "POST",
|
|
522
|
-
headers
|
|
523
|
-
};
|
|
524
|
-
if (params.reason) {
|
|
525
|
-
requestOptions.body = JSON.stringify({ reason: params.reason });
|
|
526
|
-
}
|
|
527
|
-
const res = await fetch(url, requestOptions);
|
|
528
|
-
if (!res.ok) {
|
|
529
|
-
const error = await res.text();
|
|
530
|
-
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
531
|
-
if (res.status === 404) {
|
|
532
|
-
throw new SolvaPayError(`Purchase not found: ${error}`, { status: 404 });
|
|
533
|
-
}
|
|
534
|
-
if (res.status === 400) {
|
|
535
|
-
throw new SolvaPayError(
|
|
536
|
-
`Purchase cannot be cancelled or does not belong to provider: ${error}`,
|
|
537
|
-
{ status: 400 }
|
|
538
|
-
);
|
|
539
|
-
}
|
|
540
|
-
throw new SolvaPayError(`Cancel purchase failed (${res.status}): ${error}`, {
|
|
541
|
-
status: res.status
|
|
542
|
-
});
|
|
436
|
+
await throwApiError("Cancel purchase", res);
|
|
543
437
|
}
|
|
544
438
|
const responseText = await res.text();
|
|
545
439
|
let responseData;
|
|
546
440
|
try {
|
|
547
441
|
responseData = JSON.parse(responseText);
|
|
548
442
|
} catch (parseError) {
|
|
549
|
-
|
|
443
|
+
apiDebugLog(`\u274C Failed to parse response as JSON: ${parseError}`);
|
|
550
444
|
throw new SolvaPayError(
|
|
551
445
|
`Invalid JSON response from cancel purchase endpoint: ${responseText.substring(0, 200)}`
|
|
552
446
|
);
|
|
553
447
|
}
|
|
554
448
|
if (!responseData || typeof responseData !== "object") {
|
|
555
|
-
|
|
449
|
+
apiDebugLog(`\u274C Invalid response structure: ${JSON.stringify(responseData)}`);
|
|
556
450
|
throw new SolvaPayError(`Invalid response structure from cancel purchase endpoint`);
|
|
557
451
|
}
|
|
558
452
|
let result;
|
|
@@ -564,7 +458,7 @@ function createSolvaPayClient(opts) {
|
|
|
564
458
|
result = responseData.purchase || responseData;
|
|
565
459
|
}
|
|
566
460
|
if (!result || typeof result !== "object") {
|
|
567
|
-
|
|
461
|
+
apiDebugLog(`\u274C Invalid purchase data in response. Full response:`, responseData);
|
|
568
462
|
throw new SolvaPayError(`Invalid purchase data in cancel purchase response`);
|
|
569
463
|
}
|
|
570
464
|
return result;
|
|
@@ -577,30 +471,20 @@ function createSolvaPayClient(opts) {
|
|
|
577
471
|
headers
|
|
578
472
|
});
|
|
579
473
|
if (!res.ok) {
|
|
580
|
-
|
|
581
|
-
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
582
|
-
if (res.status === 404) {
|
|
583
|
-
throw new SolvaPayError(`Purchase not found: ${error}`, { status: 404 });
|
|
584
|
-
}
|
|
585
|
-
if (res.status === 400) {
|
|
586
|
-
throw new SolvaPayError(`Purchase cannot be reactivated: ${error}`, { status: 400 });
|
|
587
|
-
}
|
|
588
|
-
throw new SolvaPayError(`Reactivate purchase failed (${res.status}): ${error}`, {
|
|
589
|
-
status: res.status
|
|
590
|
-
});
|
|
474
|
+
await throwApiError("Reactivate purchase", res);
|
|
591
475
|
}
|
|
592
476
|
const responseText = await res.text();
|
|
593
477
|
let responseData;
|
|
594
478
|
try {
|
|
595
479
|
responseData = JSON.parse(responseText);
|
|
596
480
|
} catch (parseError) {
|
|
597
|
-
|
|
481
|
+
apiDebugLog(`\u274C Failed to parse response as JSON: ${parseError}`);
|
|
598
482
|
throw new SolvaPayError(
|
|
599
483
|
`Invalid JSON response from reactivate purchase endpoint: ${responseText.substring(0, 200)}`
|
|
600
484
|
);
|
|
601
485
|
}
|
|
602
486
|
if (!responseData || typeof responseData !== "object") {
|
|
603
|
-
|
|
487
|
+
apiDebugLog(`\u274C Invalid response structure: ${JSON.stringify(responseData)}`);
|
|
604
488
|
throw new SolvaPayError(`Invalid response structure from reactivate purchase endpoint`);
|
|
605
489
|
}
|
|
606
490
|
let result;
|
|
@@ -612,7 +496,7 @@ function createSolvaPayClient(opts) {
|
|
|
612
496
|
result = responseData.purchase || responseData;
|
|
613
497
|
}
|
|
614
498
|
if (!result || typeof result !== "object") {
|
|
615
|
-
|
|
499
|
+
apiDebugLog(`\u274C Invalid purchase data in response. Full response:`, responseData);
|
|
616
500
|
throw new SolvaPayError(`Invalid purchase data in reactivate purchase response`);
|
|
617
501
|
}
|
|
618
502
|
return result;
|
|
@@ -626,11 +510,7 @@ function createSolvaPayClient(opts) {
|
|
|
626
510
|
body: JSON.stringify(params)
|
|
627
511
|
});
|
|
628
512
|
if (!res.ok) {
|
|
629
|
-
|
|
630
|
-
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
631
|
-
throw new SolvaPayError(`Get user info failed (${res.status}): ${error}`, {
|
|
632
|
-
status: res.status
|
|
633
|
-
});
|
|
513
|
+
await throwApiError("Get user info", res);
|
|
634
514
|
}
|
|
635
515
|
return await res.json();
|
|
636
516
|
},
|
|
@@ -642,11 +522,7 @@ function createSolvaPayClient(opts) {
|
|
|
642
522
|
headers
|
|
643
523
|
});
|
|
644
524
|
if (!res.ok) {
|
|
645
|
-
|
|
646
|
-
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
647
|
-
throw new SolvaPayError(`Get customer balance failed (${res.status}): ${error}`, {
|
|
648
|
-
status: res.status
|
|
649
|
-
});
|
|
525
|
+
await throwApiError("Get customer balance", res);
|
|
650
526
|
}
|
|
651
527
|
return await res.json();
|
|
652
528
|
},
|
|
@@ -659,11 +535,7 @@ function createSolvaPayClient(opts) {
|
|
|
659
535
|
body: JSON.stringify(params)
|
|
660
536
|
});
|
|
661
537
|
if (!res.ok) {
|
|
662
|
-
|
|
663
|
-
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
664
|
-
throw new SolvaPayError(`Create checkout session failed (${res.status}): ${error}`, {
|
|
665
|
-
status: res.status
|
|
666
|
-
});
|
|
538
|
+
await throwApiError("Create checkout session", res);
|
|
667
539
|
}
|
|
668
540
|
const result = await res.json();
|
|
669
541
|
return result;
|
|
@@ -677,11 +549,7 @@ function createSolvaPayClient(opts) {
|
|
|
677
549
|
body: JSON.stringify(params)
|
|
678
550
|
});
|
|
679
551
|
if (!res.ok) {
|
|
680
|
-
|
|
681
|
-
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
682
|
-
throw new SolvaPayError(`Create customer session failed (${res.status}): ${error}`, {
|
|
683
|
-
status: res.status
|
|
684
|
-
});
|
|
552
|
+
await throwApiError("Create customer session", res);
|
|
685
553
|
}
|
|
686
554
|
const result = await res.json();
|
|
687
555
|
return result;
|
|
@@ -695,11 +563,7 @@ function createSolvaPayClient(opts) {
|
|
|
695
563
|
body: JSON.stringify(params)
|
|
696
564
|
});
|
|
697
565
|
if (!res.ok) {
|
|
698
|
-
|
|
699
|
-
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
700
|
-
throw new SolvaPayError(`Activate plan failed (${res.status}): ${error}`, {
|
|
701
|
-
status: res.status
|
|
702
|
-
});
|
|
566
|
+
await throwApiError("Activate plan", res);
|
|
703
567
|
}
|
|
704
568
|
return await res.json();
|
|
705
569
|
},
|
|
@@ -708,11 +572,7 @@ function createSolvaPayClient(opts) {
|
|
|
708
572
|
url.searchParams.set("customerRef", params.customerRef);
|
|
709
573
|
const res = await fetch(url.toString(), { method: "GET", headers });
|
|
710
574
|
if (!res.ok) {
|
|
711
|
-
|
|
712
|
-
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
713
|
-
throw new SolvaPayError(`Get payment method failed (${res.status}): ${error}`, {
|
|
714
|
-
status: res.status
|
|
715
|
-
});
|
|
575
|
+
await throwApiError("Get payment method", res);
|
|
716
576
|
}
|
|
717
577
|
return await res.json();
|
|
718
578
|
},
|
|
@@ -721,11 +581,7 @@ function createSolvaPayClient(opts) {
|
|
|
721
581
|
url.searchParams.set("customerRef", params.customerRef);
|
|
722
582
|
const res = await fetch(url.toString(), { method: "GET", headers });
|
|
723
583
|
if (!res.ok) {
|
|
724
|
-
|
|
725
|
-
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
726
|
-
throw new SolvaPayError(`Get auto-recharge failed (${res.status}): ${error}`, {
|
|
727
|
-
status: res.status
|
|
728
|
-
});
|
|
584
|
+
await throwApiError("Get auto-recharge", res);
|
|
729
585
|
}
|
|
730
586
|
return await res.json();
|
|
731
587
|
},
|
|
@@ -736,11 +592,7 @@ function createSolvaPayClient(opts) {
|
|
|
736
592
|
body: JSON.stringify(params)
|
|
737
593
|
});
|
|
738
594
|
if (!res.ok) {
|
|
739
|
-
|
|
740
|
-
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
741
|
-
throw new SolvaPayError(`Save auto-recharge failed (${res.status}): ${error}`, {
|
|
742
|
-
status: res.status
|
|
743
|
-
});
|
|
595
|
+
await throwApiError("Save auto-recharge", res);
|
|
744
596
|
}
|
|
745
597
|
return await res.json();
|
|
746
598
|
},
|
|
@@ -749,11 +601,7 @@ function createSolvaPayClient(opts) {
|
|
|
749
601
|
url.searchParams.set("customerRef", params.customerRef);
|
|
750
602
|
const res = await fetch(url.toString(), { method: "DELETE", headers });
|
|
751
603
|
if (!res.ok) {
|
|
752
|
-
|
|
753
|
-
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
754
|
-
throw new SolvaPayError(`Disable auto-recharge failed (${res.status}): ${error}`, {
|
|
755
|
-
status: res.status
|
|
756
|
-
});
|
|
604
|
+
await throwApiError("Disable auto-recharge", res);
|
|
757
605
|
}
|
|
758
606
|
return await res.json();
|
|
759
607
|
},
|
|
@@ -767,11 +615,7 @@ function createSolvaPayClient(opts) {
|
|
|
767
615
|
}
|
|
768
616
|
const res = await fetch(url.toString(), { method: "GET", headers });
|
|
769
617
|
if (!res.ok) {
|
|
770
|
-
|
|
771
|
-
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
772
|
-
throw new SolvaPayError(`List purchases failed (${res.status}): ${error}`, {
|
|
773
|
-
status: res.status
|
|
774
|
-
});
|
|
618
|
+
await throwApiError("List purchases", res);
|
|
775
619
|
}
|
|
776
620
|
const data = await res.json();
|
|
777
621
|
return { purchases: data.purchases ?? [] };
|
|
@@ -782,11 +626,7 @@ function createSolvaPayClient(opts) {
|
|
|
782
626
|
if (params.limit !== void 0) url.searchParams.set("limit", String(params.limit));
|
|
783
627
|
const res = await fetch(url.toString(), { method: "GET", headers });
|
|
784
628
|
if (!res.ok) {
|
|
785
|
-
|
|
786
|
-
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
787
|
-
throw new SolvaPayError(`Get credit activity failed (${res.status}): ${error}`, {
|
|
788
|
-
status: res.status
|
|
789
|
-
});
|
|
629
|
+
await throwApiError("Get credit activity", res);
|
|
790
630
|
}
|
|
791
631
|
return await res.json();
|
|
792
632
|
}
|
|
@@ -819,6 +659,12 @@ async function verifyProductConfiguration(options) {
|
|
|
819
659
|
};
|
|
820
660
|
} catch (error) {
|
|
821
661
|
const status = error instanceof SolvaPayError2 ? error.status : void 0;
|
|
662
|
+
const code = error instanceof SolvaPayError2 ? error.code : void 0;
|
|
663
|
+
if (status === 404 && code === "non_json_response") {
|
|
664
|
+
throw new Error(
|
|
665
|
+
`Could not reach ${apiBaseUrl} while verifying SOLVAPAY_PRODUCT_REF "${productRef}": the server returned a non-JSON response (often an offline ngrok tunnel or proxy error page). Check SOLVAPAY_API_BASE_URL and that the API tunnel is running.`
|
|
666
|
+
);
|
|
667
|
+
}
|
|
822
668
|
if (status === 404) {
|
|
823
669
|
throw new Error(
|
|
824
670
|
`SOLVAPAY_PRODUCT_REF "${productRef}" does not exist on ${apiBaseUrl}. OAuth dynamic client registration resolves the provider from this ref, so every connection attempt would fail with a 400 "Invalid identifier" and no MCP host could connect. Point SOLVAPAY_PRODUCT_REF at a product that exists on this account (SolvaPay Console \u2192 Products, or \`npx solvapay init\` / \`npx solvapay doctor\`), and check SOLVAPAY_API_BASE_URL is the environment you created the product in.`
|
|
@@ -837,46 +683,92 @@ async function verifyProductConfiguration(options) {
|
|
|
837
683
|
}
|
|
838
684
|
|
|
839
685
|
// src/paywall-state.ts
|
|
686
|
+
import { minorUnitsPerMajor } from "@solvapay/core";
|
|
840
687
|
var CHECKOUT_SESSION_TTL_MINUTES = 15;
|
|
841
688
|
var DOCS_HINT = "See docs://solvapay/overview.md.";
|
|
689
|
+
function creditSignals(limits) {
|
|
690
|
+
if (!limits) return { isCreditBased: false };
|
|
691
|
+
const creditBalance = limits.balance?.creditBalance ?? limits.creditBalance;
|
|
692
|
+
const creditsPerCall = limits.balance?.creditsPerUnit ?? limits.creditsPerUnit;
|
|
693
|
+
const isCreditBased = creditBalance !== void 0 || creditsPerCall !== void 0;
|
|
694
|
+
const shortfallCredits = creditBalance !== void 0 && creditsPerCall !== void 0 ? Math.max(0, creditsPerCall - creditBalance) : void 0;
|
|
695
|
+
let remainingCalls;
|
|
696
|
+
if (limits.balance?.remainingUnits !== void 0) {
|
|
697
|
+
remainingCalls = limits.balance.remainingUnits;
|
|
698
|
+
} else if (creditBalance !== void 0 && creditsPerCall !== void 0 && creditsPerCall > 0) {
|
|
699
|
+
remainingCalls = Math.floor(creditBalance / creditsPerCall);
|
|
700
|
+
} else if (limits.remaining >= 0) {
|
|
701
|
+
remainingCalls = limits.remaining;
|
|
702
|
+
}
|
|
703
|
+
return { creditBalance, creditsPerCall, shortfallCredits, remainingCalls, isCreditBased };
|
|
704
|
+
}
|
|
705
|
+
function nextActionFor(state) {
|
|
706
|
+
switch (state.kind) {
|
|
707
|
+
case "activation_required":
|
|
708
|
+
return "activate";
|
|
709
|
+
case "topup_required":
|
|
710
|
+
return "topup";
|
|
711
|
+
case "upgrade_required":
|
|
712
|
+
case "limit_reached":
|
|
713
|
+
return "checkout";
|
|
714
|
+
case "reactivation_required":
|
|
715
|
+
return "account";
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
function activePlanRefOf(limits) {
|
|
719
|
+
const ref = limits.planRef ?? limits.plan;
|
|
720
|
+
return ref && ref.length > 0 ? ref : void 0;
|
|
721
|
+
}
|
|
842
722
|
function classifyPaywallState(limits) {
|
|
843
723
|
if (!limits) return { kind: "upgrade_required" };
|
|
844
|
-
if (limits.activationRequired === true) {
|
|
724
|
+
if (limits.activationRequired === true || limits.paywallReason === "activation_required") {
|
|
845
725
|
return { kind: "activation_required" };
|
|
846
726
|
}
|
|
727
|
+
if (limits.paywallReason === "topup_required") {
|
|
728
|
+
return { kind: "topup_required" };
|
|
729
|
+
}
|
|
847
730
|
if (limits.needsTopUp === true) {
|
|
848
731
|
return { kind: "topup_required" };
|
|
849
732
|
}
|
|
850
733
|
if (limits.needsUpgrade === true) {
|
|
851
734
|
return { kind: "upgrade_required" };
|
|
852
735
|
}
|
|
853
|
-
const
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
if (creditBalance ===
|
|
736
|
+
const signals = creditSignals(limits);
|
|
737
|
+
if (signals.isCreditBased) {
|
|
738
|
+
if (signals.creditBalance !== void 0 && signals.creditsPerCall !== void 0 && signals.creditBalance < signals.creditsPerCall) {
|
|
739
|
+
return { kind: "topup_required" };
|
|
740
|
+
}
|
|
741
|
+
if (signals.creditBalance === 0) return { kind: "topup_required" };
|
|
742
|
+
if (signals.creditsPerCall === void 0 && limits.remaining === 0) {
|
|
859
743
|
return { kind: "topup_required" };
|
|
860
744
|
}
|
|
861
745
|
}
|
|
862
|
-
|
|
746
|
+
const activeRef = activePlanRefOf(limits);
|
|
747
|
+
if ((limits.purchaseRef || activeRef) && limits.remaining <= 0) {
|
|
863
748
|
return { kind: "limit_reached" };
|
|
864
749
|
}
|
|
750
|
+
if (signals.isCreditBased) return { kind: "topup_required" };
|
|
751
|
+
if (limits.purchaseRef) return { kind: "limit_reached" };
|
|
865
752
|
return { kind: "upgrade_required" };
|
|
866
753
|
}
|
|
867
754
|
function formatMinor(amountMinor, currency) {
|
|
868
|
-
const
|
|
755
|
+
const divisor = minorUnitsPerMajor(currency);
|
|
756
|
+
const major = amountMinor / divisor;
|
|
757
|
+
const fractionDigits = divisor === 1 ? 0 : 2;
|
|
869
758
|
try {
|
|
870
759
|
return new Intl.NumberFormat("en-US", {
|
|
871
760
|
style: "currency",
|
|
872
761
|
currency: currency.toUpperCase(),
|
|
873
|
-
minimumFractionDigits:
|
|
874
|
-
maximumFractionDigits:
|
|
762
|
+
minimumFractionDigits: fractionDigits,
|
|
763
|
+
maximumFractionDigits: fractionDigits
|
|
875
764
|
}).format(major);
|
|
876
765
|
} catch {
|
|
877
|
-
return `${currency.toUpperCase()} ${major.toFixed(2)}`;
|
|
766
|
+
return `${currency.toUpperCase()} ${divisor === 1 ? String(Math.round(major)) : major.toFixed(2)}`;
|
|
878
767
|
}
|
|
879
768
|
}
|
|
769
|
+
function formatCredits(value) {
|
|
770
|
+
return value.toLocaleString("en-US");
|
|
771
|
+
}
|
|
880
772
|
function meterLabel(gate) {
|
|
881
773
|
if (!gate.meterName) return "units";
|
|
882
774
|
return gate.meterName.replace(/_/g, " ");
|
|
@@ -884,6 +776,14 @@ function meterLabel(gate) {
|
|
|
884
776
|
function namedCheckoutMarkdown(url) {
|
|
885
777
|
return `[Open checkout](${url})`;
|
|
886
778
|
}
|
|
779
|
+
function linkLabel(name) {
|
|
780
|
+
return name.replace(/([[\]])/g, "\\$1");
|
|
781
|
+
}
|
|
782
|
+
function planLadder(gate) {
|
|
783
|
+
const linkable = (gate.plans ?? []).filter((p) => typeof p.checkoutUrl === "string" && p.checkoutUrl.length > 0).filter((p) => p.reference !== gate.planRef).sort((a, b) => a.price - b.price).slice(0, 4);
|
|
784
|
+
if (linkable.length === 0) return null;
|
|
785
|
+
return linkable.map((p) => `[${linkLabel(p.name ?? p.reference)}](${p.checkoutUrl})`).join(" \xB7 ");
|
|
786
|
+
}
|
|
887
787
|
var VIEWER_TOOL = "account";
|
|
888
788
|
function callViewer(view) {
|
|
889
789
|
return view ? `call the \`${VIEWER_TOOL}\` tool with view: '${view}'` : `call the \`${VIEWER_TOOL}\` tool`;
|
|
@@ -894,25 +794,57 @@ function recoverClause(url, verb, view) {
|
|
|
894
794
|
}
|
|
895
795
|
return ` ${callViewer(view).replace(/^c/, "C")}.`;
|
|
896
796
|
}
|
|
797
|
+
function hasActivePlan(gate) {
|
|
798
|
+
return Boolean(gate.purchaseRef || gate.planRef && gate.planRef.length > 0);
|
|
799
|
+
}
|
|
800
|
+
function autoRechargeDisabled(gate) {
|
|
801
|
+
return gate.autoRecharge?.enabled === false;
|
|
802
|
+
}
|
|
897
803
|
function buildGateMessage(state, gate) {
|
|
898
804
|
const url = gate.checkoutUrl && gate.checkoutUrl.length > 0 ? gate.checkoutUrl : null;
|
|
805
|
+
const ladder = planLadder(gate);
|
|
899
806
|
switch (state.kind) {
|
|
900
807
|
case "limit_reached": {
|
|
901
808
|
const included = gate.included;
|
|
902
809
|
const price = gate.unitPriceMinor != null && gate.currency ? formatMinor(gate.unitPriceMinor, gate.currency) : null;
|
|
903
810
|
const usedLine = included ? `You've used ${included.used} of ${included.total} included ${meterLabel(gate)} this period.` : `You've reached the included usage for this period.`;
|
|
904
811
|
const nextLine = price ? ` The next call is ${price}.` : "";
|
|
905
|
-
|
|
812
|
+
const planName = gate.planName ?? "This plan";
|
|
813
|
+
const antiTrap = gate.creditBalance !== void 0 && gate.creditBalance > 0 && (gate.creditsPerCall === void 0 || gate.creditsPerCall === 0) ? ` Adding credits will not help, because ${planName} does not spend them.` : "";
|
|
814
|
+
const switchLine = ladder ? ` Or switch plan: ${ladder}.` : recoverClause(url, "continue", "checkout");
|
|
815
|
+
return `${usedLine}${nextLine}${antiTrap}${switchLine} ${DOCS_HINT}`;
|
|
906
816
|
}
|
|
907
817
|
case "activation_required":
|
|
908
818
|
return `Your plan needs activation.${recoverClause(url, "activate", "checkout")} Or call \`activate_plan\` with a \`planRef\`. ${DOCS_HINT}`;
|
|
909
819
|
case "topup_required": {
|
|
910
|
-
const
|
|
911
|
-
const
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
820
|
+
const balance = gate.creditBalance;
|
|
821
|
+
const cost = gate.creditsPerCall;
|
|
822
|
+
const shortfall = gate.shortfallCredits;
|
|
823
|
+
let lead;
|
|
824
|
+
if (balance !== void 0 && cost !== void 0 && shortfall !== void 0) {
|
|
825
|
+
lead = `Out of credits for this call. Balance ${formatCredits(balance)} credits; this call costs ${formatCredits(cost)} credits \u2014 ${formatCredits(shortfall)} short.`;
|
|
826
|
+
} else {
|
|
827
|
+
lead = "Included usage is exhausted.";
|
|
828
|
+
}
|
|
829
|
+
const topup = recoverClause(url, "add credits", "topup");
|
|
830
|
+
const auto = autoRechargeDisabled(gate) ? " Auto-recharge is off \u2014 turn it on from the account tool to avoid this next time." : "";
|
|
831
|
+
const switchLine = ladder ? ` Or switch plan: ${ladder}.` : "";
|
|
832
|
+
return `${lead}${topup}${auto}${switchLine} ${DOCS_HINT}`;
|
|
833
|
+
}
|
|
834
|
+
case "upgrade_required": {
|
|
835
|
+
if (hasActivePlan(gate)) {
|
|
836
|
+
const planName = gate.planName ?? "This plan";
|
|
837
|
+
const lead = `${planName} is active but its included usage is exhausted, and the automatic switch to the next plan did not complete.`;
|
|
838
|
+
if (ladder) {
|
|
839
|
+
return `${lead} Switch here: ${ladder}. ${DOCS_HINT}`;
|
|
840
|
+
}
|
|
841
|
+
return `${lead}${recoverClause(url, "switch plan", "checkout")} ${DOCS_HINT}`;
|
|
842
|
+
}
|
|
843
|
+
if (ladder) {
|
|
844
|
+
return `You don't have an active plan for this tool. Pick a plan to use this tool: ${ladder} (links expire in ${CHECKOUT_SESSION_TTL_MINUTES} minutes), or ${callViewer("checkout")}. ${DOCS_HINT}`;
|
|
845
|
+
}
|
|
915
846
|
return `You don't have an active plan for this tool.${recoverClause(url, "pick a plan", "checkout")} ${DOCS_HINT}`;
|
|
847
|
+
}
|
|
916
848
|
case "reactivation_required":
|
|
917
849
|
return `Your previous plan is no longer active. ${callViewer("account").replace(/^c/, "C")} to reactivate it, or ${callViewer("checkout")} to pick a new plan. ${DOCS_HINT}`;
|
|
918
850
|
}
|
|
@@ -920,11 +852,18 @@ function buildGateMessage(state, gate) {
|
|
|
920
852
|
function buildNudgeMessage(state, limits) {
|
|
921
853
|
const url = limits?.checkoutUrl && limits.checkoutUrl.length > 0 ? limits.checkoutUrl : null;
|
|
922
854
|
const visitClause = url ? `, or ${namedCheckoutMarkdown(url)}` : "";
|
|
855
|
+
const remaining = creditSignals(limits).remainingCalls;
|
|
923
856
|
switch (state.kind) {
|
|
924
857
|
case "topup_required":
|
|
858
|
+
if (remaining === 0) {
|
|
859
|
+
return `Heads up \u2014 0 calls left \u2014 the next call needs a top-up. ${callViewer("topup").replace(/^c/, "C")} to add more${visitClause}.`;
|
|
860
|
+
}
|
|
925
861
|
return `Heads up \u2014 running low on credits. ${callViewer("topup").replace(/^c/, "C")} to add more${visitClause}.`;
|
|
926
862
|
case "upgrade_required":
|
|
927
863
|
case "limit_reached":
|
|
864
|
+
if (remaining === 0) {
|
|
865
|
+
return `Heads up \u2014 0 calls left \u2014 the next call needs a top-up. ${callViewer("checkout").replace(/^c/, "C")} for more headroom${visitClause}.`;
|
|
866
|
+
}
|
|
928
867
|
return `Heads up \u2014 approaching your plan's limit this period. ${callViewer("checkout").replace(/^c/, "C")} for more headroom${visitClause}.`;
|
|
929
868
|
case "activation_required":
|
|
930
869
|
return `Heads up \u2014 this plan still needs activation. Call the \`activate_plan\` tool with a \`planRef\`${visitClause}.`;
|
|
@@ -940,11 +879,23 @@ function allPaidPlansArePayg(plans) {
|
|
|
940
879
|
if (paidPlans.length === 0) return false;
|
|
941
880
|
return paidPlans.every((p) => p.type === "usage-based" || p.type === "hybrid");
|
|
942
881
|
}
|
|
882
|
+
function activePlanRefOf2(limits) {
|
|
883
|
+
const ref = limits.planRef ?? limits.plan;
|
|
884
|
+
return ref && ref.length > 0 ? ref : void 0;
|
|
885
|
+
}
|
|
943
886
|
function activePlanOf(limits) {
|
|
944
|
-
|
|
945
|
-
|
|
887
|
+
const ref = activePlanRefOf2(limits);
|
|
888
|
+
if (!ref) return void 0;
|
|
889
|
+
return limits.plans?.find((p) => p.reference === ref);
|
|
946
890
|
}
|
|
947
891
|
function includedFromLimits(limits) {
|
|
892
|
+
if (limits.used !== void 0 && limits.limit !== void 0) {
|
|
893
|
+
return {
|
|
894
|
+
total: limits.limit,
|
|
895
|
+
used: limits.used,
|
|
896
|
+
remaining: limits.remaining
|
|
897
|
+
};
|
|
898
|
+
}
|
|
948
899
|
const total = activePlanOf(limits)?.freeUnits;
|
|
949
900
|
if (total == null || total === 0) return void 0;
|
|
950
901
|
const remaining = limits.remaining;
|
|
@@ -954,24 +905,49 @@ function includedFromLimits(limits) {
|
|
|
954
905
|
remaining
|
|
955
906
|
};
|
|
956
907
|
}
|
|
957
|
-
function
|
|
908
|
+
function recoveryLinks(limits) {
|
|
909
|
+
const links = {};
|
|
910
|
+
if (limits.checkoutUrl) {
|
|
911
|
+
if (limits.checkoutUrl.includes("/topup")) {
|
|
912
|
+
links.topup = limits.checkoutUrl;
|
|
913
|
+
} else {
|
|
914
|
+
links.checkout = limits.checkoutUrl;
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
if (limits.confirmationUrl) {
|
|
918
|
+
links.manage = limits.confirmationUrl;
|
|
919
|
+
}
|
|
920
|
+
return Object.keys(links).length > 0 ? links : void 0;
|
|
921
|
+
}
|
|
922
|
+
function recoveryFields(limits, state) {
|
|
958
923
|
const plan = activePlanOf(limits);
|
|
959
924
|
const included = includedFromLimits(limits);
|
|
960
|
-
const
|
|
925
|
+
const signals = creditSignals(limits);
|
|
926
|
+
const planRef = activePlanRefOf2(limits);
|
|
927
|
+
const links = recoveryLinks(limits);
|
|
961
928
|
return {
|
|
962
|
-
...
|
|
929
|
+
...planRef ? { planRef } : {},
|
|
930
|
+
...limits.planName !== void 0 ? { planName: limits.planName } : {},
|
|
963
931
|
...limits.plans !== void 0 ? { plans: limits.plans } : {},
|
|
964
932
|
...limits.meterName !== void 0 ? { meterName: limits.meterName } : {},
|
|
965
933
|
...plan?.perUnitChargeMinor != null ? { unitPriceMinor: plan.perUnitChargeMinor, currency: plan.currency } : limits.currency ? { currency: limits.currency } : {},
|
|
966
934
|
...included ? { included } : {},
|
|
967
|
-
...creditBalance !== void 0 ? { creditBalance } : {}
|
|
935
|
+
...signals.creditBalance !== void 0 ? { creditBalance: signals.creditBalance } : {},
|
|
936
|
+
reason: state.kind,
|
|
937
|
+
nextAction: nextActionFor(state),
|
|
938
|
+
...signals.creditsPerCall !== void 0 ? { creditsPerCall: signals.creditsPerCall } : {},
|
|
939
|
+
...signals.shortfallCredits !== void 0 ? { shortfallCredits: signals.shortfallCredits } : {},
|
|
940
|
+
...signals.remainingCalls !== void 0 ? { remainingCalls: signals.remainingCalls } : {},
|
|
941
|
+
...limits.purchaseRef !== void 0 ? { purchaseRef: limits.purchaseRef } : {},
|
|
942
|
+
...limits.autoRecharge !== void 0 ? { autoRecharge: limits.autoRecharge } : {},
|
|
943
|
+
...links ? { links } : {}
|
|
968
944
|
};
|
|
969
945
|
}
|
|
970
946
|
function buildPaywallGate(productRef, limits) {
|
|
971
947
|
const checkoutUrl = limits.checkoutUrl;
|
|
972
|
-
const state = classifyPaywallState(
|
|
948
|
+
const state = classifyPaywallState(limits);
|
|
973
949
|
const useActivationForTopup = !limits.activationRequired && state.kind === "topup_required" && allPaidPlansArePayg(limits.plans);
|
|
974
|
-
const recovery = recoveryFields(limits);
|
|
950
|
+
const recovery = recoveryFields(limits, state);
|
|
975
951
|
const preMessage = limits.activationRequired || useActivationForTopup ? {
|
|
976
952
|
kind: "activation_required",
|
|
977
953
|
product: productRef,
|
|
@@ -2475,6 +2451,15 @@ async function resolveCustomerRefFromRequest(req, options) {
|
|
|
2475
2451
|
}
|
|
2476
2452
|
|
|
2477
2453
|
// src/types/paywall.ts
|
|
2454
|
+
var PAYWALL_REASONS = [
|
|
2455
|
+
"activation_required",
|
|
2456
|
+
"topup_required",
|
|
2457
|
+
"payment_required",
|
|
2458
|
+
"upgrade_required",
|
|
2459
|
+
"limit_reached",
|
|
2460
|
+
"reactivation_required"
|
|
2461
|
+
];
|
|
2462
|
+
var PAYWALL_NEXT_ACTIONS = ["topup", "checkout", "activate", "account"];
|
|
2478
2463
|
function isPaywallStructuredContent(value) {
|
|
2479
2464
|
if (typeof value !== "object" || value === null || !("kind" in value)) {
|
|
2480
2465
|
return false;
|
|
@@ -2492,6 +2477,7 @@ var includedSchema = z.object({
|
|
|
2492
2477
|
});
|
|
2493
2478
|
var recoveryFields2 = {
|
|
2494
2479
|
planRef: z.string().optional(),
|
|
2480
|
+
planName: z.string().optional(),
|
|
2495
2481
|
plans: z.array(z.record(z.string(), z.unknown())).optional(),
|
|
2496
2482
|
meterName: z.string().optional(),
|
|
2497
2483
|
unitPriceMinor: z.number().optional(),
|
|
@@ -2499,7 +2485,23 @@ var recoveryFields2 = {
|
|
|
2499
2485
|
included: includedSchema.optional(),
|
|
2500
2486
|
creditBalance: z.number().optional(),
|
|
2501
2487
|
balance: z.record(z.string(), z.unknown()).optional(),
|
|
2502
|
-
productDetails: z.record(z.string(), z.unknown()).optional()
|
|
2488
|
+
productDetails: z.record(z.string(), z.unknown()).optional(),
|
|
2489
|
+
reason: z.enum(PAYWALL_REASONS).optional(),
|
|
2490
|
+
nextAction: z.enum(PAYWALL_NEXT_ACTIONS).optional(),
|
|
2491
|
+
creditsPerCall: z.number().optional(),
|
|
2492
|
+
shortfallCredits: z.number().optional(),
|
|
2493
|
+
remainingCalls: z.number().optional(),
|
|
2494
|
+
purchaseRef: z.string().optional(),
|
|
2495
|
+
planStatus: z.string().optional(),
|
|
2496
|
+
autoRecharge: z.object({
|
|
2497
|
+
enabled: z.boolean(),
|
|
2498
|
+
status: z.string().optional()
|
|
2499
|
+
}).optional(),
|
|
2500
|
+
links: z.object({
|
|
2501
|
+
topup: z.string().optional(),
|
|
2502
|
+
checkout: z.string().optional(),
|
|
2503
|
+
manage: z.string().optional()
|
|
2504
|
+
}).optional()
|
|
2503
2505
|
};
|
|
2504
2506
|
var PaywallStructuredContentSchema = z.discriminatedUnion("kind", [
|
|
2505
2507
|
z.object({
|
|
@@ -3664,6 +3666,7 @@ export {
|
|
|
3664
3666
|
createSolvaPay,
|
|
3665
3667
|
createSolvaPayClient,
|
|
3666
3668
|
createTopupPaymentIntentCore,
|
|
3669
|
+
creditSignals,
|
|
3667
3670
|
deriveUsageSnapshot,
|
|
3668
3671
|
disableAutoRechargeCore,
|
|
3669
3672
|
getAuthenticatedUserCore,
|
|
@@ -3677,8 +3680,11 @@ export {
|
|
|
3677
3680
|
handleRouteError,
|
|
3678
3681
|
isErrorResult,
|
|
3679
3682
|
isPaywallStructuredContent,
|
|
3683
|
+
linkLabel,
|
|
3680
3684
|
listPlansCore,
|
|
3685
|
+
nextActionFor,
|
|
3681
3686
|
paywallErrorToClientPayload,
|
|
3687
|
+
planLadder,
|
|
3682
3688
|
processPaymentIntentCore,
|
|
3683
3689
|
processTopupPaymentIntentCore,
|
|
3684
3690
|
reactivatePurchaseCore,
|