@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/client.js CHANGED
@@ -1,12 +1,10 @@
1
1
  import { BUILTIN_REGISTRY, cardUrlPatterns as deriveCardUrlPatterns, LOOPBACK_URL_PATTERNS, findRecognizer, isRecognizerRequest, recognizerRequestMethod, recognizerRequiresPreparation, urlsOnProcessorHosts, } from './registry.js';
2
2
  import { armServedProfiles, classifyProfileRequest, declareSandboxMerchant, merchantProfileEnvironment, merchantProfileFor, merchantProfileUrlPatterns, } from './merchant-hosted.js';
3
3
  import { merchantAmountExchanges, merchantAmountFromResponses, merchantProfilePricedBySource, merchantTotalWire, MerchantTotalWait, } from './merchant-total.js';
4
- import { ACI_CAPABILITY, ACI_PSP, aciCardLegAnswer } from './aci.js';
5
- import { PRZELEWY24_CAPABILITY, PRZELEWY24_PSP } from './przelewy24.js';
6
- import { EPROTECT_CAPABILITY, EPROTECT_PSP, eprotectCheckoutRefusal, eprotectScriptAnswer, isEprotectRegistration } from './eprotect.js';
7
- import { COMGATE_CAPABILITY, COMGATE_PSP } from './comgate.js';
4
+ import { ACI_PSP, aciCardLegAnswer } from './aci.js';
5
+ import { EPROTECT_PSP, eprotectCheckoutRefusal, eprotectScriptAnswer, isEprotectRegistration } from './eprotect.js';
8
6
  import { isHostedGetEntry } from './hosted-entry.js';
9
- import { TRANSBANK_CAPABILITY, TRANSBANK_PSP } from './transbank.js';
7
+ import { SBPS_PSP, isSbpsTokenGet, sbpsScriptAnswer } from './sbps.js';
10
8
  import { isMercadoTokenRequest, parseMercadoCheckoutContext, validateMercadoProcessorContext } from './mercado-checkout.generated.js';
11
9
  import { matchesPreparation, preparationMode, validPreparationEnvironment } from './prepared-processor.js';
12
10
  import { fiservKeyPinById, isFiservSubstitution, requiresFiservPreparation } from './fiserv.js';
@@ -15,34 +13,36 @@ import { hasOwnedShopMarker, parseOwnedShopOrder, parseOwnedShopReceipt } from '
15
13
  import { requestWithoutCard } from './card-fields.generated.js';
16
14
  import { merchantSessionCapture } from './merchant-session-probe.js';
17
15
  import { creditguardPageProcessUrl, creditguardPpDomainHost, creditguardPreparationEnvironment, creditguardProcessTxId, sanitizeCreditguardResponse, } from './creditguard.generated.js';
18
- import { CONVERGE_CAPABILITY } from './elavon-converge.js';
19
- import { SHIJI_STS_CAPABILITY, SHIJI_STS_PSP, isShijiStsServedCompanion, isShijiStsServedSubstitution } from './shiji-sts.js';
20
- import { AMADEUS_CHECKOUT_CAPABILITY, AMADEUS_CHECKOUT_PSP } from './amadeus-checkout.generated.js';
21
- import { PADDLE_VGS_CAPABILITY, PADDLE_VGS_PSP, paddleVgsCardFactsOf } from './paddle-vgs.generated.js';
22
- import { PADDLE_TOKENEX_CAPABILITY, PADDLE_TOKENEX_PSP, isPaddleTokenexServedFacts, isPaddleTokenexServedSubstitution } from './paddle-tokenex.js';
23
- import { TWOC2P_CAPABILITY, TWOC2P_PSP } from './twoc2p.js';
24
- import { WIX_PAYMENTS_CAPABILITY, WIX_PAYMENTS_PSP } from './wix-payments.generated.js';
25
- import { HIPAY_CAPABILITY, HIPAY_PSP } from './hipay.generated.js';
16
+ import { SHIJI_STS_PSP, isShijiStsServedCompanion, isShijiStsServedSubstitution } from './shiji-sts.js';
17
+ import { AMADEUS_CHECKOUT_PSP } from './amadeus-checkout.generated.js';
18
+ import { PADDLE_VGS_PSP, paddleVgsCardFactsOf } from './paddle-vgs.generated.js';
19
+ import { PAYGENT_PSP, paygentAnswerOutcome } from './paygent.generated.js';
20
+ import { PADDLE_TOKENEX_PSP, isPaddleTokenexServedFacts, isPaddleTokenexServedSubstitution } from './paddle-tokenex.js';
21
+ import { WIX_PAYMENTS_PSP } from './wix-payments.generated.js';
22
+ import { HIPAY_PSP } from './hipay.generated.js';
23
+ import { TOAST_PSP, isToastCiphertext } from './toast.generated.js';
26
24
  import { classifyStripeCheckoutRequest, encodeStripeCheckoutContext, hasStripeCheckoutMarker, parseStripeCheckoutContext, parseStripeCheckoutResponse, STRIPE_CHECKOUT_CONTEXT_HEADER, } from './stripe-checkout.generated.js';
27
25
  import { VoiceModule } from './voice.js';
28
26
  import { BuyModule } from './buy.js';
29
27
  import { Languages } from './languages.js';
30
28
  import { RequestRegistry } from './requests.js';
31
29
  import { AgentcardError } from './voice-types.js';
32
- import { GMO_CAPABILITY, GMO_PSP, gmoTokenAnswer } from './gmo.js';
30
+ import { HYPHEN, SLASH, maskUuids } from './url-mask-parts.js';
31
+ import { URL_MASKS } from './url-masks.generated.js';
32
+ import { DECLARED_CAPABILITIES } from './capabilities.generated.js';
33
+ import { AGENT_AMOUNT } from './agent-amount.generated.js';
34
+ import { GMO_PSP, gmoTokenAnswer } from './gmo.js';
33
35
  import { admitCybersourceFlexPreparation, isCybersourceFlexTokenShape, sameCybersourceFlexPreparation } from './cybersource-flex.generated.js';
34
- import { CYBERSOURCE_FLEX_CAPABILITY, CYBERSOURCE_FLEX_PSP, validCybersourceFlexSidecar, } from './cybersource-flex.js';
35
- import { JUSPAY_CAPABILITY, JUSPAY_PSP, juspayPageAnswer } from './juspay-gate.js';
36
+ import { CYBERSOURCE_FLEX_PSP, validCybersourceFlexSidecar, } from './cybersource-flex.js';
37
+ import { JUSPAY_PSP, juspayPageAnswer } from './juspay-gate.js';
36
38
  import { CARD_ENDPOINT_MODE, CARD_ENDPOINT_PSP, CardEndpointRefusedError, cardEndpointById, cardEndpointLive, cardEndpointSessionRefusal, } from './card-endpoint.js';
37
39
  import { merchantCardEndpointFactsRefusal as sharedFactsRefusal, readMerchantCardEndpointFacts as sharedReadFacts, } from './merchant-card-endpoints.generated.js';
