@agent-cards/sdk 0.23.21 → 0.23.22

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/client.js CHANGED
@@ -5,6 +5,7 @@ import { ACI_CAPABILITY, ACI_PSP, aciCardLegAnswer } from './aci.js';
5
5
  import { PRZELEWY24_CAPABILITY, PRZELEWY24_PSP } from './przelewy24.js';
6
6
  import { EPROTECT_CAPABILITY, EPROTECT_PSP, eprotectCheckoutRefusal, eprotectScriptAnswer, isEprotectRegistration } from './eprotect.js';
7
7
  import { COMGATE_CAPABILITY, COMGATE_PSP } from './comgate.js';
8
+ import { EXIMBAY_CAPABILITY } from './eximbay.js';
8
9
  import { isHostedGetEntry } from './hosted-entry.js';
9
10
  import { TRANSBANK_CAPABILITY, TRANSBANK_PSP } from './transbank.js';
10
11
  import { isMercadoTokenRequest, parseMercadoCheckoutContext, validateMercadoProcessorContext } from './mercado-checkout.generated.js';
@@ -19,10 +20,12 @@ import { CONVERGE_CAPABILITY } from './elavon-converge.js';
19
20
  import { SHIJI_STS_CAPABILITY, SHIJI_STS_PSP, isShijiStsServedCompanion, isShijiStsServedSubstitution } from './shiji-sts.js';
20
21
  import { AMADEUS_CHECKOUT_CAPABILITY, AMADEUS_CHECKOUT_PSP } from './amadeus-checkout.generated.js';
21
22
  import { PADDLE_VGS_CAPABILITY, PADDLE_VGS_PSP, paddleVgsCardFactsOf } from './paddle-vgs.generated.js';
23
+ import { PAYGENT_CAPABILITY, PAYGENT_PSP, paygentAnswerOutcome } from './paygent.generated.js';
22
24
  import { PADDLE_TOKENEX_CAPABILITY, PADDLE_TOKENEX_PSP, isPaddleTokenexServedFacts, isPaddleTokenexServedSubstitution } from './paddle-tokenex.js';
23
25
  import { TWOC2P_CAPABILITY, TWOC2P_PSP } from './twoc2p.js';
24
26
  import { WIX_PAYMENTS_CAPABILITY, WIX_PAYMENTS_PSP } from './wix-payments.generated.js';
25
27
  import { HIPAY_CAPABILITY, HIPAY_PSP } from './hipay.generated.js';
28
+ import { TOAST_CAPABILITY, TOAST_PSP, isToastCiphertext } from './toast.generated.js';
26
29
  import { classifyStripeCheckoutRequest, encodeStripeCheckoutContext, hasStripeCheckoutMarker, parseStripeCheckoutContext, parseStripeCheckoutResponse, STRIPE_CHECKOUT_CONTEXT_HEADER, } from './stripe-checkout.generated.js';
27
30
  import { VoiceModule } from './voice.js';
28
31
  import { BuyModule } from './buy.js';
@@ -42,6 +45,7 @@ import { PRIMER_CAPABILITY, PRIMER_PSP, primerTokenAnswer } from './primer.js';
42
45
  import { PAYRAILS_CAPABILITY, isPayrailsPaymentUrl, payrailsAnswer } from './payrails.js';
43
46
  import { WSPAY_CAPABILITY, WSPAY_PSP } from './wspay.js';
44
47
  import { EbanxFollowUpError } from './ebanx.js';
48
+ import { PAYREXX_CAPABILITY, PAYREXX_PSP, PayrexxFollowUpError } from './payrexx.js';
45
49
  import { SECURE_ACCEPTANCE_CAPABILITY, SECURE_ACCEPTANCE_FAST_POLL_MS, SECURE_ACCEPTANCE_PSP, SECURE_ACCEPTANCE_REPLY_WINDOW_MS, secureAcceptanceCheckoutProfile, secureAcceptanceCheckoutRefusal, secureAcceptanceFrameAnswer, } from './cybersource-secure-acceptance.js';
46
50
  /**
47
51
  * The modes this SDK can finish. Asked for on syncRegistry (the API serves
@@ -265,6 +269,22 @@ export const SUPPORTED_REGISTRY_FEATURES = ['card_fields', 'checkout_sessions'];
265
269
  * company until a reviewed change binds GoPay's payee and amount to the approval: the post names
266
270
  * neither. The API takes it only at a shop Agentcard reviewed.
267
271
  *
272
+ * `paygent_token`: the token request Paygent's own PaygentToken.js sends from a merchant's
273
+ * page (token.paygent.co.jp/n/token/request), only through a prepared checkout: the script
274
+ * abandons the request 35 seconds after the press that sends it, so the cardholder approves
275
+ * before the press. The cardholder's device sends the card itself and the page gets back
276
+ * Paygent's single-use token alone; the SDK refuses a second token request on the same approval.
277
+ * Served on the same terms: only to a build that lists it, for a company Agentcard turned
278
+ * Paygent on for.
279
+ *
280
+ * `payrexx_card_frame`: the card request Payrexx Pay's card frame sends to its own URL
281
+ * (checkout.payrexx.com/?pspId=44&lang=<xx>) on a Payrexx merchant's page. This build pauses only
282
+ * the frame's own one-time card request and answers it with Payrexx's answer as the API recorded
283
+ * it: the token the merchant's page then charges, or the start of 3-D Secure, whose two later sends
284
+ * of the card this build hands to the cardholder's device under the same approval (payrexx.ts,
285
+ * pre-claim.ts payrexxPreClaim). The cardholder's device sends the card through the checkout relay.
286
+ * Served only to a build that lists it, for a company Agentcard turned Payrexx on for.
287
+ *
268
288
  * `hipay_vault_token`: the token create HiPay's hosted fields send to HiPay's Secure Vault
269
289
  * (secure2-vault.hipay-tpp.com, stage-secure2-vault for a test row), only through a prepared
270
290
  * checkout: the merchant's page gives the frame 20 seconds after Pay. The cardholder's device
@@ -364,11 +384,39 @@ export const SUPPORTED_REGISTRY_FEATURES = ['card_fields', 'checkout_sessions'];
364
384
  * device sends the card itself, and the API holds the payment to a merchant Agentcard reviewed, at the
365
385
  * amount SumUp holds for the checkout. Served on the same terms: only to a build that lists it, for a
366
386
  * company Agentcard turned SumUp on for.
387
+ *
388
+ * `clover_card_token`: the card token request Clover's card frame (the hidden frame of
389
+ * checkout.clover.com that Clover's iframe mounts on a merchant's page) sends to
390
+ * token.clover.com/v1/tokens (token-sandbox.dev.clover.com for a test row), with the number
391
+ * encrypted under Clover's key. This build pauses only the frame's own body at that exact URL; the
392
+ * cardholder's device builds the request again with the real card under the key payment-core pins
393
+ * and sends it to Clover itself, and the frame gets the token answer payment-core's judge rebuilt.
394
+ * The request names no amount, so the agent's pair, above zero, is the one the cardholder approves
395
+ * (CLOVER_PSP below). Served on the same terms: only to a build that lists it, for a company
396
+ * Agentcard turned Clover on for.
397
+ *
398
+ * `eximbay_hosted_payment_page`: Eximbay's payment window, whose card-free signed entry
399
+ * (a form post to /Gateway/BasicProcessor.krp on secureapi.eximbay.com, or its test host) this
400
+ * build pauses for the cardholder's device, in the page or in a window it opened once attachToCdp
401
+ * has armed that window (eximbay.ts names the entry that can leave first), and whose
402
+ * step, amount query, card lookup and card post this build never lets the agent's browser send
403
+ * (pre-claim.ts, eximbay.ts). Served on the same terms as NMI's and Redsys's: only to a build
404
+ * that lists it, for a company Agentcard turned Eximbay on for.
405
+ *
406
+ * `toast_pinned_key`: the card request Toast's payments frame sends to
407
+ * payments.toasttab.com/v1/payment-methods on a Toast online-ordering checkout, with
408
+ * the card sealed in card.cardData under one key every Toast restaurant shares. The
409
+ * cardholder's device seals the real card under that key, which payment-core pins, and
410
+ * this build writes it over the agent's placeholder and nothing else before it
411
+ * continues the request. Served on the same terms: only to a build that lists it, for
412
+ * a company Agentcard turned Toast on for.
367
413
  */
