@rozoai/checkout 0.1.17 → 0.1.19
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/README.md +15 -2
- package/llms.txt +38 -2
- package/package.json +1 -1
- package/scripts/dist/cli.js +1073 -70
- package/scripts/dist/create-order.js +106 -34
- package/scripts/dist/quote.js +100 -33
- package/scripts/dist/receipt.js +100 -33
- package/scripts/dist/resume.js +100 -33
- package/scripts/dist/send-evm.js +100 -33
- package/scripts/dist/send-sol.js +100 -33
- package/scripts/dist/status.js +119 -35
|
@@ -13,6 +13,8 @@ var SUPPORT = Object.freeze({
|
|
|
13
13
|
discord: "https://discord.gg/EfWejgTbuU"
|
|
14
14
|
});
|
|
15
15
|
var SUPPORT_TEXT = `Need help? Email ${SUPPORT.email}, or reach ROZO on X ${SUPPORT.x} or Discord ${SUPPORT.discord}.`;
|
|
16
|
+
var BULK_DOCS_URL = "https://docs.rozo.ai/products/checkout/bulk-and-agents";
|
|
17
|
+
var BULK_HINT_TEXT = `Buying regularly or for others? ${BULK_DOCS_URL}`;
|
|
16
18
|
var CONTACT_EMAIL_MAX_LENGTH = 254;
|
|
17
19
|
var EMAIL_RE = /^[a-z0-9][^\s@]*@[^\s@.]+(\.[^\s@.]+)+$/;
|
|
18
20
|
function normalizeContactEmail(raw) {
|
|
@@ -60,6 +62,7 @@ function redact(text) {
|
|
|
60
62
|
return `${scheme}://${host}${hadMore ? "/<redacted>" : ""}`;
|
|
61
63
|
});
|
|
62
64
|
s = s.replace(/\b(bearer)\s+[A-Za-z0-9._~+/=-]{8,}/gi, "$1 <redacted>");
|
|
65
|
+
s = s.replace(/\bak_[A-Za-z0-9_-]{8,}/g, "ak_<redacted>");
|
|
63
66
|
s = s.replace(
|
|
64
67
|
/\b(api[-_]?key|apikey|access[-_]?token|auth[-_]?token|secret|token|password|passwd|pwd)\b(\s*[:=]\s*)("?)[A-Za-z0-9._~+/=-]{6,}\3/gi,
|
|
65
68
|
"$1$2<redacted>"
|
|
@@ -198,18 +201,66 @@ function maskMemo(memo) {
|
|
|
198
201
|
return `${s.slice(0, 4)}...${s.slice(-3)}`;
|
|
199
202
|
}
|
|
200
203
|
|
|
201
|
-
// scripts/src/lib/
|
|
204
|
+
// scripts/src/lib/version.mjs
|
|
202
205
|
import { createRequire } from "node:module";
|
|
206
|
+
var PKG_VERSION = (() => {
|
|
207
|
+
const requireFrom = createRequire(import.meta.url);
|
|
208
|
+
for (const candidate of ["../../package.json", "../../../package.json"]) {
|
|
209
|
+
try {
|
|
210
|
+
const pkg = requireFrom(candidate);
|
|
211
|
+
if (pkg?.name === "@rozoai/checkout" && pkg.version) return pkg.version;
|
|
212
|
+
} catch {
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return "0.0.0";
|
|
216
|
+
})();
|
|
203
217
|
|
|
204
218
|
// scripts/src/lib/http.mjs
|
|
205
219
|
var DEFAULT_TIMEOUT_MS = 2e4;
|
|
206
|
-
var USER_AGENT =
|
|
207
|
-
|
|
220
|
+
var USER_AGENT = `rozo-checkout-skill/${PKG_VERSION}`;
|
|
221
|
+
var MAX_RETRIES = 3;
|
|
222
|
+
var MAX_TOTAL_WAIT_MS = 12e4;
|
|
223
|
+
var FALLBACK_BACKOFF_MS = [2e3, 4e3, 8e3];
|
|
224
|
+
var defaultSleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
225
|
+
function parseRetryAfter(value, now = Date.now()) {
|
|
226
|
+
if (value === null || value === void 0) return null;
|
|
227
|
+
const s = String(value).trim();
|
|
228
|
+
if (!s) return null;
|
|
229
|
+
if (/^\d+(\.\d+)?$/.test(s)) return Math.round(Number(s) * 1e3);
|
|
230
|
+
const at = Date.parse(s);
|
|
231
|
+
if (!Number.isFinite(at)) return null;
|
|
232
|
+
return Math.max(0, at - now);
|
|
233
|
+
}
|
|
234
|
+
function bodyRetryAfterMs(json) {
|
|
235
|
+
const raw = json?.retryAfterSeconds ?? json?.error?.retryAfterSeconds;
|
|
236
|
+
if (raw === null || raw === void 0 || raw === "") return null;
|
|
237
|
+
const n = Number(raw);
|
|
238
|
+
return Number.isFinite(n) && n >= 0 ? Math.round(n * 1e3) : null;
|
|
239
|
+
}
|
|
240
|
+
function rateLimitInfo(res) {
|
|
241
|
+
const pick = (name) => res.headers?.get?.(name) ?? null;
|
|
242
|
+
const info = {
|
|
243
|
+
limit: pick("x-ratelimit-limit"),
|
|
244
|
+
remaining: pick("x-ratelimit-remaining"),
|
|
245
|
+
tier: pick("x-ratelimit-tier"),
|
|
246
|
+
scope: pick("x-ratelimit-scope")
|
|
247
|
+
};
|
|
248
|
+
const present = Object.fromEntries(Object.entries(info).filter(([, v]) => v !== null));
|
|
249
|
+
return Object.keys(present).length ? present : null;
|
|
250
|
+
}
|
|
251
|
+
function parseBody(text) {
|
|
252
|
+
if (!text) return null;
|
|
253
|
+
try {
|
|
254
|
+
return JSON.parse(text);
|
|
255
|
+
} catch {
|
|
256
|
+
return null;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
async function fetchOnce(method, url, body, timeoutMs) {
|
|
208
260
|
const controller = new AbortController();
|
|
209
261
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
210
|
-
let res;
|
|
211
262
|
try {
|
|
212
|
-
res = await fetch(url, {
|
|
263
|
+
const res = await fetch(url, {
|
|
213
264
|
method,
|
|
214
265
|
headers: {
|
|
215
266
|
accept: "application/json",
|
|
@@ -219,6 +270,8 @@ async function request(method, url, { body, timeoutMs = DEFAULT_TIMEOUT_MS } = {
|
|
|
219
270
|
body: body !== void 0 ? JSON.stringify(body) : void 0,
|
|
220
271
|
signal: controller.signal
|
|
221
272
|
});
|
|
273
|
+
const text = await res.text();
|
|
274
|
+
return { res, text };
|
|
222
275
|
} catch (err) {
|
|
223
276
|
throw new SkillError(
|
|
224
277
|
err?.name === "AbortError" ? "HTTP_TIMEOUT" : "HTTP_UNREACHABLE",
|
|
@@ -228,31 +281,54 @@ async function request(method, url, { body, timeoutMs = DEFAULT_TIMEOUT_MS } = {
|
|
|
228
281
|
} finally {
|
|
229
282
|
clearTimeout(timer);
|
|
230
283
|
}
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
284
|
+
}
|
|
285
|
+
async function request(method, url, {
|
|
286
|
+
body,
|
|
287
|
+
timeoutMs = DEFAULT_TIMEOUT_MS,
|
|
288
|
+
sleep = defaultSleep,
|
|
289
|
+
now = Date.now,
|
|
290
|
+
maxTotalWaitMs = MAX_TOTAL_WAIT_MS
|
|
291
|
+
} = {}) {
|
|
292
|
+
let waitedMs = 0;
|
|
293
|
+
let retries = 0;
|
|
294
|
+
for (; ; ) {
|
|
295
|
+
const { res, text } = await fetchOnce(method, url, body, timeoutMs);
|
|
296
|
+
const json = parseBody(text);
|
|
297
|
+
if (res.ok) {
|
|
298
|
+
if (json === null) {
|
|
299
|
+
throw new SkillError("HTTP_BAD_JSON", "Endpoint returned a non-JSON body.", {
|
|
300
|
+
url: redactUrl(url),
|
|
301
|
+
snippet: redact(text).slice(0, 400)
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
return json;
|
|
305
|
+
}
|
|
306
|
+
const headerWaitMs = parseRetryAfter(res.headers?.get?.("retry-after"), now());
|
|
307
|
+
const hintMs = headerWaitMs ?? bodyRetryAfterMs(json);
|
|
308
|
+
const retryable = res.status === 429 || res.status === 503 && hintMs !== null;
|
|
309
|
+
if (retryable && retries < MAX_RETRIES) {
|
|
310
|
+
const waitMs = hintMs ?? FALLBACK_BACKOFF_MS[retries];
|
|
311
|
+
if (waitedMs + waitMs <= Math.min(maxTotalWaitMs, MAX_TOTAL_WAIT_MS)) {
|
|
312
|
+
retries += 1;
|
|
313
|
+
waitedMs += waitMs;
|
|
314
|
+
await sleep(waitMs);
|
|
315
|
+
continue;
|
|
316
|
+
}
|
|
238
317
|
}
|
|
239
|
-
}
|
|
240
|
-
if (!res.ok) {
|
|
241
318
|
const code = json?.code || json?.error?.code || (typeof json?.error === "string" && /^[A-Z][A-Z0-9_]+$/.test(json.error) ? json.error : null) || `HTTP_${res.status}`;
|
|
242
319
|
const message = json?.message || (typeof json?.error === "string" ? json.error : json?.error?.message) || `HTTP ${res.status}`;
|
|
320
|
+
const rateLimit = rateLimitInfo(res);
|
|
243
321
|
throw new SkillError(code, redact(String(message)), {
|
|
244
322
|
httpStatus: res.status,
|
|
245
323
|
url: redactUrl(url),
|
|
246
|
-
body: json ?? redact(text).slice(0, 800)
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
snippet: redact(text).slice(0, 400)
|
|
324
|
+
body: json ?? redact(text).slice(0, 800),
|
|
325
|
+
...retryable ? {
|
|
326
|
+
retries,
|
|
327
|
+
retryAfterSeconds: hintMs === null ? null : Math.ceil(hintMs / 1e3)
|
|
328
|
+
} : {},
|
|
329
|
+
...rateLimit ? { rateLimit } : {}
|
|
253
330
|
});
|
|
254
331
|
}
|
|
255
|
-
return json;
|
|
256
332
|
}
|
|
257
333
|
function redactUrl(url) {
|
|
258
334
|
try {
|
|
@@ -582,17 +658,6 @@ function attributionIdentity(env = process.env) {
|
|
|
582
658
|
}
|
|
583
659
|
|
|
584
660
|
// scripts/src/lib/api.mjs
|
|
585
|
-
var PKG_VERSION = (() => {
|
|
586
|
-
const requireFrom = createRequire(import.meta.url);
|
|
587
|
-
for (const candidate of ["../../package.json", "../../../package.json"]) {
|
|
588
|
-
try {
|
|
589
|
-
const pkg = requireFrom(candidate);
|
|
590
|
-
if (pkg?.name === "@rozoai/checkout" && pkg.version) return pkg.version;
|
|
591
|
-
} catch {
|
|
592
|
-
}
|
|
593
|
-
}
|
|
594
|
-
return "0.0.0";
|
|
595
|
-
})();
|
|
596
661
|
var MPP_BASE = process.env.ROZO_CHECKOUT_MPP_BASE || "https://apiserver.mpprouter.dev/v1/services/rozo-agent-api";
|
|
597
662
|
var CLIENT_LABEL = `rozo-checkout-cli/${PKG_VERSION}`;
|
|
598
663
|
var ATTRIBUTION_CLIENT = `rozo-checkout-skill/${PKG_VERSION}`;
|
|
@@ -612,6 +677,7 @@ async function quoteInvoice({ url, linkId }) {
|
|
|
612
677
|
const body = url ? { url } : { payment_id: linkId };
|
|
613
678
|
return postJson(`${MPP_BASE}/quote-invoice`, body);
|
|
614
679
|
}
|
|
680
|
+
var QUOTE_RECEIPT_RETRY_BUDGET_MS = 2e4;
|
|
615
681
|
async function createInvoice({ url, linkId, source, quoteReceipt, utmSource, email }) {
|
|
616
682
|
const body = {
|
|
617
683
|
...url ? { url } : { payment_id: linkId },
|
|
@@ -623,7 +689,11 @@ async function createInvoice({ url, linkId, source, quoteReceipt, utmSource, ema
|
|
|
623
689
|
// sent empty, when the user gave none.
|
|
624
690
|
...email ? { email } : {}
|
|
625
691
|
};
|
|
626
|
-
return postJson(
|
|
692
|
+
return postJson(
|
|
693
|
+
`${MPP_BASE}/create-invoice`,
|
|
694
|
+
body,
|
|
695
|
+
quoteReceipt ? { maxTotalWaitMs: QUOTE_RECEIPT_RETRY_BUDGET_MS } : void 0
|
|
696
|
+
);
|
|
627
697
|
}
|
|
628
698
|
async function invoiceStatus({ linkId, rozoPaymentId }) {
|
|
629
699
|
const qs = new URLSearchParams();
|
|
@@ -812,6 +882,8 @@ var MARGINS_MS = {
|
|
|
812
882
|
};
|
|
813
883
|
var BOLT11_MIN_VALIDITY_MS = 10 * MINUTE;
|
|
814
884
|
var DEFAULT_MARGIN_MS = 10 * MINUTE;
|
|
885
|
+
var DEFAULT_WATCH_MS = 10 * MINUTE;
|
|
886
|
+
var LIGHTNING_WATCH_CAP_MS = 60 * MINUTE;
|
|
815
887
|
function formatRemaining(ms) {
|
|
816
888
|
if (!Number.isFinite(ms)) return "unknown";
|
|
817
889
|
if (ms <= 0) return "expired";
|
package/scripts/dist/quote.js
CHANGED
|
@@ -13,6 +13,8 @@ var SUPPORT = Object.freeze({
|
|
|
13
13
|
discord: "https://discord.gg/EfWejgTbuU"
|
|
14
14
|
});
|
|
15
15
|
var SUPPORT_TEXT = `Need help? Email ${SUPPORT.email}, or reach ROZO on X ${SUPPORT.x} or Discord ${SUPPORT.discord}.`;
|
|
16
|
+
var BULK_DOCS_URL = "https://docs.rozo.ai/products/checkout/bulk-and-agents";
|
|
17
|
+
var BULK_HINT_TEXT = `Buying regularly or for others? ${BULK_DOCS_URL}`;
|
|
16
18
|
|
|
17
19
|
// scripts/src/lib/output.mjs
|
|
18
20
|
var PUBLIC_SUPPORT_URLS = /* @__PURE__ */ new Set([SUPPORT.x, SUPPORT.discord]);
|
|
@@ -34,6 +36,7 @@ function redact(text) {
|
|
|
34
36
|
return `${scheme}://${host}${hadMore ? "/<redacted>" : ""}`;
|
|
35
37
|
});
|
|
36
38
|
s = s.replace(/\b(bearer)\s+[A-Za-z0-9._~+/=-]{8,}/gi, "$1 <redacted>");
|
|
39
|
+
s = s.replace(/\bak_[A-Za-z0-9_-]{8,}/g, "ak_<redacted>");
|
|
37
40
|
s = s.replace(
|
|
38
41
|
/\b(api[-_]?key|apikey|access[-_]?token|auth[-_]?token|secret|token|password|passwd|pwd)\b(\s*[:=]\s*)("?)[A-Za-z0-9._~+/=-]{6,}\3/gi,
|
|
39
42
|
"$1$2<redacted>"
|
|
@@ -152,18 +155,66 @@ function extractLinkId(urlOrId) {
|
|
|
152
155
|
);
|
|
153
156
|
}
|
|
154
157
|
|
|
155
|
-
// scripts/src/lib/
|
|
158
|
+
// scripts/src/lib/version.mjs
|
|
156
159
|
import { createRequire } from "node:module";
|
|
160
|
+
var PKG_VERSION = (() => {
|
|
161
|
+
const requireFrom = createRequire(import.meta.url);
|
|
162
|
+
for (const candidate of ["../../package.json", "../../../package.json"]) {
|
|
163
|
+
try {
|
|
164
|
+
const pkg = requireFrom(candidate);
|
|
165
|
+
if (pkg?.name === "@rozoai/checkout" && pkg.version) return pkg.version;
|
|
166
|
+
} catch {
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return "0.0.0";
|
|
170
|
+
})();
|
|
157
171
|
|
|
158
172
|
// scripts/src/lib/http.mjs
|
|
159
173
|
var DEFAULT_TIMEOUT_MS = 2e4;
|
|
160
|
-
var USER_AGENT =
|
|
161
|
-
|
|
174
|
+
var USER_AGENT = `rozo-checkout-skill/${PKG_VERSION}`;
|
|
175
|
+
var MAX_RETRIES = 3;
|
|
176
|
+
var MAX_TOTAL_WAIT_MS = 12e4;
|
|
177
|
+
var FALLBACK_BACKOFF_MS = [2e3, 4e3, 8e3];
|
|
178
|
+
var defaultSleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
179
|
+
function parseRetryAfter(value, now = Date.now()) {
|
|
180
|
+
if (value === null || value === void 0) return null;
|
|
181
|
+
const s = String(value).trim();
|
|
182
|
+
if (!s) return null;
|
|
183
|
+
if (/^\d+(\.\d+)?$/.test(s)) return Math.round(Number(s) * 1e3);
|
|
184
|
+
const at = Date.parse(s);
|
|
185
|
+
if (!Number.isFinite(at)) return null;
|
|
186
|
+
return Math.max(0, at - now);
|
|
187
|
+
}
|
|
188
|
+
function bodyRetryAfterMs(json) {
|
|
189
|
+
const raw = json?.retryAfterSeconds ?? json?.error?.retryAfterSeconds;
|
|
190
|
+
if (raw === null || raw === void 0 || raw === "") return null;
|
|
191
|
+
const n = Number(raw);
|
|
192
|
+
return Number.isFinite(n) && n >= 0 ? Math.round(n * 1e3) : null;
|
|
193
|
+
}
|
|
194
|
+
function rateLimitInfo(res) {
|
|
195
|
+
const pick = (name) => res.headers?.get?.(name) ?? null;
|
|
196
|
+
const info = {
|
|
197
|
+
limit: pick("x-ratelimit-limit"),
|
|
198
|
+
remaining: pick("x-ratelimit-remaining"),
|
|
199
|
+
tier: pick("x-ratelimit-tier"),
|
|
200
|
+
scope: pick("x-ratelimit-scope")
|
|
201
|
+
};
|
|
202
|
+
const present = Object.fromEntries(Object.entries(info).filter(([, v]) => v !== null));
|
|
203
|
+
return Object.keys(present).length ? present : null;
|
|
204
|
+
}
|
|
205
|
+
function parseBody(text) {
|
|
206
|
+
if (!text) return null;
|
|
207
|
+
try {
|
|
208
|
+
return JSON.parse(text);
|
|
209
|
+
} catch {
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
async function fetchOnce(method, url, body, timeoutMs) {
|
|
162
214
|
const controller = new AbortController();
|
|
163
215
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
164
|
-
let res;
|
|
165
216
|
try {
|
|
166
|
-
res = await fetch(url, {
|
|
217
|
+
const res = await fetch(url, {
|
|
167
218
|
method,
|
|
168
219
|
headers: {
|
|
169
220
|
accept: "application/json",
|
|
@@ -173,6 +224,8 @@ async function request(method, url, { body, timeoutMs = DEFAULT_TIMEOUT_MS } = {
|
|
|
173
224
|
body: body !== void 0 ? JSON.stringify(body) : void 0,
|
|
174
225
|
signal: controller.signal
|
|
175
226
|
});
|
|
227
|
+
const text = await res.text();
|
|
228
|
+
return { res, text };
|
|
176
229
|
} catch (err) {
|
|
177
230
|
throw new SkillError(
|
|
178
231
|
err?.name === "AbortError" ? "HTTP_TIMEOUT" : "HTTP_UNREACHABLE",
|
|
@@ -182,31 +235,54 @@ async function request(method, url, { body, timeoutMs = DEFAULT_TIMEOUT_MS } = {
|
|
|
182
235
|
} finally {
|
|
183
236
|
clearTimeout(timer);
|
|
184
237
|
}
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
238
|
+
}
|
|
239
|
+
async function request(method, url, {
|
|
240
|
+
body,
|
|
241
|
+
timeoutMs = DEFAULT_TIMEOUT_MS,
|
|
242
|
+
sleep = defaultSleep,
|
|
243
|
+
now = Date.now,
|
|
244
|
+
maxTotalWaitMs = MAX_TOTAL_WAIT_MS
|
|
245
|
+
} = {}) {
|
|
246
|
+
let waitedMs = 0;
|
|
247
|
+
let retries = 0;
|
|
248
|
+
for (; ; ) {
|
|
249
|
+
const { res, text } = await fetchOnce(method, url, body, timeoutMs);
|
|
250
|
+
const json = parseBody(text);
|
|
251
|
+
if (res.ok) {
|
|
252
|
+
if (json === null) {
|
|
253
|
+
throw new SkillError("HTTP_BAD_JSON", "Endpoint returned a non-JSON body.", {
|
|
254
|
+
url: redactUrl(url),
|
|
255
|
+
snippet: redact(text).slice(0, 400)
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
return json;
|
|
259
|
+
}
|
|
260
|
+
const headerWaitMs = parseRetryAfter(res.headers?.get?.("retry-after"), now());
|
|
261
|
+
const hintMs = headerWaitMs ?? bodyRetryAfterMs(json);
|
|
262
|
+
const retryable = res.status === 429 || res.status === 503 && hintMs !== null;
|
|
263
|
+
if (retryable && retries < MAX_RETRIES) {
|
|
264
|
+
const waitMs = hintMs ?? FALLBACK_BACKOFF_MS[retries];
|
|
265
|
+
if (waitedMs + waitMs <= Math.min(maxTotalWaitMs, MAX_TOTAL_WAIT_MS)) {
|
|
266
|
+
retries += 1;
|
|
267
|
+
waitedMs += waitMs;
|
|
268
|
+
await sleep(waitMs);
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
192
271
|
}
|
|
193
|
-
}
|
|
194
|
-
if (!res.ok) {
|
|
195
272
|
const code = json?.code || json?.error?.code || (typeof json?.error === "string" && /^[A-Z][A-Z0-9_]+$/.test(json.error) ? json.error : null) || `HTTP_${res.status}`;
|
|
196
273
|
const message = json?.message || (typeof json?.error === "string" ? json.error : json?.error?.message) || `HTTP ${res.status}`;
|
|
274
|
+
const rateLimit = rateLimitInfo(res);
|
|
197
275
|
throw new SkillError(code, redact(String(message)), {
|
|
198
276
|
httpStatus: res.status,
|
|
199
277
|
url: redactUrl(url),
|
|
200
|
-
body: json ?? redact(text).slice(0, 800)
|
|
278
|
+
body: json ?? redact(text).slice(0, 800),
|
|
279
|
+
...retryable ? {
|
|
280
|
+
retries,
|
|
281
|
+
retryAfterSeconds: hintMs === null ? null : Math.ceil(hintMs / 1e3)
|
|
282
|
+
} : {},
|
|
283
|
+
...rateLimit ? { rateLimit } : {}
|
|
201
284
|
});
|
|
202
285
|
}
|
|
203
|
-
if (json === null) {
|
|
204
|
-
throw new SkillError("HTTP_BAD_JSON", "Endpoint returned a non-JSON body.", {
|
|
205
|
-
url: redactUrl(url),
|
|
206
|
-
snippet: redact(text).slice(0, 400)
|
|
207
|
-
});
|
|
208
|
-
}
|
|
209
|
-
return json;
|
|
210
286
|
}
|
|
211
287
|
function redactUrl(url) {
|
|
212
288
|
try {
|
|
@@ -221,17 +297,6 @@ function postJson(url, body, opts) {
|
|
|
221
297
|
}
|
|
222
298
|
|
|
223
299
|
// scripts/src/lib/api.mjs
|
|
224
|
-
var PKG_VERSION = (() => {
|
|
225
|
-
const requireFrom = createRequire(import.meta.url);
|
|
226
|
-
for (const candidate of ["../../package.json", "../../../package.json"]) {
|
|
227
|
-
try {
|
|
228
|
-
const pkg = requireFrom(candidate);
|
|
229
|
-
if (pkg?.name === "@rozoai/checkout" && pkg.version) return pkg.version;
|
|
230
|
-
} catch {
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
return "0.0.0";
|
|
234
|
-
})();
|
|
235
300
|
var MPP_BASE = process.env.ROZO_CHECKOUT_MPP_BASE || "https://apiserver.mpprouter.dev/v1/services/rozo-agent-api";
|
|
236
301
|
var CLIENT_LABEL = `rozo-checkout-cli/${PKG_VERSION}`;
|
|
237
302
|
var ATTRIBUTION_CLIENT = `rozo-checkout-skill/${PKG_VERSION}`;
|
|
@@ -300,6 +365,8 @@ var MARGINS_MS = {
|
|
|
300
365
|
};
|
|
301
366
|
var BOLT11_MIN_VALIDITY_MS = 10 * MINUTE;
|
|
302
367
|
var DEFAULT_MARGIN_MS = 10 * MINUTE;
|
|
368
|
+
var DEFAULT_WATCH_MS = 10 * MINUTE;
|
|
369
|
+
var LIGHTNING_WATCH_CAP_MS = 60 * MINUTE;
|
|
303
370
|
function parseDeadline(value) {
|
|
304
371
|
if (value === null || value === void 0 || value === "") return null;
|
|
305
372
|
if (typeof value === "number" && Number.isFinite(value)) {
|
package/scripts/dist/receipt.js
CHANGED
|
@@ -13,6 +13,8 @@ var SUPPORT = Object.freeze({
|
|
|
13
13
|
discord: "https://discord.gg/EfWejgTbuU"
|
|
14
14
|
});
|
|
15
15
|
var SUPPORT_TEXT = `Need help? Email ${SUPPORT.email}, or reach ROZO on X ${SUPPORT.x} or Discord ${SUPPORT.discord}.`;
|
|
16
|
+
var BULK_DOCS_URL = "https://docs.rozo.ai/products/checkout/bulk-and-agents";
|
|
17
|
+
var BULK_HINT_TEXT = `Buying regularly or for others? ${BULK_DOCS_URL}`;
|
|
16
18
|
|
|
17
19
|
// scripts/src/lib/output.mjs
|
|
18
20
|
var PUBLIC_SUPPORT_URLS = /* @__PURE__ */ new Set([SUPPORT.x, SUPPORT.discord]);
|
|
@@ -34,6 +36,7 @@ function redact(text) {
|
|
|
34
36
|
return `${scheme}://${host}${hadMore ? "/<redacted>" : ""}`;
|
|
35
37
|
});
|
|
36
38
|
s = s.replace(/\b(bearer)\s+[A-Za-z0-9._~+/=-]{8,}/gi, "$1 <redacted>");
|
|
39
|
+
s = s.replace(/\bak_[A-Za-z0-9_-]{8,}/g, "ak_<redacted>");
|
|
37
40
|
s = s.replace(
|
|
38
41
|
/\b(api[-_]?key|apikey|access[-_]?token|auth[-_]?token|secret|token|password|passwd|pwd)\b(\s*[:=]\s*)("?)[A-Za-z0-9._~+/=-]{6,}\3/gi,
|
|
39
42
|
"$1$2<redacted>"
|
|
@@ -138,18 +141,66 @@ function maskAddress(address) {
|
|
|
138
141
|
return `${s.slice(0, 6)}...${s.slice(-4)}`;
|
|
139
142
|
}
|
|
140
143
|
|
|
141
|
-
// scripts/src/lib/
|
|
144
|
+
// scripts/src/lib/version.mjs
|
|
142
145
|
import { createRequire } from "node:module";
|
|
146
|
+
var PKG_VERSION = (() => {
|
|
147
|
+
const requireFrom = createRequire(import.meta.url);
|
|
148
|
+
for (const candidate of ["../../package.json", "../../../package.json"]) {
|
|
149
|
+
try {
|
|
150
|
+
const pkg = requireFrom(candidate);
|
|
151
|
+
if (pkg?.name === "@rozoai/checkout" && pkg.version) return pkg.version;
|
|
152
|
+
} catch {
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return "0.0.0";
|
|
156
|
+
})();
|
|
143
157
|
|
|
144
158
|
// scripts/src/lib/http.mjs
|
|
145
159
|
var DEFAULT_TIMEOUT_MS = 2e4;
|
|
146
|
-
var USER_AGENT =
|
|
147
|
-
|
|
160
|
+
var USER_AGENT = `rozo-checkout-skill/${PKG_VERSION}`;
|
|
161
|
+
var MAX_RETRIES = 3;
|
|
162
|
+
var MAX_TOTAL_WAIT_MS = 12e4;
|
|
163
|
+
var FALLBACK_BACKOFF_MS = [2e3, 4e3, 8e3];
|
|
164
|
+
var defaultSleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
165
|
+
function parseRetryAfter(value, now = Date.now()) {
|
|
166
|
+
if (value === null || value === void 0) return null;
|
|
167
|
+
const s = String(value).trim();
|
|
168
|
+
if (!s) return null;
|
|
169
|
+
if (/^\d+(\.\d+)?$/.test(s)) return Math.round(Number(s) * 1e3);
|
|
170
|
+
const at = Date.parse(s);
|
|
171
|
+
if (!Number.isFinite(at)) return null;
|
|
172
|
+
return Math.max(0, at - now);
|
|
173
|
+
}
|
|
174
|
+
function bodyRetryAfterMs(json) {
|
|
175
|
+
const raw = json?.retryAfterSeconds ?? json?.error?.retryAfterSeconds;
|
|
176
|
+
if (raw === null || raw === void 0 || raw === "") return null;
|
|
177
|
+
const n = Number(raw);
|
|
178
|
+
return Number.isFinite(n) && n >= 0 ? Math.round(n * 1e3) : null;
|
|
179
|
+
}
|
|
180
|
+
function rateLimitInfo(res) {
|
|
181
|
+
const pick = (name) => res.headers?.get?.(name) ?? null;
|
|
182
|
+
const info = {
|
|
183
|
+
limit: pick("x-ratelimit-limit"),
|
|
184
|
+
remaining: pick("x-ratelimit-remaining"),
|
|
185
|
+
tier: pick("x-ratelimit-tier"),
|
|
186
|
+
scope: pick("x-ratelimit-scope")
|
|
187
|
+
};
|
|
188
|
+
const present = Object.fromEntries(Object.entries(info).filter(([, v]) => v !== null));
|
|
189
|
+
return Object.keys(present).length ? present : null;
|
|
190
|
+
}
|
|
191
|
+
function parseBody(text) {
|
|
192
|
+
if (!text) return null;
|
|
193
|
+
try {
|
|
194
|
+
return JSON.parse(text);
|
|
195
|
+
} catch {
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
async function fetchOnce(method, url, body, timeoutMs) {
|
|
148
200
|
const controller = new AbortController();
|
|
149
201
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
150
|
-
let res;
|
|
151
202
|
try {
|
|
152
|
-
res = await fetch(url, {
|
|
203
|
+
const res = await fetch(url, {
|
|
153
204
|
method,
|
|
154
205
|
headers: {
|
|
155
206
|
accept: "application/json",
|
|
@@ -159,6 +210,8 @@ async function request(method, url, { body, timeoutMs = DEFAULT_TIMEOUT_MS } = {
|
|
|
159
210
|
body: body !== void 0 ? JSON.stringify(body) : void 0,
|
|
160
211
|
signal: controller.signal
|
|
161
212
|
});
|
|
213
|
+
const text = await res.text();
|
|
214
|
+
return { res, text };
|
|
162
215
|
} catch (err) {
|
|
163
216
|
throw new SkillError(
|
|
164
217
|
err?.name === "AbortError" ? "HTTP_TIMEOUT" : "HTTP_UNREACHABLE",
|
|
@@ -168,31 +221,54 @@ async function request(method, url, { body, timeoutMs = DEFAULT_TIMEOUT_MS } = {
|
|
|
168
221
|
} finally {
|
|
169
222
|
clearTimeout(timer);
|
|
170
223
|
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
224
|
+
}
|
|
225
|
+
async function request(method, url, {
|
|
226
|
+
body,
|
|
227
|
+
timeoutMs = DEFAULT_TIMEOUT_MS,
|
|
228
|
+
sleep = defaultSleep,
|
|
229
|
+
now = Date.now,
|
|
230
|
+
maxTotalWaitMs = MAX_TOTAL_WAIT_MS
|
|
231
|
+
} = {}) {
|
|
232
|
+
let waitedMs = 0;
|
|
233
|
+
let retries = 0;
|
|
234
|
+
for (; ; ) {
|
|
235
|
+
const { res, text } = await fetchOnce(method, url, body, timeoutMs);
|
|
236
|
+
const json = parseBody(text);
|
|
237
|
+
if (res.ok) {
|
|
238
|
+
if (json === null) {
|
|
239
|
+
throw new SkillError("HTTP_BAD_JSON", "Endpoint returned a non-JSON body.", {
|
|
240
|
+
url: redactUrl(url),
|
|
241
|
+
snippet: redact(text).slice(0, 400)
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
return json;
|
|
245
|
+
}
|
|
246
|
+
const headerWaitMs = parseRetryAfter(res.headers?.get?.("retry-after"), now());
|
|
247
|
+
const hintMs = headerWaitMs ?? bodyRetryAfterMs(json);
|
|
248
|
+
const retryable = res.status === 429 || res.status === 503 && hintMs !== null;
|
|
249
|
+
if (retryable && retries < MAX_RETRIES) {
|
|
250
|
+
const waitMs = hintMs ?? FALLBACK_BACKOFF_MS[retries];
|
|
251
|
+
if (waitedMs + waitMs <= Math.min(maxTotalWaitMs, MAX_TOTAL_WAIT_MS)) {
|
|
252
|
+
retries += 1;
|
|
253
|
+
waitedMs += waitMs;
|
|
254
|
+
await sleep(waitMs);
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
178
257
|
}
|
|
179
|
-
}
|
|
180
|
-
if (!res.ok) {
|
|
181
258
|
const code = json?.code || json?.error?.code || (typeof json?.error === "string" && /^[A-Z][A-Z0-9_]+$/.test(json.error) ? json.error : null) || `HTTP_${res.status}`;
|
|
182
259
|
const message = json?.message || (typeof json?.error === "string" ? json.error : json?.error?.message) || `HTTP ${res.status}`;
|
|
260
|
+
const rateLimit = rateLimitInfo(res);
|
|
183
261
|
throw new SkillError(code, redact(String(message)), {
|
|
184
262
|
httpStatus: res.status,
|
|
185
263
|
url: redactUrl(url),
|
|
186
|
-
body: json ?? redact(text).slice(0, 800)
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
snippet: redact(text).slice(0, 400)
|
|
264
|
+
body: json ?? redact(text).slice(0, 800),
|
|
265
|
+
...retryable ? {
|
|
266
|
+
retries,
|
|
267
|
+
retryAfterSeconds: hintMs === null ? null : Math.ceil(hintMs / 1e3)
|
|
268
|
+
} : {},
|
|
269
|
+
...rateLimit ? { rateLimit } : {}
|
|
193
270
|
});
|
|
194
271
|
}
|
|
195
|
-
return json;
|
|
196
272
|
}
|
|
197
273
|
function redactUrl(url) {
|
|
198
274
|
try {
|
|
@@ -258,17 +334,6 @@ function findByLinkId(linkId) {
|
|
|
258
334
|
}
|
|
259
335
|
|
|
260
336
|
// scripts/src/lib/api.mjs
|
|
261
|
-
var PKG_VERSION = (() => {
|
|
262
|
-
const requireFrom = createRequire(import.meta.url);
|
|
263
|
-
for (const candidate of ["../../package.json", "../../../package.json"]) {
|
|
264
|
-
try {
|
|
265
|
-
const pkg = requireFrom(candidate);
|
|
266
|
-
if (pkg?.name === "@rozoai/checkout" && pkg.version) return pkg.version;
|
|
267
|
-
} catch {
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
return "0.0.0";
|
|
271
|
-
})();
|
|
272
337
|
var MPP_BASE = process.env.ROZO_CHECKOUT_MPP_BASE || "https://apiserver.mpprouter.dev/v1/services/rozo-agent-api";
|
|
273
338
|
var CLIENT_LABEL = `rozo-checkout-cli/${PKG_VERSION}`;
|
|
274
339
|
var ATTRIBUTION_CLIENT = `rozo-checkout-skill/${PKG_VERSION}`;
|
|
@@ -619,6 +684,8 @@ var MARGINS_MS = {
|
|
|
619
684
|
};
|
|
620
685
|
var BOLT11_MIN_VALIDITY_MS = 10 * MINUTE;
|
|
621
686
|
var DEFAULT_MARGIN_MS = 10 * MINUTE;
|
|
687
|
+
var DEFAULT_WATCH_MS = 10 * MINUTE;
|
|
688
|
+
var LIGHTNING_WATCH_CAP_MS = 60 * MINUTE;
|
|
622
689
|
function formatRemaining(ms) {
|
|
623
690
|
if (!Number.isFinite(ms)) return "unknown";
|
|
624
691
|
if (ms <= 0) return "expired";
|