38
- import { DATATRANS_CAPABILITY } from './datatrans.js';
39
- import { TOKENEX_IFRAME_CAPABILITY, TOKENEX_IFRAME_PSP, tokenexIframePageAnswer, tokenexIframePlaceholderFits } from './tokenex-iframe.js';
40
- import { PELECARD_CAPABILITY, PELECARD_PSP } from './pelecard.js';
41
- import { PRIMER_CAPABILITY, PRIMER_PSP, primerTokenAnswer } from './primer.js';
42
- import { PAYRAILS_CAPABILITY, isPayrailsPaymentUrl, payrailsAnswer } from './payrails.js';
43
- import { WSPAY_CAPABILITY, WSPAY_PSP } from './wspay.js';
40
+ import { TOKENEX_IFRAME_PSP, tokenexIframePageAnswer, tokenexIframePlaceholderFits } from './tokenex-iframe.js';
41
+ import { PRIMER_PSP, primerTokenAnswer } from './primer.js';
42
+ import { isPayrailsPaymentUrl, payrailsAnswer } from './payrails.js';
44
43
  import { EbanxFollowUpError } from './ebanx.js';
45
- 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';
44
+ import { PAYREXX_PSP, PayrexxFollowUpError } from './payrexx.js';
45
+ import { SECURE_ACCEPTANCE_FAST_POLL_MS, SECURE_ACCEPTANCE_PSP, SECURE_ACCEPTANCE_REPLY_WINDOW_MS, secureAcceptanceCheckoutProfile, secureAcceptanceCheckoutRefusal, secureAcceptanceFrameAnswer, } from './cybersource-secure-acceptance.js';
46
46
  /**
47
47
  * The modes this SDK can finish. Asked for on syncRegistry (the API serves
48
48
  * only recognizers in these modes, so a request this build cannot complete
@@ -202,6 +202,12 @@ export const SUPPORTED_REGISTRY_FEATURES = ['card_fields', 'checkout_sessions'];
202
202
  * never lets the agent's browser post to (pre-claim.ts, redsys.ts). Served on the same terms as NMI's: only
203
203
  * to a build that lists it, for a company Agentcard turned Redsys on for.
204
204
  *
205
+ * `paytrail_hosted_payment_page`: Paytrail's card method, whose card-free signed Visa and Mastercard
206
+ * entry this build pauses for the cardholder's device, and whose loading page post, card page posts and
207
+ * card-number lookup on Payment Highway's host this build never lets the agent's browser send
208
+ * (pre-claim.ts, paytrail.ts). Served on the same terms as NMI's and Redsys's: only to a build that lists
209
+ * it, for a company Agentcard turned Paytrail on for.
210
+ *
205
211
  * `paddle_vgs_cards`: the card request Paddle's checkout frame sends to its VGS card
206
212
  * vault (vault.paddle.com/cards) on a checkout that names VGS, only through a prepared
207
213
  * checkout: the frame aborts the request 10 seconds after Pay and sends it once more,
@@ -265,6 +271,22 @@ export const SUPPORTED_REGISTRY_FEATURES = ['card_fields', 'checkout_sessions'];
265
271
  * company until a reviewed change binds GoPay's payee and amount to the approval: the post names
266
272
  * neither. The API takes it only at a shop Agentcard reviewed.
267
273
  *
274
+ * `paygent_token`: the token request Paygent's own PaygentToken.js sends from a merchant's
275
+ * page (token.paygent.co.jp/n/token/request), only through a prepared checkout: the script
276
+ * abandons the request 35 seconds after the press that sends it, so the cardholder approves
277
+ * before the press. The cardholder's device sends the card itself and the page gets back
278
+ * Paygent's single-use token alone; the SDK refuses a second token request on the same approval.
279
+ * Served on the same terms: only to a build that lists it, for a company Agentcard turned
280
+ * Paygent on for.
281
+ *
282
+ * `payrexx_card_frame`: the card request Payrexx Pay's card frame sends to its own URL
283
+ * (checkout.payrexx.com/?pspId=44&lang=<xx>) on a Payrexx merchant's page. This build pauses only
284
+ * the frame's own one-time card request and answers it with Payrexx's answer as the API recorded
285
+ * it: the token the merchant's page then charges, or the start of 3-D Secure, whose two later sends
286
+ * of the card this build hands to the cardholder's device under the same approval (payrexx.ts,
287
+ * pre-claim.ts payrexxPreClaim). The cardholder's device sends the card through the checkout relay.
288
+ * Served only to a build that lists it, for a company Agentcard turned Payrexx on for.
289
+ *
268
290
  * `hipay_vault_token`: the token create HiPay's hosted fields send to HiPay's Secure Vault
269
291
  * (secure2-vault.hipay-tpp.com, stage-secure2-vault for a test row), only through a prepared
270
292
  * checkout: the merchant's page gives the frame 20 seconds after Pay. The cardholder's device
@@ -302,8 +324,8 @@ export const SUPPORTED_REGISTRY_FEATURES = ['card_fields', 'checkout_sessions'];
302
324
  * This build pauses only the form's own multipart body at that exact URL; the cardholder's
303
325
  * device sends the card through the checkout relay, and the frame gets the token answer
304
326
  * payment-core's judge rebuilt, with the CORS answer for the frame's own origin, null. The
305
- * request names no amount, so the agent's pair, above zero, is the one the cardholder approves
306
- * (PAY_NL_PSP below). Served on the same terms: only to a build that lists it, for a company
327
+ * request names no amount, so the agent's pair, above zero, is the one the cardholder approves.
328
+ * Served on the same terms: only to a build that lists it, for a company
307
329
  * Agentcard turned Pay. on for.
308
330
  *
309
331
  * `paypal_card_fields`: PayPal Card Fields' confirm, the POST its v5 frame sends to
@@ -364,11 +386,76 @@ export const SUPPORTED_REGISTRY_FEATURES = ['card_fields', 'checkout_sessions'];
364
386
  * device sends the card itself, and the API holds the payment to a merchant Agentcard reviewed, at the
365
387
  * amount SumUp holds for the checkout. Served on the same terms: only to a build that lists it, for a
366
388
  * company Agentcard turned SumUp on for.
389
+ *
390
+ * `clover_card_token`: the card token request Clover's card frame (the hidden frame of
391
+ * checkout.clover.com that Clover's iframe mounts on a merchant's page) sends to
392
+ * token.clover.com/v1/tokens (token-sandbox.dev.clover.com for a test row), with the number
393
+ * encrypted under Clover's key. This build pauses only the frame's own body at that exact URL; the
394
+ * cardholder's device builds the request again with the real card under the key payment-core pins
395
+ * and sends it to Clover itself, and the frame gets the token answer payment-core's judge rebuilt.
396
+ * The request names no amount, so the agent's pair, above zero, is the one the cardholder approves.
397
+ * Served on the same terms: only to a build that lists it, for a company
398
+ * Agentcard turned Clover on for.
399
+ *
400
+ * `eximbay_hosted_payment_page`: Eximbay's payment window, whose card-free signed entry
401
+ * (a form post to /Gateway/BasicProcessor.krp on secureapi.eximbay.com, or its test host) this
402
+ * build pauses for the cardholder's device, in the page or in a window it opened once attachToCdp
403
+ * has armed that window (eximbay.ts names the entry that can leave first), and whose
404
+ * step, amount query, card lookup and card post this build never lets the agent's browser send
405
+ * (pre-claim.ts, eximbay.ts). Served on the same terms as NMI's and Redsys's: only to a build
406
+ * that lists it, for a company Agentcard turned Eximbay on for.
407
+ *
408
+ * `toast_pinned_key`: the card request Toast's payments frame sends to
409
+ * payments.toasttab.com/v1/payment-methods on a Toast online-ordering checkout, with
410
+ * the card sealed in card.cardData under one key every Toast restaurant shares. The
411
+ * cardholder's device seals the real card under that key, which payment-core pins, and
412
+ * this build writes it over the agent's placeholder and nothing else before it
413
+ * continues the request. Served on the same terms: only to a build that lists it, for
414
+ * a company Agentcard turned Toast on for.
415
+ *
416
+ * `xendit_card_token`: the card token request Xendit's hosted invoice checkout
417
+ * (checkout.xendit.co/web/<invoice id>, where Xendit's payment and donation links land) sends
418
+ * when the payer presses Pay Now: Xendit.js posts the card as JSON to
419
+ * https://api.xendit.co/v2/credit_card_tokens from the page itself, under the business's
420
+ * publishable key. This build pauses only the card form's single-use token body at that exact URL
421
+ * (payment-core xendit.js); a token that keeps the card on file, Xendit.js's code-only storeCVN post
422
+ * and any other body there fail in the browser before anything is created. The cardholder's device
423
+ * sends the request itself with the real card, and the page gets the token answer payment-core's
424
+ * judge rebuilt, after which the page runs 3-D Secure and charges the token without the card. The
425
+ * token names the invoice's amount and currency, so no agent pair is needed: the API reads the
426
+ * token's and holds an agent's pair to it. Served on the same terms: only to a build that lists it,
427
+ * for a company Agentcard turned Xendit on for.
428
+ *
429
+ * `omise_card_token`: the card token request every Omise card form sends, Omise.js's pay.html frame
430
+ * or a merchant's own page calling Omise.js or Omise's Node library: a JSON POST to
431
+ * https://vault.omise.co/tokens under the merchant's public key. This build pauses only the card
432
+ * form's token body at that exact URL (payment-core omise.js); a wallet's tokenization and any other
433
+ * body there fail in the browser before anything is created. The cardholder's device sends the
434
+ * request itself with the real card, and the page gets the token answer payment-core's judge
435
+ * rebuilt. The request names no amount, so the agent's pair, above zero, is the one the cardholder
436
+ * approves, and the API pays only a merchant payment-core pins for the checkout
437
+ * page the create names. Served on the same terms: only to a build that lists it, for a company
438
+ * Agentcard turned Omise on for.
439
+ *
440
+ * `sbps_token_jsonp`: SB Payment Service's card token request, the JSONP <script>
441
+ * GET its token script sends to /token/generateToken on token.sps-system.com. This
442
+ * build pauses it only as the browser writes it, aborts any other request on that
443
+ * path, and answers the paused script with the token as the API recorded it, in the
444
+ * request's own callback. The device rebuilds the card's ciphertext and sends it
445
+ * through the checkout relay. Served on the same terms: only to a build that lists
446
+ * it, for a company Agentcard turned SB Payment Service on for.
447
+ *
448
+ * `midtrans_card_token`: the card token Midtrans' Snap popup posts as one JSON request to
449
+ * api.midtrans.com/v2/token (api.sandbox.midtrans.com in test), under the business's
450
+ * publishable client key. The cardholder's device sends it and the page gets back Midtrans'
451
+ * token, which the merchant's own server charges afterwards, card free, running 3-D Secure
452
+ * there. The token names no amount, so authorize() needs the agent's pair.
453
+ * Served on the same terms, and the API holds the lane to Agentcard's own company
454
+ * until a reviewed change binds the client key to the merchant.
455
+ *
456
+ * The list itself is capabilities.json, sorted, one per line (scripts/generate-capabilities.mjs).
367
457
  */