368
414
  export const SUPPORTED_CAPABILITIES = ['fiserv_card_capture', GMO_CAPABILITY, CYBERSOURCE_FLEX_CAPABILITY, JUSPAY_CAPABILITY, DATATRANS_CAPABILITY, 'authorize_net_dpm_form', PELECARD_CAPABILITY, ACI_CAPABILITY, 'creditguard_pps_process', PRIMER_CAPABILITY, PAYRAILS_CAPABILITY, CONVERGE_CAPABILITY, EPROTECT_CAPABILITY, WSPAY_CAPABILITY, SHIJI_STS_CAPABILITY, AMADEUS_CHECKOUT_CAPABILITY, 'worldline_mycheckout_form', 'verifone_card_capture', 'ekashu_hosted_payment_page', SECURE_ACCEPTANCE_CAPABILITY, 'gravy_session_fields_put', 'moneris_hosted_tokenization', PADDLE_VGS_CAPABILITY, PADDLE_TOKENEX_CAPABILITY,
369
- 'redsys_hosted_payment_page', 'lyra_krypton_payment', 'payfast_card_pay', WIX_PAYMENTS_CAPABILITY, 'paymongo_checkout_pay', 'pay_nl_card_token', HIPAY_CAPABILITY, TWOC2P_CAPABILITY, 'quickpay_hosted_payment_page', 'revolut_card_field', 'paypal_card_fields', 'gopay_card_form', COMGATE_CAPABILITY, PRZELEWY24_CAPABILITY, 'makecommerce_card_frame', 'poynt_collect_nonce',
415
+ 'redsys_hosted_payment_page', 'lyra_krypton_payment', 'payfast_card_pay', WIX_PAYMENTS_CAPABILITY, 'paymongo_checkout_pay', PAYGENT_CAPABILITY, 'pay_nl_card_token', HIPAY_CAPABILITY, TWOC2P_CAPABILITY, 'quickpay_hosted_payment_page', 'revolut_card_field', 'paypal_card_fields', 'gopay_card_form', PAYREXX_CAPABILITY, COMGATE_CAPABILITY, PRZELEWY24_CAPABILITY, 'makecommerce_card_frame', 'poynt_collect_nonce',
370
416
  TOKENEX_IFRAME_CAPABILITY, TRANSBANK_CAPABILITY,
371
- 'sumup_checkout_process'];
417
+ 'sumup_checkout_process',
418
+ 'clover_card_token',
419
+ EXIMBAY_CAPABILITY, TOAST_CAPABILITY];
372
420
  /**
373
421
  * Processors whose paused request names no amount at all, so the pair the
374
422
  * agent passes is the amount the cardholder approves: authorize() refuses one
@@ -396,13 +444,21 @@ export const SUPPORTED_CAPABILITIES = ['fiserv_card_capture', GMO_CAPABILITY, CY
396
444
  * so the agent sends the total Payfast's page shows, above zero (PAYFAST_PSP below),
397
445
  * and the API holds it to the payment Payfast itself reports. Wix's tokenize request
398
446
  * carries none either: the merchant's page charges Wix's token for its own order, so the
399
- * agent's pair, which the prepared checkout carries, is the total the cardholder approves.
447
+ * agent's pair, which the prepared checkout carries, is the total the cardholder approves. Toast's
448
+ * card request carries none either: the page's card-free confirm of its payment intent
449
+ * charges whatever the intent says, so the agent's pair, above zero (as Moneris's), is
450
+ * the one figure the approval shows.
400
451
  * PayMongo's card attach carries none either: the merchant bound the total, always in
401
452
  * Philippine pesos, to the checkout session when it created it, so the agent sends that
402
453
  * total, above zero (PAYMONGO_PSP below), and the API holds it to the session PayMongo's
403
454
  * own page shows. GoPay's card post carries none either: the shop's server set the amount
404
455
  * when it created the payment at GoPay, and GoPay's gateway shows it only inside its own
405
456
  * session, so the agent sends the total the gateway shows, above zero (GOPAY_PSP below).
457
+ * Paygent's token request carries none either: the merchant's server charges the token later, so
458
+ * the agent's pair, which the prepared checkout carries, is the total the cardholder approves.
459
+ * Payrexx's card request names the page's total, but it only mints a token: the merchant's page
460
+ * charges its own total later, at its validateForm, so the agent sends the total that page shows,
461
+ * above zero (PAYREXX_PSP), and the API holds it to the frame's figure exactly.
406
462
  * 2C2P's Payment carries none either: the merchant bound the total to the
407
463
  * payment token when it created it, so the agent sends the total 2C2P's page shows, above
408
464
  * zero (TWOC2P_PSP), and the API holds it to the amount 2C2P's own PaymentOption answer names.
@@ -442,6 +498,9 @@ export const SUPPORTED_CAPABILITIES = ['fiserv_card_capture', GMO_CAPABILITY, CY
442
498
  * total and its currency to the checkout when it created it, so the agent sends the total the
443
499
  * payment page shows, above zero (SUMUP_PSP below), and the API holds it to the checkout SumUp
444
500
  * itself reports.
501
+ * Clover's card token request carries none either: the merchant's server charges the token
502
+ * later for its own order, so the agent's pair, above zero (CLOVER_PSP below), is the one figure
503
+ * the approval shows.
445
504
  */
