@rozoai/checkout 0.1.16 → 0.1.17

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.
@@ -20544,7 +20544,7 @@ var require_dist = __commonJS({
20544
20544
  }
20545
20545
  }
20546
20546
  }
20547
- function* run8(value, struct2, options = {}) {
20547
+ function* run9(value, struct2, options = {}) {
20548
20548
  const { path: path7 = [], branch = [value], coerce: coerce2 = false, mask: mask2 = false } = options;
20549
20549
  const ctx = { path: path7, branch, mask: mask2 };
20550
20550
  if (coerce2) {
@@ -20557,7 +20557,7 @@ var require_dist = __commonJS({
20557
20557
  yield [failure, void 0];
20558
20558
  }
20559
20559
  for (let [k, v, s] of struct2.entries(value, ctx)) {
20560
- const ts = run8(v, s, {
20560
+ const ts = run9(v, s, {
20561
20561
  path: k === void 0 ? path7 : [...path7, k],
20562
20562
  branch: k === void 0 ? branch : [...branch, v],
20563
20563
  coerce: coerce2,
@@ -20685,7 +20685,7 @@ var require_dist = __commonJS({
20685
20685
  return !result[0];
20686
20686
  }
20687
20687
  function validate7(value, struct2, options = {}) {
20688
- const tuples = run8(value, struct2, options);
20688
+ const tuples = run9(value, struct2, options);
20689
20689
  const tuple2 = shiftIterator(tuples);
20690
20690
  if (tuple2[0]) {
20691
20691
  const error = new StructError(tuple2[0], function* () {
@@ -21098,7 +21098,7 @@ var require_dist = __commonJS({
21098
21098
  validator(value, ctx) {
21099
21099
  const failures = [];
21100
21100
  for (const S of Structs) {
21101
- const [...tuples] = run8(value, S, ctx);
21101
+ const [...tuples] = run9(value, S, ctx);
21102
21102
  const [first] = tuples;
21103
21103
  if (!first[0]) {
21104
21104
  return [];
@@ -41526,6 +41526,22 @@ var TX_HASH_VALUE = /^(0x[0-9a-fA-F]{64}|[1-9A-HJ-NP-Za-km-z]{86,88})$/;
41526
41526
  function publicHash(value) {
41527
41527
  return typeof value === "string" && TX_HASH_VALUE.test(value) ? new PublicHash(value) : value;
41528
41528
  }
41529
+ var PUBLIC_PAY_PAGE_HOSTS = /* @__PURE__ */ new Set(["invoice.rozo.ai", "checkout.rozo.ai"]);
41530
+ var PAY_PAGE_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
41531
+ function publicPayPage(value) {
41532
+ if (typeof value !== "string") return value;
41533
+ let u;
41534
+ try {
41535
+ u = new URL(value);
41536
+ } catch {
41537
+ return value;
41538
+ }
41539
+ if (u.protocol !== "https:" || u.username || u.password || u.port || !PUBLIC_PAY_PAGE_HOSTS.has(u.hostname)) return value;
41540
+ if (u.pathname !== "/checkout") return value;
41541
+ const id = u.searchParams.get("id");
41542
+ if (!id || !PAY_PAGE_ID.test(id)) return value;
41543
+ return new PublicHash(`https://${u.hostname}/checkout?id=${id.toLowerCase()}`);
41544
+ }
41529
41545
  function redactDeep(value) {
41530
41546
  if (value instanceof PublicHash) return value.value;
41531
41547
  if (typeof value === "string") return redact(value);
@@ -41769,8 +41785,61 @@ function normalizeUtmSource(value) {
41769
41785
  return s;
41770
41786
  }
41771
41787
 
41788
+ // scripts/src/lib/ids.mjs
41789
+ var IdError = class extends Error {
41790
+ constructor(code, message) {
41791
+ super(message);
41792
+ this.code = code;
41793
+ }
41794
+ };
41795
+ var PL_RE = /(pl_[0-9a-zA-Z]+)/;
41796
+ var SESSION_RE = /(paymentSession_[A-Za-z0-9_-]+)/;
41797
+ var UUID_RE = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
41798
+ function extractLinkId(urlOrId) {
41799
+ if (typeof urlOrId !== "string" || !urlOrId.trim()) {
41800
+ throw new IdError("BAD_LINK", "A Coinbase payment link URL or id is required.");
41801
+ }
41802
+ const s = urlOrId.trim();
41803
+ const session = s.match(SESSION_RE);
41804
+ if (session) return { linkId: session[1], kind: "payment_session" };
41805
+ const link = s.match(PL_RE);
41806
+ if (link) return { linkId: link[1], kind: "payment_link" };
41807
+ if (/commerce\.coinbase\.com\/pay\//.test(s)) {
41808
+ throw new IdError(
41809
+ "LEGACY_COMMERCE_URL",
41810
+ "commerce.coinbase.com/pay/<uuid> uses the legacy protocol; this skill does not handle it."
41811
+ );
41812
+ }
41813
+ throw new IdError(
41814
+ "BAD_LINK",
41815
+ "Could not find a `pl_*` or `paymentSession_*` id in the supplied value."
41816
+ );
41817
+ }
41818
+ function assertRozoPaymentId(value) {
41819
+ const s = String(value || "").trim();
41820
+ if (!UUID_RE.test(s)) {
41821
+ throw new IdError("BAD_ROZO_PAYMENT_ID", "rozoPaymentId must be a UUID.");
41822
+ }
41823
+ return s;
41824
+ }
41825
+ function isRozoPaymentId(value) {
41826
+ return UUID_RE.test(String(value || "").trim());
41827
+ }
41828
+ function maskAddress2(address) {
41829
+ const s = String(address ?? "").trim();
41830
+ if (!s) return "(none)";
41831
+ if (s.length <= 12) return s;
41832
+ return `${s.slice(0, 6)}...${s.slice(-4)}`;
41833
+ }
41834
+ function maskMemo(memo) {
41835
+ const s = String(memo ?? "").trim();
41836
+ if (!s) return null;
41837
+ if (s.length <= 8) return s;
41838
+ return `${s.slice(0, 4)}...${s.slice(-3)}`;
41839
+ }
41840
+
41772
41841
  // scripts/src/lib/cli-args.mjs
41773
- var COMMANDS = ["pay", "quote", "status", "receipt", "help", "version"];
41842
+ var COMMANDS = ["pay", "quote", "status", "receipt", "resume", "help", "version"];
41774
41843
  var CHAIN_ALIASES = {
41775
41844
  ethereum: "1",
41776
41845
  eth: "1",
@@ -41998,6 +42067,18 @@ function parseCliArgs(argv) {
41998
42067
  }
41999
42068
  return { command, target, json, provider: flags.provider };
42000
42069
  }
42070
+ if (command === "resume") {
42071
+ if (!target) {
42072
+ throw new CliError("MISSING_TARGET", "Usage: rozo-checkout resume <rozoPaymentId>");
42073
+ }
42074
+ if (!isRozoPaymentId(target)) {
42075
+ throw new CliError(
42076
+ "BAD_VALUE",
42077
+ "resume takes the order id (rozoPaymentId, a UUID) that pay printed, not a payment link."
42078
+ );
42079
+ }
42080
+ return { command, target, json, yes: flags.yes === true };
42081
+ }
42001
42082
  if (command === "status") {
42002
42083
  if (!target) {
42003
42084
  throw new CliError(
@@ -42116,6 +42197,7 @@ USAGE
42116
42197
  npx @rozoai/checkout quote <coinbase-link>
42117
42198
  npx @rozoai/checkout status <rozoPaymentId | coinbase-link>
42118
42199
  npx @rozoai/checkout receipt <rozoPaymentId | coinbase-link>
42200
+ npx @rozoai/checkout resume <rozoPaymentId>
42119
42201
 
42120
42202
  COINS (--with)
42121
42203
  usdt-solana usdc-solana usdt-bnb usdc-bnb
@@ -42180,6 +42262,20 @@ RECEIPT
42180
42262
  nextAction. The older "success" field means the query worked, not that the
42181
42263
  invoice is paid.
42182
42264
 
42265
+ RESUME
42266
+ resume picks up an order you created earlier and did not pay yet. It reads
42267
+ the order (nothing is created or sent), and if it is still unpaid and has
42268
+ enough time left it prints the hosted payment page and, after you confirm
42269
+ (or with --yes), the exact deposit instructions again. A paid order is
42270
+ reported as paid (exit 0); an expired one says so and how to start over.
42271
+
42272
+ PRIVACY
42273
+ Each order carries an anonymous install id: a random UUID created on first
42274
+ run and stored in ~/.rozo-checkout/prefs.json. If OPENROUTER_API_KEY is set,
42275
+ a one-way salted sha256 hash of it is sent too; the key itself is never
42276
+ sent, logged or stored. Delete prefs.json
42277
+ to reset the id; set ROZO_CHECKOUT_ANON_ID=off to send neither.
42278
+
42183
42279
  Creating an order moves no money; an unfunded order simply expires. Nothing is
42184
42280
  paid until you confirm, and --send is required before anything is signed.
42185
42281
 
@@ -42601,7 +42697,42 @@ function prefsPath() {
42601
42697
  const root = process.env.ROZO_CHECKOUT_STATE_DIR ? path2.dirname(stateRoot()) : path2.join(os2.homedir(), ".rozo-checkout");
42602
42698
  return path2.join(root, "prefs.json");
42603
42699
  }
42604
- var ALLOWED = ["lastPayerAddress", "lastAddressFamily", "lastPreset", "updatedAt"];
42700
+ var ALLOWED = ["lastPayerAddress", "lastAddressFamily", "lastPreset", "installId", "updatedAt"];
42701
+ var INSTALL_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
42702
+ var LOCK_WAIT_MS2 = 2e3;
42703
+ var LOCK_STALE_MS2 = 1e4;
42704
+ function sleepSync2(ms) {
42705
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
42706
+ }
42707
+ function withPrefsLock(fn) {
42708
+ const lock = `${prefsPath()}.lock`;
42709
+ fs2.mkdirSync(path2.dirname(lock), { recursive: true, mode: 448 });
42710
+ const deadline = Date.now() + LOCK_WAIT_MS2;
42711
+ for (; ; ) {
42712
+ try {
42713
+ fs2.closeSync(fs2.openSync(lock, "wx", 384));
42714
+ break;
42715
+ } catch (err) {
42716
+ if (err.code !== "EEXIST") throw err;
42717
+ }
42718
+ try {
42719
+ if (Date.now() - fs2.statSync(lock).mtimeMs > LOCK_STALE_MS2) {
42720
+ fs2.rmSync(lock, { force: true });
42721
+ continue;
42722
+ }
42723
+ } catch {
42724
+ continue;
42725
+ }
42726
+ if (Date.now() > deadline) return void 0;
42727
+ sleepSync2(10);
42728
+ }
42729
+ try {
42730
+ return fn();
42731
+ } finally {
42732
+ fs2.rmSync(lock, { force: true });
42733
+ }
42734
+ }
42735
+ var USER_FIELDS = ["lastPayerAddress", "lastAddressFamily", "lastPreset"];
42605
42736
  function readPrefs() {
42606
42737
  let raw;
42607
42738
  try {
@@ -42623,21 +42754,26 @@ function readPrefs() {
42623
42754
  }
42624
42755
  return Object.keys(out2).length ? out2 : null;
42625
42756
  }
42626
- function savePrefs(update) {
42627
- const existing = readPrefs() || {};
42628
- const next = { ...existing };
42629
- for (const k of ALLOWED) {
42630
- if (k === "updatedAt") continue;
42631
- const v = update?.[k];
42632
- if (typeof v === "string" && v.trim()) next[k] = v.trim();
42633
- }
42634
- next.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
42757
+ function savePrefs(update, { installIdIfAbsent } = {}) {
42635
42758
  try {
42636
- writeAtomic(prefsPath(), next);
42759
+ const saved = withPrefsLock(() => {
42760
+ const existing = readPrefs() || {};
42761
+ const next = { ...existing };
42762
+ for (const k of USER_FIELDS) {
42763
+ const v = update?.[k];
42764
+ if (typeof v === "string" && v.trim()) next[k] = v.trim();
42765
+ }
42766
+ if (!INSTALL_ID_RE.test(String(existing.installId ?? "")) && INSTALL_ID_RE.test(String(installIdIfAbsent ?? ""))) {
42767
+ next.installId = installIdIfAbsent;
42768
+ }
42769
+ next.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
42770
+ writeAtomic(prefsPath(), next);
42771
+ return next;
42772
+ });
42773
+ return saved ?? null;
42637
42774
  } catch {
42638
42775
  return null;
42639
42776
  }
42640
- return next;
42641
42777
  }
42642
42778
 
42643
42779
  // scripts/src/lib/blacklist.mjs
@@ -42763,59 +42899,6 @@ function assertNotBlacklisted(targets, blacklist) {
42763
42899
  return true;
42764
42900
  }
42765
42901
 
42766
- // scripts/src/lib/ids.mjs
42767
- var IdError = class extends Error {
42768
- constructor(code, message) {
42769
- super(message);
42770
- this.code = code;
42771
- }
42772
- };
42773
- var PL_RE = /(pl_[0-9a-zA-Z]+)/;
42774
- var SESSION_RE = /(paymentSession_[A-Za-z0-9_-]+)/;
42775
- var UUID_RE = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
42776
- function extractLinkId(urlOrId) {
42777
- if (typeof urlOrId !== "string" || !urlOrId.trim()) {
42778
- throw new IdError("BAD_LINK", "A Coinbase payment link URL or id is required.");
42779
- }
42780
- const s = urlOrId.trim();
42781
- const session = s.match(SESSION_RE);
42782
- if (session) return { linkId: session[1], kind: "payment_session" };
42783
- const link = s.match(PL_RE);
42784
- if (link) return { linkId: link[1], kind: "payment_link" };
42785
- if (/commerce\.coinbase\.com\/pay\//.test(s)) {
42786
- throw new IdError(
42787
- "LEGACY_COMMERCE_URL",
42788
- "commerce.coinbase.com/pay/<uuid> uses the legacy protocol; this skill does not handle it."
42789
- );
42790
- }
42791
- throw new IdError(
42792
- "BAD_LINK",
42793
- "Could not find a `pl_*` or `paymentSession_*` id in the supplied value."
42794
- );
42795
- }
42796
- function assertRozoPaymentId(value) {
42797
- const s = String(value || "").trim();
42798
- if (!UUID_RE.test(s)) {
42799
- throw new IdError("BAD_ROZO_PAYMENT_ID", "rozoPaymentId must be a UUID.");
42800
- }
42801
- return s;
42802
- }
42803
- function isRozoPaymentId(value) {
42804
- return UUID_RE.test(String(value || "").trim());
42805
- }
42806
- function maskAddress2(address) {
42807
- const s = String(address ?? "").trim();
42808
- if (!s) return "(none)";
42809
- if (s.length <= 12) return s;
42810
- return `${s.slice(0, 6)}...${s.slice(-4)}`;
42811
- }
42812
- function maskMemo(memo) {
42813
- const s = String(memo ?? "").trim();
42814
- if (!s) return null;
42815
- if (s.length <= 8) return s;
42816
- return `${s.slice(0, 4)}...${s.slice(-3)}`;
42817
- }
42818
-
42819
42902
  // scripts/src/lib/expiry.mjs
42820
42903
  var MINUTE = 6e4;
42821
42904
  var MARGINS_MS = {
@@ -54603,6 +54686,44 @@ function postJson(url, body, opts) {
54603
54686
  return request("POST", url, { ...opts, body });
54604
54687
  }
54605
54688
 
54689
+ // scripts/src/lib/identity.mjs
54690
+ import crypto7 from "node:crypto";
54691
+ var ACCOUNT_HASH_PREFIX = "rozo-acct-v1:";
54692
+ function isValidInstallId(value) {
54693
+ return typeof value === "string" && INSTALL_ID_RE.test(value);
54694
+ }
54695
+ function attributionDisabled(env = process.env) {
54696
+ const v = String(env.ROZO_CHECKOUT_ANON_ID ?? "").trim().toLowerCase();
54697
+ return ["off", "0", "false", "no"].includes(v);
54698
+ }
54699
+ function getOrCreateInstallId() {
54700
+ const existing = readPrefs()?.installId;
54701
+ if (isValidInstallId(existing)) return existing;
54702
+ const saved = savePrefs({}, { installIdIfAbsent: crypto7.randomUUID().toLowerCase() });
54703
+ if (isValidInstallId(saved?.installId)) return saved.installId;
54704
+ const onDisk = readPrefs()?.installId;
54705
+ return isValidInstallId(onDisk) ? onDisk : crypto7.randomUUID().toLowerCase();
54706
+ }
54707
+ function hashAccount(raw) {
54708
+ return crypto7.createHash("sha256").update(`${ACCOUNT_HASH_PREFIX}${raw}`, "utf8").digest("hex");
54709
+ }
54710
+ function rawApiKey(env) {
54711
+ const v = typeof env.OPENROUTER_API_KEY === "string" ? env.OPENROUTER_API_KEY.trim() : "";
54712
+ return v && v.length <= 1024 ? v : null;
54713
+ }
54714
+ function attributionIdentity(env = process.env) {
54715
+ if (attributionDisabled(env)) return {};
54716
+ const out2 = {};
54717
+ try {
54718
+ const id = getOrCreateInstallId();
54719
+ if (isValidInstallId(id)) out2.install_id = id;
54720
+ } catch {
54721
+ }
54722
+ const raw = rawApiKey(env);
54723
+ if (raw) out2.account_hash = hashAccount(raw);
54724
+ return out2;
54725
+ }
54726
+
54606
54727
  // scripts/src/lib/api.mjs
54607
54728
  var PKG_VERSION = (() => {
54608
54729
  const requireFrom = createRequire(import.meta.url);
@@ -54618,10 +54739,16 @@ var PKG_VERSION = (() => {
54618
54739
  var MPP_BASE = process.env.ROZO_CHECKOUT_MPP_BASE || "https://apiserver.mpprouter.dev/v1/services/rozo-agent-api";
54619
54740
  var CLIENT_LABEL = `rozo-checkout-cli/${PKG_VERSION}`;
54620
54741
  var ATTRIBUTION_CLIENT = `rozo-checkout-skill/${PKG_VERSION}`;
54621
- function buildAttribution({ utmSource } = {}) {
54742
+ function buildAttribution({ utmSource, identity } = {}) {
54622
54743
  const raw = utmSource !== void 0 ? utmSource : process.env.ROZO_CHECKOUT_UTM_SOURCE;
54623
54744
  const utm_source = normalizeUtmSource(raw);
54624
- return { client: ATTRIBUTION_CLIENT, ...utm_source ? { utm_source } : {} };
54745
+ const ids = identity ?? attributionIdentity();
54746
+ return {
54747
+ client: ATTRIBUTION_CLIENT,
54748
+ ...utm_source ? { utm_source } : {},
54749
+ ...ids.install_id ? { install_id: ids.install_id } : {},
54750
+ ...ids.account_hash ? { account_hash: ids.account_hash } : {}
54751
+ };
54625
54752
  }
54626
54753
  var INTENTS_BASE = process.env.ROZO_CHECKOUT_INTENTS_BASE || "https://intentapiv4.rozo.ai/functions/v1/payment-api";
54627
54754
  async function quoteInvoice({ url, linkId }) {
@@ -56168,7 +56295,7 @@ function sendWindowFor({ provider, chainId, payment, status, bitrefillExpiry, no
56168
56295
  intentExpiresAt: payment?.expiresAt ?? deadline,
56169
56296
  coinbaseExpiry: deadline
56170
56297
  });
56171
- return expiry2.ok ? { ok: true, code: null, minutesOfSlack: Math.floor(expiry2.msOfSlack / 6e4) } : { ok: false, code: expiry2.code, reason: expiry2.reason };
56298
+ return expiry2.ok ? { ok: true, code: null, minutesOfSlack: Math.floor(expiry2.msOfSlack / 6e4), deadlineMs: expiry2.effectiveDeadlineMs } : { ok: false, code: expiry2.code, reason: expiry2.reason };
56172
56299
  }
56173
56300
  if (!status) {
56174
56301
  return { ok: false, code: "LINK_PAYABILITY_UNKNOWN", reason: "The Coinbase link state could not be read." };
@@ -56181,7 +56308,7 @@ function sendWindowFor({ provider, chainId, payment, status, bitrefillExpiry, no
56181
56308
  intentExpiresAt: payment?.expiresAt ?? status?.rozoPayment?.expiresAt,
56182
56309
  coinbaseExpiry: status?.coinbase?.preApprovalExpiry
56183
56310
  });
56184
- return expiry.ok ? { ok: true, code: null, minutesOfSlack: Math.floor(expiry.msOfSlack / 6e4) } : { ok: false, code: expiry.code, reason: expiry.reason };
56311
+ return expiry.ok ? { ok: true, code: null, minutesOfSlack: Math.floor(expiry.msOfSlack / 6e4), deadlineMs: expiry.effectiveDeadlineMs } : { ok: false, code: expiry.code, reason: expiry.reason };
56185
56312
  }
56186
56313
  async function snapshot({ rozoPaymentId, linkId, provider: explicitProvider }) {
56187
56314
  let provider = resolveProvider(explicitProvider, rozoPaymentId);
@@ -56411,6 +56538,241 @@ async function run5(argv = process.argv.slice(2)) {
56411
56538
  return main5(argv);
56412
56539
  }
56413
56540
 
56541
+ // scripts/src/resume.mjs
56542
+ var PAID_STATES = /* @__PURE__ */ new Set([
56543
+ "payin_detected",
56544
+ "payin_confirmed",
56545
+ "bridging",
56546
+ "paying_coinbase",
56547
+ "settled"
56548
+ ]);
56549
+ var EXPIRED_STATES = /* @__PURE__ */ new Set(["expired_unfunded", "invoice_expired"]);
56550
+ function localPaymentLink(id) {
56551
+ try {
56552
+ return readState(id)?.paymentLink ?? null;
56553
+ } catch {
56554
+ return null;
56555
+ }
56556
+ }
56557
+ function resumeVerdict(snap) {
56558
+ const base = {
56559
+ rozoPaymentId: snap.rozoPaymentId,
56560
+ linkId: snap.linkId ?? null,
56561
+ provider: snap.provider,
56562
+ state: snap.state,
56563
+ moneyDetected: Boolean(snap.moneyDetected)
56564
+ };
56565
+ if (snap.escalate) {
56566
+ return { ...base, resumable: false, outcome: "needs_attention", code: "ORDER_NEEDS_ATTENTION", exitCode: EXIT_ERROR, message: statusGuidance(snap) };
56567
+ }
56568
+ if (snap.unknown || !snap.authoritativeView) {
56569
+ return { ...base, resumable: false, outcome: "unknown", code: "ORDER_STATE_UNKNOWN", exitCode: EXIT_ERROR, message: statusGuidance(snap) };
56570
+ }
56571
+ if (snap.moneyDetected || PAID_STATES.has(snap.state)) {
56572
+ const done = snap.state === "settled";
56573
+ return {
56574
+ ...base,
56575
+ resumable: false,
56576
+ outcome: done ? "paid" : "paid_in_progress",
56577
+ code: "ORDER_ALREADY_PAID",
56578
+ exitCode: EXIT_OK,
56579
+ message: done ? "This order is already paid and settled. Nothing to resume. Do not pay again." : `A payment for this order was already detected (state: ${snap.state}). Do NOT pay again. Follow it with: rozo-checkout status ${snap.rozoPaymentId} --watch`
56580
+ };
56581
+ }
56582
+ if (EXPIRED_STATES.has(snap.state)) {
56583
+ return {
56584
+ ...base,
56585
+ resumable: false,
56586
+ outcome: "expired",
56587
+ code: "ORDER_EXPIRED",
56588
+ exitCode: EXIT_ERROR,
56589
+ message: snap.provider === "bitrefill" ? "This order expired before any funds arrived. Nothing was lost. Create a fresh Bitrefill invoice and run pay again." : EXPIRED_UNFUNDED_GUIDANCE
56590
+ };
56591
+ }
56592
+ if (snap.localSend) {
56593
+ return {
56594
+ ...base,
56595
+ resumable: false,
56596
+ outcome: "sent_locally",
56597
+ code: "ALREADY_SENT",
56598
+ exitCode: EXIT_ERROR,
56599
+ message: `This machine already recorded a send for this order (${snap.localSend.status}). Do NOT pay again. Check it with: rozo-checkout status ${snap.rozoPaymentId}`
56600
+ };
56601
+ }
56602
+ if (snap.state !== "awaiting_deposit") {
56603
+ return {
56604
+ ...base,
56605
+ resumable: false,
56606
+ outcome: "not_payable",
56607
+ code: "ORDER_NOT_RESUMABLE",
56608
+ exitCode: EXIT_ERROR,
56609
+ message: `This order is in state "${snap.state}" and cannot be paid now. ${statusGuidance(snap)}`
56610
+ };
56611
+ }
56612
+ if (!snap.sendWindow?.ok) {
56613
+ return {
56614
+ ...base,
56615
+ resumable: false,
56616
+ outcome: "expired",
56617
+ code: snap.sendWindow?.code ?? "SEND_WINDOW_CLOSED",
56618
+ exitCode: EXIT_ERROR,
56619
+ message: `${snap.sendWindow?.reason ?? "Not enough time is left to pay this order safely."} Do not fund it. Let it expire unfunded, then create a new order for the same link.`
56620
+ };
56621
+ }
56622
+ return { ...base, resumable: true, outcome: "awaiting_deposit", code: null, exitCode: EXIT_OK, message: null };
56623
+ }
56624
+ async function main6(argv) {
56625
+ const args = parseArgs(argv);
56626
+ const rozoPaymentId = args["rozo-payment-id"] || args._[0];
56627
+ if (!rozoPaymentId || rozoPaymentId === true) usage("Required: <rozoPaymentId> (the order id printed by pay)");
56628
+ if (!isRozoPaymentId(String(rozoPaymentId))) {
56629
+ usage("resume takes the order id (rozoPaymentId, a UUID) printed by pay, not a payment link.");
56630
+ }
56631
+ const id = String(rozoPaymentId);
56632
+ const confirmed = Boolean(args.confirm);
56633
+ let blacklist;
56634
+ try {
56635
+ blacklist = loadBlacklist();
56636
+ } catch (err) {
56637
+ throw new SkillError("BLACKLIST_UNAVAILABLE", `Compromised-address list unusable: ${err.message} Refusing to proceed.`);
56638
+ }
56639
+ const snap = await snapshot({ rozoPaymentId: id });
56640
+ const verdict = resumeVerdict(snap);
56641
+ if (!verdict.resumable) {
56642
+ emit(
56643
+ {
56644
+ success: verdict.exitCode === EXIT_OK,
56645
+ step: "resume",
56646
+ resumable: false,
56647
+ outcome: verdict.outcome,
56648
+ rozoPaymentId: id,
56649
+ linkId: verdict.linkId,
56650
+ state: verdict.state,
56651
+ moneyDetected: verdict.moneyDetected,
56652
+ ...verdict.exitCode === EXIT_OK ? { message: verdict.message } : { error: { code: verdict.code, message: verdict.message } },
56653
+ payin: snap.payin,
56654
+ ...verdict.exitCode === EXIT_OK ? {} : { support: SUPPORT }
56655
+ },
56656
+ verdict.exitCode
56657
+ );
56658
+ }
56659
+ const payment = await getPayment(id);
56660
+ const source = payment?.source || {};
56661
+ const guard = reuseGuard({ payment, requested: { chainId: source.chainId, tokenSymbol: source.tokenSymbol }, reused: true });
56662
+ if (!guard.ok) {
56663
+ emit(
56664
+ {
56665
+ success: false,
56666
+ step: "resume",
56667
+ resumable: false,
56668
+ outcome: guard.moneyDetected ? "paid_in_progress" : "not_payable",
56669
+ rozoPaymentId: id,
56670
+ moneyDetected: guard.moneyDetected,
56671
+ error: { code: guard.code, message: guard.reason },
56672
+ guidance: guard.moneyDetected ? "A payment for this order was detected. Do NOT pay again." : "This order cannot be paid as it stands. Do not fund it.",
56673
+ support: SUPPORT
56674
+ },
56675
+ EXIT_ERROR
56676
+ );
56677
+ }
56678
+ const finalWindow = checkExpiry({
56679
+ now: Date.now(),
56680
+ chainId: source.chainId,
56681
+ intentExpiresAt: payment?.expiresAt ?? snap.sendWindow?.deadlineMs,
56682
+ coinbaseExpiry: snap.sendWindow?.deadlineMs
56683
+ });
56684
+ if (!finalWindow.ok) {
56685
+ emit(
56686
+ {
56687
+ success: false,
56688
+ step: "resume",
56689
+ resumable: false,
56690
+ outcome: "expired",
56691
+ rozoPaymentId: id,
56692
+ moneyDetected: false,
56693
+ error: { code: finalWindow.code, message: finalWindow.reason },
56694
+ guidance: "Not enough time is left to pay this order safely. Do not fund it; let it expire unfunded and create a new order.",
56695
+ support: SUPPORT
56696
+ },
56697
+ EXIT_ERROR
56698
+ );
56699
+ }
56700
+ const lightning = String(source.chainId) === "lightning";
56701
+ if (!lightning) {
56702
+ try {
56703
+ assertNotBlacklisted(
56704
+ [{ address: source.receiverAddress, family: chainFamily(source.chainId), role: "deposit address" }],
56705
+ blacklist
56706
+ );
56707
+ } catch (err) {
56708
+ emit(
56709
+ {
56710
+ success: false,
56711
+ step: "resume",
56712
+ resumable: false,
56713
+ rozoPaymentId: id,
56714
+ error: { code: err.code, message: err.message },
56715
+ guidance: "Do NOT send anything. Report this to the operator immediately.",
56716
+ support: SUPPORT
56717
+ },
56718
+ EXIT_ERROR
56719
+ );
56720
+ }
56721
+ }
56722
+ const expiresAt = payment?.expiresAt ?? snap.expiry?.expiresAt ?? null;
56723
+ const msRemaining = expiresAt ? Date.parse(expiresAt) - Date.now() : NaN;
56724
+ const expiresIn = Number.isFinite(msRemaining) ? formatRemaining(msRemaining) : null;
56725
+ const paymentLink = payment?.paymentLink ?? localPaymentLink(id);
56726
+ const bolt11 = source.lnInvoice ?? payment?.lnInvoice ?? null;
56727
+ emit({
56728
+ success: true,
56729
+ step: "resume",
56730
+ resumable: true,
56731
+ outcome: "awaiting_deposit",
56732
+ confirmed,
56733
+ rozoPaymentId: id,
56734
+ linkId: snap.linkId ?? payment?.orderId ?? null,
56735
+ provider: snap.provider,
56736
+ state: snap.state,
56737
+ // Hosted pay page for this order: a human can open it and pay from there.
56738
+ // Shown in full only for Rozo's own pay-page hosts; anything else is
56739
+ // reduced to its host by the normal output redaction.
56740
+ paymentLink: publicPayPage(paymentLink),
56741
+ deposit: confirmed ? {
56742
+ chainId: source.chainId,
56743
+ chain: chainName(source.chainId),
56744
+ tokenSymbol: source.tokenSymbol,
56745
+ tokenAddress: source.tokenAddress || null,
56746
+ receiverAddress: lightning ? null : source.receiverAddress,
56747
+ receiverMemo: source.receiverMemo ?? null,
56748
+ receiverMemoType: source.receiverMemo ? STELLAR_MEMO_TYPE : null,
56749
+ amount: source.amount,
56750
+ amountUnit: source.amountUnit ?? null,
56751
+ isSats: isSatsUnit(source.amountUnit),
56752
+ lnInvoice: bolt11 || null,
56753
+ payTo: guard.deposit.payTo,
56754
+ expiresAt,
56755
+ expiresIn
56756
+ } : null,
56757
+ depositWithheld: !confirmed,
56758
+ display: {
56759
+ chain: chainName(source.chainId),
56760
+ token: source.tokenSymbol,
56761
+ amount: formatAmount(source),
56762
+ isSats: isSatsUnit(source.amountUnit),
56763
+ payToMasked: maskAddress2(guard.deposit.payTo),
56764
+ receiverMemoMasked: maskMemo(source.receiverMemo),
56765
+ hasMemo: Boolean(source.receiverMemo),
56766
+ memoType: source.receiverMemo ? STELLAR_MEMO_TYPE : null
56767
+ },
56768
+ expiry: { expiresAt, expiresIn, minutesOfSlack: snap.sendWindow?.minutesOfSlack ?? null },
56769
+ note: confirmed ? "Send exactly once, exactly this amount, on this chain. Then: rozo-checkout status <id> --watch" : "Deposit address withheld. Confirm the chain, token and amount with the payer, then re-run with --confirm, or open paymentLink."
56770
+ });
56771
+ }
56772
+ async function run6(argv = process.argv.slice(2)) {
56773
+ return main6(argv);
56774
+ }
56775
+
56414
56776
  // scripts/src/lib/passphrase.mjs
56415
56777
  import readline from "node:readline";
56416
56778
  function promptPassphrase(prompt = " Keystore passphrase: ") {
@@ -57127,7 +57489,7 @@ function chainDef(chainId, rpcUrl) {
57127
57489
  rpcUrls: { default: { http: [rpcUrl] } }
57128
57490
  });
57129
57491
  }
57130
- async function main6(argv) {
57492
+ async function main7(argv) {
57131
57493
  const args = parseArgs(argv);
57132
57494
  const rozoPaymentId = assertRozoPaymentId(args["rozo-payment-id"] || args._[0]);
57133
57495
  assertNoTrackedDotEnv();
@@ -57370,8 +57732,8 @@ async function main6(argv) {
57370
57732
  outcome.exitCode
57371
57733
  );
57372
57734
  }
57373
- async function run6(argv = process.argv.slice(2)) {
57374
- return main6(argv);
57735
+ async function run7(argv = process.argv.slice(2)) {
57736
+ return main7(argv);
57375
57737
  }
57376
57738
 
57377
57739
  // scripts/src/send-sol.mjs
@@ -57441,7 +57803,7 @@ function createTransferCheckedInstruction(source, mint, destination, owner, amou
57441
57803
  var MEMO_PROGRAM_ID = new import_web32.PublicKey("MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr");
57442
57804
  var MAINNET_GENESIS = "5eykt4UsFv8P8NJdTREpY1vzqKqZKvdpKuc147dw2N9d";
57443
57805
  var B58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
57444
- async function main7(argv) {
57806
+ async function main8(argv) {
57445
57807
  const args = parseArgs(argv);
57446
57808
  const rozoPaymentId = assertRozoPaymentId(args["rozo-payment-id"] || args._[0]);
57447
57809
  assertNoTrackedDotEnv();
@@ -57740,8 +58102,8 @@ function base58Encode(buf) {
57740
58102
  }
57741
58103
  return out2;
57742
58104
  }
57743
- async function run7(argv = process.argv.slice(2)) {
57744
- return main7(argv);
58105
+ async function run8(argv = process.argv.slice(2)) {
58106
+ return main8(argv);
57745
58107
  }
57746
58108
 
57747
58109
  // scripts/src/cli.mjs
@@ -57981,6 +58343,62 @@ async function cmdReceipt(opts) {
57981
58343
  out();
57982
58344
  return exitCode;
57983
58345
  }
58346
+ async function cmdResume(opts) {
58347
+ if (!opts.json) out(dim(" Reading the order\u2026 (no money moves)"));
58348
+ const first = await step(run6, [opts.target]);
58349
+ let payload = first.payload;
58350
+ let exitCode = first.exitCode;
58351
+ if (payload.resumable && !opts.json) {
58352
+ out();
58353
+ out(` Order ${payload.rozoPaymentId} ${dim("(unpaid)")}`);
58354
+ out(` You send ${bold(payload.display?.amount)} on ${bold(payload.display?.chain)}`);
58355
+ out(` To ${payload.display?.payToMasked} ${dim("(full address shown after you confirm)")}`);
58356
+ if (payload.display?.hasMemo) {
58357
+ out(` Memo ${payload.display.receiverMemoMasked} ${dim(`(${payload.display.memoType})`)}`);
58358
+ }
58359
+ if (payload.expiry?.expiresIn) out(` Expires in ${bold(payload.expiry.expiresIn)}`);
58360
+ if (payload.paymentLink) out(` Pay page ${payload.paymentLink}`);
58361
+ out();
58362
+ }
58363
+ if (payload.resumable) {
58364
+ let go = opts.yes;
58365
+ if (!go && !opts.json && process.stdin.isTTY) {
58366
+ go = await askYesNo(` ${bold("Show the deposit details to pay this now?")} [y/N] `);
58367
+ }
58368
+ if (go) {
58369
+ const second = await step(run6, [opts.target, "--confirm"]);
58370
+ payload = second.payload;
58371
+ exitCode = second.exitCode;
58372
+ }
58373
+ }
58374
+ if (opts.json) {
58375
+ printJson(payload);
58376
+ return exitCode;
58377
+ }
58378
+ if (payload.error) {
58379
+ printError(payload);
58380
+ printMoneyWarning(payload);
58381
+ printSupport();
58382
+ out();
58383
+ return exitCode;
58384
+ }
58385
+ if (!payload.resumable) {
58386
+ out();
58387
+ out(` State ${stateLabel(payload.state)}`);
58388
+ out(` ${payload.message}`);
58389
+ out();
58390
+ return exitCode;
58391
+ }
58392
+ if (payload.deposit) {
58393
+ printDeposit(payload.deposit, opts);
58394
+ out(dim(` Then: rozo-checkout status ${payload.rozoPaymentId} --watch`));
58395
+ out();
58396
+ } else {
58397
+ out(dim(" Deposit details withheld. Open the pay page above, or re-run with --yes."));
58398
+ out();
58399
+ }
58400
+ return exitCode;
58401
+ }
57984
58402
  function outcomeLabel(outcome) {
57985
58403
  if (outcome === "settled") return green(bold(outcome));
57986
58404
  if (outcome === "needs_attention" || outcome === "unknown") return red(bold(outcome));
@@ -58171,7 +58589,7 @@ async function cmdPay(opts) {
58171
58589
  }
58172
58590
  if (opts.send) {
58173
58591
  const family = chainFamily(deposit.chainId);
58174
- const sender = family === "evm" ? run6 : family === "solana" ? run7 : null;
58592
+ const sender = family === "evm" ? run7 : family === "solana" ? run8 : null;
58175
58593
  if (!sender) {
58176
58594
  const payload = formatFailure({
58177
58595
  code: "SEND_UNSUPPORTED_CHAIN",
@@ -58283,7 +58701,7 @@ function printDeposit(deposit, opts) {
58283
58701
  out(dim(" Send it exactly once."));
58284
58702
  out();
58285
58703
  }
58286
- async function main8() {
58704
+ async function main9() {
58287
58705
  let opts;
58288
58706
  try {
58289
58707
  opts = parseCliArgs(process.argv.slice(2));
@@ -58314,6 +58732,8 @@ async function main8() {
58314
58732
  return cmdStatus(opts);
58315
58733
  case "receipt":
58316
58734
  return cmdReceipt(opts);
58735
+ case "resume":
58736
+ return cmdResume(opts);
58317
58737
  case "pay":
58318
58738
  try {
58319
58739
  return await cmdPay(opts);
@@ -58334,7 +58754,7 @@ async function main8() {
58334
58754
  return EXIT_USAGE;
58335
58755
  }
58336
58756
  }
58337
- main8().then((code) => process.exit(code ?? EXIT_OK)).catch((err) => {
58757
+ main9().then((code) => process.exit(code ?? EXIT_OK)).catch((err) => {
58338
58758
  const payload = formatFailure(err);
58339
58759
  if (process.argv.includes("--json")) printJson(payload);
58340
58760
  else {