368
- 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',
370
- TOKENEX_IFRAME_CAPABILITY, TRANSBANK_CAPABILITY,
371
- 'sumup_checkout_process'];
458
+ export const SUPPORTED_CAPABILITIES = DECLARED_CAPABILITIES;
372
459
  /**
373
460
  * Processors whose paused request names no amount at all, so the pair the
374
461
  * agent passes is the amount the cardholder approves: authorize() refuses one
@@ -388,88 +475,101 @@ export const SUPPORTED_CAPABILITIES = ['fiserv_card_capture', GMO_CAPABILITY, CY
388
475
  * carries, is the total the cardholder approves, and the card that pays can spend
389
476
  * no more than that (the API's card rule). Moneris Hosted Tokenization's
390
477
  * request.php carries the card and no price: the merchant's server charges the
391
- * token later, so the agent's pair, above zero (MONERIS_PSP below), is the one
478
+ * token later, so the agent's pair, above zero (`moneris` in the table), is the one
392
479
  * figure the approval shows. Paddle VGS's card request carries none either: Paddle
393
480
  * charges the stored card at /pay for what the checkout says, so the agent's pair,
394
481
  * which the prepared checkout carries, is the total the cardholder approves. Payfast's
395
482
  * card pay carries none either: the merchant's entry bound the total to the payment,
396
- * so the agent sends the total Payfast's page shows, above zero (PAYFAST_PSP below),
483
+ * so the agent sends the total Payfast's page shows, above zero (`payfast` in the table),
397
484
  * and the API holds it to the payment Payfast itself reports. Wix's tokenize request
398
485
  * 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.
486
+ * agent's pair, which the prepared checkout carries, is the total the cardholder approves. Toast's
487
+ * card request carries none either: the page's card-free confirm of its payment intent
488
+ * charges whatever the intent says, so the agent's pair, above zero (as Moneris's), is
489
+ * the one figure the approval shows.
400
490
  * PayMongo's card attach carries none either: the merchant bound the total, always in
401
491
  * Philippine pesos, to the checkout session when it created it, so the agent sends that
402
- * total, above zero (PAYMONGO_PSP below), and the API holds it to the session PayMongo's
492
+ * total, above zero (`paymongo` in the table), and the API holds it to the session PayMongo's
403
493
  * own page shows. GoPay's card post carries none either: the shop's server set the amount
404
494
  * when it created the payment at GoPay, and GoPay's gateway shows it only inside its own
405
- * session, so the agent sends the total the gateway shows, above zero (GOPAY_PSP below).
495
+ * session, so the agent sends the total the gateway shows, above zero (`gopay` in the table).
496
+ * Paygent's token request carries none either: the merchant's server charges the token later, so
497
+ * the agent's pair, which the prepared checkout carries, is the total the cardholder approves.
498
+ * Payrexx's card request names the page's total, but it only mints a token: the merchant's page
499
+ * charges its own total later, at its validateForm, so the agent sends the total that page shows,
500
+ * above zero (`payrexx` in the table), and the API holds it to the frame's figure exactly.
406
501
  * 2C2P's Payment carries none either: the merchant bound the total to the
407
502
  * payment token when it created it, so the agent sends the total 2C2P's page shows, above
408
- * zero (TWOC2P_PSP), and the API holds it to the amount 2C2P's own PaymentOption answer names.
503
+ * zero (`twoc2p` in the table), and the API holds it to the amount 2C2P's own PaymentOption answer names.
409
504
  * Przelewy24's pay wall link carries none either: the merchant's server registered the amount
410
505
  * with Przelewy24 under the link's token, so the agent sends the amount the pay wall shows, above
411
- * zero (PRZELEWY24_PSP below), and the cardholder's device holds the pay wall to it.
506
+ * zero (`przelewy24` in the table), and the cardholder's device holds the pay wall to it.
412
507
  * HiPay's token create carries none either: the merchant's server charges
413
508
  * HiPay's token for its own order, so the agent's pair, which the prepared checkout carries,
414
509
  * is the total the cardholder approves, above zero as Moneris's is. Revolut's card payment
415
510
  * names the order's amount in minor units but not its currency, which only the order holds,
416
511
  * so the number alone is no price the cardholder can approve: the agent sends the pair, above
417
- * zero (REVOLUT_PSP below), and the API holds its amount to the body's and its currency to
512
+ * zero (`revolut` in the table), and the API holds its amount to the body's and its currency to
418
513
  * one that Revolut and the API both count in hundredths.
419
514
  * Pay.'s card token request carries none either: the shop's server charges the token later for
420
- * its own order total, so the agent's pair, above zero (PAY_NL_PSP below), is the one figure the
515
+ * its own order total, so the agent's pair, above zero (`pay_nl` in the table), is the one figure the
421
516
  * approval shows.
422
517
  * PayPal Card Fields' confirm carries none either: the merchant's server fixed the
423
- * amount when it created the order, so the agent's pair, above zero (PAYPAL_PSP
424
- * below), is the one figure the approval shows.
518
+ * amount when it created the order, so the agent's pair, above zero (`paypal` in
519
+ * the table), is the one figure the approval shows.
425
520
  * ComGate's gateway link carries none either: the merchant's server set
426
521
  * the amount when it created the payment at ComGate, so the agent's pair, above zero
427
- * (COMGATE_PSP below), is the figure the cardholder approves, and the cardholder's device
522
+ * (`comgate` in the table), is the figure the cardholder approves, and the cardholder's device
428
523
  * holds ComGate's own figure to it before any card leaves.
429
524
  * MakeCommerce's card post carries none either: MakeCommerce's transaction holds the total, in
430
- * euros, so the agent sends the total the shop's order page shows, above zero (MAKECOMMERCE_PSP
431
- * below), and the API refuses any other currency.
525
+ * euros, so the agent sends the total the shop's order page shows, above zero (`makecommerce` in
526
+ * the table), and the API refuses any other currency.
432
527
  * GoDaddy Payments' generate-nonce carries none either: GoDaddy's server charges
433
- * the nonce later, so its pair must be above zero too (POYNT_PSP below).
528
+ * the nonce later, so its pair must be above zero too (`poynt` in the table).
434
529
  * The TokenEx iframe's card post carries none either: it mints a token into the merchant's TokenEx
435
530
  * vault, which the merchant's server charges later for its own order, so the agent sends the total
436
- * the merchant's page shows, above zero (TOKENEX_IFRAME_PSP), and the API refuses a create without it.
531
+ * the merchant's page shows, above zero (`tokenex_iframe` in the table), and the API refuses a create without it.
437
532
  * Webpay's entry carries none either: the merchant's server bound the total, in Chilean pesos,
438
- * to the transaction when it created it, so the agent's pair, above zero (TRANSBANK_PSP below),
533
+ * to the transaction when it created it, so the agent's pair, above zero (`transbank_webpay` in the table),
439
534
  * is the figure the cardholder approves, and the cardholder's device holds Webpay's own
440
535
  * transaction to it before any card leaves.
441
536
  * SumUp's card request carries none either: the merchant's server bound the
442
537
  * total and its currency to the checkout when it created it, so the agent sends the total the
443
- * payment page shows, above zero (SUMUP_PSP below), and the API holds it to the checkout SumUp
538
+ * payment page shows, above zero (`sumup` in the table), and the API holds it to the checkout SumUp
444
539
  * itself reports.
540
+ * Clover's card token request carries none either: the merchant's server charges the token
541
+ * later for its own order, so the agent's pair, above zero (`clover` in the table), is the one figure
542
+ * the approval shows.
543
+ * Omise's card token request carries none either, for the same reason: the merchant's server
544
+ * charges the token afterwards for its own order (`omise` in the table).
545
+ * Paytrail's card method entry carries none either: the merchant's server fixed the amount, in
546
+ * euros, when it created the payment, so the agent sends that amount (`paytrail` in the table), and
547
+ * the cardholder's device holds Payment Highway's card page to it.
548
+ * SB Payment Service's token request carries none either: the merchant's server
549
+ * charges the token later, so its pair must be above zero too (`sb_payment_service` in the table), and
550
+ * agree with the page's order total (PAGE_AMOUNT_PROCESSORS).
551
+ * Midtrans' Snap card token carries none either: the merchant's server charges the token
552
+ * afterwards, so the agent's pair, above zero (`midtrans` in the table), is the one figure the
553
+ * approval shows.
554
+ * The table itself is agent-amount-processors.json, sorted, one processor per line
555
+ * (scripts/generate-agent-amount.mjs). A processor added there states its reason in the API
556
+ * create route's refusal, not in this comment.
445
557
  */
