@agent-cards/sdk 0.23.21 → 0.23.23

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.
Files changed (56) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/PREFLIGHT.md +4 -0
  3. package/README.md +53 -4
  4. package/dist/aci.generated.js +3 -3
  5. package/dist/agent-amount.generated.d.ts +7 -0
  6. package/dist/agent-amount.generated.js +3 -0
  7. package/dist/amadeus-checkout.generated.d.ts +6 -1
  8. package/dist/amadeus-checkout.generated.js +26 -2
  9. package/dist/capabilities.generated.d.ts +2 -0
  10. package/dist/capabilities.generated.js +3 -0
  11. package/dist/card-fields.generated.js +2574 -186
  12. package/dist/cdp.js +103 -29
  13. package/dist/client.d.ts +130 -9
  14. package/dist/client.js +304 -115
  15. package/dist/creditguard.generated.js +4 -2
  16. package/dist/cse-body.js +11 -0
  17. package/dist/ebanx.d.ts +1 -1
  18. package/dist/ebanx.js +1 -1
  19. package/dist/eximbay.d.ts +46 -0
  20. package/dist/eximbay.js +69 -0
  21. package/dist/hosted-entry.js +2 -1
  22. package/dist/hosted-form.d.ts +5 -0
  23. package/dist/hosted-form.js +5 -0
  24. package/dist/lifecycle.d.ts +9 -4
  25. package/dist/lifecycle.js +16 -4
  26. package/dist/paygent.generated.d.ts +60 -0
  27. package/dist/paygent.generated.js +425 -0
  28. package/dist/payrexx.d.ts +91 -0
  29. package/dist/payrexx.generated.d.ts +91 -0
  30. package/dist/payrexx.generated.js +536 -0
  31. package/dist/payrexx.js +141 -0
  32. package/dist/paytrail.d.ts +58 -0
  33. package/dist/paytrail.js +98 -0
  34. package/dist/pre-claim.d.ts +27 -0
  35. package/dist/pre-claim.js +175 -20
  36. package/dist/prepared-processor.d.ts +7 -2
  37. package/dist/prepared-processor.js +16 -2
  38. package/dist/primer.generated.js +2709 -321
  39. package/dist/registry.d.ts +4 -3
  40. package/dist/registry.js +12 -4
  41. package/dist/sbps.d.ts +57 -0
  42. package/dist/sbps.generated.d.ts +96 -0
  43. package/dist/sbps.generated.js +427 -0
  44. package/dist/sbps.js +90 -0
  45. package/dist/stripe-checkout.generated.js +744 -27
  46. package/dist/toast-seal.generated.d.ts +37 -0
  47. package/dist/toast-seal.generated.js +625 -0
  48. package/dist/toast.generated.d.ts +41 -0
  49. package/dist/toast.generated.js +113 -0
  50. package/dist/transbank.d.ts +33 -0
  51. package/dist/transbank.js +62 -0
  52. package/dist/url-mask-parts.d.ts +24 -0
  53. package/dist/url-mask-parts.js +22 -0
  54. package/dist/url-masks.generated.d.ts +3 -0
  55. package/dist/url-masks.generated.js +2 -0
  56. package/package.json +4 -4
package/dist/cdp.js CHANGED
@@ -8,23 +8,28 @@ import { MERCADO_CHECKOUT_PATTERN } from './mercado-checkout.generated.js';
8
8
  import { JuspayCheckoutGate } from './juspay-gate.js';
9
9
  import { ACI_CHECKOUT_REUSED, AciGate, playwrightReplacesNavigationBody } from './aci.js';
10
10
  import { EprotectGate, classifyEprotectRequest, eprotectScriptAnswer } from './eprotect.js';
11
+ import { SbpsGate, classifySbpsRequest, sbpsScriptAnswer } from './sbps.js';
11
12
  import { dispatchPreClaim, eventUrl, isArmedCreditguardCall, routesHipayVault } from './pre-claim.js';
12
13
  import { GMO_TOKEN_V1_PATTERNS, gmoRequestShop, isGmoTokenV1Request, recognizesGmo } from './gmo.js';
13
14
  import { isPaddleVgsCardsUrl } from './paddle-vgs.generated.js';
15
+ import { isToastPaymentMethodsUrl, readToastBody } from './toast.generated.js';
14
16
  import { PADDLE_VGS_PAY_PATTERNS, PaddleVgsPayGate, isPaddleCheckoutService, recognizesPaddleVgs } from './paddle-vgs-pay.js';
15
17
  import { DatatransRepeats } from './datatrans.js';
16
18
  import { TokenexIframeRepeats, isArmedTokenexIframeUrl } from './tokenex-iframe.js';
19
+ import { isArmedPaytrailUrl } from './paytrail.js';
17
20
  import { SHIJI_STS_COMPANION_WINDOW_MS, ShijiStsCompanions, ShijiStsError, isShijiStsHost, shijiStsTokenContinuation, } from './shiji-sts.js';
18
21
  import { paddleTokenexTokenContinuation } from './paddle-tokenex.js';
19
22
  import { twoC2pContinuation, twoC2pRetire } from './twoc2p.js';
20
23
  import { isArmedEkashuUrl } from './ekashu.js';
21
- import { isArmedTransbankUrl } from './transbank.js';
24
+ import { isArmedTransbankUrl, transbankMerchantReturn, transbankMerchantReturnAnswer } from './transbank.js';
22
25
  import { isArmedRedsysUrl } from './redsys.js';
23
26
  import { isArmedPrzelewy24Url } from './przelewy24.js';
24
27
  import { isArmedQuickpayUrl } from './quickpay.js';
25
28
  import { COMGATE_CARD_PATTERNS, isArmedComgateUrl, servesComgate } from './comgate.js';
29
+ import { isArmedEximbayUrl } from './eximbay.js';
26
30
  import { PAYPAL_HOSTED_FIELDS_PATTERNS, isPaypalHostedFieldsConfirm } from './paypal-hosted-fields.js';
