@agent-cards/sdk 0.23.28 → 0.24.0

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 (97) hide show
  1. package/CHANGELOG.md +58 -0
  2. package/PREFLIGHT.md +7 -1
  3. package/README.md +137 -99
  4. package/dist/aci.d.ts +52 -22
  5. package/dist/aci.js +79 -15
  6. package/dist/adyen-merchant-hosted.generated.d.ts +1 -1
  7. package/dist/adyen-merchant-hosted.generated.js +459 -21
  8. package/dist/agent-amount.generated.js +1 -1
  9. package/dist/agentcard.d.ts +4 -5
  10. package/dist/agentcard.js +4 -5
  11. package/dist/amadeus.d.ts +9 -3
  12. package/dist/amadeus.js +33 -4
  13. package/dist/api-module.d.ts +1 -7
  14. package/dist/api-module.js +3 -5
  15. package/dist/{voice-retry.js → api-retry.js} +1 -1
  16. package/dist/api-types.d.ts +53 -0
  17. package/dist/{voice-types.js → api-types.js} +7 -2
  18. package/dist/approvals.d.ts +166 -0
  19. package/dist/approvals.js +213 -0
  20. package/dist/buy.d.ts +20 -101
  21. package/dist/buy.js +19 -74
  22. package/dist/capabilities.generated.js +1 -1
  23. package/dist/card-fields.generated.js +1398 -106
  24. package/dist/cdp.d.ts +3 -13
  25. package/dist/cdp.js +231 -175
  26. package/dist/client.d.ts +104 -22
  27. package/dist/client.js +215 -40
  28. package/dist/creditguard.generated.d.ts +4 -4
  29. package/dist/datatrans.generated.js +22 -1
  30. package/dist/elavon-converge.generated.js +5 -3
  31. package/dist/hold.d.ts +8 -0
  32. package/dist/hold.js +43 -2
  33. package/dist/hosted-entry.js +2 -0
  34. package/dist/index.d.ts +9 -15
  35. package/dist/index.js +6 -10
  36. package/dist/lifecycle.d.ts +9 -61
  37. package/dist/lifecycle.js +24 -10
  38. package/dist/merchant-card-endpoints.generated.d.ts +6 -1
  39. package/dist/merchant-card-endpoints.generated.js +344 -19
  40. package/dist/merchant-hosted.d.ts +8 -1
  41. package/dist/nike-order.d.ts +51 -0
  42. package/dist/nike-order.js +81 -0
  43. package/dist/oceanpayment.d.ts +38 -0
  44. package/dist/oceanpayment.js +70 -0
  45. package/dist/paddle-tokenex.generated.js +105 -10
  46. package/dist/payplug.d.ts +41 -0
  47. package/dist/payplug.js +71 -0
  48. package/dist/payrails.generated.js +27 -7
  49. package/dist/pre-claim.d.ts +9 -0
  50. package/dist/pre-claim.js +86 -7
  51. package/dist/preflight-capabilities.generated.d.ts +0 -4
  52. package/dist/preflight-capabilities.generated.js +0 -5
  53. package/dist/preflight-catalog.json +46 -5
  54. package/dist/preflight-playwright.d.ts +2 -2
  55. package/dist/preflight-playwright.js +63 -14
  56. package/dist/preflight-schemas.json +4 -0
  57. package/dist/preflight.generated.d.ts +0 -4
  58. package/dist/preflight.generated.js +24 -1
  59. package/dist/prepared-processor.d.ts +18 -5
  60. package/dist/prepared-processor.js +48 -8
  61. package/dist/primer.generated.js +1418 -120
  62. package/dist/processout.generated.d.ts +61 -0
  63. package/dist/processout.generated.js +577 -0
  64. package/dist/requests.js +1 -1
  65. package/dist/shiji-sts.generated.js +105 -10
  66. package/dist/stripe-checkout.generated.js +1405 -234
  67. package/dist/substitutions.generated.js +460 -26
  68. package/dist/tap-payments.generated.js +105 -10
  69. package/dist/twoc2p.generated.js +105 -10
  70. package/dist/url-masks/bluesnap.d.ts +2 -0
  71. package/dist/url-masks/bluesnap.js +15 -0
  72. package/dist/url-masks/oceanpayment.d.ts +2 -0
  73. package/dist/url-masks/oceanpayment.js +13 -0
  74. package/dist/url-masks/payplug.d.ts +2 -0
  75. package/dist/url-masks/payplug.js +13 -0
  76. package/dist/url-masks/vtex_payments.d.ts +2 -0
  77. package/dist/url-masks/vtex_payments.js +12 -0
  78. package/dist/url-masks.generated.d.ts +1 -1
  79. package/dist/url-masks.generated.js +6 -2
  80. package/dist/verifone.generated.js +42 -2
  81. package/dist/vtex-payments.generated.d.ts +127 -0
  82. package/dist/vtex-payments.generated.js +544 -0
  83. package/dist/webhooks.d.ts +2 -2
  84. package/dist/webhooks.js +2 -2
  85. package/examples/preflight/kernel-native/inventory.json +1 -1
  86. package/package.json +6 -8
  87. package/dist/languages.d.ts +0 -23
  88. package/dist/languages.js +0 -76
  89. package/dist/voice-pkce.d.ts +0 -49
  90. package/dist/voice-pkce.js +0 -147
  91. package/dist/voice-stream.generated.d.ts +0 -118
  92. package/dist/voice-stream.generated.js +0 -170
  93. package/dist/voice-types.d.ts +0 -735
  94. package/dist/voice.d.ts +0 -373
  95. package/dist/voice.js +0 -815
  96. package/readme.test.mjs +0 -53
  97. /package/dist/{voice-retry.d.ts → api-retry.d.ts} +0 -0
package/dist/client.js CHANGED
@@ -22,13 +22,14 @@ import { TAP_PAYMENTS_PSP, isTapPaymentsServedSubstitution } from './tap-payment
22
22
  import { WIX_PAYMENTS_PSP } from './wix-payments.generated.js';
23
23
  import { HIPAY_PSP } from './hipay.generated.js';
24
24
  import { OPENPAY_PSP, openpayAnswerOutcome } from './openpay.generated.js';
25
+ import { VTEX_PAYMENTS_PSP } from './vtex-payments.generated.js';
26
+ import { PROCESSOUT_PSP, processoutAnswerOutcome } from './processout.generated.js';
25
27
  import { TOAST_PSP, isToastCiphertext } from './toast.generated.js';
26
28
  import { classifyStripeCheckoutRequest, encodeStripeCheckoutContext, hasStripeCheckoutMarker, parseStripeCheckoutContext, parseStripeCheckoutResponse, STRIPE_CHECKOUT_CONTEXT_HEADER, } from './stripe-checkout.generated.js';
27
- import { VoiceModule } from './voice.js';
28
29
  import { BuyModule } from './buy.js';
29
- import { Languages } from './languages.js';
30
+ import { ApprovalsModule } from './approvals.js';
30
31
  import { RequestRegistry } from './requests.js';
31
- import { AgentcardError } from './voice-types.js';
32
+ import { AgentcardError } from './api-types.js';
32
33
  import { HYPHEN, SLASH, maskUuids } from './url-mask-parts.js';
33
34
  import { URL_MASKS } from './url-masks.generated.js';
34
35
  import { DECLARED_CAPABILITIES } from './capabilities.generated.js';