446
- const MONERIS_PSP = 'moneris';
447
- const PAYFAST_PSP = 'payfast';
448
- const PAYMONGO_PSP = 'paymongo';
449
558
  const REVOLUT_PSP = 'revolut';
450
- const PAY_NL_PSP = 'pay_nl';
451
- const PAYPAL_PSP = 'paypal';
452
- const GOPAY_PSP = 'gopay';
453
- const MAKECOMMERCE_PSP = 'makecommerce';
454
- const POYNT_PSP = 'poynt';
455
- 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];
559
+ const AGENT_AMOUNT_PROCESSORS = AGENT_AMOUNT.processors;
560
+ /** The ones a zero amount is refused for, like a missing one (authorize()). */
561
+ const ABOVE_ZERO_PROCESSORS = AGENT_AMOUNT.aboveZero;
458
562
  /** How AmountRequiredError names a processor. */
459
- 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',
462
- [COMGATE_PSP]: 'ComGate', [PRZELEWY24_PSP]: 'Przelewy24',
463
- [MAKECOMMERCE_PSP]: 'MakeCommerce', [POYNT_PSP]: 'GoDaddy Payments', [TOKENEX_IFRAME_PSP]: 'TokenEx', [TRANSBANK_PSP]: 'Webpay',
464
- [SUMUP_PSP]: 'SumUp' };
563
+ const PROCESSOR_NAMES = AGENT_AMOUNT.names;
465
564
  /**
466
565
  * Processors whose request and answer name no amount at all, so the total the
467
566
  * payment page shows (`pageAmount`) is the one figure that ties the agent's pair
468
567
  * to what the page charges: authorize() refuses one without it
469
568
  * (PageAmountRequiredError), and the API answers 400 page_amount_required for the
470
- * same case. Pelecard only.
569
+ * same case. Pelecard, and SB Payment Service, whose token request and answer
570
+ * name no amount either.
471
571
  */