27
31
  import { EBANX_RESEND_PATTERNS, EbanxFollowUps, isEbanxResendUrl } from './ebanx.js';
32
+ import { PayrexxFollowUps } from './payrexx.js';
28
33
  import { PAYPAL_CARD_FORM_PATTERNS, isPaypalCardFormUrl } from './paypal-card-forms.js';
29
34
  import { CYBERSOURCE_SECURE_ACCEPTANCE_PATTERNS, CYBERSOURCE_SECURE_ACCEPTANCE_RECIPIENT, SecureAcceptanceFollowUpWatch, isCybersourceSecureAcceptanceUrl, secureAcceptanceFollowUp, secureAcceptanceFrameAnswer, secureAcceptanceReplyLast4, } from './cybersource-secure-acceptance.js';
30
35
  import { CheckoutAttachmentError, attachmentDeadline, attachmentFailure, withinAttachmentDeadline } from './attachment.js';
@@ -1288,9 +1293,23 @@ function requestIdentity(opts, url, method, body, documentUrl, pageAmount, flexS
1288
1293
  // out, so the shop joins the identity: a retry that names another shop needs its own approval.
1289
1294
  const shop = gmoRequestShop(url, body);
1290
1295
  const identity = shop === undefined ? shaped.shape : `${shaped.shape};gmo_shop:${JSON.stringify(shop)}`;
1291
- return { url, method, documentUrl, pageAmount, body: `${identity}${paddleVgsRetryKey(url, body, headers)}`,
1296
+ return { url, method, documentUrl, pageAmount, body: `${identity}${paddleVgsRetryKey(url, body, headers)}${toastRetryKey(url, body)}`,
1292
1297
  amountEvidence: opts.amount != null || pageAmount !== undefined || shaped.namesAmount };
1293
1298
  }
1299
+ /**
1300
+ * What a Toast card request's retry must repeat, beyond its shape: the sealed card opens at any Toast
1301
+ * restaurant whose payment intent names it, and the frame seals the ZIP inside it, so the payment session
1302
+ * and the ZIP join the identity. A request under another session or with another ZIP never receives the
1303
+ * card the cardholder approved for the first, while the frame's retry, its placeholder sealed afresh, still
1304
+ * does. A body Toast's reader refuses joins whole, so it matches nothing but itself. Empty for every other
1305
+ * request.
1306
+ */
1307
+ function toastRetryKey(url, body) {
1308
+ if (!isToastPaymentMethodsUrl(url))
1309
+ return '';
1310
+ const read = readToastBody(body);
1311
+ return `;toast:${JSON.stringify(read.ok ? [read.sessionSecret, read.postalCode] : body)}`;
1312
+ }
1294
1313
  /**
1295
1314
  * What a Paddle VGS card request's retry must repeat byte for byte, beyond its shape: Paddle's frame
1296
1315
  * sends its second request with the same body under the same bearer, and the bearer names the
@@ -1624,6 +1643,22 @@ function eprotectResumedReplay(replay, request) {
1624
1643
  throw new PaymentOutcomeUnknownError(replay.authorizationId, 'eprotect_registration_unconfirmed');
1625
1644
  return { ...replay, status: answer.status, headers: answer.headers, body: answer.body };
1626
1645
  }
1646
+ /**
1647
+ * SB Payment Service's token request, resumed by the business, on the same terms as eProtect's above. The read
1648
+ * carries the token as the API stored it, which the paused <script> cannot run, so it gets the answer authorize()
1649
+ * gives on the create path (client.ts): the token inside the callback the paused URL named, under a script type,
1650
+ * and only a token payment-core's judge accepts for that URL. A read in any other mode never answers it either.
1651
+ * The paused URL alone decides, whatever recognizer claimed it. Any other request's replay passes as it came, and
1652
+ * a create's replay never comes here, so no answer is wrapped twice.
1653
+ */
1654
+ function sbpsResumedReplay(replay, request) {
1655
+ if (classifySbpsRequest(request.url, request.method)?.kind !== 'token_request')
1656
+ return replay;
1657
+ const answer = replay.mode === 'token' ? sbpsScriptAnswer(request.url, replay) : null;
1658
+ if (replay.mode !== 'token' || !answer)
1659
+ throw new PaymentOutcomeUnknownError(replay.authorizationId, 'sbps_token_unconfirmed');
1660
+ return { ...replay, status: answer.status, headers: answer.headers, body: answer.body };
1661
+ }
1627
1662
  /** The recogniser that claimed a paused request, by processor name, so a caller never re-matches the URL; null on a duck-typed vault without the read. */
1628
1663
  function recognizerNameOf(opts, url, method) {
1629
1664
  const read = opts.vault.recognizerOf;
@@ -1867,8 +1902,11 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
1867
1902
  const juspayCheckout = new JuspayCheckoutGate(opts.vault);
1868
1903
  // The approved EBANX token request on this attachment and the sends claimed under it (ebanx.ts).
1869
1904
  const ebanxFollowUps = new EbanxFollowUps();
1905
+ // The approved Payrexx card request on this attachment and the 3-D Secure sends claimed under it (payrexx.ts).
1906
+ const payrexxFollowUps = new PayrexxFollowUps();
1870
1907
  const aci = new AciGate(opts.vault);
1871
1908
  const eprotect = new EprotectGate(opts.vault);
1909
+ const sbps = new SbpsGate(opts.vault);
1872
1910
  // The merchant's follow-up request after a delivered Secure Acceptance reply (cybersource-secure-acceptance.ts).
1873
1911
  const secureAcceptanceFollowUpWatch = new SecureAcceptanceFollowUpWatch(opts.onEvent);
1874
1912
  lifecycle.abort.signal.addEventListener('abort', () => secureAcceptanceFollowUpWatch.end(), { once: true });
@@ -2786,7 +2824,7 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
2786
2824
  // headers and body are read lazily, inside the gate that needs them (see PreClaimContext.request).
2787
2825
  request: { url: request.url, method: request.method, get headers() { return request.headers; }, get body() { return pausedBody(request); },
2788
2826
  navigation: resourceType === 'Document' },
2789
- vault: opts.vault, guards, mercado: mercadoCheckout, stripe: stripeCheckout, aci, eprotect,
2827
+ vault: opts.vault, guards, mercado: mercadoCheckout, stripe: stripeCheckout, aci, eprotect, sbps,
2790
2828
  // This adapter drives Chromium, which sends the body Fetch.continueRequest replaces on a
2791
2829
  // navigation (the ACI browser test's classic case, design gate G3).
2792
2830
  replacesNavigationBody: async () => true,
@@ -2821,6 +2859,7 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
2821
2859
  datatrans: datatransRepeats,
2822
2860
  tokenexIframe: tokenexIframeRepeats,
2823
2861
  ebanx: ebanxFollowUps,
2862
+ payrexx: payrexxFollowUps,
2824
2863
  frame: typeof frameId === 'string' && frameId ? frameId : undefined,
2825
2864
  payrails,
2826
2865
  shijiSts: { companions: shijiCompanions, page: () => cdpShijiPage(sessionId, frameId) },
@@ -2934,8 +2973,9 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
2934
2973
  // A request that skipped the spent preparation's claim and was not bound as
2935
2974
  // its retry (the approval ended while it was judged) is refused as that
2936
2975
  // claim would refuse it, never asked as a new approval.
2937
- // An EBANX send still out (a confirm that may pay) holds the attachment too: a new card request the page sends
2938
- // meanwhile, a second tap on the hosted page's Pay among them, would start another payment over it.
2976
+ // An EBANX or Payrexx send still out (a confirm that may pay, a 3-D Secure step whose answer names the token the
2977
+ // page charges) holds the attachment too: a new card request the page sends meanwhile, a second tap on the hosted
2978
+ // page's Pay among them, would start another payment over it.
2939
2979
  const followUpOut = lifecycle.followUpsInFlight();
2940
2980
  if (terminal || lifecycle.isBlocked() || awaitingApproval || followUpOut || preparedRetry || Date.now() < quietUntil) {
2941
2981
  let repeatedAuthorizationId = null;
@@ -2946,7 +2986,7 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
2946
2986
  }
2947
2987
  }
2948
2988
  const why = terminal ?? (lifecycle.isBlocked() ? lifecycle.getState().status : awaitingApproval ? 'an approval is already outstanding'
2949
- : followUpOut ? 'an EBANX send is in flight' : preparedRetry ? 'CheckoutPreparationError' : 'awaiting approval cooldown');
2989
+ : followUpOut ? 'a follow-up send is in flight' : preparedRetry ? 'CheckoutPreparationError' : 'awaiting approval cooldown');
2950
2990
  opts.onEvent?.({ type: 'blocked', detail: why instanceof Error ? failureSummary(why) : String(why) });