@@ -114,6 +115,15 @@ export const SUPPORTED_REGISTRY_FEATURES = ['card_fields', 'checkout_sessions'];
114
115
  * recognizer is never in BUILTIN_REGISTRY, so a build reaches it only through a
115
116
  * sync that declares its capability, and whether an org may pay through it
116
117
  * stays the API's decision.
118
+ * `bluesnap_hosted_payment_fields`: BlueSnap's Hosted Payment Fields, whose card
119
+ * number, expiry and code frames each PUT their own field as JSON to the
120
+ * merchant's payment-fields token. This build pauses the card number PUT, holds
121
+ * the expiry and code PUTs and answers them with its answer (aci.ts, the JSON
122
+ * split card), and the cardholder's device sends the whole card in that one PUT
123
+ * (payment-core processors/bluesnap.js). The request names no amount, so the
124
+ * agent's pair, above zero, is the one the cardholder approves. Served on the
125
+ * same terms: only to a build that lists it, for a company Agentcard turned
126
+ * BlueSnap on for.
117
127
  * `creditguard_pps_process`: CreditGuard's hosted payment page (Hyp PPS), whose
118
128
  * card submit this build pays only under a prepared checkout that names the
119
129
  * framed page (prepare({ psp: 'creditguard', environment, paymentPageUrl })). The
@@ -452,6 +462,18 @@ export const SUPPORTED_REGISTRY_FEATURES = ['card_fields', 'checkout_sessions'];
452
462
  * page the create names. Served on the same terms: only to a build that lists it, for a company
453
463
  * Agentcard turned Pin Payments on for.
454
464
  *
465
+ * `paymentwall_card_token`: the card request Paymentwall's card form (Brick) sends, from its number
466
+ * frame on pwgateway.com inside Paymentwall's hosted widget or from a merchant's own page: a form POST
467
+ * to https://pwgateway.com/api/token (https://api.paymentwall.com/api/brick/token for a test key)
468
+ * under the merchant's public key. This build pauses only the card form's own body at those exact
469
+ * URLs (payment-core processors/paymentwall.js); a stored card's code, a wallet's token, a brand from
470
+ * the page's select or any other body there fails in the browser before anything is created. The
471
+ * cardholder's device sends the request itself with the real card, and the page gets the token answer
472
+ * payment-core's judge rebuilt. The request names no amount, so the agent's pair, above zero, is the
473
+ * one the cardholder approves, and the API pays only a Paymentwall project payment-core pins for the
474
+ * checkout page the create names. Served on the same terms: only to a build that lists it, for a
475
+ * company Agentcard turned Paymentwall on for.
476
+ *
455
477
  * `sbps_token_jsonp`: SB Payment Service's card token request, the JSONP <script>
456
478
  * GET its token script sends to /token/generateToken on token.sps-system.com. This
457
479
  * build pauses it only as the browser writes it, aborts any other request on that
@@ -468,6 +490,28 @@ export const SUPPORTED_REGISTRY_FEATURES = ['card_fields', 'checkout_sessions'];
468
490
  * Served on the same terms, and the API holds the lane to Agentcard's own company
469
491
  * until a reviewed change binds the client key to the merchant.
470
492
  *
493
+ * `iyzico_checkout_form`: the card payment iyzico's Checkout Form posts as one JSON request to
494
+ * api.iyzipay.com/payment/iyzipos/checkoutform/auth/ecom, or initialize3ds/ecom for 3-D Secure
495
+ * (sandbox-api.iyzipay.com in test), under the form's X-IYZI-TOKEN. This build pauses only the
496
+ * form's own payment with a new card at those exact URLs (payment-core processors/iyzico.js); a saved
497
+ * card, a card kept on file, installments and a currency conversion fail in the browser before
498
+ * anything is created. The cardholder's device sends the payment itself, and the page gets iyzico's
499
+ * answer as payment-core's judge rebuilt it: the payment done, or the bank's 3-D Secure page, which
500
+ * the form then runs in the agent's browser. The payment names its price but not its currency, so
501
+ * authorize() needs the agent's pair, and the API holds its amount to the price. Served on the same
502
+ * terms, and the API holds the lane to Agentcard's own company.
503
+ *
504
+ * `usaepay_payment_key`: the card request USAePay's Pay.js card frame sends as one JSON request to
505
+ * www.usaepay.com/api/v2/pub/payment_keys (secure.usaepay.com alike; sandbox.usaepay.com in test), under
506
+ * the merchant's public key in its Authorization. This build pauses only the frame's own card body at
507
+ * that exact URL (payment-core processors/usaepay.js); a Google Pay or Apple Pay token and a bank
508
+ * account on the same URL fail in the browser before anything is created. The cardholder's device sends
509
+ * the card through Agentcard's checkout relay, since USAePay answers no browser origin, and the frame
510
+ * gets USAePay's single-use payment key as payment-core's judge rebuilt it; the merchant's server
511
+ * charges that key afterwards. The request names no amount, so authorize() needs the agent's pair, and
512
+ * the API pays a live key only to a merchant payment-core pins for the checkout page. Served on the same
513
+ * terms, and the API holds the lane to Agentcard's own company.
514
+ *
471
515
  * `tap_card_sdk_token`: the token request Tap Payments' card frame sends to sdk.tap.company, carrying
472
516
  * the card only as one ciphertext the frame writes under the merchant's own RSA key. This build pauses
473
517
  * it and writes over the placeholder's ciphertext the one the cardholder's device wrote under the key
@@ -495,6 +539,16 @@ export const SUPPORTED_REGISTRY_FEATURES = ['card_fields', 'checkout_sessions'];
495
539
  * (agent-amount-processors.json), is the one the cardholder approves. Served on the same terms: only
496
540
  * to a build that lists it, for a company Agentcard turned Openpay on for.
497
541
  *
542
+ * `processout_card_token`: the card request ProcessOut.js's card frame sends from js.processout.com to
543
+ * api.processout.com/cards under the store's project id, only through a prepared checkout: the frame
544
+ * gives the page 20 seconds from the shop's continue press, so the cardholder approves before it. The
545
+ * card URL names the project in its query, so the claim binds the request by its project's environment
546
+ * and its shape, never by the prepared URL. The cardholder's device sends the card to ProcessOut itself
547
+ * and the frame gets back ProcessOut's token alone, rebuilt by payment-core's judge. The request names
548
+ * no amount, so the agent's pair, above zero (agent-amount-processors.json), is the one the cardholder
549
+ * approves. Served on the same terms: only to a build that lists it, for a company Agentcard turned
550
+ * ProcessOut on for.
551
+ *
498
552
  * `aps_custom_merchant_page`: Amazon Payment Services' custom merchant page, whose own card form
499
553
  * posts top-level to /FortAPI/paymentPage on checkout.payfort.com (sbcheckout.payfort.com on APS's
500
554
  * sandbox) with service_command=TOKENIZATION. This build pauses that navigation; the cardholder's
@@ -515,6 +569,14 @@ export const SUPPORTED_REGISTRY_FEATURES = ['card_fields', 'checkout_sessions'];
515
569
  * the cardholder approves, and the API holds its amount to the body's. Served on the same terms: only to a build
516
570
  * that lists it, for a company Agentcard turned Nexi Checkout on for.
517
571
  *