472
- const PAGE_AMOUNT_PROCESSORS = [PELECARD_PSP];
572
+ const PAGE_AMOUNT_PROCESSORS = AGENT_AMOUNT.pageAmount;
473
573
  /** An integer in the smallest unit, or a decimal string in normal units with a point; nothing else. */
474
574
  export function validAmountInput(amount) {
475
575
  if (typeof amount === 'number')
@@ -741,8 +841,12 @@ export class AmountRequiredError extends Error {
741
841
  code = 'amount_required';
742
842
  constructor(psp) {
743
843
  const processor = PROCESSOR_NAMES[psp] ?? psp;
744
- const lacks = psp === REVOLUT_PSP ? 'names its amount but not its currency' : 'names no amount';
745
- 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.`);
844
+ // A Payrexx card request names the frame's total, but the merchant's page charges its own total after the token.
845
+ const lacks = psp === REVOLUT_PSP ? 'names its amount but not its currency'
846
+ : psp === PAYREXX_PSP ? "only mints a token the merchant's page charges later" : 'names no amount';
847
+ // "An Omise payment request", "A Clover payment request": the article follows the name's first letter.
848
+ const article = /^[AEIOU]/.test(processor) ? 'An' : 'A';
849
+ super(`${article} ${processor} payment request ${lacks}, so pass amount and currency: that is the amount the cardholder approves. Nothing was created and nothing was charged.`);
746
850
  this.psp = psp;
747
851
  this.name = 'AmountRequiredError';
748
852
  }
@@ -760,7 +864,7 @@ export class PageAmountRequiredError extends Error {
760
864
  psp;
761
865
  code = 'page_amount_required';
762
866
  constructor(psp) {
763
- const processor = psp === PELECARD_PSP ? 'Pelecard' : psp;
867
+ const processor = PROCESSOR_NAMES[psp] ?? psp;
764
868
  super(`A ${processor} payment request names no amount, so pass the total its payment page shows as pageAmount beside amount and currency. Nothing was created and nothing was charged.`);
765
869
  this.psp = psp;
766
870
  this.name = 'PageAmountRequiredError';
@@ -1086,12 +1190,12 @@ function refusalsOf(v) {
1086
1190
  return out;
1087
1191
  }
1088
1192
  /**
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).
1193
+ * How often sendEbanxFollowUp and sendPayrexxFollowUp read a send back while the cardholder's device has it: every
1194
+ * half second, whatever the client's own `pollIntervalMs`. One send is out at a time per approval, so this is 120
1195
+ * reads a minute, inside the API's 240 a minute per caller and authorization (routes/api/v2/checkout-follow-ups.ts).
1092
1196
  */
1093
1197
  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. */
1198
+ /** The longest a follow-up send waits after reads the API refused as too many (429): 1, 2, 4, 8, then 15 seconds. */
1095
1199
  export const EBANX_FOLLOW_UP_BACKOFF_MAX_MS = 15_000;
1096
1200
  export class CheckoutApiError extends Error {
1097
1201
  status;
@@ -1199,23 +1303,7 @@ function parseErrorEnvelope(bodyText) {
1199
1303
  }
1200
1304
  return { code: null, details: {} };
1201
1305
  }
1202
- /**
1203
- * A slash as the masks below read one: plain, or escaped once or more in either case (%2F, %2f, %252F). A path keeps
1204
- * an escape as it was written, and the id behind it is the same id, so every slash a mask names reads in any of these
1205
- * spellings. The slashes right before an id read as a run, and a doubled slash hides nothing either.
1206
- */
1207
- const SLASH = '(?:/|%(?:25)*2f)';
1208
- /** A hyphen inside an id: plain, or escaped once or more in either case (%2D, %252d). */
1209
- const HYPHEN = '(?:-|%(?:25)*2d)';
1210
- /**
1211
- * The masks that find a uuid by its shape take it in any case, with or without its hyphens, each hyphen in any
1212
- * spelling HYPHEN reads. The pattern reads a percent escape as one unit and keeps it, so the hex digits of an escaped
1213
- * separator before the id (%2F, %252F, %3D) never start a match and leave the id's last digits showing.
1214
- */
1215
- const UUID_OR_ESCAPE = new RegExp(`%(?:25)*[0-9a-f]{2}|[0-9a-f]{8}${HYPHEN}?[0-9a-f]{4}${HYPHEN}?[0-9a-f]{4}${HYPHEN}?[0-9a-f]{4}${HYPHEN}?[0-9a-f]{12}`, 'gi');
1216
- function maskUuids(path, label) {
1217
- return path.replace(UUID_OR_ESCAPE, (token) => (token.startsWith('%') ? token : label));
1218
- }
1306
+ // SLASH, HYPHEN and maskUuids, which every mask below reads, live in url-mask-parts.ts with the UrlMask shape.
1219
1307
  const CREDITGUARD_HOST = /(?:^|\.)creditguard\.co\.il\.?$/i;
1220
1308
  const CREDITGUARD_TXN = new RegExp(`(${SLASH}txn${SLASH}+)[^/]+`, 'gi');
1221
1309
  function maskCreditguardTxId(path) {
@@ -1384,6 +1472,32 @@ const SUMUP_CHECKOUT = new RegExp(`(${SLASH}checkouts${SLASH}+)[^/]*`, 'gi');
1384
1472
  function maskSumupCheckout(path) {
1385
1473
  return maskUuids(path.replace(SUMUP_CHECKOUT, '$1{checkout}'), '{checkout}');
1386
1474
  }
1475
+ /**
1476
+ * FastSpring's checkout posts its card payment to <store>.onfastspring.com[/popup-<name>]/session/<id>/payment
1477
+ * (payment-core fastspring.js), and the session id in that path is a credential: the session's own document,
1478
+ * which anyone holding the id can read with no cookie, carries the order and the session's token. On every
1479
+ * host under onfastspring.com the segment after /session/, in any case and in any spelling of the slashes around
1480
+ * /session/ that SLASH reads, reads {session}.
1481
+ */
1482
+ const FASTSPRING_HOST = /(?:^|\.)onfastspring\.com\.?$/i;
1483
+ const FASTSPRING_SESSION = new RegExp(`(${SLASH}session${SLASH}+)[^/]*`, 'gi');
1484
+ function maskFastspringSession(path) {
1485
+ return path.replace(FASTSPRING_SESSION, '$1{session}');
1486
+ }
1487
+ /**
1488
+ * PayU Latam's WebCheckout sends the card to /ppp-web-gateway-payu/checkout/submit/<country>/credit_card/<key>
1489
+ * on its own host (payment-core payu-latam.js), and the key, 32 hex characters, is the checkout session's
1490
+ * only credential: the page's card-free routes answer it with no cookie, the order, the payer and the
1491
+ * merchant among them. Every route of the page carries the same key, so on every host under
1492
+ * payulatam.com any run of 32 or more hex characters, in any case or after an escaped slash, reads {key}. A hex
1493
+ * character escaped once or more (%66 or %2566 for f, in either case) counts as that character, since PayU's server
1494
+ * reads the same key, so an escape inside the key cannot split it into two runs too short to mask.
1495
+ */
1496
+ const PAYU_LATAM_HOST = /(?:^|\.)payulatam\.com\.?$/i;
1497
+ const PAYU_LATAM_KEY = /(?:[0-9a-f]|%(?:25)*(?:3[0-9]|4[1-6]|6[1-6])){32,}/gi;
1498
+ function maskPayuLatamKey(path) {
1499
+ return path.replace(PAYU_LATAM_KEY, '{key}');
1500
+ }
1387
1501
  /**
1388
1502
  * The processors whose card request carries its own credential in the URL path, with the hosts
1389
1503
  * that send it, the text that names those hosts anywhere else (a string that is not a URL, or a
@@ -1531,10 +1645,7 @@ const QUICKPAY_SESSION = /(\/|%2f)[0-9a-f]{32,}/gi;
1531
1645
  function maskQuickpaySession(path) {
1532
1646
  return path.replace(QUICKPAY_SESSION, '$1{session}');
1533
1647
  }
1534
- /**
1535
- * `mask` reads a text that names the processor's host; `pathMask`, where a processor has one, reads the path of a
1536
- * URL already parsed on that host, which `mask` reads otherwise.
1537
- */
1648
+ /** Today's masks here; each later processor's in its own src/url-masks/<psp>.ts (URL_MASKS). */
1538
1649
  const PATH_CREDENTIALS = [
1539
1650
  { host: PATH_SECRET_HOST, named: /(?:wspay\.biz|monri\.com)/i, mask: maskPathSecret },
1540
1651
  { host: CREDITGUARD_HOST, named: /creditguard\.co\.il/i, mask: maskCreditguardTxId },
@@ -1558,6 +1669,9 @@ const PATH_CREDENTIALS = [
1558
1669
  { host: PAYMENT_HIGHWAY_HOST, named: /(?:paymenthighway\.io|sph-test-solinor\.com)/i, mask: maskPaymentHighwaySession },
1559
1670
  { host: TWOC2P_PAGE_HOST, named: /pgw-ui\.2c2p\.com/i, mask: maskTwoC2pToken, pathMask: maskTwoC2pPath },
1560
1671
  { host: QUICKPAY_HOST, named: /quickpay\.net/i, mask: maskQuickpaySession },
1672
+ { host: FASTSPRING_HOST, named: /onfastspring\.com/i, mask: maskFastspringSession },
1673
+ { host: PAYU_LATAM_HOST, named: /payulatam\.com/i, mask: maskPayuLatamKey },
1674
+ ...URL_MASKS,
1561
1675
  ];
1562
1676
  /**
1563
1677
  * A URL as it may appear in an error message: origin + path only. A paused
@@ -1595,6 +1709,11 @@ const PATH_CREDENTIALS = [
1595
1709
  * https://pgw-ui.2c2p.com/payment/4.1/{token}.
1596
1710
  * On QuickPay's host the window's session reads `{session}`:
1597
1711
  * https://payment.quickpay.net/{session}/3ds_auth.
1712
+ * On FastSpring's store hosts the session id reads `{session}`:
1713
+ * https://daisydisk.onfastspring.com/session/{session}/payment.
1714
+ * On PayU Latam's hosts the checkout session's key reads `{key}`:
1715
+ * https://checkout.payulatam.com/ppp-web-gateway-payu/checkout/submit/co/credit_card/{key}.
1716
+ * Each mask in src/url-masks/ says what it reads in its own file.
1598
1717
  * A string that is not a URL keeps the part before any `?` or `#`, with the
1599
1718
  * same segments masked for every processor whose host it names.
1600
1719
  * A URL is read by its host only when the parser finds one. Text before a URL
@@ -1703,6 +1822,18 @@ const PADDLE_VGS_PREPARATION_REFUSALS = [
1703
1822
  * `unsupported_checkout`, which PREPARATION_REFUSALS names for every checkout. Only for a Wix checkout.
1704
1823
  */
1705
1824
  const WIX_PAYMENTS_PREPARATION_REFUSALS = ['processor_unavailable', 'live_host_on_sandbox_row', 'merchant_profile_mismatch'];
1825
+ /**
1826
+ * The API's refusals of a Paygent preparation, before any preparation exists, that prepareCheckout passes on
1827
+ * by name: Paygent is off for the company (`processor_unavailable`), the environment is not the client's test
1828
+ * or live mode, a live checkout's currency is not the one the reviewed merchant charges in
1829
+ * (`unsupported_currency`), and the price is zero (`amount_required`): Paygent's token request names no
1830
+ * amount, so the price the agent sends is the one the cardholder approves. A page and name no reviewed pin
1831
+ * lists answers `unsupported_checkout`, which PREPARATION_REFUSALS names for every checkout. The API holds the
1832
+ * pins, not this build, so a merchant a later release reviews needs no new SDK. Only for a Paygent checkout.
1833
+ */
1834
+ const PAYGENT_PREPARATION_REFUSALS = [
1835
+ 'processor_unavailable', 'sandbox_host_on_live_row', 'live_host_on_sandbox_row', 'unsupported_currency', 'amount_required',
1836
+ ];
1706
1837
  /**
1707
1838
  * The API's refusals of a HiPay preparation, before any preparation exists, that prepareCheckout surfaces by name:
1708
1839
  * the company is not turned on for HiPay (`processor_unavailable`), the environment is not the caller's key mode's
@@ -2293,6 +2424,8 @@ export class VaultClient {
2293
2424
  throw fail(secureAcceptanceRefusal);
2294
2425
  const secureAcceptanceProfileId = secureAcceptance ? secureAcceptanceCheckoutProfile({ environment: input.environment,
2295
2426
  profileId: input.secureAcceptanceProfileId, merchantOrigin: input.merchantOrigin }) : null;
2427
+ // A live Paygent checkout's merchant is the API's to judge against the reviewed pins it holds, before anyone is
2428
+ // asked to approve (PAYGENT_PREPARATION_REFUSALS); test mode needs no pin.
2296
2429
  const timeoutMs = input.timeoutMs ?? 15 * 60_000;
2297
2430
  if (!Number.isInteger(timeoutMs) || timeoutMs <= 0 || timeoutMs > 2_147_483_647)
2298
2431
  throw fail('timeout_invalid');
@@ -2412,6 +2545,7 @@ export class VaultClient {
2412
2545
  || (input.psp === EPROTECT_PSP && EPROTECT_PREPARATION_REFUSALS.includes(error.code))
2413
2546
  || (input.psp === PADDLE_VGS_PSP && PADDLE_VGS_PREPARATION_REFUSALS.includes(error.code))
2414
2547
  || (input.psp === WIX_PAYMENTS_PSP && WIX_PAYMENTS_PREPARATION_REFUSALS.includes(error.code))
2548
+ || (input.psp === PAYGENT_PSP && PAYGENT_PREPARATION_REFUSALS.includes(error.code))
2415
2549
  || (input.psp === HIPAY_PSP && HIPAY_PREPARATION_REFUSALS.includes(error.code))
2416
2550
  || (!!flexContext && CYBERSOURCE_FLEX_PREPARATION_REFUSALS.includes(error.code))))
2417
2551
  || (input.psp === 'fiserv' && error.status === 502 && error.code === 'cse_key_unavailable')))
@@ -2667,14 +2801,15 @@ export class VaultClient {
2667
2801
  : `checkout authorization is POST-only; got ${method} for ${redactUrl(input.request.url)}. ` +
2668
2802
  'A non-POST tokenizer needs recognizer support before it can be intercepted.');
2669
2803
  }
2670
- // A GET card request is its URL alone. This build completes two kinds: Worldpay
2671
- // eProtect's registration exactly as payment-core admits it, with no body, and a
2672
- // hosted page's entry link exactly as its rule admits it (hosted-entry.ts), with no
2673
- // body, which the cardholder's device takes over.
2804
+ // A GET card request is its URL alone. This build completes three kinds: Worldpay
2805
+ // eProtect's registration and SB Payment Service's token request, each exactly as
2806
+ // payment-core admits it, with no body, and a hosted page's entry link exactly as its
2807
+ // rule admits it (hosted-entry.ts), with no body, which the cardholder's device takes over.
2674
2808
  const eprotect = rec?.psp === EPROTECT_PSP;
2675
2809
  const hostedGetEntry = mode === 'hosted_form' && !!rec && isHostedGetEntry(rec.psp, input.request.url)
2676
2810
  && (input.request.body === undefined || input.request.body === null || input.request.body === '');
2677
- if (cardMethod === 'GET' && !(eprotect && isEprotectRegistration(input.request)) && !hostedGetEntry) {
2811
+ const sbps = rec?.psp === SBPS_PSP;
2812
+ if (cardMethod === 'GET' && !(eprotect && isEprotectRegistration(input.request)) && !hostedGetEntry && !(sbps && isSbpsTokenGet(input.request))) {
2678
2813
  throw new Error(`this ${rec?.psp} GET is not a card registration this version of @agent-cards/checkout can complete: ${redactUrl(input.request.url)}.`);
2679
2814
  }
2680
2815
  // A CyberSource Secure Acceptance token create (always prepared: its recognizer requires it). The
@@ -2709,17 +2844,17 @@ export class VaultClient {
2709
2844
  // the total GoPay's payment already holds, so a zero pair would show the cardholder no price at all: it is refused
2710
2845
  // like a missing one.
2711
2846
  // 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.
2847
+ // total the merchant's server bound to it, and a Payrexx page charges its own
2848
+ // total after the frame's token.
2713
2849
  // A Moneris token is charged later for whatever the merchant's server decides, and a Payfast pay, a
2714
2850
  // PayMongo attach or a MakeCommerce card post charges the total its payment, checkout session or
2715
2851
  // transaction already holds, so a zero pair would show the cardholder no price at all: it is refused
2716
2852
  // like a missing one. So is a TokenEx iframe token, which the merchant's server charges later for its own order.
2717
- if (hasAmount && rec && rec.psp === PRZELEWY24_PSP && Number(input.amount) <= 0)
2718
- throw new AmountRequiredError(rec.psp);
2719
- if (hasAmount && rec && (rec.psp === MONERIS_PSP || rec.psp === PAYFAST_PSP || rec.psp === PAYMONGO_PSP || rec.psp === HIPAY_PSP || rec.psp === TWOC2P_PSP
2720
- || rec.psp === REVOLUT_PSP || rec.psp === PAY_NL_PSP || rec.psp === PAYPAL_PSP
2721
- || 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)
2853
+ // A Clover token is charged later for whatever the merchant's server decides, as a Moneris one is.
2854
+ // A Toast payment method is charged by the page's own confirm for whatever its intent says, the same way.
2855
+ // An Omise token is charged later for whatever the merchant's server decides, as a Clover one is.
2856
+ // An SB Payment Service token is charged later the same way.
2857
+ if (hasAmount && rec && ABOVE_ZERO_PROCESSORS.includes(rec.psp) && Number(input.amount) <= 0)
2723
2858
  throw new AmountRequiredError(rec.psp);
2724
2859
  // Nor is the agent's pair tied to the page by anything but the total the
2725
2860
  // page shows: without it the cardholder could approve one figure while the
@@ -3055,6 +3190,10 @@ export class VaultClient {
3055
3190
  // source.encryptionData and removes nothing (fiserv.ts); any other shape is not one to act on.
3056
3191
  if (rec.psp === 'fiserv' && !isFiservSubstitution(sub))
3057
3192
  throw new PaymentOutcomeUnknownError(authorizationId, 'cse_substitutions_malformed');
3193
+ // A Toast approval carries exactly one sealed card, cardData at `card`, and removes nothing
3194
+ // (payment-core toast-seal.js substituteToastCardData checks the rest at the swap).
3195
+ if (rec.psp === TOAST_PSP && !isToastSubstitution(sub))
3196
+ throw new PaymentOutcomeUnknownError(authorizationId, 'cse_substitutions_malformed');
3058
3197
  // `remove`: sibling keys the API says to drop with the swap (Adyen's
3059
3198
  // `brand`, stamped by adyen-web from the agent's dummy digits). Absent
3060
3199
  // on an older API; anything but a list of names is refused, since a
@@ -3153,6 +3292,18 @@ export class VaultClient {
3153
3292
  response.headers = answer.headers;
3154
3293
  response.body = answer.body;
3155
3294
  }
3295
+ if (sbps) {
3296
+ // SB Payment Service: the paused <script> is answered the way SBPS answers it,
3297
+ // `<callback>({"tokenRes": ...})` under a script type, with the callback the paused URL named
3298
+ // and only a token payment-core's judge accepts for that URL. Anything else is not an answer to
3299
+ // hand the page, and the device may already have sent the card to SBPS.
3300
+ const answer = sbpsScriptAnswer(input.request.url, response);
3301
+ if (!answer)
3302
+ throw new PaymentOutcomeUnknownError(authorizationId, 'sbps_token_unconfirmed');
3303
+ response.status = answer.status;
3304
+ response.headers = answer.headers;
3305
+ response.body = answer.body;
3306
+ }
3156
3307
  if (secureAcceptance) {
3157
3308
  // CyberSource Secure Acceptance: the paused frame navigation is answered with CyberSource's own
3158
3309
  // reply page, byte for byte, with status 200 and its one content type, and only a page
@@ -3225,6 +3376,18 @@ export class VaultClient {
3225
3376
  response.headers = answer.headers;
3226
3377
  response.body = answer.body;
3227
3378
  }
3379
+ if (rec?.psp === PAYGENT_PSP) {
3380
+ // Paygent's answer enters the page only as payment-core rebuilds an approved token from its members
3381
+ // (paygent.js paygentAnswerOutcome: HTTP 200, result 0000 and a token in shape), under a JSON content
3382
+ // type. Anything else, a refusal Paygent answers inside HTTP 200 included, is never handed over as an
3383
+ // approval; the card may have left the device, so the outcome is unknown rather than declined.
3384
+ const judged = paygentAnswerOutcome(response.status, response.body, null);
3385
+ if (judged.outcome !== 'approved' || typeof judged.body !== 'string')
3386
+ throw new PaymentOutcomeUnknownError(authorizationId, 'paygent_token_unconfirmed');
3387
+ response.status = 200;
3388
+ response.headers = { 'content-type': 'application/json' };
3389
+ response.body = judged.body;
3390
+ }
3228
3391
  if (rec?.psp === PRIMER_PSP) {
3229
3392
  // Primer's answer enters the page only as one single-use card token rebuilt from
3230
3393
  // payment-core's allow-list, under a JSON content type (primer.ts primerTokenAnswer).
@@ -3279,8 +3442,9 @@ export class VaultClient {
3279
3442
  chargedAmount: typeof s.charged_amount === 'number' ? s.charged_amount : null,
3280
3443
  chargedCurrency: typeof s.charged_currency === 'string' ? s.charged_currency : null,
3281
3444
  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))
3445
+ // An EBANX token approval, or a Payrexx card request whose answer started 3-D Secure, that covers the sends
3446
+ // after it names until when (ebanx.ts EbanxFollowUps, payrexx.ts PayrexxFollowUps).
3447
+ ...((rec?.psp === 'ebanx' || rec?.psp === PAYREXX_PSP) && typeof s.follow_ups?.until === 'string' && Number.isFinite(Date.parse(s.follow_ups.until))
3284
3448
  ? { followUpsUntil: s.follow_ups.until } : {}),
3285
3449
  ...amountAuthority,
3286
3450
  ...execution,
@@ -3493,6 +3657,20 @@ export class VaultClient {
3493
3657
  * an answer that carried the card, or `options.until` (the approval's window, plus the API's own clocks) passed.
3494
3658
  */
3495
3659
  async sendEbanxFollowUp(authorizationId, request, options = {}) {
3660
+ return this.sendFollowUp(authorizationId, request, options, (reason, cardSent) => new EbanxFollowUpError(reason, cardSent, authorizationId));
3661
+ }
3662
+ /**
3663
+ * Send one of Payrexx's 3-D Secure sends an approved Payrexx card request covers (payrexx.ts says which: the card
3664
+ * frame's authentication after the ACS method and its post-authentication after the bank's challenge, bound to that
3665
+ * approval) from the cardholder's device, and resolve with Payrexx's answer for the paused request, exactly as
3666
+ * sendEbanxFollowUp does for EBANX: the same create, reads and clocks, and no prompt. Rejects with a
3667
+ * PayrexxFollowUpError naming why there is no answer and whether the card may have reached Payrexx.
3668
+ */
3669
+ async sendPayrexxFollowUp(authorizationId, request, options = {}) {
3670
+ return this.sendFollowUp(authorizationId, request, options, (reason, cardSent) => new PayrexxFollowUpError(reason, cardSent, authorizationId));
3671
+ }
3672
+ /** The send behind sendEbanxFollowUp and sendPayrexxFollowUp; `fail` builds the processor's error for a send with no answer. */
3673
+ async sendFollowUp(authorizationId, request, options, fail) {
3496
3674
  if (!/^cauth_[A-Za-z0-9_-]{1,128}$/.test(authorizationId))
3497
3675
  throw new Error('Invalid authorization ID.');
3498
3676
  const base = `/v2/checkout/authorizations/${authorizationId}/follow_ups`;
@@ -3506,16 +3684,16 @@ export class VaultClient {
3506
3684
  catch (error) {
3507
3685
  // A refusal (a 4xx answer) stored no send, so nothing left. Any other failure (a 5xx, a timeout, a dropped
3508
3686
  // 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.
3687
+ // the card may reach the processor, and for an EBANX confirm that is a payment whose outcome is unknown.
3510
3688
  const refused = error instanceof CheckoutApiError && error.status >= 400 && error.status < 500;
3511
3689
  const reason = error instanceof CheckoutApiError
3512
3690
  ? (typeof error.details.reason === 'string' && /^[a-z_]{1,64}$/.test(error.details.reason) ? error.details.reason : error.code ?? `http_${error.status}`)
3513
3691
  : 'follow_up_unavailable';
3514
- throw new EbanxFollowUpError(reason, !refused, authorizationId);
3692
+ throw fail(reason, !refused);
3515
3693
  }
3516
3694
  const id = created?.id;
3517
3695
  if (typeof id !== 'string' || !/^cfu_[0-9a-f]{24}$/.test(id) || created.authorization !== authorizationId) {
3518
- throw new EbanxFollowUpError('follow_up_unrecognized', true, authorizationId);
3696
+ throw fail('follow_up_unrecognized', true);
3519
3697
  }
3520
3698
  // Reads the API refused in a row as too many (429): each waits twice as long as the last before the next read.
3521
3699
  let limited = 0;
@@ -3527,14 +3705,14 @@ export class VaultClient {
3527
3705
  }
3528
3706
  catch (error) {
3529
3707
  if (signal.aborted)
3530
- throw new EbanxFollowUpError('answer_late', true, authorizationId);
3708
+ throw fail('answer_late', true);
3531
3709
  if (error instanceof CheckoutApiError && error.status === 429)
3532
3710
  limited += 1;
3533
3711
  }
3534
3712
  if (read?.status === 'answered') {
3535
3713
  const r = read.response;
3536
3714
  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);
3715
+ throw fail('follow_up_unrecognized', true);
3538
3716
  }
3539
3717
  const headers = {};
3540
3718
  for (const [name, value] of Object.entries(r.headers))
@@ -3544,10 +3722,10 @@ export class VaultClient {
3544
3722
  }
3545
3723
  if (read?.status === 'failed' || read?.status === 'expired') {
3546
3724
  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);
3725
+ throw fail(reason, read.card_sent !== false);
3548
3726
  }
3549
3727
  if (signal.aborted || Date.now() >= deadline)
3550
- throw new EbanxFollowUpError('answer_late', true, authorizationId);
3728
+ throw fail('answer_late', true);
3551
3729
  const wait = limited === 0 ? EBANX_FOLLOW_UP_READ_MS : Math.min(EBANX_FOLLOW_UP_BACKOFF_MAX_MS, 1000 * 2 ** (limited - 1));
3552
3730
  await interruptibleSleep(Math.max(1, Math.min(wait, deadline - Date.now())), signal);
3553
3731
  }
@@ -4108,3 +4286,14 @@ function interruptibleSleep(ms, signal) {
4108
4286
  signal?.addEventListener('abort', done, { once: true });
4109
4287
  });
4110
4288
  }
4289
+ /** A served Toast set: exactly { cardData } at `card`, a 256-byte base64 value, with nothing to remove. */
4290
+ function isToastSubstitution(sub) {
4291
+ if (!sub || typeof sub !== 'object')
4292
+ return false;
4293
+ const { at, fields, remove } = sub;
4294
+ if (at !== 'card' || !fields || typeof fields !== 'object' || Array.isArray(fields))
4295
+ return false;
4296
+ const keys = Object.keys(fields);
4297
+ return keys.length === 1 && keys[0] === 'cardData' && isToastCiphertext(fields.cardData)
4298
+ && (remove === undefined || (Array.isArray(remove) && remove.length === 0));
4299
+ }