2951
2991
  if (preparation)
2952
2992
  preparationGate.retireUnboundClaim();
@@ -2987,8 +3027,9 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
2987
3027
  // The approval the device gave, and whether it reached a browser request.
2988
3028
  let approvedId = null;
2989
3029
  let deliveryBegun = false;
2990
- // Whether this handoff kept an EBANX approval's follow-ups (ebanxFollowUps.remember), which a failed handoff stops.
2991
- let ebanxRemembered = false;
3030
+ // Whether this handoff kept an approval's follow-ups (ebanxFollowUps.remember, payrexxFollowUps.remember), which a
3031
+ // failed handoff stops.
3032
+ let followUpsRemembered = false;
2992
3033
  // The business's pause, so resume resolves once the page has its answer.
2993
3034
  let businessPause = null;
2994
3035
  // Hold mode: the person's release, settled with what came of it.
@@ -3169,7 +3210,7 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
3169
3210
  holdRelease.settle(outcome);
3170
3211
  throw new HoldAnsweredError(outcome);
3171
3212
  }
3172
- const replay = pause ? eprotectResumedReplay(await pause.replay, request) : await authorization;
3213
+ const replay = pause ? sbpsResumedReplay(eprotectResumedReplay(await pause.replay, request), request) : await authorization;
3173
3214
  attempt.assertLive();
3174
3215
  approvedId = replay.authorizationId;
3175
3216
  if (lifecycle.isCancelled())
@@ -3219,8 +3260,14 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
3219
3260
  // answered the device. The paused NAVIGATION is fulfilled with a page
3220
3261
  // that says exactly that (see hosted-form.ts for why not an abort and
3221
3262
  // why not a fake result page), and the same form is refused if the
3222
- // page posts it again.
3223
- const page = hostedFormSubmittedPage({ authorizationId: replay.authorizationId, merchant: opts.merchant, submittedAt: replay.submittedAt });
3263
+ // page posts it again. A Webpay entry whose reviewed shop names its
3264
+ // return page goes to that page instead, with the token of the link
3265
+ // the approval was created from, the one the phone paid, since Webpay
3266
+ // tells the shop of a payment only through that return (transbank.ts
3267
+ // transbankMerchantReturn).
3268
+ const merchantReturn = transbankMerchantReturn(request.url, pageOrigin);
3269
+ const page = merchantReturn ? transbankMerchantReturnAnswer(merchantReturn)
3270
+ : hostedFormSubmittedPage({ authorizationId: replay.authorizationId, merchant: opts.merchant, submittedAt: replay.submittedAt });
3224
3271
  // A navigation response is never CORS-checked, so the CORS wrap is
3225
3272
  // inert here; it is applied so every fulfill goes through one path.