572
+ * `oceanpayment_hosted_payment_page`: the card post of Oceanpayment's hosted payment page (secure.oceanpayment.com, where
573
+ * Oceanpayment's Shopify redirect app sends the buyer), a native form post to /gateway/sendPay;jsessionid=<session> with
574
+ * the card in clear (test-secure.oceanpayment.com for a test row). This build pauses only that post, exactly as the page's
575
+ * form action writes it, and aborts every other post to Oceanpayment's two hosts (oceanpayment.ts), so the agent's
576
+ * placeholder never reaches Oceanpayment; the cardholder's device writes the real card into the paused post and sends it
577
+ * through the checkout relay. The post names no amount, so the agent's pair, above zero, is the one the cardholder
578
+ * approves. Served on the same terms: only to a build that lists it, for a company Agentcard turned Oceanpayment on for.
579
+ *
518
580
  * `barion_hosted_payment_page`: Barion's Smart Gateway, whose card-free link
519
581
  * (secure.barion.com/Pay?Id=<payment id>, a plain GET the merchant's redirect or script sends
520
582
  * the buyer to) this build pauses for the cardholder's device by its exact link (hosted-entry.ts),
@@ -522,6 +584,14 @@ export const SUPPORTED_REGISTRY_FEATURES = ['card_fields', 'checkout_sessions'];
522
584
  * send (pre-claim.ts, barion.ts). Served on the same terms as Przelewy24's: only to a build that
523
585
  * lists it, for a company Agentcard turned Barion on for.
524
586
  *
587
+ * `payplug_hosted_payment_page`: PayPlug's payment page, whose card-free link
588
+ * (secure.payplug.com/pay/<payment id>, or /pay/test/<payment id>, a plain GET the merchant's
589
+ * redirect or script sends the buyer to) this build pauses for the cardholder's device by its exact
590
+ * link (hosted-entry.ts), and whose card post this build never lets the agent's browser send
591
+ * (pre-claim.ts, payplug.ts). The link names no amount, so authorize() asks for the agent's, above
592
+ * zero. Served only to a build that lists it, and the API turns PayPlug on for Agentcard's own
593
+ * company alone.
594
+ *
525
595
  * `paymob_checkout_pay`: the card payment Paymob's own checkout sends, from Unified Checkout
526
596
  * (<country>.checkout.paymob.com) or from the Pixel card frame a merchant's page embeds, as JSON to
527
597
  * https://<accept|uae|ksa|oman>.paymob.com/api/acceptance/payments/pay. This build pauses only the
@@ -651,6 +721,8 @@ export const SUPPORTED_CAPABILITIES = DECLARED_CAPABILITIES;
651
721
  const REVOLUT_PSP = 'revolut';
652
722
  /** Nexi Checkout's card payment names the payment's amount in minor units but not its currency, as Revolut's does. */
653
723
  const NEXI_CHECKOUT_PSP = 'nexi_checkout';
724
+ /** iyzico's Checkout Form payment names its price but not its currency, which the form's token holds. */
725
+ const IYZICO_PSP = 'iyzico';
654
726
  const AGENT_AMOUNT_PROCESSORS = AGENT_AMOUNT.processors;
655
727
  /** The ones a zero amount is refused for, like a missing one (authorize()). */
656
728
  const ABOVE_ZERO_PROCESSORS = AGENT_AMOUNT.aboveZero;
