@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
|
|
227
|
-
//
|
|
228
|
-
//
|
|
229
|
-
//
|
|
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
|
|
237
|
-
`
|
|
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 —
|
|
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
|
-
//
|
|
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
|
|
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
|
|
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
|