3226
3273
  await cdp.send('Fetch.fulfillRequest', {
@@ -3232,8 +3279,10 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
3232
3279
  attempt.assertLive();
3233
3280
  lastSubmitted = { url: deliverRequest.url, body: deliverBody, at: Date.now(), authorizationId: replay.authorizationId };
3234
3281
  // Named for what it is: a device-attested submission with no
3235
- // processor evidence, never an `authorized` event.
3236
- opts.onEvent?.({ type: 'submitted_on_device', detail: { authorizationId: replay.authorizationId, submittedAt: replay.submittedAt, outcome: replay.outcome } });
3282
+ // processor evidence, never an `authorized` event. A return names the
3283
+ // shop's host, never the URL, which carries Webpay's token.
3284
+ opts.onEvent?.({ type: 'submitted_on_device', detail: { authorizationId: replay.authorizationId, submittedAt: replay.submittedAt, outcome: replay.outcome,
3285
+ ...(merchantReturn ? { merchantReturn: { host: new URL(merchantReturn).hostname } } : {}) } });
3237
3286
  }
3238
3287
  else if (replay.mode === 'cse') {
3239
3288
  // The device encrypted the card for the processor; the request itself
@@ -3322,12 +3371,14 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
3322
3371
  throw new PaymentOutcomeUnknownError(replay.authorizationId, 'paddle_vgs_card_facts_unavailable');
3323
3372
  }
3324
3373
  // An approved EBANX token request whose read names a window covers the 3-D Secure calls and the confirm the
3325
- // page sends next (ebanx.ts): kept as the API stored it, the request that raised the approval. Kept before the
3326
- // page has the answer, since the page may send its first 3-D Secure call the moment the answer lands; a
3327
- // handoff that then fails stops them (the catch below).
3374
+ // page sends next (ebanx.ts), and an approved Payrexx card request whose answer started 3-D Secure covers the
3375
+ // card frame's two 3-D Secure sends (payrexx.ts): kept as the API stored it, the request that raised the
3376
+ // approval. Kept before the page has the answer, since the page may send its first 3-D Secure call the moment
3377
+ // the answer lands; a handoff that then fails stops them (the catch below).
3328
3378
  if (replay.mode !== 'device_encrypt') {
3329
3379
  ebanxFollowUps.remember({ url: request.url, body }, replay);
3330
- ebanxRemembered = true;
3380
+ payrexxFollowUps.remember({ url: request.url, body }, replay);
3381
+ followUpsRemembered = true;
3331
3382
  }
3332
3383
  await cdp.send('Fetch.fulfillRequest', {
3333
3384
  requestId: deliverTo,
@@ -3365,10 +3416,12 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
3365
3416
  authorizationId: approvedId ?? lifecycle.getState().authorizationId, detail: failureSummary(err) });
3366
3417
  // Ignore the failure event caused by our own safe decline/error abort.
3367
3418
  const dead = attempt.target ?? { requestId, sessionId };
3368
- // An EBANX token answer this handoff kept follow-ups for and did not finish handing over: nothing after it is
3369
- // claimed (ebanx.ts EbanxFollowUps.stop).
3370
- if (ebanxRemembered)
3419
+ // An answer this handoff kept follow-ups for and did not finish handing over: nothing after it is claimed
3420
+ // (ebanx.ts EbanxFollowUps.stop, payrexx.ts PayrexxFollowUps.stop).
3421
+ if (followUpsRemembered) {
3371
3422
  ebanxFollowUps.stop();
3423
+ payrexxFollowUps.stop();
3424
+ }
3372
3425
  // The business's resume settles once the request is answered: a terminal read is an answer (failed, nothing charged), a lost handoff is not.
3373
3426
  const settlePause = () => { if (businessPause) {
3374
3427
  if (isApprovalOutcome(err))
@@ -3541,9 +3594,12 @@ export async function attachToPlaywright(page, opts) {
3541
3594
  const juspayCheckout = new JuspayCheckoutGate(opts.vault);
3542
3595
  // The approved EBANX token request on this attachment and the sends claimed under it (ebanx.ts).
3543
3596
  const ebanxFollowUps = new EbanxFollowUps();
3597
+ // The approved Payrexx card request on this attachment and the 3-D Secure sends claimed under it (payrexx.ts).
3598
+ const payrexxFollowUps = new PayrexxFollowUps();
3544
3599
  const aci = new AciGate(opts.vault);
3545
3600
  let navigationBodyReplaced;
3546
3601
  const eprotect = new EprotectGate(opts.vault);
3602
+ const sbps = new SbpsGate(opts.vault);
3547
3603
  // The merchant's follow-up request after a delivered Secure Acceptance reply (cybersource-secure-acceptance.ts).
3548
3604
  const secureAcceptanceFollowUpWatch = new SecureAcceptanceFollowUpWatch(opts.onEvent);
3549
3605
  lifecycle.abort.signal.addEventListener('abort', () => secureAcceptanceFollowUpWatch.end(), { once: true });
@@ -3845,6 +3901,9 @@ export async function attachToPlaywright(page, opts) {
3845
3901
  || opts.vault.isCardRequest(url.toString(), 'GET')
3846
3902
  || guards.matches(url.toString()) || stripeCheckout.matches(url.toString()) || mercadoCheckout.matches(url.toString())
3847
3903
  || eprotect.matches(url.toString())
3904
+ // SB Payment Service's token request is a GET too, so its family is routed by the SBPS gate (never
3905
+ // its scripts or the 3-D Secure info request).
3906
+ || sbps.matches(url.toString())
3848
3907
  // An armed merchant profile's endpoint or a sibling of it (merchant-hosted.ts), as attachToCdp arms it.
3849
3908
  || (typeof opts.vault.merchantProfileOf === 'function' && opts.vault.merchantProfileOf(url.toString()) !== null)
3850
3909
  // An armed Verifone merchant's origin (verifone.ts), as attachToCdp arms it.
@@ -3870,6 +3929,10 @@ export async function attachToPlaywright(page, opts) {
3870
3929
  // Every request to a Webpay host once its entry is served (transbank.ts), as attachToCdp arms the hosts: the
3871
3930
  // token's submit and Webpay's card steps reach the Webpay gate and fail, and the entry pauses.
3872
3931
  || isArmedTransbankUrl(opts.vault, url.toString())
3932
+ // Every request to Paytrail's entry host and Payment Highway's card page host once Paytrail's entry is served
3933
+ // (paytrail.ts), as attachToCdp arms the recognizer's host and its abort host: the loading page's post and the
3934
+ // card page's card requests reach the Paytrail gate and fail.
3935
+ || isArmedPaytrailUrl(opts.vault, url.toString())
3873
3936
  // A CreditGuard page's own calls beside its submit (its card lookup and its 3-D Secure calls), for an
3874
3937
  // org the API served CreditGuard to, as attachToCdp arms its hosts: the CreditGuard gate answers them
3875
3938
  // under a ready preparation and lets them go on with none (pre-claim.ts isArmedCreditguardCall).
@@ -3886,6 +3949,9 @@ export async function attachToPlaywright(page, opts) {
3886
3949
  // Every request on the TokenEx iframe's /iframe/v3 path once its entry is served (tokenex-iframe.ts), as attachToCdp
3887
3950
  // arms its hosts: a card post the recognizer's exact URL does not match reaches the TokenEx gate and fails.
3888
3951
  || isArmedTokenexIframeUrl(opts.vault, url.toString())
3952
+ // Every request to an Eximbay host once its entry is served there (eximbay.ts), as attachToCdp arms the host: the
3953
+ // step, the window's lookups and its card post reach the Eximbay gate and fail.
3954
+ || isArmedEximbayUrl(opts.vault, url.toString())
3889
3955
  // PayPal's legacy Hosted Fields confirm, for every org, as attachToCdp arms it (paypal-hosted-fields.ts).
3890
3956
  || isPaypalHostedFieldsConfirm(url.toString())
3891
3957
  // PayPal's Card Fields confirm and GraphQL on www.paypal.com, for every org, as attachToCdp arms them (paypal-card-forms.ts).
@@ -3934,7 +4000,7 @@ export async function attachToPlaywright(page, opts) {
3934
4000
  catch {
3935
4001
  return false;
3936
4002
  } } },
3937
- vault: opts.vault, guards, mercado: mercadoCheckout, stripe: stripeCheckout, aci, eprotect,
4003
+ vault: opts.vault, guards, mercado: mercadoCheckout, stripe: stripeCheckout, aci, eprotect, sbps,
3938
4004
  // Asked once, and only for ACI's classic-mode /payment: whether this page's engine sends
3939
4005
  // the body route.continue replaces on a navigation (aci.ts playwrightReplacesNavigationBody).
3940
4006
  replacesNavigationBody: () => (navigationBodyReplaced ??= playwrightReplacesNavigationBody(page)),
@@ -3993,6 +4059,7 @@ export async function attachToPlaywright(page, opts) {
3993
4059
  datatrans: datatransRepeats,
3994
4060
  tokenexIframe: tokenexIframeRepeats,
3995
4061
  ebanx: ebanxFollowUps,
4062
+ payrexx: payrexxFollowUps,
3996
4063
  frame: frameOf(request),
3997
4064
  payrails,
3998
4065
  shijiSts: { companions: shijiCompanions, page: async () => (await playwrightShijiDocumentPage(request)) ?? `pw:none:${++playwrightShijiSeq}` },
@@ -4087,7 +4154,7 @@ export async function attachToPlaywright(page, opts) {
4087
4154
  // identically whichever adapter is attached. An Amadeus card request is
4088
4155
  // 'failed' (ERR_FAILED) in both too, see failsAsNetworkError (refusalFailure).
4089
4156
  // As in attachToCdp: an unbound request that skipped the spent preparation's claim is refused.
4090
- // As in attachToCdp: an EBANX send still out holds the attachment too.
4157
+ // As in attachToCdp: an EBANX or Payrexx send still out holds the attachment too.
4091
4158
  const followUpOut = lifecycle.followUpsInFlight();
4092
4159
  if (terminal || lifecycle.isBlocked() || awaitingApproval || followUpOut || preparedRetry || Date.now() < quietUntil) {
4093
4160
  let repeatedAuthorizationId = null;
@@ -4102,7 +4169,7 @@ export async function attachToPlaywright(page, opts) {
4102
4169
  }
4103
4170
  }
4104
4171
  const why = terminal ?? (lifecycle.isBlocked() ? lifecycle.getState().status : awaitingApproval ? 'an approval is already outstanding'
4105
- : followUpOut ? 'an EBANX send is in flight' : preparedRetry ? 'CheckoutPreparationError' : 'awaiting approval cooldown');
4172
+ : followUpOut ? 'a follow-up send is in flight' : preparedRetry ? 'CheckoutPreparationError' : 'awaiting approval cooldown');
4106
4173
  opts.onEvent?.({ type: 'blocked', detail: why instanceof Error ? failureSummary(why) : String(why) });
4107
4174
  if (preparation)
4108
4175
  preparationGate.retireUnboundClaim();
@@ -4156,7 +4223,7 @@ export async function attachToPlaywright(page, opts) {
4156
4223
  let approvedId = null;
4157
4224
  let deliveryBegun = false;
4158
4225
  // See the same flag in attachToCdp.
4159
- let ebanxRemembered = false;
4226
+ let followUpsRemembered = false;
4160
4227
  // A prepared Amadeus SDK add must be sent within its window (see amadeus.ts).
4161
4228
  let disarmSendWindow = null;
4162
4229
  try {
@@ -4291,13 +4358,17 @@ export async function attachToPlaywright(page, opts) {
4291
4358
  }
4292
4359
  else if (replay.mode === 'hosted_form') {
4293
4360
  // Same as the CDP path: the paused navigation resolves to the
4294
- // synthetic page, and a re-post of this form is refused.
4295
- const synthetic = hostedFormSubmittedPage({ authorizationId: replay.authorizationId, merchant: opts.merchant, submittedAt: replay.submittedAt });
4361
+ // synthetic page, or a reviewed Webpay shop's return page, and a
4362
+ // re-post of this form is refused.
4363
+ const merchantReturn = transbankMerchantReturn(request.url(), pageOrigin);
4364
+ const synthetic = merchantReturn ? transbankMerchantReturnAnswer(merchantReturn)
4365
+ : hostedFormSubmittedPage({ authorizationId: replay.authorizationId, merchant: opts.merchant, submittedAt: replay.submittedAt });
4296
4366
  // Inert on a navigation (never CORS-checked); one path for every fulfill.
4297
4367
  await deliverTo.fulfill({ status: synthetic.status, headers: withCorsHeaders(synthetic.headers, corsHeadersFor(deliverRequest.url(), deliverRequest.headers())), body: synthetic.body });
4298
4368
  assertRequestLive();
4299
4369
  lastSubmitted = { url: deliverRequest.url(), body: deliverBody, at: Date.now(), authorizationId: replay.authorizationId };
4300
- opts.onEvent?.({ type: 'submitted_on_device', detail: { authorizationId: replay.authorizationId, submittedAt: replay.submittedAt, outcome: replay.outcome } });
4370
+ opts.onEvent?.({ type: 'submitted_on_device', detail: { authorizationId: replay.authorizationId, submittedAt: replay.submittedAt, outcome: replay.outcome,
4371
+ ...(merchantReturn ? { merchantReturn: { host: new URL(merchantReturn).hostname } } : {}) } });
4301
4372
  }
4302
4373
  else if (replay.mode === 'cse') {
4303
4374
  // Same as the CDP path: the request continues from this browser
@@ -4374,7 +4445,8 @@ export async function attachToPlaywright(page, opts) {
4374
4445
  // See the same step in attachToCdp: kept before the page has the answer.
4375
4446
  if (replay.mode !== 'device_encrypt') {
4376
4447
  ebanxFollowUps.remember({ url: request.url(), body }, replay);
4377
- ebanxRemembered = true;
4448
+ payrexxFollowUps.remember({ url: request.url(), body }, replay);
4449
+ followUpsRemembered = true;
4378
4450
  }
4379
4451
  await deliverTo.fulfill({ status: replay.status, headers: withCorsHeaders(replay.headers, cors.headers), body: replay.body });
4380
4452
  datatransRepeats.remember(frameOf(deliverRequest), deliverRequest.url(), deliverBody, replay);
@@ -4393,8 +4465,10 @@ export async function attachToPlaywright(page, opts) {
4393
4465
  catch (err) {
4394
4466
  const dead = attempt.target?.route ?? route;
4395
4467
  // See the same step in attachToCdp.
4396
- if (ebanxRemembered)
4468
+ if (followUpsRemembered) {
4397
4469
  ebanxFollowUps.stop();
4470
+ payrexxFollowUps.stop();
4471
+ }
4398
4472
  if (activeRequest?.attempt === attempt)
4399
4473
  activeRequest = null;
4400
4474
  try {
package/dist/client.d.ts CHANGED
@@ -211,6 +211,12 @@ export declare const SUPPORTED_REGISTRY_FEATURES: readonly string[];
211
211
  * never lets the agent's browser post to (pre-claim.ts, redsys.ts). Served on the same terms as NMI's: only
212
212
  * to a build that lists it, for a company Agentcard turned Redsys on for.
213
213
  *
214
+ * `paytrail_hosted_payment_page`: Paytrail's card method, whose card-free signed Visa and Mastercard
215
+ * entry this build pauses for the cardholder's device, and whose loading page post, card page posts and
216
+ * card-number lookup on Payment Highway's host this build never lets the agent's browser send
217
+ * (pre-claim.ts, paytrail.ts). Served on the same terms as NMI's and Redsys's: only to a build that lists
218
+ * it, for a company Agentcard turned Paytrail on for.
219
+ *
214
220
  * `paddle_vgs_cards`: the card request Paddle's checkout frame sends to its VGS card
215
221
  * vault (vault.paddle.com/cards) on a checkout that names VGS, only through a prepared
216
222
  * checkout: the frame aborts the request 10 seconds after Pay and sends it once more,
@@ -274,6 +280,22 @@ export declare const SUPPORTED_REGISTRY_FEATURES: readonly string[];
274
280
  * company until a reviewed change binds GoPay's payee and amount to the approval: the post names
275
281
  * neither. The API takes it only at a shop Agentcard reviewed.
276
282
  *
283
+ * `paygent_token`: the token request Paygent's own PaygentToken.js sends from a merchant's
284
+ * page (token.paygent.co.jp/n/token/request), only through a prepared checkout: the script
285
+ * abandons the request 35 seconds after the press that sends it, so the cardholder approves
286
+ * before the press. The cardholder's device sends the card itself and the page gets back
287
+ * Paygent's single-use token alone; the SDK refuses a second token request on the same approval.
288
+ * Served on the same terms: only to a build that lists it, for a company Agentcard turned
289
+ * Paygent on for.
290
+ *
291
+ * `payrexx_card_frame`: the card request Payrexx Pay's card frame sends to its own URL
292
+ * (checkout.payrexx.com/?pspId=44&lang=<xx>) on a Payrexx merchant's page. This build pauses only
293
+ * the frame's own one-time card request and answers it with Payrexx's answer as the API recorded
294
+ * it: the token the merchant's page then charges, or the start of 3-D Secure, whose two later sends
295
+ * of the card this build hands to the cardholder's device under the same approval (payrexx.ts,
296
+ * pre-claim.ts payrexxPreClaim). The cardholder's device sends the card through the checkout relay.
297
+ * Served only to a build that lists it, for a company Agentcard turned Payrexx on for.
298
+ *
277
299
  * `hipay_vault_token`: the token create HiPay's hosted fields send to HiPay's Secure Vault
278
300
  * (secure2-vault.hipay-tpp.com, stage-secure2-vault for a test row), only through a prepared
279
301
  * checkout: the merchant's page gives the frame 20 seconds after Pay. The cardholder's device
@@ -311,8 +333,8 @@ export declare const SUPPORTED_REGISTRY_FEATURES: readonly string[];
311
333
  * This build pauses only the form's own multipart body at that exact URL; the cardholder's
312
334
  * device sends the card through the checkout relay, and the frame gets the token answer
313
335
  * payment-core's judge rebuilt, with the CORS answer for the frame's own origin, null. The
314
- * request names no amount, so the agent's pair, above zero, is the one the cardholder approves
315
- * (PAY_NL_PSP below). Served on the same terms: only to a build that lists it, for a company
336
+ * request names no amount, so the agent's pair, above zero, is the one the cardholder approves.
337
+ * Served on the same terms: only to a build that lists it, for a company
316
338
  * Agentcard turned Pay. on for.
317
339
  *
318
340
  * `paypal_card_fields`: PayPal Card Fields' confirm, the POST its v5 frame sends to
@@ -373,6 +395,74 @@ export declare const SUPPORTED_REGISTRY_FEATURES: readonly string[];
373
395
  * device sends the card itself, and the API holds the payment to a merchant Agentcard reviewed, at the
374
396
  * amount SumUp holds for the checkout. Served on the same terms: only to a build that lists it, for a
375
397
  * company Agentcard turned SumUp on for.
398
+ *
399
+ * `clover_card_token`: the card token request Clover's card frame (the hidden frame of
400
+ * checkout.clover.com that Clover's iframe mounts on a merchant's page) sends to
401
+ * token.clover.com/v1/tokens (token-sandbox.dev.clover.com for a test row), with the number
402
+ * encrypted under Clover's key. This build pauses only the frame's own body at that exact URL; the
403
+ * cardholder's device builds the request again with the real card under the key payment-core pins
404
+ * and sends it to Clover itself, and the frame gets the token answer payment-core's judge rebuilt.
405
+ * The request names no amount, so the agent's pair, above zero, is the one the cardholder approves.
406
+ * Served on the same terms: only to a build that lists it, for a company
407
+ * Agentcard turned Clover on for.
408
+ *
409
+ * `eximbay_hosted_payment_page`: Eximbay's payment window, whose card-free signed entry
410
+ * (a form post to /Gateway/BasicProcessor.krp on secureapi.eximbay.com, or its test host) this
411
+ * build pauses for the cardholder's device, in the page or in a window it opened once attachToCdp
412
+ * has armed that window (eximbay.ts names the entry that can leave first), and whose
413
+ * step, amount query, card lookup and card post this build never lets the agent's browser send
414
+ * (pre-claim.ts, eximbay.ts). Served on the same terms as NMI's and Redsys's: only to a build
415
+ * that lists it, for a company Agentcard turned Eximbay on for.
416
+ *
417
+ * `toast_pinned_key`: the card request Toast's payments frame sends to
418
+ * payments.toasttab.com/v1/payment-methods on a Toast online-ordering checkout, with
419
+ * the card sealed in card.cardData under one key every Toast restaurant shares. The
420
+ * cardholder's device seals the real card under that key, which payment-core pins, and
421
+ * this build writes it over the agent's placeholder and nothing else before it
422
+ * continues the request. Served on the same terms: only to a build that lists it, for
423
+ * a company Agentcard turned Toast on for.
424
+ *
425
+ * `xendit_card_token`: the card token request Xendit's hosted invoice checkout
426
+ * (checkout.xendit.co/web/<invoice id>, where Xendit's payment and donation links land) sends
427
+ * when the payer presses Pay Now: Xendit.js posts the card as JSON to
428
+ * https://api.xendit.co/v2/credit_card_tokens from the page itself, under the business's
429
+ * publishable key. This build pauses only the card form's single-use token body at that exact URL
430
+ * (payment-core xendit.js); a token that keeps the card on file, Xendit.js's code-only storeCVN post
431
+ * and any other body there fail in the browser before anything is created. The cardholder's device
432
+ * sends the request itself with the real card, and the page gets the token answer payment-core's
433
+ * judge rebuilt, after which the page runs 3-D Secure and charges the token without the card. The
434
+ * token names the invoice's amount and currency, so no agent pair is needed: the API reads the
435
+ * token's and holds an agent's pair to it. Served on the same terms: only to a build that lists it,
436
+ * for a company Agentcard turned Xendit on for.
437
+ *
438
+ * `omise_card_token`: the card token request every Omise card form sends, Omise.js's pay.html frame
439
+ * or a merchant's own page calling Omise.js or Omise's Node library: a JSON POST to
440
+ * https://vault.omise.co/tokens under the merchant's public key. This build pauses only the card
441
+ * form's token body at that exact URL (payment-core omise.js); a wallet's tokenization and any other
442
+ * body there fail in the browser before anything is created. The cardholder's device sends the
443
+ * request itself with the real card, and the page gets the token answer payment-core's judge
444
+ * rebuilt. The request names no amount, so the agent's pair, above zero, is the one the cardholder
445
+ * approves, and the API pays only a merchant payment-core pins for the checkout
446
+ * page the create names. Served on the same terms: only to a build that lists it, for a company
447
+ * Agentcard turned Omise on for.
448
+ *
449
+ * `sbps_token_jsonp`: SB Payment Service's card token request, the JSONP <script>
450
+ * GET its token script sends to /token/generateToken on token.sps-system.com. This
451
+ * build pauses it only as the browser writes it, aborts any other request on that
452
+ * path, and answers the paused script with the token as the API recorded it, in the
453
+ * request's own callback. The device rebuilds the card's ciphertext and sends it
454
+ * through the checkout relay. Served on the same terms: only to a build that lists
455
+ * it, for a company Agentcard turned SB Payment Service on for.
456
+ *
457
+ * `midtrans_card_token`: the card token Midtrans' Snap popup posts as one JSON request to
458
+ * api.midtrans.com/v2/token (api.sandbox.midtrans.com in test), under the business's
459
+ * publishable client key. The cardholder's device sends it and the page gets back Midtrans'
460
+ * token, which the merchant's own server charges afterwards, card free, running 3-D Secure
461
+ * there. The token names no amount, so authorize() needs the agent's pair.
462
+ * Served on the same terms, and the API holds the lane to Agentcard's own company
463
+ * until a reviewed change binds the client key to the merchant.
464
+ *
465
+ * The list itself is capabilities.json, sorted, one per line (scripts/generate-capabilities.mjs).
376
466
  */
377
467
  export declare const SUPPORTED_CAPABILITIES: readonly string[];
378
468
  /**
@@ -452,8 +542,9 @@ export interface TokenReplay extends ExecutionMetadata {
452
542
  amountAuthority?: AmountAuthority;
453
543
  /**
454
544
  * An EBANX token approval that covers the sends EBANX's page makes after it (its 3-D Secure calls and the hosted
455
- * page's confirm), for a company Agentcard turned that on for: until when (ISO 8601) the adapters may ask the
456
- * cardholder's device to send them (sendEbanxFollowUp). Absent on every other approval.
545
+ * page's confirm), for a company Agentcard turned that on for, or a Payrexx card request whose answer started 3-D
546
+ * Secure, which covers the card frame's two 3-D Secure sends: until when (ISO 8601) the adapters may ask the
547
+ * cardholder's device to send them (sendEbanxFollowUp, sendPayrexxFollowUp). Absent on every other approval.
457
548
  */
458
549
  followUpsUntil?: string;
459
550
  }
@@ -556,7 +647,10 @@ export interface VerifoneReplay extends ExecutionMetadata {
556
647
  * paused the navigation, so there is no response to replay and no outcome
557
648
  * here: resolve the paused navigation with hostedFormSubmittedPage() (the
558
649
  * adapters do), never resubmit the form, and confirm the order with the
559
- * merchant, which learns the outcome from the processor.
650
+ * merchant, which learns the outcome from the processor. Webpay's merchant
651
+ * learns it only from the buyer's browser, so the adapters send a paused
652
+ * Webpay entry whose reviewed shop names its return page to that page instead
653
+ * (transbankMerchantReturn).
560
654
  *
561
655
  * THIS IS NOT AN APPROVED PAYMENT. `kind` and `outcome` say so in words so
562
656
  * no caller mistakes it for one: the API finishes such an authorization as
@@ -1230,12 +1324,12 @@ export interface PresetRefusal {
1230
1324
  /** A decline reason only the company presets stamp (see PresetRefusedError). */
1231
1325
  export declare const PRESET_REFUSAL_REASONS: ReadonlySet<string>;
1232
1326
  /**
1233
- * How often sendEbanxFollowUp reads a send back while the cardholder's device has it: every half second, whatever the
1234
- * client's own `pollIntervalMs`. One send is out at a time per approval, so this is 120 reads a minute, inside the
1235
- * API's 240 a minute per caller and authorization (routes/api/v2/checkout-follow-ups.ts).
1327
+ * How often sendEbanxFollowUp and sendPayrexxFollowUp read a send back while the cardholder's device has it: every
1328
+ * half second, whatever the client's own `pollIntervalMs`. One send is out at a time per approval, so this is 120
1329
+ * reads a minute, inside the API's 240 a minute per caller and authorization (routes/api/v2/checkout-follow-ups.ts).
1236
1330
  */
1237
1331
  export declare const EBANX_FOLLOW_UP_READ_MS = 500;
1238
- /** The longest sendEbanxFollowUp waits after reads the API refused as too many (429): 1, 2, 4, 8, then 15 seconds. */
1332
+ /** The longest a follow-up send waits after reads the API refused as too many (429): 1, 2, 4, 8, then 15 seconds. */
1239
1333
  export declare const EBANX_FOLLOW_UP_BACKOFF_MAX_MS = 15000;
1240
1334
  export declare class CheckoutApiError extends Error {
1241
1335
  status: number;
@@ -1318,6 +1412,11 @@ export declare class CheckoutAuthError extends Error {
1318
1412
  * https://pgw-ui.2c2p.com/payment/4.1/{token}.
1319
1413
  * On QuickPay's host the window's session reads `{session}`:
1320
1414
  * https://payment.quickpay.net/{session}/3ds_auth.
1415
+ * On FastSpring's store hosts the session id reads `{session}`:
1416
+ * https://daisydisk.onfastspring.com/session/{session}/payment.
1417
+ * On PayU Latam's hosts the checkout session's key reads `{key}`:
1418
+ * https://checkout.payulatam.com/ppp-web-gateway-payu/checkout/submit/co/credit_card/{key}.
1419
+ * Each mask in src/url-masks/ says what it reads in its own file.
1321
1420
  * A string that is not a URL keeps the part before any `?` or `#`, with the
1322
1421
  * same segments masked for every processor whose host it names.
1323
1422
  * A URL is read by its host only when the parser finds one. Text before a URL
@@ -1708,6 +1807,28 @@ export declare class VaultClient {
1708
1807
  headers: Record<string, string>;
1709
1808
  body: string;
1710
1809
  }>;
1810
+ /**
1811
+ * Send one of Payrexx's 3-D Secure sends an approved Payrexx card request covers (payrexx.ts says which: the card
1812
+ * frame's authentication after the ACS method and its post-authentication after the bank's challenge, bound to that
1813
+ * approval) from the cardholder's device, and resolve with Payrexx's answer for the paused request, exactly as
1814
+ * sendEbanxFollowUp does for EBANX: the same create, reads and clocks, and no prompt. Rejects with a
1815
+ * PayrexxFollowUpError naming why there is no answer and whether the card may have reached Payrexx.
1816
+ */
1817
+ sendPayrexxFollowUp(authorizationId: string, request: {
1818
+ url: string;
1819
+ method: 'POST';
1820
+ headers: Record<string, string>;
1821
+ body: string;
1822
+ }, options?: {
1823
+ signal?: AbortSignal;
1824
+ until?: number;
1825
+ }): Promise<{
1826
+ status: number;
1827
+ headers: Record<string, string>;
1828
+ body: string;
1829
+ }>;
1830
+ /** The send behind sendEbanxFollowUp and sendPayrexxFollowUp; `fail` builds the processor's error for a send with no answer. */
1831
+ private sendFollowUp;
1711
1832
  /**
1712
1833
  * Create a merchant-session probe, the phase-0 read of the deterministic merchant total:
1713
1834
  * `user` is the org's connected user, who opens the returned Vault link and reads the