446
505
  const MONERIS_PSP = 'moneris';
447
506
  const PAYFAST_PSP = 'payfast';
@@ -453,15 +512,18 @@ const GOPAY_PSP = 'gopay';
453
512
  const MAKECOMMERCE_PSP = 'makecommerce';
454
513
  const POYNT_PSP = 'poynt';
455
514
  const SUMUP_PSP = 'sumup';
456
- const AGENT_AMOUNT_PROCESSORS = [GMO_PSP, PELECARD_PSP, WSPAY_PSP, SECURE_ACCEPTANCE_PSP, MONERIS_PSP, PADDLE_VGS_PSP, PAYFAST_PSP, WIX_PAYMENTS_PSP, PAYMONGO_PSP, PAY_NL_PSP, HIPAY_PSP, TWOC2P_PSP, REVOLUT_PSP, PAYPAL_PSP, GOPAY_PSP, COMGATE_PSP,
457
- PRZELEWY24_PSP, MAKECOMMERCE_PSP, POYNT_PSP, TOKENEX_IFRAME_PSP, TRANSBANK_PSP, SUMUP_PSP];
515
+ const CLOVER_PSP = 'clover';
516
+ const AGENT_AMOUNT_PROCESSORS = [GMO_PSP, PELECARD_PSP, WSPAY_PSP, SECURE_ACCEPTANCE_PSP, MONERIS_PSP, PADDLE_VGS_PSP, PAYFAST_PSP, WIX_PAYMENTS_PSP, PAYMONGO_PSP, PAYGENT_PSP, PAY_NL_PSP, HIPAY_PSP, TWOC2P_PSP, REVOLUT_PSP, PAYPAL_PSP, GOPAY_PSP, PAYREXX_PSP, COMGATE_PSP,
517
+ PRZELEWY24_PSP, MAKECOMMERCE_PSP, POYNT_PSP, TOKENEX_IFRAME_PSP, TRANSBANK_PSP, SUMUP_PSP,
518
+ CLOVER_PSP, TOAST_PSP];
458
519
  /** How AmountRequiredError names a processor. */
459
520
  const PROCESSOR_NAMES = { [GMO_PSP]: 'GMO', [PELECARD_PSP]: 'Pelecard', [WSPAY_PSP]: 'WSPay',
460
- [SECURE_ACCEPTANCE_PSP]: 'CyberSource Secure Acceptance', [MONERIS_PSP]: 'Moneris', [PADDLE_VGS_PSP]: 'Paddle', [PAYFAST_PSP]: 'Payfast', [WIX_PAYMENTS_PSP]: 'Wix', [PAYMONGO_PSP]: 'PayMongo', [PAY_NL_PSP]: 'Pay.', [HIPAY_PSP]: 'HiPay', [TWOC2P_PSP]: '2C2P',
461
- [REVOLUT_PSP]: 'Revolut', [PAYPAL_PSP]: 'PayPal', [GOPAY_PSP]: 'GoPay',
521
+ [SECURE_ACCEPTANCE_PSP]: 'CyberSource Secure Acceptance', [MONERIS_PSP]: 'Moneris', [PADDLE_VGS_PSP]: 'Paddle', [PAYFAST_PSP]: 'Payfast', [WIX_PAYMENTS_PSP]: 'Wix', [PAYMONGO_PSP]: 'PayMongo', [PAYGENT_PSP]: 'Paygent', [PAY_NL_PSP]: 'Pay.', [HIPAY_PSP]: 'HiPay', [TWOC2P_PSP]: '2C2P',
522
+ [REVOLUT_PSP]: 'Revolut', [PAYPAL_PSP]: 'PayPal', [GOPAY_PSP]: 'GoPay', [PAYREXX_PSP]: 'Payrexx',
462
523
  [COMGATE_PSP]: 'ComGate', [PRZELEWY24_PSP]: 'Przelewy24',
463
524
  [MAKECOMMERCE_PSP]: 'MakeCommerce', [POYNT_PSP]: 'GoDaddy Payments', [TOKENEX_IFRAME_PSP]: 'TokenEx', [TRANSBANK_PSP]: 'Webpay',
464
- [SUMUP_PSP]: 'SumUp' };
525
+ [SUMUP_PSP]: 'SumUp',
526
+ [CLOVER_PSP]: 'Clover', [TOAST_PSP]: 'Toast' };
465
527
  /**
466
528
  * Processors whose request and answer name no amount at all, so the total the
467
529
  * payment page shows (`pageAmount`) is the one figure that ties the agent's pair
@@ -741,7 +803,9 @@ export class AmountRequiredError extends Error {
741
803
  code = 'amount_required';
742
804
  constructor(psp) {
743
805
  const processor = PROCESSOR_NAMES[psp] ?? psp;
744
- const lacks = psp === REVOLUT_PSP ? 'names its amount but not its currency' : 'names no amount';
806
+ // A Payrexx card request names the frame's total, but the merchant's page charges its own total after the token.
807
+ const lacks = psp === REVOLUT_PSP ? 'names its amount but not its currency'
808
+ : psp === PAYREXX_PSP ? "only mints a token the merchant's page charges later" : 'names no amount';
745
809
  super(`A ${processor} payment request ${lacks}, so pass amount and currency: that is the amount the cardholder approves. Nothing was created and nothing was charged.`);
746
810
  this.psp = psp;
747
811
  this.name = 'AmountRequiredError';
@@ -1086,12 +1150,12 @@ function refusalsOf(v) {
1086
1150
  return out;
1087
1151
  }
1088
1152
  /**
1089
- * How often sendEbanxFollowUp reads a send back while the cardholder's device has it: every half second, whatever the
1090
- * client's own `pollIntervalMs`. One send is out at a time per approval, so this is 120 reads a minute, inside the
1091
- * API's 240 a minute per caller and authorization (routes/api/v2/checkout-follow-ups.ts).
1153
+ * How often sendEbanxFollowUp and sendPayrexxFollowUp read a send back while the cardholder's device has it: every
1154
+ * half second, whatever the client's own `pollIntervalMs`. One send is out at a time per approval, so this is 120
1155
+ * reads a minute, inside the API's 240 a minute per caller and authorization (routes/api/v2/checkout-follow-ups.ts).
1092
1156
  */
