@visa/cli 4.1.0-rc.261 → 4.1.0-rc.263
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/dist/checkout-engine/adapters/shopify.d.ts +20 -2
- package/dist/checkout-engine/adapters/shopify.js +67 -11
- package/dist/checkout-engine/amount.d.ts +2 -0
- package/dist/checkout-engine/cli-engine.d.ts +42 -1
- package/dist/checkout-engine/cli-engine.js +212 -30
- package/dist/checkout-engine/executor.d.ts +31 -1
- package/dist/checkout-engine/executor.js +168 -43
- package/dist/checkout-engine/index.d.ts +4 -2
- package/dist/checkout-engine/index.js +3 -2
- package/dist/checkout-engine/mandate.d.ts +8 -0
- package/dist/checkout-engine/mandate.js +44 -9
- package/dist/checkout-engine/vgs-gateway/server-mint-client.d.ts +37 -0
- package/dist/checkout-engine/vgs-gateway/server-mint-client.js +139 -25
- package/dist/cli.js +414 -414
- package/dist/mcp-server/index.js +321 -321
- package/dist/merchant-ucp-mcp/index.js +3 -3
- package/native/bin/win32-x64/visa-keychain-win.exe +0 -0
- package/package.json +1 -1
- package/server.json +2 -2
|
@@ -63,9 +63,10 @@ export class ServerCryptogramRefusedError extends Error {
|
|
|
63
63
|
this.status = status;
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
66
|
+
async function readRouteErrorDoc(res) {
|
|
67
|
+
return (await res.json().catch(() => null));
|
|
68
|
+
}
|
|
69
|
+
function routeErrorMessage(res, doc) {
|
|
69
70
|
const base = doc?.error || doc?.error_code || `HTTP ${res.status}`;
|
|
70
71
|
// Carry the provider's own status and rejected-attribute identifiers into the
|
|
71
72
|
// message when the route reflected them. Without this the operator sees only
|
|
@@ -79,6 +80,61 @@ async function routeError(res) {
|
|
|
79
80
|
}
|
|
80
81
|
return parts.length > 0 ? `${base} [${parts.join(' ')}]` : base;
|
|
81
82
|
}
|
|
83
|
+
async function routeError(res) {
|
|
84
|
+
return routeErrorMessage(res, await readRouteErrorDoc(res));
|
|
85
|
+
}
|
|
86
|
+
const BOOTSTRAP_STATES = new Set([
|
|
87
|
+
'none',
|
|
88
|
+
'pending',
|
|
89
|
+
'created',
|
|
90
|
+
'ambiguous',
|
|
91
|
+
'conflict',
|
|
92
|
+
'unknown',
|
|
93
|
+
]);
|
|
94
|
+
function bootstrapStateOf(value) {
|
|
95
|
+
return typeof value === 'string' && BOOTSTRAP_STATES.has(value)
|
|
96
|
+
? value
|
|
97
|
+
: 'unknown';
|
|
98
|
+
}
|
|
99
|
+
const SAFE_REQUEST_ID = /^[A-Za-z0-9._:-]{1,128}$/;
|
|
100
|
+
function requestIdOf(res, doc) {
|
|
101
|
+
const candidate = res?.headers.get('x-request-id') ?? doc?.request_id ?? null;
|
|
102
|
+
return typeof candidate === 'string' && SAFE_REQUEST_ID.test(candidate) ? candidate : null;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* The budget-intent route's failure, kept structured (#8470). `status` 0 means
|
|
106
|
+
* the request never produced an HTTP response (network failure or abort), which
|
|
107
|
+
* is outcome-uncertain exactly like a 5xx: the server may have dispatched.
|
|
108
|
+
*/
|
|
109
|
+
export class ServerIntentError extends Error {
|
|
110
|
+
facts;
|
|
111
|
+
code = 'SERVER_INTENT_FAILED';
|
|
112
|
+
constructor(message, facts) {
|
|
113
|
+
super(message);
|
|
114
|
+
this.facts = facts;
|
|
115
|
+
this.name = 'ServerIntentError';
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
function serverIntentErrorFrom(res, doc) {
|
|
119
|
+
const errorCode = typeof doc?.error_code === 'string'
|
|
120
|
+
? doc.error_code
|
|
121
|
+
: typeof doc?.error === 'string' && /^[a-z0-9_]{1,64}$/.test(doc.error)
|
|
122
|
+
? doc.error
|
|
123
|
+
: null;
|
|
124
|
+
const outcome = doc?.outcome === 'uncertain' || doc?.outcome === 'not_created'
|
|
125
|
+
? doc.outcome
|
|
126
|
+
: res.status >= 500 || res.status === 0
|
|
127
|
+
? 'uncertain'
|
|
128
|
+
: 'not_created';
|
|
129
|
+
return new ServerIntentError(`server intent failed (${res.status}): ${routeErrorMessage(res, doc)}`, {
|
|
130
|
+
status: res.status,
|
|
131
|
+
errorCode,
|
|
132
|
+
retryable: doc?.retryable === true,
|
|
133
|
+
requestId: requestIdOf(res, doc),
|
|
134
|
+
bootstrapState: bootstrapStateOf(doc?.bootstrap_state),
|
|
135
|
+
outcome,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
82
138
|
function bearer(mintToken) {
|
|
83
139
|
return { 'content-type': 'application/json', authorization: `Bearer ${mintToken}` };
|
|
84
140
|
}
|
|
@@ -99,34 +155,92 @@ export async function serverCreateIntent(base, mintToken, input, deps = {}) {
|
|
|
99
155
|
const effectiveUntil = override.effectiveUntil ?? new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString();
|
|
100
156
|
const consumerPrompt = override.consumerPrompt ??
|
|
101
157
|
`Buy an item from ${t.merchantName} for ${t.transactionCurrencyCode.toUpperCase()} ${t.transactionAmount}`;
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
158
|
+
let res;
|
|
159
|
+
try {
|
|
160
|
+
res = await fetchImpl(`${stripTrailingSlashes(base)}/api/vgs/intent`, {
|
|
161
|
+
method: 'POST',
|
|
162
|
+
headers: bearer(mintToken),
|
|
163
|
+
body: JSON.stringify({
|
|
164
|
+
tokenId,
|
|
165
|
+
consumerPrompt,
|
|
166
|
+
assuranceData,
|
|
167
|
+
mandates: [
|
|
168
|
+
{
|
|
169
|
+
description: `Purchase at ${t.merchantName}`,
|
|
170
|
+
declineThresholdAmount,
|
|
171
|
+
declineThresholdCurrencyCode: t.transactionCurrencyCode.toUpperCase(),
|
|
172
|
+
effectiveUntil,
|
|
173
|
+
merchantCategory: 'Retail',
|
|
174
|
+
merchantCategoryCode: '5999',
|
|
175
|
+
preferredMerchantName: t.merchantName,
|
|
176
|
+
quantity,
|
|
177
|
+
},
|
|
178
|
+
],
|
|
179
|
+
}),
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
catch (err) {
|
|
183
|
+
// No HTTP response at all: the request may or may not have reached the
|
|
184
|
+
// server, so this is outcome-uncertain, never a proven non-creation.
|
|
185
|
+
throw new ServerIntentError(`server intent failed (0): ${err instanceof Error ? err.message : String(err)}`, {
|
|
186
|
+
status: 0,
|
|
187
|
+
errorCode: null,
|
|
188
|
+
retryable: false,
|
|
189
|
+
requestId: null,
|
|
190
|
+
bootstrapState: 'unknown',
|
|
191
|
+
outcome: 'uncertain',
|
|
192
|
+
});
|
|
193
|
+
}
|
|
123
194
|
if (!res.ok)
|
|
124
|
-
throw
|
|
195
|
+
throw serverIntentErrorFrom(res, await readRouteErrorDoc(res));
|
|
125
196
|
const doc = (await res.json().catch(() => null));
|
|
126
197
|
if (!doc?.intentId)
|
|
127
198
|
throw new Error('server intent response missing intentId');
|
|
128
199
|
return { intentId: doc.intentId, status: typeof doc.status === 'string' ? doc.status : null };
|
|
129
200
|
}
|
|
201
|
+
/**
|
|
202
|
+
* Read the durable budget-bootstrap state for a budget mint token via
|
|
203
|
+
* GET {base}/api/vgs/intent (#8470). A `created` answer carries the intent the
|
|
204
|
+
* server already minted under this token, so the runner registers it instead
|
|
205
|
+
* of dispatching again; `pending` and `ambiguous` are never redispatched.
|
|
206
|
+
*/
|
|
207
|
+
export async function serverReadIntentBootstrap(base, mintToken, deps = {}) {
|
|
208
|
+
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
209
|
+
let res;
|
|
210
|
+
try {
|
|
211
|
+
res = await fetchImpl(`${stripTrailingSlashes(base)}/api/vgs/intent`, {
|
|
212
|
+
method: 'GET',
|
|
213
|
+
headers: bearer(mintToken),
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
catch (err) {
|
|
217
|
+
throw new ServerIntentError(`server intent status failed (0): ${err instanceof Error ? err.message : String(err)}`, {
|
|
218
|
+
status: 0,
|
|
219
|
+
errorCode: null,
|
|
220
|
+
retryable: true,
|
|
221
|
+
requestId: null,
|
|
222
|
+
bootstrapState: 'unknown',
|
|
223
|
+
outcome: 'unknown',
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
const doc = (await res.json().catch(() => null));
|
|
227
|
+
if (!res.ok) {
|
|
228
|
+
const error = serverIntentErrorFrom(res, doc);
|
|
229
|
+
throw new ServerIntentError(error.message.replace('server intent failed', 'server intent status failed'), {
|
|
230
|
+
...error.facts,
|
|
231
|
+
retryable: error.facts.retryable || res.status === 503,
|
|
232
|
+
outcome: 'unknown',
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
const state = bootstrapStateOf(doc?.bootstrap_state);
|
|
236
|
+
const intentId = typeof doc?.intent?.intentId === 'string' && doc.intent.intentId ? doc.intent.intentId : null;
|
|
237
|
+
return {
|
|
238
|
+
state: state === 'created' && !intentId ? 'unknown' : state,
|
|
239
|
+
intentId,
|
|
240
|
+
intentStatus: typeof doc?.intent?.status === 'string' ? doc.intent.status : null,
|
|
241
|
+
requestId: requestIdOf(res, doc),
|
|
242
|
+
};
|
|
243
|
+
}
|
|
130
244
|
/**
|
|
131
245
|
* Mint the FULL payment credential via POST {base}/api/vgs/payment-cryptogram.
|
|
132
246
|
* The server's route does a SINGLE gateway call and 502s on a not-COMPLETED
|