@visa/cli 4.1.0-rc.136 → 4.1.0-rc.138

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.
@@ -223,18 +223,18 @@ export function createCliCheckoutEngine(deps = {}) {
223
223
  }
224
224
  // Register failed: the server `card_mandate_spend` row was never created, so
225
225
  // a delegated draw against this mandate would 404 `no_mandate`. Mark it
226
- // register-failed so findCovering() SKIPS it and the owner's next checkout
227
- // falls through to a fresh per-purchase tap, rather than silently selecting
228
- // a mandate that can't be drawn. Marking is best-effort too a failure here
229
- // must not abort the flow.
226
+ // register-failed so findCovering() SKIPS it rather than silently selecting
227
+ // a mandate that can't be drawn. A v4 grant then refuses until a usable
228
+ // mandate exists; only a legacy credential-file runtime can still use the
229
+ // old per-purchase approval. Marking is best-effort too.
230
230
  const marked = await ledger
231
231
  .markRegisterFailed(args.mandateId, now())
232
232
  .then(() => true)
233
233
  .catch(() => false);
234
234
  process.stderr.write(marked
235
235
  ? `warning: card-mandate register failed (${reg.reason ?? 'unknown'}) — this mandate ` +
236
- `will NOT be used for tap-free draws; your next checkout will interrupt the owner ` +
237
- `for a fresh per-purchase approval. Re-run 'mandate start' to try again.\n`
236
+ `will NOT be used for draws. A v4 card grant cannot spend until a usable mandate ` +
237
+ `is registered. Re-run 'mandate start' to try again.\n`
238
238
  : // Escalate: the mark write ALSO failed, so the mandate is persisted but
239
239
  // NOT disabled — findCovering could still select an undrawable mandate.
240
240
  // Tell the owner loudly not to rely on it and how to recover.
@@ -492,6 +492,11 @@ export function createCliCheckoutEngine(deps = {}) {
492
492
  },
493
493
  async pay(input) {
494
494
  const session = sessions.get(input.reviewId);
495
+ // One signal for the whole pay lifecycle. `cardTokenId` is populated only
496
+ // by the resolved v4 card-grant authority; legacy credential-file calls
497
+ // omit it. Keeping the classification here prevents copy and fallback
498
+ // behavior from drifting onto different grant detectors.
499
+ const isV4CardGrant = typeof input.cardTokenId === 'string' && input.cardTokenId.trim() !== '';
495
500
  if (!session) {
496
501
  return {
497
502
  outcome: 'failed',
@@ -740,7 +745,9 @@ export function createCliCheckoutEngine(deps = {}) {
740
745
  }
741
746
  throw new Error(verdictFailure.transient
742
747
  ? `the card-mandate draw could not be authorized right now (${verdictFailure.reasons.join(', ') || 'temporary error'}) — retry shortly`
743
- : `this card mandate can no longer be drawn (${verdictFailure.reasons.join(', ') || 'refused'}); it has been disabled — retry the checkout to use a fresh per-purchase approval`, { cause: err });
748
+ : `this card mandate can no longer be drawn (${verdictFailure.reasons.join(', ') || 'refused'}); it has been disabled — ${isV4CardGrant
749
+ ? 'create and claim a new mandate before retrying this v4 checkout'
750
+ : 'retry the checkout to use a legacy fresh per-purchase approval'}`, { cause: err });
744
751
  }
745
752
  if (!isTransientDrawFailure(err.cause ?? err)) {
746
753
  await ledger.markUnhonored(mandateId, now());
@@ -758,9 +765,31 @@ export function createCliCheckoutEngine(deps = {}) {
758
765
  instrument = new VgsLiveInstrument(reference, session.contact.fullName ?? '', fetchCredential);
759
766
  }
760
767
  else {
761
- // --- 1:1 fresh-tap flow (unchanged) ---------------------------------
768
+ // A v4 card grant is aggregate authority, not a credential-only escape
769
+ // hatch. Its spend must be represented by a claimed mandate so auth can
770
+ // atomically reserve both the card scope and aggregate budget before a
771
+ // payable cryptogram is minted. Falling through to the legacy 1:1 tap
772
+ // here would create a real card credential with zero canonical draw.
773
+ if (isV4CardGrant) {
774
+ return {
775
+ outcome: 'failed',
776
+ confirmationRef: null,
777
+ receiptPath: null,
778
+ detail: 'this v4 card grant has no active mandate covering the purchase; run ' +
779
+ '`visa mandate start --ceiling <usd> --per-transaction <usd>` for the selected ' +
780
+ 'agent, approve and claim the mandate, then retry this checkout. No payment ' +
781
+ 'credential was requested and no charge was made.',
782
+ vicConfirmation: null,
783
+ source: null,
784
+ remainingMinor: null,
785
+ };
786
+ }
787
+ // --- Legacy 1:1 fresh-tap flow --------------------------------------
788
+ // Kept only for a pre-v4 credential-file runtime. A selected v4 grant
789
+ // can never reach this parallel, non-canonical accounting path.
762
790
  source = 'fresh-tap';
763
- // Legacy credential file OR the activated card:vic grant's token.
791
+ // Legacy credential file only. A v4 grant was refused above unless a
792
+ // covering mandate selected the canonical draw path.
764
793
  const credential = await resolveCardInstrument(input);
765
794
  const assurance = await runHostedApproval({
766
795
  baseUrl: input.approvalBaseUrl,
@@ -144,7 +144,7 @@ export async function createCardMandate(input, deps) {
144
144
  const DRAW_REMEDY = 'the card-mandate draw did not complete — a ceiling-scoped assurance may not be honored ' +
145
145
  'for this sub-amount draw. The budget is untouched. If this was a transient network error ' +
146
146
  '(gateway 5xx / try again), retry — the mandate stays active; a persistent decline disables ' +
147
- 'the mandate and your next pay_merchant uses a fresh per-purchase tap';
147
+ 'the mandate; a v4 card grant then requires a new mandate before another checkout';
148
148
  /**
149
149
  * Thrown ONLY for the post-reservation network/validation rejection class — the
150
150
  * cryptogram fetch failed or returned a non-payable credential (the DRAW_REMEDY