@rozoai/checkout 0.1.1 → 0.1.3

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.
@@ -1,6 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire as __rozoCreateRequire } from 'node:module';
3
+ import { fileURLToPath as __rozoFileURLToPath } from 'node:url';
4
+ import { dirname as __rozoDirname } from 'node:path';
3
5
  const require = __rozoCreateRequire(import.meta.url);
6
+ const __filename = __rozoFileURLToPath(import.meta.url);
7
+ const __dirname = __rozoDirname(__filename);
4
8
 
5
9
  // scripts/src/lib/output.mjs
6
10
  var EXIT_OK = 0;
@@ -396,6 +400,7 @@ function formatAmount(source) {
396
400
  function chainName(chainId) {
397
401
  return CHAIN_NAMES[String(chainId)] || CHAIN_NAMES[chainId] || `chain ${chainId}`;
398
402
  }
403
+ var STELLAR_MEMO_TYPE = "MEMO_TEXT";
399
404
  function chainFamily(chainId) {
400
405
  return CHAIN_FAMILY[String(chainId)] || CHAIN_FAMILY[chainId] || null;
401
406
  }
@@ -418,6 +423,16 @@ var MARGINS_MS = {
418
423
  };
419
424
  var BOLT11_MIN_VALIDITY_MS = 10 * MINUTE;
420
425
  var DEFAULT_MARGIN_MS = 10 * MINUTE;
426
+ function formatRemaining(ms) {
427
+ if (!Number.isFinite(ms)) return "unknown";
428
+ if (ms <= 0) return "expired";
429
+ const totalMinutes = Math.floor(ms / 6e4);
430
+ if (totalMinutes < 1) return `${Math.floor(ms / 1e3)}s`;
431
+ if (totalMinutes < 60) return `${totalMinutes}m`;
432
+ const h = Math.floor(totalMinutes / 60);
433
+ const m = totalMinutes % 60;
434
+ return m ? `${h}h ${m}m` : `${h}h`;
435
+ }
421
436
  function marginFor(chainId) {
422
437
  const key = String(chainId);
423
438
  return MARGINS_MS[key] ?? MARGINS_MS[chainId] ?? DEFAULT_MARGIN_MS;
@@ -1135,6 +1150,33 @@ async function main(argv) {
1135
1150
  const rozoPaymentId = assertRozoPaymentId(created.rozoPaymentId);
1136
1151
  const verify = verifyCreateAgainstQuote({ snapshot, created, requested });
1137
1152
  if (!verify.ok) {
1153
+ const onlySourceDrift = verify.drift.length > 0 && verify.drift.every((d) => d.field.startsWith("source."));
1154
+ if (created.reused && onlySourceDrift) {
1155
+ const existing = created.source || {};
1156
+ const remainingMs = created.expiresAt ? Date.parse(created.expiresAt) - Date.now() : NaN;
1157
+ emit(
1158
+ {
1159
+ success: false,
1160
+ step: "reuse-source-mismatch",
1161
+ error: {
1162
+ code: "REUSED_SOURCE_MISMATCH",
1163
+ message: `This link already has an unpaid order, created for ${existing.tokenSymbol ?? "?"} on chain ${existing.chainId ?? "?"}. You asked for ${tokenSymbol} on chain ${chainId}. Only one order exists per link at a time, so nothing new was created.`
1164
+ },
1165
+ linkId: created.linkId ?? parsedLinkId,
1166
+ rozoPaymentId,
1167
+ paymentLink: created.paymentLink ?? null,
1168
+ existingOrder: {
1169
+ rozoPaymentId,
1170
+ chainId: existing.chainId ?? null,
1171
+ tokenSymbol: existing.tokenSymbol ?? null,
1172
+ expiresAt: created.expiresAt ?? null,
1173
+ expiresIn: Number.isFinite(remainingMs) ? formatRemaining(remainingMs) : null
1174
+ },
1175
+ guidance: `Either pay the existing order with ${existing.tokenSymbol ?? "its own coin"} (re-run with --chain ${existing.chainId} --token ${existing.tokenSymbol}), or wait ${Number.isFinite(remainingMs) ? formatRemaining(remainingMs) : "for it to expire"} for it to expire and then create a new one. Unpaid orders cost nothing.`
1176
+ },
1177
+ EXIT_ERROR
1178
+ );
1179
+ }
1138
1180
  emit(
1139
1181
  {
1140
1182
  success: false,
@@ -1256,7 +1298,8 @@ async function main(argv) {
1256
1298
  step: "create-order",
1257
1299
  confirmed,
1258
1300
  reused: Boolean(created.reused),
1259
- reusedNote: created.reused ? "An existing unfunded order for this link was reused; it passed the funded-check above." : null,
1301
+ reusedNote: created.reused ? `An existing unpaid order for this link was reused (${rozoPaymentId}), valid for another ${formatRemaining(expiry.msRemaining)}. Nothing new was created.` : null,
1302
+ orderCost: "Creating an order moves no money. An order you never fund simply expires and costs nothing.",
1260
1303
  linkId: created.linkId ?? parsedLinkId,
1261
1304
  rozoPaymentId,
1262
1305
  paymentLink: created.paymentLink ?? null,
@@ -1274,12 +1317,16 @@ async function main(argv) {
1274
1317
  // Lightning has no deposit address: the BOLT11 IS the instruction.
1275
1318
  receiverAddress: lightning ? null : source.receiverAddress,
1276
1319
  receiverMemo: source.receiverMemo ?? null,
1320
+ // Stellar memos are TEXT even when they look numeric. Sending one as
1321
+ // MEMO_ID produces a different memo and the payment will not match.
1322
+ receiverMemoType: source.receiverMemo ? STELLAR_MEMO_TYPE : null,
1277
1323
  amount: source.amount,
1278
1324
  amountUnit: source.amountUnit ?? null,
1279
1325
  isSats: isSatsUnit(source.amountUnit),
1280
1326
  lnInvoice: bolt11 || null,
1281
1327
  payTo: depositInfo.payTo,
1282
- expiresAt: payment?.expiresAt ?? null
1328
+ expiresAt: payment?.expiresAt ?? null,
1329
+ expiresIn: formatRemaining(expiry.msRemaining)
1283
1330
  } : null,
1284
1331
  depositWithheld: !confirmed,
1285
1332
  // Safe for prose / chat.
@@ -1291,12 +1338,16 @@ async function main(argv) {
1291
1338
  payToMasked: maskAddress(depositInfo.payTo),
1292
1339
  receiverMemoMasked: maskMemo(source.receiverMemo),
1293
1340
  hasMemo: Boolean(source.receiverMemo),
1341
+ memoType: source.receiverMemo ? STELLAR_MEMO_TYPE : null,
1294
1342
  memoRequirement
1295
1343
  },
1296
1344
  expiry: {
1297
1345
  intentExpiresAt: payment?.expiresAt ?? null,
1298
1346
  coinbaseExpiry: snapshot?.coinbase?.preApprovalExpiry ?? null,
1299
1347
  effectiveDeadlineIso: new Date(expiry.effectiveDeadlineMs).toISOString(),
1348
+ // A duration, not just a timestamp: this is what a payer actually needs.
1349
+ expiresIn: formatRemaining(expiry.msRemaining),
1350
+ msRemaining: expiry.msRemaining,
1300
1351
  marginMinutes: Math.round(expiry.marginMs / 6e4),
1301
1352
  minutesOfSlack: Math.floor(expiry.msOfSlack / 6e4)
1302
1353
  },
@@ -1,6 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire as __rozoCreateRequire } from 'node:module';
3
+ import { fileURLToPath as __rozoFileURLToPath } from 'node:url';
4
+ import { dirname as __rozoDirname } from 'node:path';
3
5
  const require = __rozoCreateRequire(import.meta.url);
6
+ const __filename = __rozoFileURLToPath(import.meta.url);
7
+ const __dirname = __rozoDirname(__filename);
4
8
 
5
9
  // scripts/src/lib/output.mjs
6
10
  var EXIT_OK = 0;