@@ -937,11 +1009,12 @@ export class AmountRequiredError extends Error {
937
1009
  constructor(psp) {
938
1010
  const processor = PROCESSOR_NAMES[psp] ?? psp;
939
1011
  // A Payrexx card request names the frame's total, but the merchant's page charges its own total after the token.
940
- const lacks = psp === REVOLUT_PSP || psp === NEXI_CHECKOUT_PSP ? 'names its amount but not its currency'
1012
+ const lacks = psp === REVOLUT_PSP || psp === NEXI_CHECKOUT_PSP || psp === IYZICO_PSP ? 'names its amount but not its currency'
941
1013
  : psp === PAYREXX_PSP ? "only mints a token the merchant's page charges later"
942
1014
  : psp === TAP_PAYMENTS_PSP ? 'does not decide the amount charged' : 'names no amount';
943
- // "An Omise payment request", "A Clover payment request": the article follows the name's first letter.
944
- const article = /^[AEIOU]/.test(processor) ? 'An' : 'A';
1015
+ // "An Omise payment request", "A Clover payment request", "An iyzico payment request": the article follows the
1016
+ // name's first letter, in either case, and "A USAePay payment request" its first sound, a consonant's.
1017
+ const article = /^(?:[AEIO]|U(?!SA))/i.test(processor) ? 'An' : 'A';
945
1018
  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.`);
946
1019
  this.psp = psp;
947
1020
  this.name = 'AmountRequiredError';
@@ -1365,11 +1438,11 @@ export class CheckoutAuthError extends Error {
1365
1438
  }
1366
1439
  }
1367
1440
  /**
1368
- * A failure as the voice module reports it: an `AgentcardError` carrying the
1369
- * API's code (`invalid_return_url`, `voice_not_enrolled`) or `oauth_failed`;
1441
+ * A failure as the modules report it: an `AgentcardError` carrying the
1442
+ * API's code or `oauth_failed`;
1370
1443
  * a failure to reach Agentcard at all passes through as it came.
1371
1444
  */
1372
- function voiceError(err) {
1445
+ function moduleError(err) {
1373
1446
  if (err instanceof CheckoutAuthError)
1374
1447
  return new AgentcardError('oauth_failed', `Could not get a token for the client (${err.status}${err.code ? ', ' + err.code : ''}).`, err.status);
1375
1448
  if (err instanceof CheckoutApiError) {
@@ -1951,6 +2024,27 @@ const HIPAY_PREPARATION_REFUSALS = ['processor_unavailable', 'live_host_on_sandb
1951
2024
  */
1952
2025
  const OPENPAY_PREPARATION_REFUSALS = ['processor_unavailable', 'live_host_on_sandbox_row', 'sandbox_host_on_live_row',
1953
2026
  'merchant_profile_mismatch', 'amount_required'];
2027
+ /**
2028
+ * The API's refusals of a VTEX Payments preparation, before any preparation exists, that prepareCheckout passes on by
2029
+ * name: the company is not turned on for VTEX (`processor_unavailable`), the client is in test mode, which VTEX has no
2030
+ * host for (`live_host_on_sandbox_row`), the merchant is not the name the reviewed pin for the checkout's page carries
2031
+ * (`merchant_profile_mismatch`), the currency is not the store's (`unsupported_currency`), and the price is zero
2032
+ * (`amount_required`). A page no reviewed pin lists answers `unsupported_checkout`, which PREPARATION_REFUSALS names for
2033
+ * every checkout. The API holds the pins, so a store a later release reviews needs no new SDK. Only for a VTEX checkout.
2034
+ */
2035
+ const VTEX_PAYMENTS_PREPARATION_REFUSALS = ['processor_unavailable', 'live_host_on_sandbox_row', 'merchant_profile_mismatch',
2036
+ 'unsupported_currency', 'amount_required'];
2037
+ /**
2038
+ * The API's refusals of a ProcessOut preparation, before any preparation exists, that prepareCheckout passes on by
2039
+ * name: the company is not turned on for ProcessOut (`processor_unavailable`), the environment is not the caller's key
2040
+ * mode's (`processout_environment_mismatch`, since a live and a sandbox project share one host), a live checkout's
2041
+ * merchant is not the name the reviewed pin for its origin carries (`merchant_profile_mismatch`), and the price is zero
2042
+ * (`amount_required`), since the card request names no amount. An origin no reviewed pin lists answers
2043
+ * `unsupported_checkout`, which PREPARATION_REFUSALS names for every checkout. The API holds the pins, not this build,
2044
+ * so a store a later release reviews needs no new SDK. Only for a ProcessOut checkout.
2045
+ */
2046
+ const PROCESSOUT_PREPARATION_REFUSALS = ['processor_unavailable', 'processout_environment_mismatch', 'merchant_profile_mismatch',
2047
+ 'amount_required'];
1954
2048
  /**
1955
2049
  * The reason the cardholder's device declines a bound prepared row with, per processor, when its own
1956
2050
  * send boundary passed before the card left it (the Vault's sendWindow, 12 seconds after it last saw
@@ -1959,7 +2053,9 @@ const OPENPAY_PREPARATION_REFUSALS = ['processor_unavailable', 'live_host_on_san
1959
2053
  * closes on (amadeus.ts), which can close later, so authorize() ends the attempt the way that window
1960
2054
  * does, as a CheckoutPreparationError with that reason, and never as the cardholder's "no". A prepared
1961
2055
  * HiPay token create too, and a prepared Openpay token create, whose device sends the card at most 25
1962
- * seconds after it last saw the checkout unbound, inside the adapters' 30.
2056
+ * seconds after it last saw the checkout unbound, inside the adapters' 30. And a prepared VTEX card post,
2057
+ * whose device sends the card at most 6 seconds after it last saw the checkout unbound, inside the adapters' 8, and a
2058
+ * prepared ProcessOut card request, whose device keeps HiPay's 10 seconds, inside the adapters' 12.
1963
2059
  */
1964
2060
  const PREPARED_SEND_WINDOW_DECLINES = Object.freeze({
1965
2061
  amadeus_checkout: 'amadeus_send_window_closed',
@@ -1967,7 +2063,17 @@ const PREPARED_SEND_WINDOW_DECLINES = Object.freeze({
1967
2063
  [WIX_PAYMENTS_PSP]: 'wix_payments_send_window_closed',
1968
2064
  [HIPAY_PSP]: 'hipay_send_window_closed',
1969
2065
  [OPENPAY_PSP]: 'openpay_send_window_closed',
2066
+ [VTEX_PAYMENTS_PSP]: 'vtex_payments_send_window_closed',
2067
+ [PROCESSOUT_PSP]: 'processout_send_window_closed',
1970
2068
  });
2069
+ /**
2070
+ * The reason the API declines a bound VTEX card post with when its send-by passed before the card left the cardholder's
2071
+ * device (backend services/commerce/checkout-vtex-payments.ts): Ollie's checkout gives the post 12 seconds, and the API
2072
+ * counts 8 of them from the pause the create named. A known non-payment, as a closed send window is.
2073
+ */
2074
+ const VTEX_PAYMENTS_POST_TIMED_OUT = 'vtex_payments_post_timed_out';
2075
+ /** A pause the create can count `paused_ms` from: a whole, non-negative epoch millisecond on this machine's clock. */
2076
+ const validPausedAt = (value) => Number.isSafeInteger(value) && value >= 0;
1971
2077
  /**
1972
2078
  * Refusals a CyberSource Flex preparation that tokenizes before Pay answers (4xx) before any
1973
2079
  * preparation exists, surfaced as the CheckoutPreparationError reason: the processor is off for
@@ -2050,22 +2156,22 @@ export class VaultClient {
2050
2156
  * false: a live client.
2051
2157
  */
2052
2158
  get sandbox() { return this.sandboxNamed || this.sandboxLearned === true; }
2053
- /** What every module shares: the token, the HTTP path, the poll settings and the language list. */
2159
+ /** What every module shares: the token, the HTTP path and the poll settings. */
2054
2160
  transport;
2055
- /**
2056
- * Approve purchases by voice inside your own product: connect a person once,
2057
- * create the purchase, ask for the key word, read the result. Same
2058
- * credentials, same token as the checkout calls. Approves on Agentcard's
2059
- * page unless `voice: { approval: 'in_app' }` was named at construction.
2060
- */
2061
- voice;
2062
2161
  /**
2063
2162
  * The Purchase API from the same client: ask for a cart on behalf of a
2064
2163
  * person you connected, confirm it by its hash, and name an approval the
2065
- * person already gave by voice so the API places against it and asks for
2066
- * nothing a second time. Same credentials, same token as the voice calls.
2164
+ * person already gave so the API places against it and asks for
2165
+ * nothing a second time. Same credentials, same token as the checkout calls.
2067
2166
  */
2068
2167
  buy;
2168
+ /**
2169
+ * Purchases your server creates for a person, approved on Agentcard's page:
2170
+ * create one (`request: 'sandbox'` under a sandbox client), send the person
2171
+ * its `approval_url`, then read the decision or wait for it. Same
2172
+ * credentials, same token as the checkout calls.
2173
+ */
2174
+ approvals;
2069
2175
  /**
2070
2176
  * The request builders: the processor requests a purchase pauses on, the
2071
2177
  * shipped ones and yours (`requests.register`, `requests.build`).
@@ -2090,7 +2196,7 @@ export class VaultClient {
2090
2196
  });
2091
2197
  }
2092
2198
  catch (err) {
2093
- throw voiceError(err);
2199
+ throw moduleError(err);
2094
2200
  }
2095
2201
  };
2096
2202
  this.transport = {
@@ -2099,18 +2205,14 @@ export class VaultClient {
2099
2205
  return await this.accessToken();
2100
2206
  }
2101
2207
  catch (err) {
2102
- throw voiceError(err);
2208
+ throw moduleError(err);
2103
2209
  }
2104
2210
  },
2105
2211
  request,
2106
2212
  pollIntervalMs: this.pollIntervalMs,
2107
- pollTimeoutMs: opts.voice?.pollTimeoutMs,
2108
- backoffMs: opts.voice?.backoffMs,
2109
- wait: opts.voice?.wait,
2110
- languages: new Languages(request),
2111
2213
  };
2112
- this.voice = new VoiceModule(this.transport, opts.voice, () => this.sandbox);
2113
2214
  this.buy = new BuyModule(this.transport);
2215
+ this.approvals = new ApprovalsModule(this.transport, () => this.sandbox);
2114
2216
  const declared = (opts.sandboxMerchants ?? []).map(declareSandboxMerchant);
2115
2217
  if (new Set(declared.map((profile) => profile.id)).size !== declared.length) {
2116
2218
  throw new TypeError('sandboxMerchants: declare each profile at most once.');
@@ -2660,6 +2762,8 @@ export class VaultClient {
2660
2762
  || (input.psp === PAYGENT_PSP && PAYGENT_PREPARATION_REFUSALS.includes(error.code))
2661
2763
  || (input.psp === HIPAY_PSP && HIPAY_PREPARATION_REFUSALS.includes(error.code))
2662
2764
  || (input.psp === OPENPAY_PSP && OPENPAY_PREPARATION_REFUSALS.includes(error.code))
2765
+ || (input.psp === VTEX_PAYMENTS_PSP && VTEX_PAYMENTS_PREPARATION_REFUSALS.includes(error.code))
2766
+ || (input.psp === PROCESSOUT_PSP && PROCESSOUT_PREPARATION_REFUSALS.includes(error.code))
2663
2767
  || (!!flexContext && CYBERSOURCE_FLEX_PREPARATION_REFUSALS.includes(error.code))))
2664
2768
  || (input.psp === 'fiserv' && error.status === 502 && error.code === 'cse_key_unavailable')))
2665
2769
  throw fail(error.code);
@@ -2778,6 +2882,11 @@ export class VaultClient {
2778
2882
  if (preparation) {
2779
2883
  if (!this.preparations.has(preparation) || this.usedPreparations.has(preparation))
2780
2884
  throw new CheckoutPreparationError(preparation.id ?? null, 'already_used_or_foreign');
2885
+ // VTEX Payments: the API counts the row's send-by from the pause the create names (`paused_ms`) and refuses a create
2886
+ // that names none, so a caller without `pausedAt` is refused here, before the preparation is spent. The adapters fill it.
2887
+ if (preparation.psp === VTEX_PAYMENTS_PSP && !validPausedAt(input.pausedAt)) {
2888
+ throw new Error('A VTEX Payments card post needs pausedAt: when the card request paused, in epoch milliseconds. Nothing was created.');
2889
+ }
2781
2890
  // Consume locally before any await, including OAuth, and never recycle it.
2782
2891
  this.usedPreparations.add(preparation);
2783
2892
  if (Date.parse(preparation.expiresAt) <= Date.now())
@@ -3065,8 +3174,14 @@ export class VaultClient {
3065
3174
  ...(payrailsPayment ? { holder_reference: sidecar.holder_reference } : {}) } } : {}),
3066
3175
  },
3067
3176
  };
3177
+ // VTEX Payments: how long ago the post paused, on this machine's clock as each create request leaves, after any wait
3178
+ // for a token or a refresh after a 401, so the API counts the row's send-by from Ollie's own 12 seconds on its own
3179
+ // clock, whatever the offset between the two machines.
3180
+ const pausedAt = input.pausedAt;
3181
+ const atSend = rec?.psp === VTEX_PAYMENTS_PSP && validPausedAt(pausedAt)
3182
+ ? () => ({ paused_ms: Math.max(0, Date.now() - pausedAt) }) : undefined;
3068
3183
  try {
3069
- created = await this.createAuthorization(payload, input.currency, AbortSignal.any([operationSignal, AbortSignal.timeout(30_000)]), input.merchantSignal);
3184
+ created = await this.createAuthorization(payload, input.currency, AbortSignal.any([operationSignal, AbortSignal.timeout(30_000)]), input.merchantSignal, atSend);
3070
3185
  }
3071
3186
  catch (error) {
3072
3187
  if (error instanceof PaymentOutcomeUnknownError) {
@@ -3095,6 +3210,14 @@ export class VaultClient {
3095
3210
  await this.cancelPreparation(preparation.id).catch(() => { });
3096
3211
  throw new CheckoutPreparationError(preparation?.id ?? null, error.code);
3097
3212
  }
3213
+ // A VTEX create the API refused past its send-by (409 vtex_payments_post_timed_out), before any row, bind or
3214
+ // notification: the card never left the cardholder's device, and Ollie stops waiting for the post before one could.
3215
+ // The same known non-payment as the polled decline below. The preparation, which no later post of this attempt can
3216
+ // use, is retired so the cardholder's page stops waiting for it.
3217
+ if (preparation?.psp === VTEX_PAYMENTS_PSP && error instanceof CheckoutApiError && error.status === 409 && error.code === VTEX_PAYMENTS_POST_TIMED_OUT) {
3218
+ await this.cancelPreparation(preparation.id).catch(() => { });
3219
+ throw new CheckoutPreparationError(preparation.id, error.code);
3220
+ }
3098
3221
  // A missing answer or generic 5xx can hide a committed row and a delivered approval link.
3099
3222
  // Only the documented pre-create read-back errors prove it is safe to retry.
3100
3223
  const safeReadFailure = error instanceof CheckoutApiError
@@ -3127,11 +3250,17 @@ export class VaultClient {
3127
3250
  // A prepared Openpay token create too, for 40 seconds: openpay.js gives it 40 seconds from Pay, so the token
3128
3251
  // reaches the page within half a second of the device's answer, and the API names 500 ms as such a row's poll
3129
3252
  // interval.
3253
+ // A prepared VTEX card post too, for 12 seconds: Ollie's container aborts the post 12 seconds after it starts it,
3254
+ // so VTEX's answer reaches the container within half a second of the device's report, and the API names 500 ms as
3255
+ // such a row's poll interval.
3256
+ // A prepared ProcessOut card request too, for 20 seconds: ProcessOut.js gives its card frame 20 seconds from the
3257
+ // shop's press, and the API names 500 ms as such a row's poll interval.
3130
3258
  const preparedFastPollUntil = preparation?.psp === 'mercado_pago' ? Date.now() + 10_000
3131
- : preparation?.psp === PADDLE_VGS_PSP || preparation?.psp === HIPAY_PSP ? Date.now() + 20_000
3259
+ : preparation?.psp === PADDLE_VGS_PSP || preparation?.psp === HIPAY_PSP || preparation?.psp === PROCESSOUT_PSP ? Date.now() + 20_000
3132
3260
  : preparation?.psp === OPENPAY_PSP ? Date.now() + 40_000
3133
- : preparation?.psp === EPROTECT_PSP ? Date.now() + 15_000
3134
- : preparation?.psp === SECURE_ACCEPTANCE_PSP ? Date.now() + SECURE_ACCEPTANCE_FAST_POLL_MS : 0;
3261
+ : preparation?.psp === VTEX_PAYMENTS_PSP ? Date.now() + 12_000
3262
+ : preparation?.psp === EPROTECT_PSP ? Date.now() + 15_000
3263
+ : preparation?.psp === SECURE_ACCEPTANCE_PSP ? Date.now() + SECURE_ACCEPTANCE_FAST_POLL_MS : 0;
3135
3264
  // A preparation that tokenized before Pay: the cardholder's device already holds CyberSource's
3136
3265
  // token and approves as soon as this bind reaches it, while the page may give createToken only
3137
3266
  // seconds (Alaska Airlines' flight checkout gives it three). Poll every 100 ms for 10 seconds,
@@ -3529,6 +3658,19 @@ export class VaultClient {
3529
3658
  response.headers = { 'content-type': 'application/json' };
3530
3659
  response.body = judged.body;
3531
3660
  }
3661
+ if (rec?.psp === PROCESSOUT_PSP) {
3662
+ // ProcessOut's answer enters the card frame only as payment-core rebuilds an approved token from the card facts
3663
+ // the page reads (processout.js processoutAnswerOutcome: HTTP 200 or 201, success and a card token in shape),
3664
+ // under its approved status and a JSON content type. The frame reads the body of any answer, so anything else, a
3665
+ // refusal ProcessOut answers inside a 2xx or a pending answer included, is never handed over as an approval;
3666
+ // the card may have left the device, so the outcome is unknown rather than declined.
3667
+ const judged = processoutAnswerOutcome(response.status, response.body, null, null);
3668
+ if (judged.outcome !== 'approved' || typeof judged.body !== 'string')
3669
+ throw new PaymentOutcomeUnknownError(authorizationId, 'processout_token_unconfirmed');
3670
+ response.status = response.status === 201 ? 201 : 200;
3671
+ response.headers = { 'content-type': 'application/json' };
3672
+ response.body = judged.body;
3673
+ }
3532
3674
  if (rec?.psp === PRIMER_PSP) {
3533
3675
  // Primer's answer enters the page only as one single-use card token rebuilt from
3534
3676
  // payment-core's allow-list, under a JSON content type (primer.ts primerTokenAnswer).
@@ -3629,6 +3771,10 @@ export class VaultClient {
3629
3771
  if (preparation && typeof s.reason === 'string' && PREPARED_SEND_WINDOW_DECLINES[preparation.psp] === s.reason) {
3630
3772
  throw new CheckoutPreparationError(preparation.id, s.reason);
3631
3773
  }
3774
+ // The API declined a bound VTEX post past its send-by, counted from the pause this create named: the card
3775
+ // never left the cardholder's device, so the checkout failed unsent the same way.
3776
+ if (preparation?.psp === VTEX_PAYMENTS_PSP && s.reason === VTEX_PAYMENTS_POST_TIMED_OUT)
3777
+ throw new CheckoutPreparationError(preparation.id, s.reason);
3632
3778
  throw new ApprovalDeclinedError(s.reason ?? 'no reason given');
3633
3779
  }
3634
3780
  if (s.status === 'cancelled') {
@@ -3766,9 +3912,9 @@ export class VaultClient {
3766
3912
  * exactly the approved payment method (the page's own, or written by the
3767
3913
  * adapters in place of a card the confirm carries again).
3768
3914
  * The API allows a card save for any company, for an approval given on the
3769
- * Vault page, not by voice, with no amount, and a SetupIntent Stripe reports
3770
- * still confirmable on the same Stripe account; any other answer rejects with
3771
- * a CheckoutApiError whose code says why (for example `amount_mismatch`, or
3915
+ * Vault page, with no amount, and a SetupIntent Stripe reports still
3916
+ * confirmable on the same Stripe account; any other answer rejects with a
3917
+ * CheckoutApiError whose code says why (for example `amount_mismatch`, or
3772
3918
  * `continuation_invalid` for a confirm that is not a card-free SetupIntent
3773
3919
  * confirm). Nothing
3774
3920
  * is charged: the adapters continue the page's request once this resolves.
@@ -3973,13 +4119,17 @@ export class VaultClient {
3973
4119
  * (Stripe did not answer the read-back) is retried on a short backoff
3974
4120
  * instead of being left to the page's own retry loop, and a 409
3975
4121
  * `amount_mismatch` becomes an AmountMismatchError at stage 'create' so the
3976
- * adapters treat it as an answered request, not a dead page.
4122
+ * adapters treat it as an answered request, not a dead page. `atSend` adds
4123
+ * fields counted as each request leaves, after any token wait, 401 refresh or
4124
+ * backoff, so a field that measures time (VTEX's paused_ms) measures it then.
3977
4125
  */
3978
- async createAuthorization(payload, currency, signal, stopRetries) {
4126
+ async createAuthorization(payload, currency, signal, stopRetries, atSend) {
3979
4127
  for (let attempt = 0;; attempt++) {
3980
4128
  if (stopRetries?.aborted)
3981
4129
  throw new PaymentOutcomeUnknownError(null, 'merchant_request_aborted');
3982
4130
  try {
4131
+ if (atSend)
4132
+ return await this.postAtSend('/v2/checkout/authorizations', () => ({ ...payload, ...atSend() }), signal, stopRetries);
3983
4133
  return await this.post('/v2/checkout/authorizations', payload, signal, stopRetries);
3984
4134
  }
3985
4135
  catch (err) {
@@ -4065,8 +4215,10 @@ export class VaultClient {
4065
4215
  * Only fetch and a successful response's body read belong to that retry:
4066
4216
  * OAuth, HTTP errors, invalid JSON and every mutation remain outside it.
4067
4217
  * The existing one-time 401 refresh carries the remaining read budget.
4218
+ * `bodyAt`, when given, builds the body as each request leaves, after the
4219
+ * token and on the 401 refresh's resend, in place of `init.body`.
4068
4220
  */
4069
- async call(path, init = {}, retried = false, stopNewRequests, preparationReadAttempt) {
4221
+ async call(path, init = {}, retried = false, stopNewRequests, preparationReadAttempt, bodyAt) {
4070
4222
  const signal = init.signal ?? AbortSignal.timeout(30_000);
4071
4223
  const token = await withSignal(this.accessToken(), signal);
4072
4224
  // Auth can outlive the merchant request. Stop a create that has not left
@@ -4081,6 +4233,7 @@ export class VaultClient {
4081
4233
  try {
4082
4234
  r = await withSignal(this.fetch(`${this.baseUrl}${path}`, {
4083
4235
  ...init,
4236
+ ...(bodyAt ? { body: bodyAt() } : {}),
4084
4237
  signal,
4085
4238
  headers: { ...(init.headers ?? {}), authorization: `Bearer ${token}`, 'content-type': 'application/json' },
4086
4239
  }), signal);
@@ -4100,7 +4253,7 @@ export class VaultClient {
4100
4253
  if (stopNewRequests?.aborted)
4101
4254
  throw new PaymentOutcomeUnknownError(null, 'merchant_request_aborted');
4102
4255
  await withSignal(this.accessToken(true), signal);
4103
- return this.call(path, { ...init, signal }, true, stopNewRequests, preparationReadAttempt);
4256
+ return this.call(path, { ...init, signal }, true, stopNewRequests, preparationReadAttempt, bodyAt);
4104
4257
  }
4105
4258
  throw new CheckoutApiError(r.status, path, await withSignal(r.text(), signal));
4106
4259
  }
@@ -4108,6 +4261,10 @@ export class VaultClient {
4108
4261
  post(path, body, signal, stopNewRequests) {
4109
4262
  return this.call(path, { method: 'POST', body: JSON.stringify(body), signal }, false, stopNewRequests);
4110
4263
  }
4264
+ /** A POST whose body is built as each request leaves (call's `bodyAt`), for a body that counts time. */
4265
+ postAtSend(path, body, signal, stopNewRequests) {
4266
+ return this.call(path, { method: 'POST', signal }, false, stopNewRequests, undefined, () => JSON.stringify(body()));
4267
+ }
4111
4268
  get(path, signal) {
4112
4269
  return this.call(path, { signal });
4113
4270
  }
@@ -4209,7 +4366,9 @@ function cardEndpointReplay(s, authorizationId, profile, metadata) {
4209
4366
  const answer = named.answer;
4210
4367
  if (!isRecord(answer) || !Number.isInteger(answer.status) || answer.status < 100 || answer.status > 599)
4211
4368
  throw unknown('merchant_answer_malformed');
4212
- const base = { mode: CARD_ENDPOINT_MODE, authorizationId, profile, status: answer.status, ...metadata };
4369
+ const approved = Number.isSafeInteger(s.amount) && s.amount > 0 && typeof s.currency === 'string' && /^[a-z]{3}$/i.test(s.currency)
4370
+ ? { approved: { amount: s.amount, currency: s.currency.toLowerCase() } } : {};
4371
+ const base = { mode: CARD_ENDPOINT_MODE, authorizationId, profile, status: answer.status, ...approved, ...metadata };
4213
4372
  if (answer.withheld !== undefined) {
4214
4373
  if (answer.withheld !== 'card_in_response' && answer.withheld !== 'unreadable_in_response' && answer.withheld !== 'refused_step_in_response') {
4215
4374
  throw unknown('merchant_answer_malformed');
@@ -4341,6 +4500,22 @@ function jsonPathOf(value) {
4341
4500
  || (typeof step === 'number' && Number.isSafeInteger(step) && step >= 0));
4342
4501
  return ok ? [...value] : null;
4343
4502
  }
4503
+ /** Whether a served step is the step into JSON text, exactly `{text: 'json'}`. */
4504
+ const isTextStep = (step) => isRecord(step) && Object.keys(step).length === 1 && step.text === 'json';
4505
+ /**
4506
+ * A served holder path: a JSON path that may also step into a string holding JSON text, at most twice, never first
4507
+ * or last and never twice in a row (payment-core json-path.js isProfilePath); null for anything else. The substitution
4508
+ * itself checks it against this build's own profile.
4509
+ */
4510
+ function holderPathOf(value) {
4511
+ if (!Array.isArray(value) || value.length > 16)
4512
+ return null;
4513
+ const texts = value.map((step, i) => (isTextStep(step) ? i : -1)).filter((i) => i !== -1);
4514
+ if (texts.length > 2 || texts.some((i) => i === 0 || i === value.length - 1 || texts.includes(i - 1)))
4515
+ return null;
4516
+ const plain = jsonPathOf(value.filter((step) => !isTextStep(step)));
4517
+ return plain ? value.map((step) => (isTextStep(step) ? { text: 'json' } : step)) : null;
4518
+ }
4344
4519
  /** Served card facts: a list of {path, value}; null when anything in it is not one. */
4345
4520
  function factsOf(value) {
4346
4521
  if (!Array.isArray(value))
@@ -4386,7 +4561,7 @@ function merchantHostedApproval(s, authorizationId, profile) {
4386
4561
  if (!isRecord(served) || served.profile !== profile.id || typeof served.body_sha256 !== 'string'
4387
4562
  || !/^[0-9a-f]{64}$/.test(served.body_sha256))
4388
4563
  throw malformed();
4389
- const path = jsonPathOf(sub.path);
4564
+ const path = holderPathOf(sub.path);
4390
4565
  const fields = isRecord(sub.fields) && Object.keys(sub.fields).length > 0
4391
4566
  && Object.values(sub.fields).every((value) => typeof value === 'string' && value.length > 0) ? { ...sub.fields } : null;
4392
4567
  const remove = sub.remove === undefined ? [] : Array.isArray(sub.remove) && sub.remove.every((key) => typeof key === 'string' && key.length > 0)
@@ -14,11 +14,11 @@ declare function isCreditguardPpsDocument(url: any): boolean;
14
14
  declare function creditguardNuveiRefusal(requestUrl: any, initiatorUrl: any): "creditguard_three_d_secure" | null;
15
15
  declare function isCreditguardJweCompact(value: any): boolean;
16
16
  declare function creditguardPpDomainHost(ppDomain: any): string | null;
17
- declare function creditguardBodyRefusal(body: any, txId: any, options: any): "pp_domain_unbound" | "malformed_body" | "saved_card" | "otp" | "wallet" | "unexpected_field" | "keep_on_file" | "gift_card" | "dcc" | "installments" | "personal_id" | "txid_mismatch" | "pp_domain" | "jwe_fallback" | "jwe_unsupported" | "card_fields" | null;
17
+ declare function creditguardBodyRefusal(body: any, txId: any, options: any): "wallet" | "pp_domain_unbound" | "malformed_body" | "saved_card" | "otp" | "unexpected_field" | "keep_on_file" | "gift_card" | "dcc" | "installments" | "personal_id" | "txid_mismatch" | "pp_domain" | "jwe_fallback" | "jwe_unsupported" | "card_fields" | null;
18
18
  declare function classifyCreditguardRequest(url: any, method: any, body: any, options: any): "card" | "unsupported" | null;
19
- declare function creditguardProcessRefusal(request: any, options: any): "not_process_request" | "headers" | "pp_domain_unbound" | "malformed_body" | "saved_card" | "otp" | "wallet" | "unexpected_field" | "keep_on_file" | "gift_card" | "dcc" | "installments" | "personal_id" | "txid_mismatch" | "pp_domain" | "jwe_fallback" | "jwe_unsupported" | "card_fields" | null;
19
+ declare function creditguardProcessRefusal(request: any, options: any): "wallet" | "not_process_request" | "headers" | "pp_domain_unbound" | "malformed_body" | "saved_card" | "otp" | "unexpected_field" | "keep_on_file" | "gift_card" | "dcc" | "installments" | "personal_id" | "txid_mismatch" | "pp_domain" | "jwe_fallback" | "jwe_unsupported" | "card_fields" | null;
20
20
  declare function isCreditguardProcessRequest(request: any, options: any): boolean;
21
- declare function creditguardReplayRefusal(request: any, options: any): "not_process_request" | "headers" | "pp_domain_unbound" | "malformed_body" | "saved_card" | "otp" | "wallet" | "unexpected_field" | "keep_on_file" | "gift_card" | "dcc" | "installments" | "personal_id" | "txid_mismatch" | "pp_domain" | "jwe_fallback" | "jwe_unsupported" | "card_fields" | null;
21
+ declare function creditguardReplayRefusal(request: any, options: any): "wallet" | "not_process_request" | "headers" | "pp_domain_unbound" | "malformed_body" | "saved_card" | "otp" | "unexpected_field" | "keep_on_file" | "gift_card" | "dcc" | "installments" | "personal_id" | "txid_mismatch" | "pp_domain" | "jwe_fallback" | "jwe_unsupported" | "card_fields" | null;
22
22
  declare function creditguardRefusalMessage(reason: any): string;
23
23
  declare function injectCreditguardCard(body: any, card: any, options: any): {
24
24
  body: string;
@@ -141,7 +141,7 @@ declare function creditguardDeviceThreeDSOutcomeRequest(processUrl: any): {
141
141
  };
142
142
  body: string;
143
143
  };
144
- declare function creditguardDeviceStepRefusal(request: any, processUrl: any): "headers" | "malformed_body" | "wallet" | "unexpected_field" | "gift_card" | "dcc" | "txid_mismatch" | "card_fields" | "not_companion_request" | null;
144
+ declare function creditguardDeviceStepRefusal(request: any, processUrl: any): "wallet" | "headers" | "malformed_body" | "unexpected_field" | "gift_card" | "dcc" | "txid_mismatch" | "card_fields" | "not_companion_request" | null;
145
145
  declare function creditguardCardinalUrl(url: any, purpose: any, environment: any): string | null;
146
146
  declare function isCreditguardCardinalOrigin(origin: any): boolean;
147
147
  declare function creditguardCardinalMessage(data: any): {
@@ -1,6 +1,25 @@
1
1
  // @ts-nocheck
2
2
  // Generated from @agent-cards/payment-core. Do not edit.
3
- // artifact-sha256: 829167e03a7ffe9fc109ababe9755f1263a840fd5294ebec970e08d41aa9741b
3
+ // artifact-sha256: 169752c8d6a6c9f131c83502766f0026b2ffa45cf9f27c450735eef5ad2fa931
4
+ // src/answer-card-leak.js
5
+ var DECIMAL = /\p{Nd}/u;
6
+ function digitValue(char) {
7
+ const point = char.codePointAt(0);
8
+ let first = point;
9
+ while (DECIMAL.test(String.fromCodePoint(first - 1)))
10
+ first -= 1;
11
+ return String((point - first) % 10);
12
+ }
13
+ var ENCLOSED = /* @__PURE__ */ Object.freeze([[9451, 10, 11], [9461, 10, 1], [9471, 1, 0], [10102, 30, 1], [127243, 2, 0]]);
14
+ var ENCLOSED_CHAR = /[\u24eb-\u24ff\u2776-\u2793\u{1f10b}\u{1f10c}]/gu;
15
+ function enclosedValue(char) {
16
+ const point = char.codePointAt(0);
17
+ const [first, , start] = ENCLOSED.find(([from, size]) => point >= from && point < from + size);
18
+ return String(first === 127243 ? 0 : first === 10102 ? (point - first) % 10 + 1 : start + point - first);
19
+ }
20
+ var asciiDigits = (text2) => (typeof text2.normalize === "function" ? text2.normalize("NFKC") : text2).replace(ENCLOSED_CHAR, enclosedValue).replace(/\p{Nd}/gu, (char) => char >= "0" && char <= "9" ? char : digitValue(char));
21
+ var digitCount = (text2) => (asciiDigits(String(text2)).match(/\p{N}/gu) ?? []).length;
22
+ var NAME_DIGITS = 3;
4
23
  // src/datatrans.js
5
24
  var DATATRANS_HOSTS = Object.freeze(["pay.datatrans.com", "pay.sandbox.datatrans.com"]);
6
25
  var DATATRANS_TOKENIZE_PATH = "/upp/payment/SecureFields/paymentField";
@@ -374,6 +393,8 @@ function rebuildCardInfo(info) {
374
393
  return null;
375
394
  if (owns(info, "brand") && !optionalText(info.brand, CARD_WORD) || owns(info, "type") && !optionalText(info.type, CARD_WORD) || owns(info, "usage") && !optionalText(info.usage, CARD_WORD) || owns(info, "country") && !optionalText(info.country, COUNTRY) || owns(info, "issuer") && info.issuer !== null && !plain(info.issuer, 128) || owns(info, "coBrand") && info.coBrand !== null && !plain(info.coBrand, 64) || owns(info, "accountType") && !optionalText(info.accountType, CARD_WORD) || owns(info, "binLength") && !optionalBinLength(info.binLength))
376
395
  return null;
396
+ if (digitCount(`${info.issuer ?? ""} ${info.coBrand ?? ""}`) > NAME_DIGITS)
397
+ return null;
377
398
  const safe = {};
378
399
  for (const key of CARD_INFO_KEYS)
379
400
  if (owns(info, key))
@@ -1,6 +1,6 @@
1
1
  // @ts-nocheck
2
2
  // Generated from @agent-cards/payment-core. Do not edit.
3
- // artifact-sha256: 136e1c2b0fd2bdee0cfbf5efbbfaf1a92ace7d1cc52e17a3fd5a4584370e5e53
3
+ // artifact-sha256: 7f75fa0deecb8db3b33aa07bdaf4a70e5e548c9bd528b45348022c22138599b3
4
4
  // src/elavon-converge.js
5
5
  var CONVERGE_HOSTS = /* @__PURE__ */ Object.freeze(["api.convergepay.com", "api.demo.convergepay.com", "www.convergepay.com"]);
6
6
  var CONVERGE_DEMO_HOST = "api.demo.convergepay.com";
@@ -339,6 +339,8 @@ var RESPONSE_FIELDS = /* @__PURE__ */ Object.freeze([
339
339
  "ssl_customer_code"
340
340
  ]);
341
341
  var MASKED_CARD = /^(?=.{12,19}$)\d{0,6}[*Xx#]+\d{0,4}$/;
342
+ var CARD_WORDS = /* @__PURE__ */ Object.freeze(["ssl_card_short_description", "ssl_card_type"]);
343
+ var hasDigit = (value) => /\p{N}/u.test(String(value).normalize("NFKC"));
342
344
  var FIELD_NAME = /^[A-Za-z0-9_.-]{1,64}$/;
343
345
  function luhn(digits) {
344
346
  let sum = 0;
@@ -391,7 +393,7 @@ function keepReturnField(name, value) {
391
393
  return false;
392
394
  if (lower === "ssl_card_number")
393
395
  return typeof value === "string" && MASKED_CARD.test(value);
394
- return safeScalar(value);
396
+ return safeScalar(value) && !(CARD_WORDS.includes(lower) && hasDigit(value));
395
397
  }
396
398
  function sanitizeReturnList(list) {
397
399
  if (Array.isArray(list)) {
@@ -423,7 +425,7 @@ function sanitizeConvergeResponse(text) {
423
425
  if (!owns(answer.responseFields, name))
424
426
  continue;
425
427
  const value = answer.responseFields[name];
426
- if (name === "ssl_card_number" ? typeof value === "string" && MASKED_CARD.test(value) : safeScalar(value))
428
+ if (name === "ssl_card_number" ? typeof value === "string" && MASKED_CARD.test(value) : safeScalar(value) && !(CARD_WORDS.includes(name) && hasDigit(value)))
427
429
  fields[name] = value;
428
430
  }
429
431
  out.responseFields = fields;
package/dist/hold.d.ts CHANGED
@@ -32,6 +32,7 @@ export declare const HOLD_DEADLINE_MARGIN_MS = 2000;
32
32
  * processor's frame encrypts first (Bolt, Adyen): those need their own rule. Null without a usable number.
33
33
  */
34
34
  export declare function placeholderHoldRule(numbers: readonly string[], deadlineMs?: number): HoldOnlyRule | null;
35
+ export declare function encryptedCardHoldRule(deadlineMs?: number): HoldOnlyRule;
35
36
  /** The hold-only rule a request is a card request under, or null. */
36
37
  export declare function matchHoldOnlyRule(request: {
37
38
  url: string;
@@ -99,6 +100,12 @@ export interface CardRequestHold {
99
100
  readonly held: HeldCardRequest | null;
100
101
  /** Card requests failed without being held: a second one, a retry, or one after a decision. */
101
102
  readonly refused: number;
103
+ /**
104
+ * The hold-only rules armed beside the registry's recognisers (`bolt`, `placeholder_card`, `encrypted_card`). A
105
+ * processor whose card is encrypted in the page and may be sent inside the merchant's own request (Adyen's
106
+ * Advanced flow) is covered only with `encrypted_card`.
107
+ */
108
+ readonly covers: readonly string[];
102
109
  /** Resolves with the held request once one is held, or null after `timeoutMs` or once the hold is over. */
103
110
  whenHeld(timeoutMs?: number): Promise<HeldCardRequest | null>;
104
111
  release(input: HoldReleaseInput): Promise<HoldReleaseOutcome>;
@@ -149,6 +156,7 @@ export declare class HoldGate implements CardRequestHold {
149
156
  get state(): HoldState;
150
157
  get held(): HeldCardRequest | null;
151
158
  get refused(): number;
159
+ get covers(): readonly string[];
152
160
  /** Fetch patterns the hold-only rules need beside the registry's. */
153
161
  patterns(): Array<{
154
162
  urlPattern: string;