@agent-cards/sdk 0.23.27 → 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.
- package/CHANGELOG.md +66 -0
- package/PREFLIGHT.md +7 -1
- package/README.md +137 -99
- package/dist/aci.d.ts +52 -22
- package/dist/aci.js +79 -15
- package/dist/adyen-merchant-hosted.generated.d.ts +1 -1
- package/dist/adyen-merchant-hosted.generated.js +459 -21
- package/dist/agent-amount.generated.js +1 -1
- package/dist/agentcard.d.ts +4 -5
- package/dist/agentcard.js +4 -5
- package/dist/amadeus.d.ts +9 -3
- package/dist/amadeus.js +33 -4
- package/dist/api-module.d.ts +1 -7
- package/dist/api-module.js +3 -5
- package/dist/{voice-retry.js → api-retry.js} +1 -1
- package/dist/api-types.d.ts +53 -0
- package/dist/{voice-types.js → api-types.js} +7 -2
- package/dist/approvals.d.ts +166 -0
- package/dist/approvals.js +213 -0
- package/dist/buy.d.ts +20 -101
- package/dist/buy.js +19 -74
- package/dist/capabilities.generated.js +1 -1
- package/dist/card-fields.generated.js +1398 -106
- package/dist/cdp.d.ts +3 -13
- package/dist/cdp.js +231 -175
- package/dist/client.d.ts +104 -22
- package/dist/client.js +215 -40
- package/dist/creditguard.generated.d.ts +4 -4
- package/dist/datatrans.generated.js +22 -1
- package/dist/elavon-converge.generated.js +5 -3
- package/dist/hold.d.ts +8 -0
- package/dist/hold.js +43 -2
- package/dist/hosted-entry.js +2 -0
- package/dist/index.d.ts +9 -15
- package/dist/index.js +6 -10
- package/dist/juspay-gate.js +2 -2
- package/dist/juspay.generated.d.ts +2 -2
- package/dist/juspay.generated.js +174 -4
- package/dist/lifecycle.d.ts +9 -61
- package/dist/lifecycle.js +24 -10
- package/dist/merchant-card-endpoints.generated.d.ts +6 -1
- package/dist/merchant-card-endpoints.generated.js +344 -19
- package/dist/merchant-hosted.d.ts +8 -1
- package/dist/nike-order.d.ts +51 -0
- package/dist/nike-order.js +81 -0
- package/dist/oceanpayment.d.ts +38 -0
- package/dist/oceanpayment.js +70 -0
- package/dist/paddle-tokenex.generated.js +105 -10
- package/dist/payplug.d.ts +41 -0
- package/dist/payplug.js +71 -0
- package/dist/payrails.generated.js +27 -7
- package/dist/payrexx.generated.js +176 -2
- package/dist/pre-claim.d.ts +9 -0
- package/dist/pre-claim.js +86 -7
- package/dist/preflight-capabilities.generated.d.ts +0 -4
- package/dist/preflight-capabilities.generated.js +0 -5
- package/dist/preflight-catalog.json +46 -5
- package/dist/preflight-playwright.d.ts +2 -2
- package/dist/preflight-playwright.js +63 -14
- package/dist/preflight-schemas.json +4 -0
- package/dist/preflight.generated.d.ts +0 -4
- package/dist/preflight.generated.js +24 -1
- package/dist/prepared-processor.d.ts +18 -5
- package/dist/prepared-processor.js +48 -8
- package/dist/primer.generated.js +1418 -120
- package/dist/processout.generated.d.ts +61 -0
- package/dist/processout.generated.js +577 -0
- package/dist/requests.js +1 -1
- package/dist/shiji-sts.generated.js +105 -10
- package/dist/stripe-checkout.generated.js +1405 -234
- package/dist/substitutions.generated.js +460 -26
- package/dist/tap-payments.generated.js +105 -10
- package/dist/twoc2p.generated.js +105 -10
- package/dist/url-masks/bluesnap.d.ts +2 -0
- package/dist/url-masks/bluesnap.js +15 -0
- package/dist/url-masks/oceanpayment.d.ts +2 -0
- package/dist/url-masks/oceanpayment.js +13 -0
- package/dist/url-masks/payplug.d.ts +2 -0
- package/dist/url-masks/payplug.js +13 -0
- package/dist/url-masks/vtex_payments.d.ts +2 -0
- package/dist/url-masks/vtex_payments.js +12 -0
- package/dist/url-masks.generated.d.ts +1 -1
- package/dist/url-masks.generated.js +6 -2
- package/dist/verifone.generated.js +42 -2
- package/dist/vtex-payments.generated.d.ts +127 -0
- package/dist/vtex-payments.generated.js +544 -0
- package/dist/webhooks.d.ts +2 -2
- package/dist/webhooks.js +2 -2
- package/dist/wspay.generated.d.ts +1 -1
- package/dist/wspay.generated.js +175 -3
- package/examples/preflight/kernel-native/inventory.json +1 -1
- package/package.json +6 -8
- package/dist/languages.d.ts +0 -23
- package/dist/languages.js +0 -76
- package/dist/voice-pkce.d.ts +0 -49
- package/dist/voice-pkce.js +0 -147
- package/dist/voice-stream.generated.d.ts +0 -118
- package/dist/voice-stream.generated.js +0 -170
- package/dist/voice-types.d.ts +0 -735
- package/dist/voice.d.ts +0 -373
- package/dist/voice.js +0 -815
- package/readme.test.mjs +0 -53
- /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 {
|
|
30
|
+
import { ApprovalsModule } from './approvals.js';
|
|
30
31
|
import { RequestRegistry } from './requests.js';
|
|
31
|
-
import { AgentcardError } from './
|
|
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
|
|
944
|
-
|
|
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
|
|
1369
|
-
* API's code
|
|
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
|
|
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
|
|
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
|
|
2066
|
-
* nothing a second time. Same credentials, same token as the
|
|
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
|
|
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
|
|
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 ===
|
|
3134
|
-
: preparation?.psp ===
|
|
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,
|
|
3770
|
-
*
|
|
3771
|
-
*
|
|
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
|
|
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 =
|
|
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): "
|
|
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" | "
|
|
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" | "
|
|
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): "
|
|
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:
|
|
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:
|
|
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;
|