@agent-cards/sdk 0.23.9 → 0.23.11
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/CHANGELOG.md +48 -0
- package/PREFLIGHT.md +7 -5
- package/README.md +211 -102
- package/dist/aci.d.ts +7 -5
- package/dist/aci.generated.d.ts +2 -1
- package/dist/aci.generated.js +14 -4
- package/dist/aci.js +28 -7
- package/dist/amadeus-checkout.generated.js +2 -2
- package/dist/builtin-registry.generated.js +1 -1
- package/dist/card-fields.generated.js +165 -10
- package/dist/cdp.js +86 -34
- package/dist/client.d.ts +73 -33
- package/dist/client.js +136 -45
- package/dist/creditguard.generated.d.ts +1 -1
- package/dist/creditguard.generated.js +1 -3
- package/dist/cse-body.d.ts +7 -6
- package/dist/cse-body.js +8 -7
- package/dist/datatrans.generated.js +8 -5
- package/dist/elavon-converge.d.ts +5 -2
- package/dist/elavon-converge.generated.js +9 -5
- package/dist/elavon-converge.js +8 -4
- package/dist/fiserv.d.ts +9 -5
- package/dist/fiserv.generated.d.ts +3 -3
- package/dist/fiserv.generated.js +50 -20
- package/dist/fiserv.js +10 -6
- package/dist/lifecycle.d.ts +9 -1
- package/dist/lifecycle.js +34 -0
- package/dist/payrails.d.ts +78 -18
- package/dist/payrails.generated.d.ts +12 -2
- package/dist/payrails.generated.js +213 -18
- package/dist/payrails.js +165 -44
- package/dist/pre-claim.d.ts +7 -0
- package/dist/pre-claim.js +98 -36
- package/dist/preflight-capabilities.generated.d.ts +0 -106
- package/dist/preflight-capabilities.generated.js +0 -153
- package/dist/preflight-catalog.json +61 -181
- package/dist/preflight-schemas.json +6 -6
- package/dist/preflight.generated.d.ts +0 -106
- package/dist/preflight.generated.js +27 -15
- package/dist/preparation.d.ts +2 -0
- package/dist/preparation.js +4 -2
- package/dist/prepared-processor.d.ts +19 -5
- package/dist/prepared-processor.js +21 -5
- package/dist/primer.generated.d.ts +5 -1
- package/dist/primer.generated.js +202 -34
- package/dist/registry.d.ts +18 -12
- package/dist/registry.js +11 -8
- package/dist/shiji-sts.d.ts +24 -22
- package/dist/shiji-sts.generated.d.ts +1 -1
- package/dist/shiji-sts.generated.js +12 -8
- package/dist/shiji-sts.js +49 -46
- package/dist/stripe-checkout.generated.js +157 -49
- package/dist/verifone.generated.js +192 -17
- package/dist/verifone.js +1 -1
- package/dist/worldline-mycheckout.d.ts +27 -0
- package/dist/worldline-mycheckout.generated.d.ts +29 -0
- package/dist/worldline-mycheckout.generated.js +162 -0
- package/dist/worldline-mycheckout.js +93 -0
- package/dist/wspay.d.ts +17 -16
- package/dist/wspay.generated.d.ts +6 -1
- package/dist/wspay.generated.js +13 -3
- package/dist/wspay.js +9 -11
- package/examples/preflight/kernel-native/README.md +1 -1
- package/examples/preflight/kernel-native/inventory.json +1 -19
- package/package.json +3 -3
package/dist/cdp.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { BUILTIN_REGISTRY, cardUrlPatterns } from './registry.js';
|
|
2
2
|
import { AdyenTestPlatformRefusedError, AmountRequiredError, ApprovalDeclinedError, ApprovalTimeoutError, CardEncryptedError, CheckoutApiError, CheckoutCancelledError, CheckoutPreparationError, MerchantTotalError, PaymentOutcomeUnknownError, PreparationRequiredError, ProcessorRefusedError, UnsupportedCapabilityError, UnsupportedModeError, } from './client.js';
|
|
3
3
|
import { PreparationGate } from './preparation.js';
|
|
4
|
+
import { preparedApprovalAnswersRetry } from './prepared-processor.js';
|
|
4
5
|
import { StripeCheckoutGate } from './stripe-checkout.js';
|
|
5
6
|
import { MercadoCheckoutGate } from './mercado-checkout.js';
|
|
6
7
|
import { MERCADO_CHECKOUT_PATTERN } from './mercado-checkout.generated.js';
|
|
@@ -29,6 +30,7 @@ import { CARD_ENDPOINT_REFUSED_STEP_ANSWER, CARD_ENDPOINT_WITHHELD_ANSWER, CardE
|
|
|
29
30
|
import { CardEndpointFactsWatch } from './card-endpoint-facts.js';
|
|
30
31
|
import { PAYRAILS_GUARD_PATTERNS, PAYRAILS_INIT_BODY_LIMIT, PayrailsSessionCapture, isPayrailsHost, payrailsInitCandidate, recognizesPayrails, } from './payrails.js';
|
|
31
32
|
import { cardEndpointStoppedPage, refusedNavigationOutcome } from './card-endpoint-page.js';
|
|
33
|
+
import { WorldlineMyCheckoutProducts } from './worldline-mycheckout.js';
|
|
32
34
|
/**
|
|
33
35
|
* How an adapter fails a paused request it will not let through: as a network error for a
|
|
34
36
|
* CyberSource Flex request (refusedRequestFailure, so the Microform field reports it to the page)
|
|
@@ -328,15 +330,15 @@ function cseAuthorizedDetail(replay, url, shijiCvv = null) {
|
|
|
328
330
|
* getCvvId companion right before the continue (shiji-sts.ts). It is written
|
|
329
331
|
* only into `approved`, the request the approval was created from, byte for
|
|
330
332
|
* byte, so the accessToken and keySequence are the ones the API reviewed, and
|
|
331
|
-
* the cvv is bound to the reviewed getCvvId keys the approval
|
|
332
|
-
* (`
|
|
333
|
-
* takes cseContinuationBody (cse-body.ts cseBody), and `shijiCvv` and
|
|
334
|
-
* `
|
|
333
|
+
* the cvv is bound to the SHA-256 of the reviewed getCvvId keys the approval
|
|
334
|
+
* served (`shijiCvvKeySequenceSha256`): no keys, no continuation. Every other
|
|
335
|
+
* processor takes cseContinuationBody (cse-body.ts cseBody), and `shijiCvv` and
|
|
336
|
+
* `shijiCvvKeySequenceSha256` are null.
|
|
335
337
|
*/
|
|
336
338
|
function cseContinuation(opts, url, body, replay, approved, preparation) {
|
|
337
339
|
const shiji = shijiStsTokenContinuation(url, body, replay.substitutions, approved, replay.kind === 'merchant_hosted' || replay.kind === 'verifone' ? undefined : replay.shijiStsCompanion);
|
|
338
|
-
return shiji ? { body: shiji.body, shijiCvv: shiji.cvv,
|
|
339
|
-
: { body: cseContinuationBody(opts, body, replay, url, preparation), shijiCvv: null,
|
|
340
|
+
return shiji ? { body: shiji.body, shijiCvv: shiji.cvv, shijiCvvKeySequenceSha256: shiji.cvvKeySequenceSha256 }
|
|
341
|
+
: { body: cseContinuationBody(opts, body, replay, url, preparation), shijiCvv: null, shijiCvvKeySequenceSha256: null };
|
|
340
342
|
}
|
|
341
343
|
/**
|
|
342
344
|
* The opaque page id a Shiji STS companion is bound to: the CDP session and
|
|
@@ -1287,7 +1289,10 @@ function sameCheckoutRequest(first, retry) {
|
|
|
1287
1289
|
* answer is a token or ciphertext, and any request for the same purchase can
|
|
1288
1290
|
* carry it. No for a hosted form (a navigation: there is no request to
|
|
1289
1291
|
* answer once the page has moved on), a prepared checkout (single use, bound
|
|
1290
|
-
* to one native request by design)
|
|
1292
|
+
* to one native request by design) other than a Bambora token create, whose
|
|
1293
|
+
* card frame gives up 5 seconds after sending it and whose page asks again
|
|
1294
|
+
* with the same body (preparedApprovalAnswersRetry), from that same card frame
|
|
1295
|
+
* only (sameFrame), a native Stripe Checkout step, a
|
|
1291
1296
|
* Mercado Pago request whose claim is bound to one document read, a Juspay
|
|
1292
1297
|
* /txns (the device pays that request's own order; a page that clicks Pay
|
|
1293
1298
|
* again creates a new order, which the earlier answer would send to the
|
|
@@ -1303,12 +1308,23 @@ function sameCheckoutRequest(first, retry) {
|
|
|
1303
1308
|
* getTokenId while the cardholder decides. Unknown modes (a duck-typed vault
|
|
1304
1309
|
* without checkoutModeOf) are taken as `token`.
|
|
1305
1310
|
*/
|
|
1306
|
-
function survivesAbandonment(opts, url, method, navigation, bound) {
|
|
1311
|
+
function survivesAbandonment(opts, url, method, navigation, bound, preparation) {
|
|
1307
1312
|
if (navigation || bound || isShijiStsHost(url))
|
|
1308
1313
|
return false;
|
|
1314
|
+
if (preparation && !preparedApprovalAnswersRetry(preparation.psp))
|
|
1315
|
+
return false;
|
|
1309
1316
|
const mode = typeof opts.vault?.checkoutModeOf === 'function' ? opts.vault.checkoutModeOf(url, method) : 'token';
|
|
1310
1317
|
return mode === 'token' || mode === 'cse';
|
|
1311
1318
|
}
|
|
1319
|
+
/**
|
|
1320
|
+
* Whether `frameId` on `sessionId` is `owner`, the frame a prepared request came from. A prepared
|
|
1321
|
+
* approval that outlives its request (survivesAbandonment) belongs to that card frame for as long as
|
|
1322
|
+
* it lasts: only that frame's retry is answered from it, and the frame's removal stops the attachment
|
|
1323
|
+
* even after the page gave the request up. A frame with no id is nobody's.
|
|
1324
|
+
*/
|
|
1325
|
+
function sameFrame(owner, sessionId, frameId) {
|
|
1326
|
+
return !!owner && typeof owner.frameId === 'string' && owner.frameId !== '' && owner.frameId === frameId && owner.sessionId === sessionId;
|
|
1327
|
+
}
|
|
1312
1328
|
/**
|
|
1313
1329
|
* One card request's attempt at an approval, and the browser request that
|
|
1314
1330
|
* currently carries it. Separate from explicit cancellation: the client can
|
|
@@ -1973,6 +1989,8 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
|
|
|
1973
1989
|
let lastSubmitted = null;
|
|
1974
1990
|
// The approved Datatrans answers, per frameId: the repeat rule (datatrans.ts).
|
|
1975
1991
|
const datatransRepeats = new DatatransRepeats();
|
|
1992
|
+
// The card product each Worldline MyCheckout hosted checkout chose (worldline-mycheckout.ts).
|
|
1993
|
+
const worldlineProducts = new WorldlineMyCheckoutProducts();
|
|
1976
1994
|
// The Shiji STS getCvvId companion of the getTokenId this attachment last continued (shiji-sts.ts).
|
|
1977
1995
|
// A cancelled attachment drops it, and the device's CVV ciphertext with it, at once: no getCvvId
|
|
1978
1996
|
// can use it any more (pre-claim.ts refuses every one once the lifecycle is aborted). The page,
|
|
@@ -2284,9 +2302,12 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
|
|
|
2284
2302
|
if (!(method === 'Page.frameDetached' && params.reason === 'swap')) {
|
|
2285
2303
|
microforms.forget(method === 'Page.frameDetached' ? params.frameId : method === 'Target.detachedFromTarget' ? params.targetId : undefined);
|
|
2286
2304
|
}
|
|
2305
|
+
// A prepared request's card frame ends its approval even once the page gave the request up and its
|
|
2306
|
+
// live frame id is gone; a target's detach is still read from the session, which a loss keeps.
|
|
2287
2307
|
if (activeRequest && (method === 'Target.detachedFromTarget' ? activeRequest.sessionId === params.sessionId
|
|
2288
2308
|
: method === 'Inspector.detached' ? activeRequest.sessionId === sessionId
|
|
2289
|
-
: activeRequest.sessionId === sessionId && activeRequest.frameId === params.frameId)
|
|
2309
|
+
: (activeRequest.sessionId === sessionId && activeRequest.frameId === params.frameId)
|
|
2310
|
+
|| sameFrame(activeRequest.preparedFrame, sessionId, params.frameId)))
|
|
2290
2311
|
activeRequest.attempt.stop();
|
|
2291
2312
|
// The frame or target the bound getTokenId came from is gone, so its getCvvId cannot follow:
|
|
2292
2313
|
// drop the companion binding and the CVV ciphertext it holds (a binding elsewhere stays).
|
|
@@ -2437,6 +2458,7 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
|
|
|
2437
2458
|
frame: typeof frameId === 'string' && frameId ? frameId : undefined,
|
|
2438
2459
|
payrails,
|
|
2439
2460
|
shijiSts: { companions: shijiCompanions, page: () => cdpShijiPage(sessionId, frameId) },
|
|
2461
|
+
worldlineMyCheckout: worldlineProducts,
|
|
2440
2462
|
});
|
|
2441
2463
|
const preClaim = preClaimResult instanceof Promise ? await preClaimResult : preClaimResult;
|
|
2442
2464
|
if (preClaim.kind !== 'pause')
|
|
@@ -2477,8 +2499,12 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
|
|
|
2477
2499
|
return;
|
|
2478
2500
|
}
|
|
2479
2501
|
let preparation;
|
|
2502
|
+
// The page asking again while a prepared approval outlives its first request (survivesAbandonment):
|
|
2503
|
+
// the preparation is already bound to that approval, so the request is judged as its retry below
|
|
2504
|
+
// and never claimed again.
|
|
2505
|
+
const preparedRetry = awaitingApproval && activeRequest?.attempt.lost === true && preparationGate.isConsumed();
|
|
2480
2506
|
try {
|
|
2481
|
-
preparation = stripeStep ? undefined : preparationGate.claim(request.url, request.method, pausedBody(request), request.headers);
|
|
2507
|
+
preparation = stripeStep || preparedRetry ? undefined : preparationGate.claim(request.url, request.method, pausedBody(request), request.headers);
|
|
2482
2508
|
}
|
|
2483
2509
|
catch (error) {
|
|
2484
2510
|
opts.onEvent?.({ type: 'blocked', detail: failureSummary(error) });
|
|
@@ -2499,7 +2525,10 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
|
|
|
2499
2525
|
&& typeof networkId === 'string' && networkId) {
|
|
2500
2526
|
const retryBody = pausedBody(request);
|
|
2501
2527
|
const retry = retryBody === null ? null : requestIdentity(opts, request.url, request.method, retryBody, documentKey(await documentUrlOf(readDocumentUrl, lifecycle.abort.signal)), await pageAmountOf(opts), flexPause?.shape);
|
|
2502
|
-
|
|
2528
|
+
// A prepared approval is answered only in the card frame it was bound in: the same request from
|
|
2529
|
+
// another frame (a replacement for a removed one, or a second card frame) is refused below.
|
|
2530
|
+
if (retry && activeRequest?.attempt.lost && sameCheckoutRequest(activeRequest.identity, retry)
|
|
2531
|
+
&& (!activeRequest.preparedFrame || sameFrame(activeRequest.preparedFrame, sessionId, frameId))) {
|
|
2503
2532
|
if (preparation)
|
|
2504
2533
|
preparationGate.retireUnboundClaim();
|
|
2505
2534
|
activeRequest.requestId = requestId;
|
|
@@ -2535,7 +2564,10 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
|
|
|
2535
2564
|
}
|
|
2536
2565
|
// Same stop condition as the Playwright adapter: once a failure proves
|
|
2537
2566
|
// retrying is pointless, fail the request without calling the API again.
|
|
2538
|
-
|
|
2567
|
+
// A request that skipped the spent preparation's claim and was not bound as
|
|
2568
|
+
// its retry (the approval ended while it was judged) is refused as that
|
|
2569
|
+
// claim would refuse it, never asked as a new approval.
|
|
2570
|
+
if (terminal || lifecycle.isBlocked() || awaitingApproval || preparedRetry || Date.now() < quietUntil) {
|
|
2539
2571
|
let repeatedAuthorizationId = null;
|
|
2540
2572
|
if (lastSubmitted) {
|
|
2541
2573
|
const body = pausedBody(request);
|
|
@@ -2543,7 +2575,8 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
|
|
|
2543
2575
|
repeatedAuthorizationId = lastSubmitted.authorizationId;
|
|
2544
2576
|
}
|
|
2545
2577
|
}
|
|
2546
|
-
const why = terminal ?? (lifecycle.isBlocked() ? lifecycle.getState().status : awaitingApproval ? 'an approval is already outstanding'
|
|
2578
|
+
const why = terminal ?? (lifecycle.isBlocked() ? lifecycle.getState().status : awaitingApproval ? 'an approval is already outstanding'
|
|
2579
|
+
: preparedRetry ? 'CheckoutPreparationError' : 'awaiting approval cooldown');
|
|
2547
2580
|
opts.onEvent?.({ type: 'blocked', detail: why instanceof Error ? failureSummary(why) : String(why) });
|
|
2548
2581
|
if (preparation)
|
|
2549
2582
|
preparationGate.retireUnboundClaim();
|
|
@@ -2571,8 +2604,8 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
|
|
|
2571
2604
|
awaitingApproval = true;
|
|
2572
2605
|
// A card endpoint approval never outlives its request: the device answers that
|
|
2573
2606
|
// request, and a retry after the page gave up on it is refused, not bound.
|
|
2574
|
-
const attempt = merchantAttempt(lifecycle, survivesAbandonment(opts, request.url, request.method, resourceType === 'Document', !!
|
|
2575
|
-
|| juspayCheckout.bindsApproval(request.url, request.method)));
|
|
2607
|
+
const attempt = merchantAttempt(lifecycle, survivesAbandonment(opts, request.url, request.method, resourceType === 'Document', !!stripeStep || !!cardEndpoint || mercadoCheckout.requiresDocument(request.url, request.method)
|
|
2608
|
+
|| juspayCheckout.bindsApproval(request.url, request.method), preparation));
|
|
2576
2609
|
let handoffStarted = false;
|
|
2577
2610
|
// The approval the device gave, and whether it reached a browser request.
|
|
2578
2611
|
let approvedId = null;
|
|
@@ -2614,7 +2647,8 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
|
|
|
2614
2647
|
}
|
|
2615
2648
|
attempt.bind({ requestId, sessionId, request });
|
|
2616
2649
|
activeRequest = { attempt, requestId, networkId, sessionId, frameId, request,
|
|
2617
|
-
identity: requestIdentity(opts, request.url, request.method, body, undefined, undefined, flexPause?.shape)
|
|
2650
|
+
identity: requestIdentity(opts, request.url, request.method, body, undefined, undefined, flexPause?.shape),
|
|
2651
|
+
preparedFrame: preparation ? { sessionId, frameId } : undefined };
|
|
2618
2652
|
// An ACI card-number request: the page's CVC and expiry requests for its checkout are held
|
|
2619
2653
|
// until this approval ends (see AciGate). One whose checkout already has a card-number
|
|
2620
2654
|
// request running or answered never begins; the pre-claim gate refuses it before this.
|
|
@@ -2787,7 +2821,7 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
|
|
|
2787
2821
|
const continuation = cseContinuation(opts, deliverRequest.url, deliverBody, replay, { url: request.url, body }, preparation);
|
|
2788
2822
|
const postData = Buffer.from(continuation.body).toString('base64');
|
|
2789
2823
|
if (continuation.shijiCvv !== null) {
|
|
2790
|
-
shijiCompanions.bind({ url: request.url, method: request.method, body }, cdpShijiPage(sessionId, frameId), continuation.shijiCvv, continuation.
|
|
2824
|
+
shijiCompanions.bind({ url: request.url, method: request.method, body }, cdpShijiPage(sessionId, frameId), continuation.shijiCvv, continuation.shijiCvvKeySequenceSha256 ?? []);
|
|
2791
2825
|
}
|
|
2792
2826
|
handoffStarted = true;
|
|
2793
2827
|
if (replay.kind === 'verifone') {
|
|
@@ -3276,7 +3310,8 @@ export async function attachToPlaywright(page, opts) {
|
|
|
3276
3310
|
}
|
|
3277
3311
|
});
|
|
3278
3312
|
page.on?.('framedetached', (frame) => {
|
|
3279
|
-
|
|
3313
|
+
// As in attachToCdp: a prepared request's card frame ends its approval even after the page gave the request up.
|
|
3314
|
+
if (activeRequest && (activeRequest.frames.includes(frame) || activeRequest.preparedFrames?.includes(frame)))
|
|
3280
3315
|
activeRequest.attempt.stop();
|
|
3281
3316
|
const gone = frame && typeof frame === 'object' ? playwrightShijiFrames.get(frame) : undefined;
|
|
3282
3317
|
if (gone)
|
|
@@ -3287,6 +3322,8 @@ export async function attachToPlaywright(page, opts) {
|
|
|
3287
3322
|
let lastSubmitted = null;
|
|
3288
3323
|
// The approved Datatrans answers, per Frame: the repeat rule (datatrans.ts).
|
|
3289
3324
|
const datatransRepeats = new DatatransRepeats();
|
|
3325
|
+
// The card product each Worldline MyCheckout hosted checkout chose (worldline-mycheckout.ts).
|
|
3326
|
+
const worldlineProducts = new WorldlineMyCheckoutProducts();
|
|
3290
3327
|
const frameOf = (request) => { try {
|
|
3291
3328
|
return request.frame?.() ?? undefined;
|
|
3292
3329
|
}
|
|
@@ -3304,7 +3341,10 @@ export async function attachToPlaywright(page, opts) {
|
|
|
3304
3341
|
// so the ACI gate routes them (never ACI's widget script, /bins or /v1/collect).
|
|
3305
3342
|
// The matcher sees only the URL. Worldpay eProtect's registration is a GET, so its family and
|
|
3306
3343
|
// the vendor's give-up reports are routed by the eProtect gate (never its scripts).
|
|
3307
|
-
|
|
3344
|
+
// Gravy's session fields are a PUT, so a URL a PUT recognizer claims is routed too; the
|
|
3345
|
+
// pre-claim gates then pause only the PUT and continue the URL's other methods.
|
|
3346
|
+
(url) => opts.vault.isCardRequest(url.toString()) || opts.vault.isCardRequest(url.toString(), 'PUT')
|
|
3347
|
+
|| guards.matches(url.toString()) || stripeCheckout.matches(url.toString()) || mercadoCheckout.matches(url.toString())
|
|
3308
3348
|
|| eprotect.matches(url.toString())
|
|
3309
3349
|
// An armed merchant profile's endpoint or a sibling of it (merchant-hosted.ts), as attachToCdp arms it.
|
|
3310
3350
|
|| (typeof opts.vault.merchantProfileOf === 'function' && opts.vault.merchantProfileOf(url.toString()) !== null)
|
|
@@ -3423,6 +3463,7 @@ export async function attachToPlaywright(page, opts) {
|
|
|
3423
3463
|
frame: frameOf(request),
|
|
3424
3464
|
payrails,
|
|
3425
3465
|
shijiSts: { companions: shijiCompanions, page: async () => (await playwrightShijiDocumentPage(request)) ?? `pw:none:${++playwrightShijiSeq}` },
|
|
3466
|
+
worldlineMyCheckout: worldlineProducts,
|
|
3426
3467
|
});
|
|
3427
3468
|
const preClaim = preClaimResult instanceof Promise ? await preClaimResult : preClaimResult;
|
|
3428
3469
|
if (preClaim.kind !== 'pause')
|
|
@@ -3458,8 +3499,10 @@ export async function attachToPlaywright(page, opts) {
|
|
|
3458
3499
|
if (withoutCard === 'continue')
|
|
3459
3500
|
return route.fallback();
|
|
3460
3501
|
let preparation;
|
|
3502
|
+
// As in attachToCdp: the retry of a prepared request whose approval outlived it is never claimed again.
|
|
3503
|
+
const preparedRetry = awaitingApproval && activeRequest?.attempt.lost === true && preparationGate.isConsumed();
|
|
3461
3504
|
try {
|
|
3462
|
-
preparation = stripeStep ? undefined : preparationGate.claim(request.url(), request.method(), request.postData() ?? '', request.headers());
|
|
3505
|
+
preparation = stripeStep || preparedRetry ? undefined : preparationGate.claim(request.url(), request.method(), request.postData() ?? '', request.headers());
|
|
3463
3506
|
}
|
|
3464
3507
|
catch (error) {
|
|
3465
3508
|
opts.onEvent?.({ type: 'blocked', detail: failureSummary(error) });
|
|
@@ -3473,15 +3516,17 @@ export async function attachToPlaywright(page, opts) {
|
|
|
3473
3516
|
// merchantAttempt and the same check in attachToCdp).
|
|
3474
3517
|
if (awaitingApproval && activeRequest?.attempt.lost && !terminal && !setupStop.signal.aborted && !lifecycle.isCancelled()) {
|
|
3475
3518
|
const retry = requestIdentity(opts, request.url(), request.method(), request.postData() ?? '', documentKey(await documentUrlOf(readDocumentUrl, lifecycle.abort.signal)), await pageAmountOf(opts), flexPause?.shape);
|
|
3476
|
-
|
|
3519
|
+
const retryFrames = [];
|
|
3520
|
+
try {
|
|
3521
|
+
for (let frame = request.frame?.(); frame; frame = frame.parentFrame?.())
|
|
3522
|
+
retryFrames.push(frame);
|
|
3523
|
+
}
|
|
3524
|
+
catch { /* covered by requestfailed */ }
|
|
3525
|
+
// As in attachToCdp: a prepared approval is answered only in the card frame it was bound in.
|
|
3526
|
+
if (activeRequest?.attempt.lost && sameCheckoutRequest(activeRequest.identity, retry)
|
|
3527
|
+
&& (!activeRequest.preparedFrames || (retryFrames.length > 0 && retryFrames[0] === activeRequest.preparedFrames[0]))) {
|
|
3477
3528
|
if (preparation)
|
|
3478
3529
|
preparationGate.retireUnboundClaim();
|
|
3479
|
-
const retryFrames = [];
|
|
3480
|
-
try {
|
|
3481
|
-
for (let frame = request.frame?.(); frame; frame = frame.parentFrame?.())
|
|
3482
|
-
retryFrames.push(frame);
|
|
3483
|
-
}
|
|
3484
|
-
catch { /* covered by requestfailed */ }
|
|
3485
3530
|
activeRequest.request = request;
|
|
3486
3531
|
activeRequest.frames = retryFrames;
|
|
3487
3532
|
opts.onEvent?.({ type: 'card_request_paused', detail: { url: eventUrl(opts.vault, request.url()), recognizer: recognizerNameOf(opts, request.url(), request.method()), authorizationId: lifecycle.getState().authorizationId, retry: true } });
|
|
@@ -3507,7 +3552,8 @@ export async function attachToPlaywright(page, opts) {
|
|
|
3507
3552
|
// CDP adapter's Fetch.failRequest uses, so a refused request resolves
|
|
3508
3553
|
// identically whichever adapter is attached. An Amadeus card request is
|
|
3509
3554
|
// 'failed' (ERR_FAILED) in both too, see failsAsNetworkError (refusalFailure).
|
|
3510
|
-
|
|
3555
|
+
// As in attachToCdp: an unbound request that skipped the spent preparation's claim is refused.
|
|
3556
|
+
if (terminal || lifecycle.isBlocked() || awaitingApproval || preparedRetry || Date.now() < quietUntil) {
|
|
3511
3557
|
let repeatedAuthorizationId = null;
|
|
3512
3558
|
if (lastSubmitted) {
|
|
3513
3559
|
let body = null;
|
|
@@ -3519,7 +3565,8 @@ export async function attachToPlaywright(page, opts) {
|
|
|
3519
3565
|
repeatedAuthorizationId = lastSubmitted.authorizationId;
|
|
3520
3566
|
}
|
|
3521
3567
|
}
|
|
3522
|
-
const why = terminal ?? (lifecycle.isBlocked() ? lifecycle.getState().status : awaitingApproval ? 'an approval is already outstanding'
|
|
3568
|
+
const why = terminal ?? (lifecycle.isBlocked() ? lifecycle.getState().status : awaitingApproval ? 'an approval is already outstanding'
|
|
3569
|
+
: preparedRetry ? 'CheckoutPreparationError' : 'awaiting approval cooldown');
|
|
3523
3570
|
opts.onEvent?.({ type: 'blocked', detail: why instanceof Error ? failureSummary(why) : String(why) });
|
|
3524
3571
|
if (preparation)
|
|
3525
3572
|
preparationGate.retireUnboundClaim();
|
|
@@ -3546,8 +3593,8 @@ export async function attachToPlaywright(page, opts) {
|
|
|
3546
3593
|
navigation = false;
|
|
3547
3594
|
}
|
|
3548
3595
|
// A card endpoint approval never outlives its request (see attachToCdp).
|
|
3549
|
-
const attempt = merchantAttempt(lifecycle, survivesAbandonment(opts, request.url(), request.method(), navigation, !!
|
|
3550
|
-
|| juspayCheckout.bindsApproval(request.url(), request.method())));
|
|
3596
|
+
const attempt = merchantAttempt(lifecycle, survivesAbandonment(opts, request.url(), request.method(), navigation, !!stripeStep || !!cardEndpoint || mercadoCheckout.requiresDocument(request.url(), request.method())
|
|
3597
|
+
|| juspayCheckout.bindsApproval(request.url(), request.method()), preparation));
|
|
3551
3598
|
const frames = [];
|
|
3552
3599
|
try {
|
|
3553
3600
|
for (let frame = request.frame?.(); frame; frame = frame.parentFrame?.())
|
|
@@ -3555,9 +3602,13 @@ export async function attachToPlaywright(page, opts) {
|
|
|
3555
3602
|
}
|
|
3556
3603
|
catch { /* requestfailed/page close still cover unavailable frame metadata */ }
|
|
3557
3604
|
// Judged on the request that currently carries the attempt: after a
|
|
3558
|
-
// retry binds, the first request's failure is history, not a loss.
|
|
3605
|
+
// retry binds, the first request's failure is history, not a loss. A
|
|
3606
|
+
// prepared request's own frames are judged for as long as the approval
|
|
3607
|
+
// lasts, as framedetached judges them: their removal ends it.
|
|
3559
3608
|
const assertRequestLive = () => {
|
|
3560
3609
|
const current = attempt.target;
|
|
3610
|
+
if (preparation && frames.some((frame) => frame.isDetached?.()))
|
|
3611
|
+
attempt.stop();
|
|
3561
3612
|
if (current && (current.request.failure?.() || current.frames.some((frame) => frame.isDetached?.())))
|
|
3562
3613
|
attempt.lose();
|
|
3563
3614
|
if (page.isClosed?.())
|
|
@@ -3587,7 +3638,8 @@ export async function attachToPlaywright(page, opts) {
|
|
|
3587
3638
|
const payToInterceptMs = readPayToInterceptMs(opts.payClickedAt, approvalStartedAt);
|
|
3588
3639
|
attempt.bind({ route, request, frames });
|
|
3589
3640
|
activeRequest = { request, frames, attempt,
|
|
3590
|
-
identity: requestIdentity(opts, request.url(), request.method(), body, undefined, undefined, flexPause?.shape)
|
|
3641
|
+
identity: requestIdentity(opts, request.url(), request.method(), body, undefined, undefined, flexPause?.shape),
|
|
3642
|
+
preparedFrames: preparation ? [...frames] : undefined };
|
|
3591
3643
|
// An ACI card-number request: see the same step in attachToCdp.
|
|
3592
3644
|
if (!aci.begin(request.url(), request.method(), body, preparation?.merchantOrigin)) {
|
|
3593
3645
|
throw new CheckoutPreparationError(preparation?.id ?? null, ACI_CHECKOUT_REUSED);
|
|
@@ -3728,7 +3780,7 @@ export async function attachToPlaywright(page, opts) {
|
|
|
3728
3780
|
if (boundPage === null)
|
|
3729
3781
|
throw new ShijiStsError('unsupported');
|
|
3730
3782
|
assertRequestLive();
|
|
3731
|
-
shijiCompanions.bind({ url: request.url(), method: request.method(), body }, boundPage, continuation.shijiCvv, continuation.
|
|
3783
|
+
shijiCompanions.bind({ url: request.url(), method: request.method(), body }, boundPage, continuation.shijiCvv, continuation.shijiCvvKeySequenceSha256 ?? []);
|
|
3732
3784
|
}
|
|
3733
3785
|
handoffStarted = true;
|
|
3734
3786
|
if (replay.kind === 'verifone') {
|
package/dist/client.d.ts
CHANGED
|
@@ -41,11 +41,13 @@ export interface PausedRequest {
|
|
|
41
41
|
facts?: CardEndpointFactsRecord;
|
|
42
42
|
};
|
|
43
43
|
/**
|
|
44
|
-
* A Payrails card save (mode device_encrypt) only: the
|
|
45
|
-
* Payrails init payload named
|
|
46
|
-
* the
|
|
47
|
-
*
|
|
48
|
-
*
|
|
44
|
+
* A Payrails card save or card-form payment (mode device_encrypt) only: the
|
|
45
|
+
* session key the page's Payrails init payload named
|
|
46
|
+
* (vaultConfiguration.encryptionPublicKey), which the request itself does not
|
|
47
|
+
* carry, and for a card-form payment the payload's holderReference, which the
|
|
48
|
+
* payment's ciphertext carries and its body does not. The adapters capture
|
|
49
|
+
* both; a caller running its own interception passes them here, and a request
|
|
50
|
+
* without them is refused before anything is created.
|
|
49
51
|
*/
|
|
50
52
|
payrails?: PayrailsSidecar;
|
|
51
53
|
}
|
|
@@ -54,7 +56,7 @@ export interface PausedRequest {
|
|
|
54
56
|
* only recognizers in these modes, so a request this build cannot complete
|
|
55
57
|
* is never paused) and sent on every create. `device_encrypt`: the adapters
|
|
56
58
|
* capture the Payrails session key the page's init payload names (payrails.ts),
|
|
57
|
-
* and the cardholder's device encrypts and sends the save.
|
|
59
|
+
* and the cardholder's device encrypts and sends the save or card-form payment.
|
|
58
60
|
*/
|
|
59
61
|
export declare const SUPPORTED_MODES: readonly CheckoutMode[];
|
|
60
62
|
/**
|
|
@@ -124,14 +126,17 @@ export declare const SUPPORTED_REGISTRY_FEATURES: readonly string[];
|
|
|
124
126
|
* `primer_payment_instruments`: Primer's card tokenization, which this build
|
|
125
127
|
* judges before it pauses (primer.ts) and hands back to the page only as a
|
|
126
128
|
* single-use token. A merchant's client session can save the card after the
|
|
127
|
-
* payment with no sign in that request, so the API creates a Primer
|
|
128
|
-
* authorization only
|
|
129
|
-
*
|
|
130
|
-
*
|
|
131
|
-
*
|
|
132
|
-
*
|
|
133
|
-
*
|
|
134
|
-
*
|
|
129
|
+
* payment with no sign in that request, so the API creates a live Primer
|
|
130
|
+
* authorization only when it names the checkout page's https origin
|
|
131
|
+
* (checkout_origin, which this build sends from the page's origin): the
|
|
132
|
+
* approval says the merchant keeps the card on file, under the merchant name
|
|
133
|
+
* the create sent, which Agentcard cannot confirm, and pays only with a card
|
|
134
|
+
* the person stored. A refused create aborts the paused request with nothing
|
|
135
|
+
* sent. A gated recognizer never sits in BUILTIN_REGISTRY, so it is paused only
|
|
136
|
+
* after a sync.
|
|
137
|
+
* `payrails_device_encrypt`: Payrails' card save and card-form payment, which
|
|
138
|
+
* this build pauses only once it has bound the request to a captured session
|
|
139
|
+
* (payrails.ts). A gated
|
|
135
140
|
* recognizer never sits in BUILTIN_REGISTRY, so it is paused only after a sync.
|
|
136
141
|
* `converge_hpp_process`: Elavon Converge's Hosted Payment Page card request
|
|
137
142
|
* (elavon-converge.ts), paused in token mode and judged by the pre-claim gate.
|
|
@@ -183,6 +188,22 @@ export declare const SUPPORTED_REGISTRY_FEATURES: readonly string[];
|
|
|
183
188
|
* finds a card in it (cybersource-secure-acceptance.ts). Served on the same terms,
|
|
184
189
|
* so every other company keeps the stop every build applies to a card posted to
|
|
185
190
|
* Secure Acceptance.
|
|
191
|
+
* `gravy_session_fields_put`: Gravy Secure Fields' update of a checkout session,
|
|
192
|
+
* the one card request a recognizer declares as a PUT. This build pauses that PUT
|
|
193
|
+
* from Gravy's controller frame as it pauses a POST (isRecognizerRequest), creates
|
|
194
|
+
* the authorization with the PUT, and answers the frame with Gravy's own answer to
|
|
195
|
+
* the cardholder's device. Served on the same terms; an older build never sees the
|
|
196
|
+
* entry, and every company Agentcard has not turned Gravy on for keeps today's path,
|
|
197
|
+
* where the request continues to Gravy untouched.
|
|
198
|
+
* `moneris_hosted_tokenization`: Moneris Hosted Tokenization's card request, the
|
|
199
|
+
* frame's form post to /HPPtoken/request.php. This build pauses it only when it
|
|
200
|
+
* carries the number, the expiry and the CVD the cardholder's device fills, fails a
|
|
201
|
+
* frame that leaves the expiry or CVD to the merchant's page in the browser with a
|
|
202
|
+
* `blocked` event, `request_without_card`, and answers with Moneris's token as the
|
|
203
|
+
* API recorded it. The device sends the card through the checkout relay. Served on
|
|
204
|
+
* the same terms: only to a build that lists it, for a company Agentcard turned
|
|
205
|
+
* Moneris on for. A gated recognizer never sits in BUILTIN_REGISTRY, so it is paused
|
|
206
|
+
* only after a sync.
|
|
186
207
|
*/
|
|
187
208
|
export declare const SUPPORTED_CAPABILITIES: readonly string[];
|
|
188
209
|
/**
|
|
@@ -276,12 +297,13 @@ export interface CseReplay extends ExecutionMetadata {
|
|
|
276
297
|
authorizationId: string;
|
|
277
298
|
substitutions: Substitutions;
|
|
278
299
|
/**
|
|
279
|
-
* Shiji STS only: the getCvvId keySequence
|
|
280
|
-
* served beside the set (`shiji_sts_companion`). The adapters bind the
|
|
281
|
-
* (shiji-sts.ts ShijiStsCompanions), so the
|
|
300
|
+
* Shiji STS only: the SHA-256 of each getCvvId keySequence the API reviewed as the booking page's
|
|
301
|
+
* own, served beside the set (`shiji_sts_companion.key_sequence_sha256`). The adapters bind the
|
|
302
|
+
* companion getCvvId to a key that hashes to one of them (shiji-sts.ts ShijiStsCompanions), so the
|
|
303
|
+
* device's CVV goes under no other key.
|
|
282
304
|
*/
|
|
283
305
|
shijiStsCompanion?: Readonly<{
|
|
284
|
-
|
|
306
|
+
keySequenceSha256: readonly string[];
|
|
285
307
|
}>;
|
|
286
308
|
amountAuthority?: AmountAuthority;
|
|
287
309
|
}
|
|
@@ -401,12 +423,15 @@ export interface CardEndpointReplay extends ExecutionMetadata {
|
|
|
401
423
|
/**
|
|
402
424
|
* The device_encrypt flow (Payrails): the cardholder's device encrypted the
|
|
403
425
|
* real card under the session key the page's init payload named and sent the
|
|
404
|
-
* save to Payrails itself. This is Payrails'
|
|
405
|
-
* allow-list
|
|
406
|
-
*
|
|
407
|
-
*
|
|
408
|
-
*
|
|
409
|
-
*
|
|
426
|
+
* save or card-form payment to Payrails itself. This is Payrails' answer,
|
|
427
|
+
* rebuilt from an allow-list, to replay into the paused request as in the token
|
|
428
|
+
* flow: for a save its 201 (the instrument's identifiers, its status, the
|
|
429
|
+
* network, the BIN cut to six digits, the last four, the expiry, the holder
|
|
430
|
+
* name and the fingerprint); for a card-form payment its 2xx naming the
|
|
431
|
+
* authorize action (its name, id, time and execution link, which the page's
|
|
432
|
+
* Payrails SDK polls for the issuer's answer). The ciphertext the device made
|
|
433
|
+
* never reaches the runtime or the page. Never an autopilot result: autopilot
|
|
434
|
+
* refuses this mode.
|
|
410
435
|
*/
|
|
411
436
|
export interface DeviceEncryptReplay extends ExecutionMetadata {
|
|
412
437
|
mode: 'device_encrypt';
|
|
@@ -430,11 +455,14 @@ export interface PrepareCheckoutOptions {
|
|
|
430
455
|
* card request is then the one request this preparation pays.
|
|
431
456
|
*
|
|
432
457
|
* A Fiserv checkout (psp 'fiserv'): the id of the key Agentcard pinned for the
|
|
433
|
-
* merchant
|
|
458
|
+
* merchant: `homedepot_orangepay` for Blinds.com and homedepot.com, and
|
|
459
|
+
* `agentcard_sandbox` in the sandbox environment. Required for Fiserv,
|
|
434
460
|
* and refused for every processor but Adyen and Fiserv. The cardholder is asked to
|
|
435
461
|
* approve a payment to the merchant that key belongs to, so the checkout's `merchant`
|
|
436
|
-
* must be that merchant's name (`
|
|
437
|
-
* one card capture this preparation pays
|
|
462
|
+
* must be that merchant's name (`Home Depot` for `homedepot_orangepay`, `Agentcard
|
|
463
|
+
* sandbox` for `agentcard_sandbox`), and the one card capture this preparation pays
|
|
464
|
+
* must be that merchant's, under that key or under a credentials session's key,
|
|
465
|
+
* which the envelope's key replaces.
|
|
438
466
|
*
|
|
439
467
|
* A Verifone checkout (psp 'verifone'): the id of the Verifone profile Agentcard
|
|
440
468
|
* reviewed for the merchant, which this client must have armed and enabled
|
|
@@ -711,6 +739,12 @@ export interface PlaceholderCard {
|
|
|
711
739
|
/** The stored card's expiry year, four digits ("2031"). */
|
|
712
740
|
readonly expiryYear: string;
|
|
713
741
|
}
|
|
742
|
+
/**
|
|
743
|
+
* The placeholder number for a network (lowercase, as GET /api/v2/vault_cards names it) on a
|
|
744
|
+
* processor's page: the processor's own number for that network when `overrides` names one, else
|
|
745
|
+
* the network's default, else undefined for a network with no placeholder.
|
|
746
|
+
*/
|
|
747
|
+
export declare function placeholderNumber(brand: string, psp?: string, overrides?: Readonly<Record<string, Readonly<Record<string, string>>>>): string | undefined;
|
|
714
748
|
/**
|
|
715
749
|
* The recognizer says this processor is preparation-required, and no preparation
|
|
716
750
|
* was passed. Its card may be sent only after the cardholder approved a
|
|
@@ -1198,8 +1232,9 @@ export declare class VaultClient {
|
|
|
1198
1232
|
private mergedRegistry;
|
|
1199
1233
|
/**
|
|
1200
1234
|
* The recogniser that claims a card request, or null; what a paused request names as
|
|
1201
|
-
* `recognizer`. A POST to a recognised endpoint,
|
|
1202
|
-
* GET (Worldpay eProtect) that
|
|
1235
|
+
* `recognizer`. A POST to a recognised endpoint, a PUT for a recognizer whose card request is
|
|
1236
|
+
* a PUT (Gravy), or for a recognizer whose card request is a GET (Worldpay eProtect) that
|
|
1237
|
+
* exact GET, query included.
|
|
1203
1238
|
*/
|
|
1204
1239
|
recognizerOf(url: string, method?: string): Recognizer | null;
|
|
1205
1240
|
/** Refresh recognizers from the API so new PSPs work without a redeploy. */
|
|
@@ -1244,8 +1279,9 @@ export declare class VaultClient {
|
|
|
1244
1279
|
cardOnFilePolicy(url: string, method?: string): string | null;
|
|
1245
1280
|
/**
|
|
1246
1281
|
* True when this request is a card tokenization we can take over: a POST to
|
|
1247
|
-
* a recognized endpoint,
|
|
1248
|
-
* (
|
|
1282
|
+
* a recognized endpoint, a PUT for a recognizer whose card request is a PUT
|
|
1283
|
+
* (Gravy), or for a recognizer whose card request is a GET (Worldpay eProtect)
|
|
1284
|
+
* that exact GET, query included.
|
|
1249
1285
|
*/
|
|
1250
1286
|
isCardRequest(url: string, method?: string): boolean;
|
|
1251
1287
|
/**
|
|
@@ -1306,12 +1342,16 @@ export declare class VaultClient {
|
|
|
1306
1342
|
* preselects that card: without a pin the page preselects its own choice (its recommended
|
|
1307
1343
|
* card, or the first card the cardholder's device can open), which can be another card. After
|
|
1308
1344
|
* prepare(), pass the prepared checkout's `cardId`, the card the cardholder already approved.
|
|
1309
|
-
* The facts hold only while the cardholder pays with that card.
|
|
1310
|
-
*
|
|
1345
|
+
* The facts hold only while the cardholder pays with that card. `psp` names the processor
|
|
1346
|
+
* whose card form the agent types into (the recognizer's name, `worldline_mycheckout`): a
|
|
1347
|
+
* processor that needs its own number for the card's network gets it, and every other gets the
|
|
1348
|
+
* network's default. Throws PlaceholderCardError when there is no such card, its network has no
|
|
1349
|
+
* placeholder, or it carries no expiry.
|
|
1311
1350
|
*/
|
|
1312
1351
|
placeholderCard(input: {
|
|
1313
1352
|
user: string;
|
|
1314
1353
|
cardId?: string;
|
|
1354
|
+
psp?: string;
|
|
1315
1355
|
signal?: AbortSignal;
|
|
1316
1356
|
}): Promise<PlaceholderCard>;
|
|
1317
1357
|
/** Wait for real device approval before the caller starts native tokenization. */
|