1093
1157
  export const EBANX_FOLLOW_UP_READ_MS = 500;
1094
- /** The longest sendEbanxFollowUp waits after reads the API refused as too many (429): 1, 2, 4, 8, then 15 seconds. */
1158
+ /** The longest a follow-up send waits after reads the API refused as too many (429): 1, 2, 4, 8, then 15 seconds. */
1095
1159
  export const EBANX_FOLLOW_UP_BACKOFF_MAX_MS = 15_000;
1096
1160
  export class CheckoutApiError extends Error {
1097
1161
  status;
@@ -1384,6 +1448,18 @@ const SUMUP_CHECKOUT = new RegExp(`(${SLASH}checkouts${SLASH}+)[^/]*`, 'gi');
1384
1448
  function maskSumupCheckout(path) {
1385
1449
  return maskUuids(path.replace(SUMUP_CHECKOUT, '$1{checkout}'), '{checkout}');
1386
1450
  }
1451
+ /**
1452
+ * FastSpring's checkout posts its card payment to <store>.onfastspring.com[/popup-<name>]/session/<id>/payment
1453
+ * (payment-core fastspring.js), and the session id in that path is a credential: the session's own document,
1454
+ * which anyone holding the id can read with no cookie, carries the order and the session's token. On every
1455
+ * host under onfastspring.com the segment after /session/, in any case or after an escaped slash, reads
1456
+ * {session}.
1457
+ */
1458
+ const FASTSPRING_HOST = /(?:^|\.)onfastspring\.com\.?$/i;
1459
+ const FASTSPRING_SESSION = /(\/session(?:\/|%2f))[^/]*/gi;
1460
+ function maskFastspringSession(path) {
1461
+ return path.replace(FASTSPRING_SESSION, '$1{session}');
1462
+ }
1387
1463
  /**
1388
1464
  * The processors whose card request carries its own credential in the URL path, with the hosts
1389
1465
  * that send it, the text that names those hosts anywhere else (a string that is not a URL, or a
@@ -1558,6 +1634,7 @@ const PATH_CREDENTIALS = [
1558
1634
  { host: PAYMENT_HIGHWAY_HOST, named: /(?:paymenthighway\.io|sph-test-solinor\.com)/i, mask: maskPaymentHighwaySession },
1559
1635
  { host: TWOC2P_PAGE_HOST, named: /pgw-ui\.2c2p\.com/i, mask: maskTwoC2pToken, pathMask: maskTwoC2pPath },
1560
1636
  { host: QUICKPAY_HOST, named: /quickpay\.net/i, mask: maskQuickpaySession },
1637
+ { host: FASTSPRING_HOST, named: /onfastspring\.com/i, mask: maskFastspringSession },
1561
1638
  ];
1562
1639
  /**
1563
1640
  * A URL as it may appear in an error message: origin + path only. A paused
@@ -1595,6 +1672,8 @@ const PATH_CREDENTIALS = [
1595
1672
  * https://pgw-ui.2c2p.com/payment/4.1/{token}.
1596
1673
  * On QuickPay's host the window's session reads `{session}`:
1597
1674
  * https://payment.quickpay.net/{session}/3ds_auth.
1675
+ * On FastSpring's store hosts the session id reads `{session}`:
1676
+ * https://daisydisk.onfastspring.com/session/{session}/payment.
1598
1677
  * A string that is not a URL keeps the part before any `?` or `#`, with the
1599
1678
  * same segments masked for every processor whose host it names.
1600
1679
  * A URL is read by its host only when the parser finds one. Text before a URL
@@ -1703,6 +1782,18 @@ const PADDLE_VGS_PREPARATION_REFUSALS = [
1703
1782
  * `unsupported_checkout`, which PREPARATION_REFUSALS names for every checkout. Only for a Wix checkout.
1704
1783
  */
1705
1784
  const WIX_PAYMENTS_PREPARATION_REFUSALS = ['processor_unavailable', 'live_host_on_sandbox_row', 'merchant_profile_mismatch'];
1785
+ /**
1786
+ * The API's refusals of a Paygent preparation, before any preparation exists, that prepareCheckout passes on
1787
+ * by name: Paygent is off for the company (`processor_unavailable`), the environment is not the client's test
1788
+ * or live mode, a live checkout's currency is not the one the reviewed merchant charges in
1789
+ * (`unsupported_currency`), and the price is zero (`amount_required`): Paygent's token request names no
1790
+ * amount, so the price the agent sends is the one the cardholder approves. A page and name no reviewed pin
1791
+ * lists answers `unsupported_checkout`, which PREPARATION_REFUSALS names for every checkout. The API holds the
1792
+ * pins, not this build, so a merchant a later release reviews needs no new SDK. Only for a Paygent checkout.
1793
+ */
1794
+ const PAYGENT_PREPARATION_REFUSALS = [
1795
+ 'processor_unavailable', 'sandbox_host_on_live_row', 'live_host_on_sandbox_row', 'unsupported_currency', 'amount_required',
1796
+ ];
1706
1797
  /**
1707
1798
  * The API's refusals of a HiPay preparation, before any preparation exists, that prepareCheckout surfaces by name:
1708
1799
  * the company is not turned on for HiPay (`processor_unavailable`), the environment is not the caller's key mode's
@@ -2293,6 +2384,8 @@ export class VaultClient {
2293
2384
  throw fail(secureAcceptanceRefusal);
2294
2385
  const secureAcceptanceProfileId = secureAcceptance ? secureAcceptanceCheckoutProfile({ environment: input.environment,
2295
2386
  profileId: input.secureAcceptanceProfileId, merchantOrigin: input.merchantOrigin }) : null;
2387
+ // A live Paygent checkout's merchant is the API's to judge against the reviewed pins it holds, before anyone is
2388
+ // asked to approve (PAYGENT_PREPARATION_REFUSALS); test mode needs no pin.
2296
2389
  const timeoutMs = input.timeoutMs ?? 15 * 60_000;
2297
2390
  if (!Number.isInteger(timeoutMs) || timeoutMs <= 0 || timeoutMs > 2_147_483_647)
2298
2391
  throw fail('timeout_invalid');
@@ -2412,6 +2505,7 @@ export class VaultClient {
2412
2505
  || (input.psp === EPROTECT_PSP && EPROTECT_PREPARATION_REFUSALS.includes(error.code))
2413
2506
  || (input.psp === PADDLE_VGS_PSP && PADDLE_VGS_PREPARATION_REFUSALS.includes(error.code))
2414
2507
  || (input.psp === WIX_PAYMENTS_PSP && WIX_PAYMENTS_PREPARATION_REFUSALS.includes(error.code))
2508
+ || (input.psp === PAYGENT_PSP && PAYGENT_PREPARATION_REFUSALS.includes(error.code))
2415
2509
  || (input.psp === HIPAY_PSP && HIPAY_PREPARATION_REFUSALS.includes(error.code))
2416
2510
  || (!!flexContext && CYBERSOURCE_FLEX_PREPARATION_REFUSALS.includes(error.code))))
2417
2511
  || (input.psp === 'fiserv' && error.status === 502 && error.code === 'cse_key_unavailable')))
@@ -2709,17 +2803,21 @@ export class VaultClient {
2709
2803
  // the total GoPay's payment already holds, so a zero pair would show the cardholder no price at all: it is refused
2710
2804
  // like a missing one.
2711
2805
  // A ComGate payment charges the total ComGate already holds for it, the same way, and a Webpay transaction the
2712
- // total the merchant's server bound to it.
2806
+ // total the merchant's server bound to it, and a Payrexx page charges its own
2807
+ // total after the frame's token.
2713
2808
  // A Moneris token is charged later for whatever the merchant's server decides, and a Payfast pay, a
2714
2809
  // PayMongo attach or a MakeCommerce card post charges the total its payment, checkout session or
2715
2810
  // transaction already holds, so a zero pair would show the cardholder no price at all: it is refused
2716
2811
  // like a missing one. So is a TokenEx iframe token, which the merchant's server charges later for its own order.
2812
+ // A Clover token is charged later for whatever the merchant's server decides, as a Moneris one is.
2813
+ // A Toast payment method is charged by the page's own confirm for whatever its intent says, the same way.
2717
2814
  if (hasAmount && rec && rec.psp === PRZELEWY24_PSP && Number(input.amount) <= 0)
2718
2815
  throw new AmountRequiredError(rec.psp);
2719
2816
  if (hasAmount && rec && (rec.psp === MONERIS_PSP || rec.psp === PAYFAST_PSP || rec.psp === PAYMONGO_PSP || rec.psp === HIPAY_PSP || rec.psp === TWOC2P_PSP
2720
2817
  || rec.psp === REVOLUT_PSP || rec.psp === PAY_NL_PSP || rec.psp === PAYPAL_PSP
2721
2818
  || rec.psp === GOPAY_PSP || rec.psp === COMGATE_PSP || rec.psp === MAKECOMMERCE_PSP
2722
- || rec.psp === POYNT_PSP || rec.psp === TOKENEX_IFRAME_PSP || rec.psp === TRANSBANK_PSP || rec.psp === SUMUP_PSP) && Number(input.amount) <= 0)
2819
+ || rec.psp === POYNT_PSP || rec.psp === TOKENEX_IFRAME_PSP || rec.psp === TRANSBANK_PSP || rec.psp === SUMUP_PSP
2820
+ || rec.psp === CLOVER_PSP || rec.psp === PAYREXX_PSP || rec.psp === TOAST_PSP) && Number(input.amount) <= 0)
2723
2821
  throw new AmountRequiredError(rec.psp);
2724
2822
  // Nor is the agent's pair tied to the page by anything but the total the
2725
2823
  // page shows: without it the cardholder could approve one figure while the
@@ -3055,6 +3153,10 @@ export class VaultClient {
3055
3153
  // source.encryptionData and removes nothing (fiserv.ts); any other shape is not one to act on.
3056
3154
  if (rec.psp === 'fiserv' && !isFiservSubstitution(sub))
3057
3155
  throw new PaymentOutcomeUnknownError(authorizationId, 'cse_substitutions_malformed');
3156
+ // A Toast approval carries exactly one sealed card, cardData at `card`, and removes nothing
3157
+ // (payment-core toast-seal.js substituteToastCardData checks the rest at the swap).
3158
+ if (rec.psp === TOAST_PSP && !isToastSubstitution(sub))
3159
+ throw new PaymentOutcomeUnknownError(authorizationId, 'cse_substitutions_malformed');
3058
3160
  // `remove`: sibling keys the API says to drop with the swap (Adyen's
3059
3161
  // `brand`, stamped by adyen-web from the agent's dummy digits). Absent
3060
3162
  // on an older API; anything but a list of names is refused, since a
@@ -3225,6 +3327,18 @@ export class VaultClient {
3225
3327
  response.headers = answer.headers;
3226
3328
  response.body = answer.body;
3227
3329
  }
3330
+ if (rec?.psp === PAYGENT_PSP) {
3331
+ // Paygent's answer enters the page only as payment-core rebuilds an approved token from its members
3332
+ // (paygent.js paygentAnswerOutcome: HTTP 200, result 0000 and a token in shape), under a JSON content
3333
+ // type. Anything else, a refusal Paygent answers inside HTTP 200 included, is never handed over as an
3334
+ // approval; the card may have left the device, so the outcome is unknown rather than declined.
3335
+ const judged = paygentAnswerOutcome(response.status, response.body, null);
3336
+ if (judged.outcome !== 'approved' || typeof judged.body !== 'string')
3337
+ throw new PaymentOutcomeUnknownError(authorizationId, 'paygent_token_unconfirmed');
3338
+ response.status = 200;
3339
+ response.headers = { 'content-type': 'application/json' };
3340
+ response.body = judged.body;
3341
+ }
3228
3342
  if (rec?.psp === PRIMER_PSP) {
3229
3343
  // Primer's answer enters the page only as one single-use card token rebuilt from
3230
3344
  // payment-core's allow-list, under a JSON content type (primer.ts primerTokenAnswer).
@@ -3279,8 +3393,9 @@ export class VaultClient {
3279
3393
  chargedAmount: typeof s.charged_amount === 'number' ? s.charged_amount : null,
3280
3394
  chargedCurrency: typeof s.charged_currency === 'string' ? s.charged_currency : null,
3281
3395
  chargedKind: s.charged_kind === 'captured' || s.charged_kind === 'authorized' || s.charged_kind === 'none' ? s.charged_kind : null,
3282
- // An EBANX token approval that covers the sends after it names until when (ebanx.ts EbanxFollowUps).
3283
- ...(rec?.psp === 'ebanx' && typeof s.follow_ups?.until === 'string' && Number.isFinite(Date.parse(s.follow_ups.until))
3396
+ // An EBANX token approval, or a Payrexx card request whose answer started 3-D Secure, that covers the sends
3397
+ // after it names until when (ebanx.ts EbanxFollowUps, payrexx.ts PayrexxFollowUps).
3398
+ ...((rec?.psp === 'ebanx' || rec?.psp === PAYREXX_PSP) && typeof s.follow_ups?.until === 'string' && Number.isFinite(Date.parse(s.follow_ups.until))
3284
3399
  ? { followUpsUntil: s.follow_ups.until } : {}),
3285
3400
  ...amountAuthority,
3286
3401
  ...execution,
@@ -3493,6 +3608,20 @@ export class VaultClient {
3493
3608
  * an answer that carried the card, or `options.until` (the approval's window, plus the API's own clocks) passed.
3494
3609
  */
3495
3610
  async sendEbanxFollowUp(authorizationId, request, options = {}) {
3611
+ return this.sendFollowUp(authorizationId, request, options, (reason, cardSent) => new EbanxFollowUpError(reason, cardSent, authorizationId));
3612
+ }
3613
+ /**
3614
+ * Send one of Payrexx's 3-D Secure sends an approved Payrexx card request covers (payrexx.ts says which: the card
3615
+ * frame's authentication after the ACS method and its post-authentication after the bank's challenge, bound to that
3616
+ * approval) from the cardholder's device, and resolve with Payrexx's answer for the paused request, exactly as
3617
+ * sendEbanxFollowUp does for EBANX: the same create, reads and clocks, and no prompt. Rejects with a
3618
+ * PayrexxFollowUpError naming why there is no answer and whether the card may have reached Payrexx.
3619
+ */
3620
+ async sendPayrexxFollowUp(authorizationId, request, options = {}) {
3621
+ return this.sendFollowUp(authorizationId, request, options, (reason, cardSent) => new PayrexxFollowUpError(reason, cardSent, authorizationId));
3622
+ }
3623
+ /** The send behind sendEbanxFollowUp and sendPayrexxFollowUp; `fail` builds the processor's error for a send with no answer. */
3624
+ async sendFollowUp(authorizationId, request, options, fail) {
3496
3625
  if (!/^cauth_[A-Za-z0-9_-]{1,128}$/.test(authorizationId))
3497
3626
  throw new Error('Invalid authorization ID.');
3498
3627
  const base = `/v2/checkout/authorizations/${authorizationId}/follow_ups`;
@@ -3506,16 +3635,16 @@ export class VaultClient {
3506
3635
  catch (error) {
3507
3636
  // A refusal (a 4xx answer) stored no send, so nothing left. Any other failure (a 5xx, a timeout, a dropped
3508
3637
  // connection, an unreadable 201) may have come after the API stored the send, which the device then sends:
3509
- // the card may reach EBANX, and for a confirm that is a payment whose outcome is unknown.
3638
+ // the card may reach the processor, and for an EBANX confirm that is a payment whose outcome is unknown.
3510
3639
  const refused = error instanceof CheckoutApiError && error.status >= 400 && error.status < 500;
3511
3640
  const reason = error instanceof CheckoutApiError
3512
3641
  ? (typeof error.details.reason === 'string' && /^[a-z_]{1,64}$/.test(error.details.reason) ? error.details.reason : error.code ?? `http_${error.status}`)
3513
3642
  : 'follow_up_unavailable';
3514
- throw new EbanxFollowUpError(reason, !refused, authorizationId);
3643
+ throw fail(reason, !refused);
3515
3644
  }
3516
3645
  const id = created?.id;
3517
3646
  if (typeof id !== 'string' || !/^cfu_[0-9a-f]{24}$/.test(id) || created.authorization !== authorizationId) {
3518
- throw new EbanxFollowUpError('follow_up_unrecognized', true, authorizationId);
3647
+ throw fail('follow_up_unrecognized', true);
3519
3648
  }
3520
3649
  // Reads the API refused in a row as too many (429): each waits twice as long as the last before the next read.
3521
3650
  let limited = 0;
@@ -3527,14 +3656,14 @@ export class VaultClient {
3527
3656
  }
3528
3657
  catch (error) {
3529
3658
  if (signal.aborted)
3530
- throw new EbanxFollowUpError('answer_late', true, authorizationId);
3659
+ throw fail('answer_late', true);
3531
3660
  if (error instanceof CheckoutApiError && error.status === 429)
3532
3661
  limited += 1;
3533
3662
  }
3534
3663
  if (read?.status === 'answered') {
3535
3664
  const r = read.response;
3536
3665
  if (!r || !Number.isInteger(r.status) || r.status < 200 || r.status > 599 || typeof r.body !== 'string' || !r.headers || typeof r.headers !== 'object') {
3537
- throw new EbanxFollowUpError('follow_up_unrecognized', true, authorizationId);
3666
+ throw fail('follow_up_unrecognized', true);
3538
3667
  }
3539
3668
  const headers = {};
3540
3669
  for (const [name, value] of Object.entries(r.headers))
@@ -3544,10 +3673,10 @@ export class VaultClient {
3544
3673
  }
3545
3674
  if (read?.status === 'failed' || read?.status === 'expired') {
3546
3675
  const reason = typeof read.failure_reason === 'string' && /^[a-z_]{1,64}$/.test(read.failure_reason) ? read.failure_reason : read.status;
3547
- throw new EbanxFollowUpError(reason, read.card_sent !== false, authorizationId);
3676
+ throw fail(reason, read.card_sent !== false);
3548
3677
  }
3549
3678
  if (signal.aborted || Date.now() >= deadline)
3550
- throw new EbanxFollowUpError('answer_late', true, authorizationId);
3679
+ throw fail('answer_late', true);
3551
3680
  const wait = limited === 0 ? EBANX_FOLLOW_UP_READ_MS : Math.min(EBANX_FOLLOW_UP_BACKOFF_MAX_MS, 1000 * 2 ** (limited - 1));
3552
3681
  await interruptibleSleep(Math.max(1, Math.min(wait, deadline - Date.now())), signal);
3553
3682
  }
@@ -4108,3 +4237,14 @@ function interruptibleSleep(ms, signal) {
4108
4237
  signal?.addEventListener('abort', done, { once: true });
4109
4238
  });
4110
4239
  }
4240
+ /** A served Toast set: exactly { cardData } at `card`, a 256-byte base64 value, with nothing to remove. */
4241
+ function isToastSubstitution(sub) {
4242
+ if (!sub || typeof sub !== 'object')
4243
+ return false;
4244
+ const { at, fields, remove } = sub;
4245
+ if (at !== 'card' || !fields || typeof fields !== 'object' || Array.isArray(fields))
4246
+ return false;
4247
+ const keys = Object.keys(fields);
4248
+ return keys.length === 1 && keys[0] === 'cardData' && isToastCiphertext(fields.cardData)
4249
+ && (remove === undefined || (Array.isArray(remove) && remove.length === 0));
4250
+ }
@@ -1,6 +1,6 @@
1
1
  // @ts-nocheck
2
2
  // Generated from @agent-cards/payment-core. Do not edit.
3
- // artifact-sha256: 0110d9fe2e862aad2c1191c5927ebb98dcc4be9905554ed46e18719a0bad05fb
3
+ // artifact-sha256: 24cac2580feefe7d273b89de8e5b84e744e561fd258252f98008801750370460
4
4
  // src/creditguard.js
5
5
  var CREDITGUARD_PPS_HOSTS = Object.freeze(["pps.creditguard.co.il", "pps2.creditguard.co.il", "ppsuat.creditguard.co.il"]);
6
6
  var CREDITGUARD_SANDBOX_HOST = "ppsuat.creditguard.co.il";
@@ -707,6 +707,7 @@ var THREE_DS_INIT_KEYS = Object.freeze([
707
707
  "cvv",
708
708
  "isGooglePayTxn",
709
709
  "isPayboxTxn",
710
+ "recaptchaValue",
710
711
  "useDcc",
711
712
  "acsWindowSize",
712
713
  "browserColorDepth",
@@ -837,6 +838,7 @@ function creditguardDeviceThreeDSInitRequest(processUrl, card, options = {}) {
837
838
  cvv: parts.cvc,
838
839
  isGooglePayTxn: false,
839
840
  isPayboxTxn: false,
841
+ recaptchaValue: "",
840
842
  useDcc: false,
841
843
  acsWindowSize: "4",
842
844
  browserColorDepth: browser.colorDepth,
@@ -887,7 +889,7 @@ function deviceStepBodyRefusal(step, txId, body) {
887
889
  return "dcc";
888
890
  if (value.txId !== txId)
889
891
  return "txid_mismatch";
890
- if (value.acsWindowSize !== "4" || value.browserHeader !== BROWSER_HEADER || value.browserJavascriptEnabled !== true)
892
+ if (value.recaptchaValue !== "" || value.acsWindowSize !== "4" || value.browserHeader !== BROWSER_HEADER || value.browserJavascriptEnabled !== true)
891
893
  return "malformed_body";
892
894
  try {
893
895
  deviceBrowser({
package/dist/cse-body.js CHANGED
@@ -4,6 +4,8 @@ import { substituteEncryptedFields } from './substitute.js';
4
4
  import { substituteMerchantHostedBody } from './merchant-hosted.js';
5
5
  import { fiservCapturePinId, isFiservCaptureHost, substituteFiservEnvelope } from './fiserv.js';
6
6
  import { substituteVerifoneBody } from './verifone.js';
7
+ import { isToastHost } from './toast.generated.js';
8
+ import { substituteToastCardData } from './toast-seal.generated.js';
7
9
  /**
8
10
  * The cse continuation: the paused body with the vault's ciphertext in place
9
11
  * of the dummy blobs. The adapters send it as postData ALONE. Chromium
@@ -44,6 +46,15 @@ export function cseBody(body, replay, url, preparation) {
44
46
  }
45
47
  if (isFiservCaptureHost(url))
46
48
  throw new Error('Refusing to substitute: a Fiserv capture continues only through a prepared Fiserv approval.');
49
+ // A request on Toast's payments host takes only Toast's own swap: the device's sealed card written over
50
+ // the paused card.cardData and no other byte, refused unless the paused body is still the frame's guest
51
+ // card request and the card is sealed under the key payment-core pins (toast-seal.js substituteToastCardData).
52
+ if (isToastHost(url)) {
53
+ if (replay.kind !== undefined || replay.substitutions.at !== 'card' || (replay.substitutions.remove ?? []).length > 0) {
54
+ throw new Error('Refusing to substitute: the paused request is not the Toast card request the cardholder approved.');
55
+ }
56
+ return substituteToastCardData(url, body, replay.substitutions.fields);
57
+ }
47
58
  // A merchant-hosted approval is written into the LIVE paused body, which must
48
59
  // still be the body the API checked at create (merchant-hosted.ts).
49
60
  if (replay.kind === 'merchant_hosted')
package/dist/ebanx.d.ts CHANGED
@@ -75,7 +75,7 @@ export declare class EbanxFollowUps {
75
75
  stop(): void;
76
76
  /**
77
77
  * The approval the remembered token request went under, or null when none is remembered. The gate sends nothing
78
- * unless the checkout's own state names this same approval (pre-claim.ts ebanxMaySend).
78
+ * unless the checkout's own state names this same approval (pre-claim.ts followUpMaySend).
79
79
  */
80
80
  get authorizationId(): string | null;
81
81
  /**
package/dist/ebanx.js CHANGED
@@ -108,7 +108,7 @@ export class EbanxFollowUps {
108
108
  stop() { this.stopped = true; }
109
109
  /**
110
110
  * The approval the remembered token request went under, or null when none is remembered. The gate sends nothing
111
- * unless the checkout's own state names this same approval (pre-claim.ts ebanxMaySend).
111
+ * unless the checkout's own state names this same approval (pre-claim.ts followUpMaySend).
112
112
  */
113
113
  get authorizationId() { return this.parent?.authorizationId ?? null; }
114
114
  /**
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Eximbay's payment window (secureapi.eximbay.com, the Korean gateway that takes foreign cards for Korean merchants),
3
+ * as this SDK reads it.
4
+ *
5
+ * The merchant's page posts a signed entry form, top-level or into a window it opens, to
6
+ * https://secureapi.eximbay.com/Gateway/BasicProcessor.krp (Eximbay's test host for a test merchant), and Eximbay
7
+ * answers with a page that posts itself, with the session's paramkey, into its window. That entry carries no card,
8
+ * and it is the one request this SDK pauses: the cardholder's device repeats it over the end-to-end TLS relay and
9
+ * fills the card into the window Eximbay returns to it (payment-core processors/eximbay.js). Everything else the
10
+ * agent's browser posts to Eximbay follows the entry and leads to a card: the step that opens the window
11
+ * (/Gateway/BasicProcessor/230/NewGlobalStep1.jsp), the window's amount query and card lookup, which carries the
12
+ * card's first eight digits (controll/checkApprvJson.do, controll/checkCardBin.do), its card post
13
+ * (controll/checkApprv.jsp) and its other posts. This SDK aborts every one of them in a page it watches, so a
14
+ * placeholder card never reaches Eximbay from one. attachToCdp watches a window the merchant's page opens once it has
15
+ * armed that window. Eximbay's popup integration opens the window and posts the entry into it at once, so that entry
16
+ * can leave before the window is armed; the window's next post, the step, is then aborted like any other, and the
17
+ * checkout reads unsupported. attachToPlaywright routes the page alone, never a window it opens.
18
+ *
19
+ * EXIMBAY_HOSTS is payment-core's EXIMBAY_HOSTS, and isEximbayEntry takes the URLs payment-core's isEximbayEntryUrl
20
+ * takes; eximbay.test.mjs pins both to payment-core.
21
+ */
22
+ export declare const EXIMBAY_PSP = "eximbay";
23
+ /** The dark-launch capability the API serves Eximbay's recognizer behind (payment-core definitions.js). */
24
+ export declare const EXIMBAY_CAPABILITY = "eximbay_hosted_payment_page";
25
+ /** The live host and Eximbay's test host, which serves the same window for a test merchant. */
26
+ export declare const EXIMBAY_HOSTS: readonly string[];
27
+ /** Whether this URL is on one of Eximbay's two hosts. */
28
+ export declare function isEximbayHost(url: string): boolean;
29
+ /**
30
+ * The only request on Eximbay's hosts that is the merchant's entry: a POST to the entry path, written exactly as a
31
+ * browser posts a form to it, with no port, query or fragment (payment-core isEximbayEntryUrl takes those two strings
32
+ * only). The entry the SDK pauses; every other POST there is aborted.
33
+ */
34
+ export declare function isEximbayEntry(url: string, method: string): boolean;
35
+ /** The entry URL on the same Eximbay host, which the registry recognizes when this build is armed for Eximbay, or null. */
36
+ export declare function eximbayEntryUrl(url: string): string | null;
37
+ /**
38
+ * Whether an adapter must hand this request to the pre-claim gates: it is on an Eximbay host and this build's registry
39
+ * serves Eximbay's entry there. The step, the window's lookups and its card post are not the entry, so the registry's
40
+ * card-request test does not name them; the CDP adapter still pauses them through the recognizer's host pattern, and
41
+ * attachToPlaywright's route predicate asks this, so both adapters show the gates every request to Eximbay once
42
+ * Eximbay is armed.
43
+ */
44
+ export declare function isArmedEximbayUrl(vault: {
45
+ isCardRequest(url: string, method?: string): boolean;
46
+ }, url: string): boolean;
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Eximbay's payment window (secureapi.eximbay.com, the Korean gateway that takes foreign cards for Korean merchants),
3
+ * as this SDK reads it.
4
+ *
5
+ * The merchant's page posts a signed entry form, top-level or into a window it opens, to
6
+ * https://secureapi.eximbay.com/Gateway/BasicProcessor.krp (Eximbay's test host for a test merchant), and Eximbay
7
+ * answers with a page that posts itself, with the session's paramkey, into its window. That entry carries no card,
8
+ * and it is the one request this SDK pauses: the cardholder's device repeats it over the end-to-end TLS relay and
9
+ * fills the card into the window Eximbay returns to it (payment-core processors/eximbay.js). Everything else the
10
+ * agent's browser posts to Eximbay follows the entry and leads to a card: the step that opens the window
11
+ * (/Gateway/BasicProcessor/230/NewGlobalStep1.jsp), the window's amount query and card lookup, which carries the
12
+ * card's first eight digits (controll/checkApprvJson.do, controll/checkCardBin.do), its card post
13
+ * (controll/checkApprv.jsp) and its other posts. This SDK aborts every one of them in a page it watches, so a
14
+ * placeholder card never reaches Eximbay from one. attachToCdp watches a window the merchant's page opens once it has
15
+ * armed that window. Eximbay's popup integration opens the window and posts the entry into it at once, so that entry
16
+ * can leave before the window is armed; the window's next post, the step, is then aborted like any other, and the
17
+ * checkout reads unsupported. attachToPlaywright routes the page alone, never a window it opens.
18
+ *
19
+ * EXIMBAY_HOSTS is payment-core's EXIMBAY_HOSTS, and isEximbayEntry takes the URLs payment-core's isEximbayEntryUrl
20
+ * takes; eximbay.test.mjs pins both to payment-core.
21
+ */
22
+ export const EXIMBAY_PSP = 'eximbay';
23
+ /** The dark-launch capability the API serves Eximbay's recognizer behind (payment-core definitions.js). */
24
+ export const EXIMBAY_CAPABILITY = 'eximbay_hosted_payment_page';
25
+ /** The live host and Eximbay's test host, which serves the same window for a test merchant. */
26
+ export const EXIMBAY_HOSTS = Object.freeze(['secureapi.eximbay.com', 'secureapi.test.eximbay.com']);
27
+ const ENTRY_PATH = '/Gateway/BasicProcessor.krp';
28
+ /** The Eximbay host a URL is on (https, no credentials, any port, any letter case, a trailing dot or not), or null. */
29
+ function eximbayHost(raw) {
30
+ let url;
31
+ try {
32
+ url = new URL(raw);
33
+ }
34
+ catch {
35
+ return null;
36
+ }
37
+ if (url.protocol !== 'https:' || url.username || url.password)
38
+ return null;
39
+ const host = url.hostname.toLowerCase().replace(/\.$/, '');
40
+ return EXIMBAY_HOSTS.includes(host) ? host : null;
41
+ }
42
+ /** Whether this URL is on one of Eximbay's two hosts. */
43
+ export function isEximbayHost(url) {
44
+ return eximbayHost(url) !== null;
45
+ }
46
+ /**
47
+ * The only request on Eximbay's hosts that is the merchant's entry: a POST to the entry path, written exactly as a
48
+ * browser posts a form to it, with no port, query or fragment (payment-core isEximbayEntryUrl takes those two strings
49
+ * only). The entry the SDK pauses; every other POST there is aborted.
50
+ */
51
+ export function isEximbayEntry(url, method) {
52
+ return String(method).toUpperCase() === 'POST' && EXIMBAY_HOSTS.some((host) => url === `https://${host}${ENTRY_PATH}`);
53
+ }
54
+ /** The entry URL on the same Eximbay host, which the registry recognizes when this build is armed for Eximbay, or null. */
55
+ export function eximbayEntryUrl(url) {
56
+ const host = eximbayHost(url);
57
+ return host === null ? null : `https://${host}${ENTRY_PATH}`;
58
+ }
59
+ /**
60
+ * Whether an adapter must hand this request to the pre-claim gates: it is on an Eximbay host and this build's registry
61
+ * serves Eximbay's entry there. The step, the window's lookups and its card post are not the entry, so the registry's
62
+ * card-request test does not name them; the CDP adapter still pauses them through the recognizer's host pattern, and
63
+ * attachToPlaywright's route predicate asks this, so both adapters show the gates every request to Eximbay once
64
+ * Eximbay is armed.
65
+ */
66
+ export function isArmedEximbayUrl(vault, url) {
67
+ const entry = eximbayEntryUrl(url);
68
+ return entry !== null && vault.isCardRequest(entry, 'POST');
69
+ }