@agent-cards/sdk 0.23.20 → 0.23.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +55 -2
- package/PREFLIGHT.md +6 -0
- package/README.md +169 -5
- package/dist/amadeus-checkout.generated.d.ts +6 -1
- package/dist/amadeus-checkout.generated.js +26 -2
- package/dist/card-fields.generated.js +2341 -127
- package/dist/cdp.js +199 -28
- package/dist/client.d.ts +177 -9
- package/dist/client.js +387 -37
- package/dist/comgate.d.ts +50 -0
- package/dist/comgate.js +80 -0
- package/dist/creditguard.generated.js +4 -2
- package/dist/cse-body.js +11 -0
- package/dist/cybersource-secure-acceptance.generated.d.ts +1 -1
- package/dist/ebanx.d.ts +1 -1
- package/dist/ebanx.js +1 -1
- package/dist/eprotect.generated.d.ts +1 -1
- package/dist/eximbay.d.ts +46 -0
- package/dist/eximbay.js +69 -0
- package/dist/gmo.generated.js +7 -3
- package/dist/hipay.generated.d.ts +5 -5
- package/dist/hipay.generated.js +5 -2
- package/dist/hold.d.ts +1 -1
- package/dist/hold.js +10 -4
- package/dist/hosted-entry.d.ts +4 -0
- package/dist/hosted-entry.js +32 -0
- package/dist/index.d.ts +1 -0
- package/dist/lifecycle.d.ts +9 -4
- package/dist/lifecycle.js +16 -4
- package/dist/paddle-tokenex.generated.js +7 -3
- package/dist/paygent.generated.d.ts +60 -0
- package/dist/paygent.generated.js +425 -0
- package/dist/payrails.generated.js +7 -3
- package/dist/payrexx.d.ts +91 -0
- package/dist/payrexx.generated.d.ts +91 -0
- package/dist/payrexx.generated.js +536 -0
- package/dist/payrexx.js +141 -0
- package/dist/pre-claim.d.ts +30 -1
- package/dist/pre-claim.js +257 -39
- package/dist/preflight-catalog.json +58 -0
- package/dist/preflight-schemas.json +4 -0
- package/dist/preflight.generated.js +26 -2
- package/dist/prepared-processor.d.ts +7 -2
- package/dist/prepared-processor.js +16 -2
- package/dist/primer.generated.js +2469 -255
- package/dist/przelewy24.d.ts +47 -0
- package/dist/przelewy24.js +88 -0
- package/dist/registry.d.ts +6 -5
- package/dist/registry.js +27 -7
- package/dist/shiji-sts.generated.js +7 -3
- package/dist/stripe-checkout.generated.js +896 -19
- package/dist/toast-seal.generated.d.ts +37 -0
- package/dist/toast-seal.generated.js +625 -0
- package/dist/toast.generated.d.ts +41 -0
- package/dist/toast.generated.js +113 -0
- package/dist/tokenex-iframe.d.ts +93 -0
- package/dist/tokenex-iframe.generated.d.ts +81 -0
- package/dist/tokenex-iframe.generated.js +403 -0
- package/dist/tokenex-iframe.js +195 -0
- package/dist/transbank.d.ts +45 -0
- package/dist/transbank.js +73 -0
- package/dist/twoc2p.generated.js +7 -3
- package/examples/preflight/kernel-native/inventory.json +1 -1
- package/package.json +3 -3
package/dist/client.js
CHANGED
|
@@ -2,7 +2,12 @@ import { BUILTIN_REGISTRY, cardUrlPatterns as deriveCardUrlPatterns, LOOPBACK_UR
|
|
|
2
2
|
import { armServedProfiles, classifyProfileRequest, declareSandboxMerchant, merchantProfileEnvironment, merchantProfileFor, merchantProfileUrlPatterns, } from './merchant-hosted.js';
|
|
3
3
|
import { merchantAmountExchanges, merchantAmountFromResponses, merchantProfilePricedBySource, merchantTotalWire, MerchantTotalWait, } from './merchant-total.js';
|
|
4
4
|
import { ACI_CAPABILITY, ACI_PSP, aciCardLegAnswer } from './aci.js';
|
|
5
|
+
import { PRZELEWY24_CAPABILITY, PRZELEWY24_PSP } from './przelewy24.js';
|
|
5
6
|
import { EPROTECT_CAPABILITY, EPROTECT_PSP, eprotectCheckoutRefusal, eprotectScriptAnswer, isEprotectRegistration } from './eprotect.js';
|
|
7
|
+
import { COMGATE_CAPABILITY, COMGATE_PSP } from './comgate.js';
|
|
8
|
+
import { EXIMBAY_CAPABILITY } from './eximbay.js';
|
|
9
|
+
import { isHostedGetEntry } from './hosted-entry.js';
|
|
10
|
+
import { TRANSBANK_CAPABILITY, TRANSBANK_PSP } from './transbank.js';
|
|
6
11
|
import { isMercadoTokenRequest, parseMercadoCheckoutContext, validateMercadoProcessorContext } from './mercado-checkout.generated.js';
|
|
7
12
|
import { matchesPreparation, preparationMode, validPreparationEnvironment } from './prepared-processor.js';
|
|
8
13
|
import { fiservKeyPinById, isFiservSubstitution, requiresFiservPreparation } from './fiserv.js';
|
|
@@ -15,10 +20,12 @@ import { CONVERGE_CAPABILITY } from './elavon-converge.js';
|
|
|
15
20
|
import { SHIJI_STS_CAPABILITY, SHIJI_STS_PSP, isShijiStsServedCompanion, isShijiStsServedSubstitution } from './shiji-sts.js';
|
|
16
21
|
import { AMADEUS_CHECKOUT_CAPABILITY, AMADEUS_CHECKOUT_PSP } from './amadeus-checkout.generated.js';
|
|
17
22
|
import { PADDLE_VGS_CAPABILITY, PADDLE_VGS_PSP, paddleVgsCardFactsOf } from './paddle-vgs.generated.js';
|
|
23
|
+
import { PAYGENT_CAPABILITY, PAYGENT_PSP, paygentAnswerOutcome } from './paygent.generated.js';
|
|
18
24
|
import { PADDLE_TOKENEX_CAPABILITY, PADDLE_TOKENEX_PSP, isPaddleTokenexServedFacts, isPaddleTokenexServedSubstitution } from './paddle-tokenex.js';
|
|
19
25
|
import { TWOC2P_CAPABILITY, TWOC2P_PSP } from './twoc2p.js';
|
|
20
26
|
import { WIX_PAYMENTS_CAPABILITY, WIX_PAYMENTS_PSP } from './wix-payments.generated.js';
|
|
21
27
|
import { HIPAY_CAPABILITY, HIPAY_PSP } from './hipay.generated.js';
|
|
28
|
+
import { TOAST_CAPABILITY, TOAST_PSP, isToastCiphertext } from './toast.generated.js';
|
|
22
29
|
import { classifyStripeCheckoutRequest, encodeStripeCheckoutContext, hasStripeCheckoutMarker, parseStripeCheckoutContext, parseStripeCheckoutResponse, STRIPE_CHECKOUT_CONTEXT_HEADER, } from './stripe-checkout.generated.js';
|
|
23
30
|
import { VoiceModule } from './voice.js';
|
|
24
31
|
import { BuyModule } from './buy.js';
|
|
@@ -32,11 +39,13 @@ import { JUSPAY_CAPABILITY, JUSPAY_PSP, juspayPageAnswer } from './juspay-gate.j
|
|
|
32
39
|
import { CARD_ENDPOINT_MODE, CARD_ENDPOINT_PSP, CardEndpointRefusedError, cardEndpointById, cardEndpointLive, cardEndpointSessionRefusal, } from './card-endpoint.js';
|
|
33
40
|
import { merchantCardEndpointFactsRefusal as sharedFactsRefusal, readMerchantCardEndpointFacts as sharedReadFacts, } from './merchant-card-endpoints.generated.js';
|
|
34
41
|
import { DATATRANS_CAPABILITY } from './datatrans.js';
|
|
42
|
+
import { TOKENEX_IFRAME_CAPABILITY, TOKENEX_IFRAME_PSP, tokenexIframePageAnswer, tokenexIframePlaceholderFits } from './tokenex-iframe.js';
|
|
35
43
|
import { PELECARD_CAPABILITY, PELECARD_PSP } from './pelecard.js';
|
|
36
44
|
import { PRIMER_CAPABILITY, PRIMER_PSP, primerTokenAnswer } from './primer.js';
|
|
37
45
|
import { PAYRAILS_CAPABILITY, isPayrailsPaymentUrl, payrailsAnswer } from './payrails.js';
|
|
38
46
|
import { WSPAY_CAPABILITY, WSPAY_PSP } from './wspay.js';
|
|
39
47
|
import { EbanxFollowUpError } from './ebanx.js';
|
|
48
|
+
import { PAYREXX_CAPABILITY, PAYREXX_PSP, PayrexxFollowUpError } from './payrexx.js';
|
|
40
49
|
import { SECURE_ACCEPTANCE_CAPABILITY, SECURE_ACCEPTANCE_FAST_POLL_MS, SECURE_ACCEPTANCE_PSP, SECURE_ACCEPTANCE_REPLY_WINDOW_MS, secureAcceptanceCheckoutProfile, secureAcceptanceCheckoutRefusal, secureAcceptanceFrameAnswer, } from './cybersource-secure-acceptance.js';
|
|
41
50
|
/**
|
|
42
51
|
* The modes this SDK can finish. Asked for on syncRegistry (the API serves
|
|
@@ -228,6 +237,13 @@ export const SUPPORTED_REGISTRY_FEATURES = ['card_fields', 'checkout_sessions'];
|
|
|
228
237
|
* checkout relay. Served on the same terms: only to a build that lists it, for a company
|
|
229
238
|
* Agentcard turned Payfast on for.
|
|
230
239
|
*
|
|
240
|
+
* `przelewy24_hosted_payment_page`: Przelewy24's pay wall, whose card-free link
|
|
241
|
+
* (secure.przelewy24.pl/trnRequest/<token>, a plain GET the merchant's redirect or script sends
|
|
242
|
+
* the buyer to) this build pauses for the cardholder's device by its exact link (hosted-entry.ts),
|
|
243
|
+
* and whose card method post, card form post and Saferpay resolver this build never lets the
|
|
244
|
+
* agent's browser send (pre-claim.ts, przelewy24.ts). Served on the same terms as NMI's and
|
|
245
|
+
* Redsys's: only to a build that lists it, for a company Agentcard turned Przelewy24 on for.
|
|
246
|
+
*
|
|
231
247
|
* `wix_payments_tokenize`: the card request Wix Cashier's card frame sends to Wix's
|
|
232
248
|
* tokenizer (cashier-pci.wixapps.net), only through a prepared checkout: Wix's payment
|
|
233
249
|
* widget fails the payment 30 seconds after it asks the frame for a token. The
|
|
@@ -244,6 +260,31 @@ export const SUPPORTED_REGISTRY_FEATURES = ['card_fields', 'checkout_sessions'];
|
|
|
244
260
|
* the checkout relay. Served on the same terms: only to a build that lists it, for a company
|
|
245
261
|
* Agentcard turned PayMongo on for.
|
|
246
262
|
*
|
|
263
|
+
* `gopay_card_form`: the card post GoPay's card frame sends to its own URL
|
|
264
|
+
* (secure.gopay.com/gp-card-comm/h/<token>) inside the GoPay gateway a shop opens. This build pauses
|
|
265
|
+
* only the frame's own card post and answers it with GoPay's answer as the API recorded it, the
|
|
266
|
+
* empty JSON object the frame reads; the gateway then reads the payment's end from GoPay and runs
|
|
267
|
+
* 3-D Secure in the agent's browser when the bank asks for it. The cardholder's device sends the card
|
|
268
|
+
* through the checkout relay. Served only to a build that lists it, and only for Agentcard's proof
|
|
269
|
+
* company until a reviewed change binds GoPay's payee and amount to the approval: the post names
|
|
270
|
+
* neither. The API takes it only at a shop Agentcard reviewed.
|
|
271
|
+
*
|
|
272
|
+
* `paygent_token`: the token request Paygent's own PaygentToken.js sends from a merchant's
|
|
273
|
+
* page (token.paygent.co.jp/n/token/request), only through a prepared checkout: the script
|
|
274
|
+
* abandons the request 35 seconds after the press that sends it, so the cardholder approves
|
|
275
|
+
* before the press. The cardholder's device sends the card itself and the page gets back
|
|
276
|
+
* Paygent's single-use token alone; the SDK refuses a second token request on the same approval.
|
|
277
|
+
* Served on the same terms: only to a build that lists it, for a company Agentcard turned
|
|
278
|
+
* Paygent on for.
|
|
279
|
+
*
|
|
280
|
+
* `payrexx_card_frame`: the card request Payrexx Pay's card frame sends to its own URL
|
|
281
|
+
* (checkout.payrexx.com/?pspId=44&lang=<xx>) on a Payrexx merchant's page. This build pauses only
|
|
282
|
+
* the frame's own one-time card request and answers it with Payrexx's answer as the API recorded
|
|
283
|
+
* it: the token the merchant's page then charges, or the start of 3-D Secure, whose two later sends
|
|
284
|
+
* of the card this build hands to the cardholder's device under the same approval (payrexx.ts,
|
|
285
|
+
* pre-claim.ts payrexxPreClaim). The cardholder's device sends the card through the checkout relay.
|
|
286
|
+
* Served only to a build that lists it, for a company Agentcard turned Payrexx on for.
|
|
287
|
+
*
|
|
247
288
|
* `hipay_vault_token`: the token create HiPay's hosted fields send to HiPay's Secure Vault
|
|
248
289
|
* (secure2-vault.hipay-tpp.com, stage-secure2-vault for a test row), only through a prepared
|
|
249
290
|
* checkout: the merchant's page gives the frame 20 seconds after Pay. The cardholder's device
|
|
@@ -265,9 +306,117 @@ export const SUPPORTED_REGISTRY_FEATURES = ['card_fields', 'checkout_sessions'];
|
|
|
265
306
|
* Secure step and card lookup this build never lets the agent's browser send (pre-claim.ts,
|
|
266
307
|
* quickpay.ts). Served on the same terms as NMI's and Redsys's: only to a build that lists it, for a
|
|
267
308
|
* company Agentcard turned QuickPay on for.
|
|
309
|
+
*
|
|
310
|
+
* `revolut_card_field`: the card payment Revolut Business's card field or card popup sends from
|
|
311
|
+
* its frame to /api/public/orders/<order token>/payments on merchant.revolut.com, on a merchant's
|
|
312
|
+
* own page or Revolut's hosted checkout. This build pauses only the frame's card payment and
|
|
313
|
+
* answers it with Revolut's answer as the API recorded it: the payment Revolut created, which the
|
|
314
|
+
* frame then follows, through any 3-D Secure step, in the agent's browser. A payment by Apple Pay,
|
|
315
|
+
* Google Pay or Revolut Pay on the same URL carries no card and continues untouched. Revolut's
|
|
316
|
+
* host answers the Vault's origin, so the cardholder's device sends the card itself, with no relay.
|
|
317
|
+
* Served on the same terms: only to a build that lists it, for a company Agentcard turned Revolut
|
|
318
|
+
* on for.
|
|
319
|
+
*
|
|
320
|
+
* `pay_nl_card_token`: the card token request a shop's card form sends to Pay. (pay.nl),
|
|
321
|
+
* Weeztix's sandboxed frame posting its FormData to payment.pay.nl/v1/Payment/getCardToken/json.
|
|
322
|
+
* This build pauses only the form's own multipart body at that exact URL; the cardholder's
|
|
323
|
+
* device sends the card through the checkout relay, and the frame gets the token answer
|
|
324
|
+
* payment-core's judge rebuilt, with the CORS answer for the frame's own origin, null. The
|
|
325
|
+
* request names no amount, so the agent's pair, above zero, is the one the cardholder approves
|
|
326
|
+
* (PAY_NL_PSP below). Served on the same terms: only to a build that lists it, for a company
|
|
327
|
+
* Agentcard turned Pay. on for.
|
|
328
|
+
*
|
|
329
|
+
* `paypal_card_fields`: PayPal Card Fields' confirm, the POST its v5 frame sends to
|
|
330
|
+
* www.paypal.com and its v6 frame to api-m.paypal.com. This build pauses it only in
|
|
331
|
+
* the frames' own shape, refuses one that asks PayPal to keep the card or forces
|
|
332
|
+
* 3-D Secure in the browser with a `blocked` event, and answers the frame with the
|
|
333
|
+
* order's id and status as the API recorded them. The device sends a v5 confirm
|
|
334
|
+
* through the checkout relay and a v6 one directly. Served on the same terms: only
|
|
335
|
+
* to a build that lists it, for a company Agentcard turned PayPal Card Fields on
|
|
336
|
+
* for. Every other company keeps the checkout guard's refusal (paypal-card-forms.ts).
|
|
337
|
+
*
|
|
338
|
+
* `comgate_hosted_payment_page`: ComGate's payment gateway, whose card-free gateway link
|
|
339
|
+
* (payN.comgate.cz/init?id=<transaction id>, a plain GET) this build pauses for the
|
|
340
|
+
* cardholder's device by its exact link (hosted-entry.ts), in the page, in a frame of it or
|
|
341
|
+
* in a window it opened, and whose card hosts this build never lets the agent's browser post
|
|
342
|
+
* to (pre-claim.ts, comgate.ts). Served on the same terms as NMI's and Redsys's: only to a
|
|
343
|
+
* build that lists it, for a company Agentcard turned ComGate on for.
|
|
344
|
+
*
|
|
345
|
+
* `makecommerce_card_frame`: the card post MakeCommerce's card frame sends to its own card endpoint
|
|
346
|
+
* (cc.maksekeskus.ee/card-payment) under the shop's publishable key. This build pauses only the
|
|
347
|
+
* frame's own one-time card payment and answers it with MakeCommerce's answer as the API recorded
|
|
348
|
+
* it: the URL of MakeCommerce's 3-D Secure page, which the frame then opens in the agent's browser.
|
|
349
|
+
* The cardholder's device sends the card through the checkout relay. Served on the same terms: only
|
|
350
|
+
* to a build that lists it, for a company Agentcard turned MakeCommerce on for.
|
|
351
|
+
*
|
|
352
|
+
* `poynt_collect_nonce`: GoDaddy Payments' card request, the POST Poynt Collect's
|
|
353
|
+
* card frame sends to a store's generate-nonce on services.poynt.net. This build
|
|
354
|
+
* pauses it only as the frame writes it, refuses a body that asks Poynt to keep the
|
|
355
|
+
* card in the browser with a `blocked` event, and answers the frame with the nonce as
|
|
356
|
+
* the API recorded it. The device sends the card through the checkout relay. Served
|
|
357
|
+
* on the same terms: only to a build that lists it, for a company Agentcard turned
|
|
358
|
+
* GoDaddy Payments on for.
|
|
359
|
+
*
|
|
360
|
+
* `tokenex_iframe_tokenize`: the card post TokenEx's hosted iframe v3 sends from its card-number frame
|
|
361
|
+
* to its own document URL on htp.tokenex.com (JetBlue's card form). This build pauses only the PCI frame's
|
|
362
|
+
* own card post with its CVV frame, refuses in the browser every other post on that path that carries a
|
|
363
|
+
* card (tokenex-iframe.ts), answers it with TokenEx's token as the API recorded it, rebuilt from
|
|
364
|
+
* payment-core's allow-list, and answers the same frame's byte-identical repeat from that answer while the
|
|
365
|
+
* approval is being handed off. Served on the same terms: only to a build that lists it, for a company
|
|
366
|
+
* Agentcard turned the TokenEx iframe on for.
|
|
367
|
+
*
|
|
368
|
+
* `transbank_webpay_hosted_payment_page`: Transbank's Webpay Plus, whose card-free entry
|
|
369
|
+
* (webpay3g.transbank.cl/webpayserver/initTransaction?token_ws=<token>, the merchant's
|
|
370
|
+
* redirect, a plain GET) this build pauses for the cardholder's device by its exact link
|
|
371
|
+
* (hosted-entry.ts) in the page or a frame of it, and with attachToCdp also in a window the
|
|
372
|
+
* page opened, and whose hosts this build never lets the agent's browser post to (pre-claim.ts,
|
|
373
|
+
* transbank.ts), so the token is never spent there. attachToPlaywright covers the page it is
|
|
374
|
+
* attached to and that page's frames only, and pauses only a direct or scripted navigation to
|
|
375
|
+
* the entry: its page.route sees no HTTP redirect hop, so a merchant's redirect needs attachToCdp.
|
|
376
|
+
* Served on the same terms as NMI's and Redsys's: only to a build that lists it, for a company
|
|
377
|
+
* Agentcard turned Webpay on for.
|
|
378
|
+
*
|
|
379
|
+
* `sumup_checkout_process`: the card request SumUp's Payment Widget sends from its hidden form frame
|
|
380
|
+
* on gateway.sumup.com, a PUT to https://api.sumup.com/v0.2/checkouts/<checkout id>. This build pauses
|
|
381
|
+
* that PUT as it pauses Gravy's, so only the CDP and Playwright adapters see it (the form frame is
|
|
382
|
+
* cross-origin to the page), and answers the frame with SumUp's own answer as the API recorded it: the
|
|
383
|
+
* paid checkout, or the 3-D Secure step the widget then runs in the agent's browser. The cardholder's
|
|
384
|
+
* device sends the card itself, and the API holds the payment to a merchant Agentcard reviewed, at the
|
|
385
|
+
* amount SumUp holds for the checkout. Served on the same terms: only to a build that lists it, for a
|
|
386
|
+
* company Agentcard turned SumUp on for.
|
|
387
|
+
*
|
|
388
|
+
* `clover_card_token`: the card token request Clover's card frame (the hidden frame of
|
|
389
|
+
* checkout.clover.com that Clover's iframe mounts on a merchant's page) sends to
|
|
390
|
+
* token.clover.com/v1/tokens (token-sandbox.dev.clover.com for a test row), with the number
|
|
391
|
+
* encrypted under Clover's key. This build pauses only the frame's own body at that exact URL; the
|
|
392
|
+
* cardholder's device builds the request again with the real card under the key payment-core pins
|
|
393
|
+
* and sends it to Clover itself, and the frame gets the token answer payment-core's judge rebuilt.
|
|
394
|
+
* The request names no amount, so the agent's pair, above zero, is the one the cardholder approves
|
|
395
|
+
* (CLOVER_PSP below). Served on the same terms: only to a build that lists it, for a company
|
|
396
|
+
* Agentcard turned Clover on for.
|
|
397
|
+
*
|
|
398
|
+
* `eximbay_hosted_payment_page`: Eximbay's payment window, whose card-free signed entry
|
|
399
|
+
* (a form post to /Gateway/BasicProcessor.krp on secureapi.eximbay.com, or its test host) this
|
|
400
|
+
* build pauses for the cardholder's device, in the page or in a window it opened once attachToCdp
|
|
401
|
+
* has armed that window (eximbay.ts names the entry that can leave first), and whose
|
|
402
|
+
* step, amount query, card lookup and card post this build never lets the agent's browser send
|
|
403
|
+
* (pre-claim.ts, eximbay.ts). Served on the same terms as NMI's and Redsys's: only to a build
|
|
404
|
+
* that lists it, for a company Agentcard turned Eximbay on for.
|
|
405
|
+
*
|
|
406
|
+
* `toast_pinned_key`: the card request Toast's payments frame sends to
|
|
407
|
+
* payments.toasttab.com/v1/payment-methods on a Toast online-ordering checkout, with
|
|
408
|
+
* the card sealed in card.cardData under one key every Toast restaurant shares. The
|
|
409
|
+
* cardholder's device seals the real card under that key, which payment-core pins, and
|
|
410
|
+
* this build writes it over the agent's placeholder and nothing else before it
|
|
411
|
+
* continues the request. Served on the same terms: only to a build that lists it, for
|
|
412
|
+
* a company Agentcard turned Toast on for.
|
|
268
413
|
*/
|
|
269
414
|
export const SUPPORTED_CAPABILITIES = ['fiserv_card_capture', GMO_CAPABILITY, CYBERSOURCE_FLEX_CAPABILITY, JUSPAY_CAPABILITY, DATATRANS_CAPABILITY, 'authorize_net_dpm_form', PELECARD_CAPABILITY, ACI_CAPABILITY, 'creditguard_pps_process', PRIMER_CAPABILITY, PAYRAILS_CAPABILITY, CONVERGE_CAPABILITY, EPROTECT_CAPABILITY, WSPAY_CAPABILITY, SHIJI_STS_CAPABILITY, AMADEUS_CHECKOUT_CAPABILITY, 'worldline_mycheckout_form', 'verifone_card_capture', 'ekashu_hosted_payment_page', SECURE_ACCEPTANCE_CAPABILITY, 'gravy_session_fields_put', 'moneris_hosted_tokenization', PADDLE_VGS_CAPABILITY, PADDLE_TOKENEX_CAPABILITY,
|
|
270
|
-
'redsys_hosted_payment_page', 'lyra_krypton_payment', 'payfast_card_pay', WIX_PAYMENTS_CAPABILITY, 'paymongo_checkout_pay', HIPAY_CAPABILITY, TWOC2P_CAPABILITY, 'quickpay_hosted_payment_page'
|
|
415
|
+
'redsys_hosted_payment_page', 'lyra_krypton_payment', 'payfast_card_pay', WIX_PAYMENTS_CAPABILITY, 'paymongo_checkout_pay', PAYGENT_CAPABILITY, 'pay_nl_card_token', HIPAY_CAPABILITY, TWOC2P_CAPABILITY, 'quickpay_hosted_payment_page', 'revolut_card_field', 'paypal_card_fields', 'gopay_card_form', PAYREXX_CAPABILITY, COMGATE_CAPABILITY, PRZELEWY24_CAPABILITY, 'makecommerce_card_frame', 'poynt_collect_nonce',
|
|
416
|
+
TOKENEX_IFRAME_CAPABILITY, TRANSBANK_CAPABILITY,
|
|
417
|
+
'sumup_checkout_process',
|
|
418
|
+
'clover_card_token',
|
|
419
|
+
EXIMBAY_CAPABILITY, TOAST_CAPABILITY];
|
|
271
420
|
/**
|
|
272
421
|
* Processors whose paused request names no amount at all, so the pair the
|
|
273
422
|
* agent passes is the amount the cardholder approves: authorize() refuses one
|
|
@@ -295,24 +444,86 @@ export const SUPPORTED_CAPABILITIES = ['fiserv_card_capture', GMO_CAPABILITY, CY
|
|
|
295
444
|
* so the agent sends the total Payfast's page shows, above zero (PAYFAST_PSP below),
|
|
296
445
|
* and the API holds it to the payment Payfast itself reports. Wix's tokenize request
|
|
297
446
|
* carries none either: the merchant's page charges Wix's token for its own order, so the
|
|
298
|
-
* agent's pair, which the prepared checkout carries, is the total the cardholder approves.
|
|
447
|
+
* agent's pair, which the prepared checkout carries, is the total the cardholder approves. Toast's
|
|
448
|
+
* card request carries none either: the page's card-free confirm of its payment intent
|
|
449
|
+
* charges whatever the intent says, so the agent's pair, above zero (as Moneris's), is
|
|
450
|
+
* the one figure the approval shows.
|
|
299
451
|
* PayMongo's card attach carries none either: the merchant bound the total, always in
|
|
300
452
|
* Philippine pesos, to the checkout session when it created it, so the agent sends that
|
|
301
453
|
* total, above zero (PAYMONGO_PSP below), and the API holds it to the session PayMongo's
|
|
302
|
-
* own page shows.
|
|
454
|
+
* own page shows. GoPay's card post carries none either: the shop's server set the amount
|
|
455
|
+
* when it created the payment at GoPay, and GoPay's gateway shows it only inside its own
|
|
456
|
+
* session, so the agent sends the total the gateway shows, above zero (GOPAY_PSP below).
|
|
457
|
+
* Paygent's token request carries none either: the merchant's server charges the token later, so
|
|
458
|
+
* the agent's pair, which the prepared checkout carries, is the total the cardholder approves.
|
|
459
|
+
* Payrexx's card request names the page's total, but it only mints a token: the merchant's page
|
|
460
|
+
* charges its own total later, at its validateForm, so the agent sends the total that page shows,
|
|
461
|
+
* above zero (PAYREXX_PSP), and the API holds it to the frame's figure exactly.
|
|
462
|
+
* 2C2P's Payment carries none either: the merchant bound the total to the
|
|
303
463
|
* payment token when it created it, so the agent sends the total 2C2P's page shows, above
|
|
304
464
|
* zero (TWOC2P_PSP), and the API holds it to the amount 2C2P's own PaymentOption answer names.
|
|
465
|
+
* Przelewy24's pay wall link carries none either: the merchant's server registered the amount
|
|
466
|
+
* with Przelewy24 under the link's token, so the agent sends the amount the pay wall shows, above
|
|
467
|
+
* zero (PRZELEWY24_PSP below), and the cardholder's device holds the pay wall to it.
|
|
305
468
|
* HiPay's token create carries none either: the merchant's server charges
|
|
306
469
|
* HiPay's token for its own order, so the agent's pair, which the prepared checkout carries,
|
|
307
|
-
* is the total the cardholder approves, above zero as Moneris's is.
|
|
470
|
+
* is the total the cardholder approves, above zero as Moneris's is. Revolut's card payment
|
|
471
|
+
* names the order's amount in minor units but not its currency, which only the order holds,
|
|
472
|
+
* so the number alone is no price the cardholder can approve: the agent sends the pair, above
|
|
473
|
+
* zero (REVOLUT_PSP below), and the API holds its amount to the body's and its currency to
|
|
474
|
+
* one that Revolut and the API both count in hundredths.
|
|
475
|
+
* Pay.'s card token request carries none either: the shop's server charges the token later for
|
|
476
|
+
* its own order total, so the agent's pair, above zero (PAY_NL_PSP below), is the one figure the
|
|
477
|
+
* approval shows.
|
|
478
|
+
* PayPal Card Fields' confirm carries none either: the merchant's server fixed the
|
|
479
|
+
* amount when it created the order, so the agent's pair, above zero (PAYPAL_PSP
|
|
480
|
+
* below), is the one figure the approval shows.
|
|
481
|
+
* ComGate's gateway link carries none either: the merchant's server set
|
|
482
|
+
* the amount when it created the payment at ComGate, so the agent's pair, above zero
|
|
483
|
+
* (COMGATE_PSP below), is the figure the cardholder approves, and the cardholder's device
|
|
484
|
+
* holds ComGate's own figure to it before any card leaves.
|
|
485
|
+
* MakeCommerce's card post carries none either: MakeCommerce's transaction holds the total, in
|
|
486
|
+
* euros, so the agent sends the total the shop's order page shows, above zero (MAKECOMMERCE_PSP
|
|
487
|
+
* below), and the API refuses any other currency.
|
|
488
|
+
* GoDaddy Payments' generate-nonce carries none either: GoDaddy's server charges
|
|
489
|
+
* the nonce later, so its pair must be above zero too (POYNT_PSP below).
|
|
490
|
+
* The TokenEx iframe's card post carries none either: it mints a token into the merchant's TokenEx
|
|
491
|
+
* vault, which the merchant's server charges later for its own order, so the agent sends the total
|
|
492
|
+
* the merchant's page shows, above zero (TOKENEX_IFRAME_PSP), and the API refuses a create without it.
|
|
493
|
+
* Webpay's entry carries none either: the merchant's server bound the total, in Chilean pesos,
|
|
494
|
+
* to the transaction when it created it, so the agent's pair, above zero (TRANSBANK_PSP below),
|
|
495
|
+
* is the figure the cardholder approves, and the cardholder's device holds Webpay's own
|
|
496
|
+
* transaction to it before any card leaves.
|
|
497
|
+
* SumUp's card request carries none either: the merchant's server bound the
|
|
498
|
+
* total and its currency to the checkout when it created it, so the agent sends the total the
|
|
499
|
+
* payment page shows, above zero (SUMUP_PSP below), and the API holds it to the checkout SumUp
|
|
500
|
+
* itself reports.
|
|
501
|
+
* Clover's card token request carries none either: the merchant's server charges the token
|
|
502
|
+
* later for its own order, so the agent's pair, above zero (CLOVER_PSP below), is the one figure
|
|
503
|
+
* the approval shows.
|
|
308
504
|
*/
|
|
309
505
|
const MONERIS_PSP = 'moneris';
|
|
310
506
|
const PAYFAST_PSP = 'payfast';
|
|
311
507
|
const PAYMONGO_PSP = 'paymongo';
|
|
312
|
-
const
|
|
508
|
+
const REVOLUT_PSP = 'revolut';
|
|
509
|
+
const PAY_NL_PSP = 'pay_nl';
|
|
510
|
+
const PAYPAL_PSP = 'paypal';
|
|
511
|
+
const GOPAY_PSP = 'gopay';
|
|
512
|
+
const MAKECOMMERCE_PSP = 'makecommerce';
|
|
513
|
+
const POYNT_PSP = 'poynt';
|
|
514
|
+
const SUMUP_PSP = 'sumup';
|
|
515
|
+
const CLOVER_PSP = 'clover';
|
|
516
|
+
const AGENT_AMOUNT_PROCESSORS = [GMO_PSP, PELECARD_PSP, WSPAY_PSP, SECURE_ACCEPTANCE_PSP, MONERIS_PSP, PADDLE_VGS_PSP, PAYFAST_PSP, WIX_PAYMENTS_PSP, PAYMONGO_PSP, PAYGENT_PSP, PAY_NL_PSP, HIPAY_PSP, TWOC2P_PSP, REVOLUT_PSP, PAYPAL_PSP, GOPAY_PSP, PAYREXX_PSP, COMGATE_PSP,
|
|
517
|
+
PRZELEWY24_PSP, MAKECOMMERCE_PSP, POYNT_PSP, TOKENEX_IFRAME_PSP, TRANSBANK_PSP, SUMUP_PSP,
|
|
518
|
+
CLOVER_PSP, TOAST_PSP];
|
|
313
519
|
/** How AmountRequiredError names a processor. */
|
|
314
520
|
const PROCESSOR_NAMES = { [GMO_PSP]: 'GMO', [PELECARD_PSP]: 'Pelecard', [WSPAY_PSP]: 'WSPay',
|
|
315
|
-
[SECURE_ACCEPTANCE_PSP]: 'CyberSource Secure Acceptance', [MONERIS_PSP]: 'Moneris', [PADDLE_VGS_PSP]: 'Paddle', [PAYFAST_PSP]: 'Payfast', [WIX_PAYMENTS_PSP]: 'Wix', [PAYMONGO_PSP]: 'PayMongo', [HIPAY_PSP]: 'HiPay', [TWOC2P_PSP]: '2C2P'
|
|
521
|
+
[SECURE_ACCEPTANCE_PSP]: 'CyberSource Secure Acceptance', [MONERIS_PSP]: 'Moneris', [PADDLE_VGS_PSP]: 'Paddle', [PAYFAST_PSP]: 'Payfast', [WIX_PAYMENTS_PSP]: 'Wix', [PAYMONGO_PSP]: 'PayMongo', [PAYGENT_PSP]: 'Paygent', [PAY_NL_PSP]: 'Pay.', [HIPAY_PSP]: 'HiPay', [TWOC2P_PSP]: '2C2P',
|
|
522
|
+
[REVOLUT_PSP]: 'Revolut', [PAYPAL_PSP]: 'PayPal', [GOPAY_PSP]: 'GoPay', [PAYREXX_PSP]: 'Payrexx',
|
|
523
|
+
[COMGATE_PSP]: 'ComGate', [PRZELEWY24_PSP]: 'Przelewy24',
|
|
524
|
+
[MAKECOMMERCE_PSP]: 'MakeCommerce', [POYNT_PSP]: 'GoDaddy Payments', [TOKENEX_IFRAME_PSP]: 'TokenEx', [TRANSBANK_PSP]: 'Webpay',
|
|
525
|
+
[SUMUP_PSP]: 'SumUp',
|
|
526
|
+
[CLOVER_PSP]: 'Clover', [TOAST_PSP]: 'Toast' };
|
|
316
527
|
/**
|
|
317
528
|
* Processors whose request and answer name no amount at all, so the total the
|
|
318
529
|
* payment page shows (`pageAmount`) is the one figure that ties the agent's pair
|
|
@@ -543,13 +754,18 @@ const PROCESSOR_PLACEHOLDER_NUMBERS = Object.freeze({});
|
|
|
543
754
|
/**
|
|
544
755
|
* The placeholder number for a network (lowercase, as GET /api/v2/vault_cards names it) on a
|
|
545
756
|
* processor's page: the processor's own number for that network when `overrides` names one, else
|
|
546
|
-
* the network's default, else undefined for a network with no placeholder.
|
|
757
|
+
* the network's default, else undefined for a network with no placeholder. On a TokenEx iframe
|
|
758
|
+
* (`tokenex_iframe`) the number must also be one TokenEx's frame reads as that network, since the frame
|
|
759
|
+
* names the brand to the page from the typed digits and the cardholder's device refuses a mismatch: the
|
|
760
|
+
* frame's table takes Visa, Mastercard and American Express, so every other network gets undefined.
|
|
547
761
|
*/
|
|
548
762
|
export function placeholderNumber(brand, psp, overrides = PROCESSOR_PLACEHOLDER_NUMBERS) {
|
|
549
763
|
const own = psp !== undefined && Object.prototype.hasOwnProperty.call(overrides, psp) ? overrides[psp] : undefined;
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
764
|
+
const number = own && Object.prototype.hasOwnProperty.call(own, brand) ? own[brand]
|
|
765
|
+
: Object.prototype.hasOwnProperty.call(PLACEHOLDER_NUMBERS, brand) ? PLACEHOLDER_NUMBERS[brand] : undefined;
|
|
766
|
+
if (number !== undefined && psp === TOKENEX_IFRAME_PSP && !tokenexIframePlaceholderFits(brand, number))
|
|
767
|
+
return undefined;
|
|
768
|
+
return number;
|
|
553
769
|
}
|
|
554
770
|
/**
|
|
555
771
|
* The recognizer says this processor is preparation-required, and no preparation
|
|
@@ -575,7 +791,9 @@ export class PreparationRequiredError extends Error {
|
|
|
575
791
|
* when the merchant's server created the payment), so the `amount` and
|
|
576
792
|
* `currency` you pass are what the cardholder approves, and for WSPay the
|
|
577
793
|
* figure the API compares with WSPay's answer when that answer names one (a
|
|
578
|
-
* 3-D Secure step names none).
|
|
794
|
+
* 3-D Secure step names none). A Revolut card payment names the order's
|
|
795
|
+
* amount but not its currency, so it takes the same pair, above zero.
|
|
796
|
+
* Thrown locally before any create or
|
|
579
797
|
* prompt, and terminal: the same request without an amount fails the same
|
|
580
798
|
* way. The API answers 400 amount_required for the same case. A page total
|
|
581
799
|
* (`pageAmount`) does not count.
|
|
@@ -585,7 +803,10 @@ export class AmountRequiredError extends Error {
|
|
|
585
803
|
code = 'amount_required';
|
|
586
804
|
constructor(psp) {
|
|
587
805
|
const processor = PROCESSOR_NAMES[psp] ?? psp;
|
|
588
|
-
|
|
806
|
+
// A Payrexx card request names the frame's total, but the merchant's page charges its own total after the token.
|
|
807
|
+
const lacks = psp === REVOLUT_PSP ? 'names its amount but not its currency'
|
|
808
|
+
: psp === PAYREXX_PSP ? "only mints a token the merchant's page charges later" : 'names no amount';
|
|
809
|
+
super(`A ${processor} payment request ${lacks}, so pass amount and currency: that is the amount the cardholder approves. Nothing was created and nothing was charged.`);
|
|
589
810
|
this.psp = psp;
|
|
590
811
|
this.name = 'AmountRequiredError';
|
|
591
812
|
}
|
|
@@ -929,12 +1150,12 @@ function refusalsOf(v) {
|
|
|
929
1150
|
return out;
|
|
930
1151
|
}
|
|
931
1152
|
/**
|
|
932
|
-
* How often sendEbanxFollowUp
|
|
933
|
-
* client's own `pollIntervalMs`. One send is out at a time per approval, so this is 120
|
|
934
|
-
* API's 240 a minute per caller and authorization (routes/api/v2/checkout-follow-ups.ts).
|
|
1153
|
+
* How often sendEbanxFollowUp and sendPayrexxFollowUp read a send back while the cardholder's device has it: every
|
|
1154
|
+
* half second, whatever the client's own `pollIntervalMs`. One send is out at a time per approval, so this is 120
|
|
1155
|
+
* reads a minute, inside the API's 240 a minute per caller and authorization (routes/api/v2/checkout-follow-ups.ts).
|
|
935
1156
|
*/
|
|
936
1157
|
export const EBANX_FOLLOW_UP_READ_MS = 500;
|
|
937
|
-
/** The longest
|
|
1158
|
+
/** The longest a follow-up send waits after reads the API refused as too many (429): 1, 2, 4, 8, then 15 seconds. */
|
|
938
1159
|
export const EBANX_FOLLOW_UP_BACKOFF_MAX_MS = 15_000;
|
|
939
1160
|
export class CheckoutApiError extends Error {
|
|
940
1161
|
status;
|
|
@@ -1183,12 +1404,19 @@ function maskPaymongoSession(path) {
|
|
|
1183
1404
|
* https://secure.gopay.com/gp-card-comm/h/<hex token>?lang=..&ver=..&SG=<64 hex> (payment-core gopay.js). The
|
|
1184
1405
|
* token names that payment's card form, and with the SG, which redactUrl drops with the query, anyone who holds
|
|
1185
1406
|
* the URL can open the form and post a card to it. On GoPay's two card frame hosts the segment after /h/, in any
|
|
1186
|
-
* case or after an escaped slash, reads {token}: https://secure.gopay.com/gp-card-comm/h/{token}.
|
|
1407
|
+
* case or after an escaped slash, reads {token}: https://secure.gopay.com/gp-card-comm/h/{token}. Then every run of
|
|
1408
|
+
* 16 or more hex digits left reads {token} too, so a token after a path the frame never writes (an escaped h, a
|
|
1409
|
+
* path parameter) is masked as well; the token is 16 hex digits or more, and no other path the card frame uses holds
|
|
1410
|
+
* such a run. The pattern reads a percent escape as one unit: an escaped hex digit (%61, %2561) counts toward a run,
|
|
1411
|
+
* so escaping some of a token's digits never splits it into runs that show, and any other escape (%2F, %252F, %68)
|
|
1412
|
+
* is kept whole, so its own hex digits never start a run and leave part of a token showing.
|
|
1187
1413
|
*/
|
|
1188
1414
|
const GOPAY_CARD_FORM_HOST = /^secure(?:\.sandbox)?\.gopay\.com\.?$/i;
|
|
1189
1415
|
const GOPAY_CARD_FORM_TOKEN = new RegExp(`(${SLASH}h${SLASH}+)[^/?#]*`, 'gi');
|
|
1416
|
+
const GOPAY_TOKEN_RUN = /(?:[0-9a-f]|%(?:25)*(?:3[0-9]|[46][1-6])){16,}|%(?:25)*[0-9a-f]{2}/gi;
|
|
1417
|
+
const GOPAY_LONE_ESCAPE = /^%(?:25)*[0-9a-f]{2}$/i;
|
|
1190
1418
|
function maskGopayToken(path) {
|
|
1191
|
-
return path.replace(GOPAY_CARD_FORM_TOKEN, '$1{token}');
|
|
1419
|
+
return path.replace(GOPAY_CARD_FORM_TOKEN, '$1{token}').replace(GOPAY_TOKEN_RUN, (run) => (GOPAY_LONE_ESCAPE.test(run) ? run : '{token}'));
|
|
1192
1420
|
}
|
|
1193
1421
|
/**
|
|
1194
1422
|
* KOMOJU's Hosted Fields frame pays a session with POST /api/v1/sessions/<id>/pay on komoju.com under
|
|
@@ -1220,6 +1448,18 @@ const SUMUP_CHECKOUT = new RegExp(`(${SLASH}checkouts${SLASH}+)[^/]*`, 'gi');
|
|
|
1220
1448
|
function maskSumupCheckout(path) {
|
|
1221
1449
|
return maskUuids(path.replace(SUMUP_CHECKOUT, '$1{checkout}'), '{checkout}');
|
|
1222
1450
|
}
|
|
1451
|
+
/**
|
|
1452
|
+
* FastSpring's checkout posts its card payment to <store>.onfastspring.com[/popup-<name>]/session/<id>/payment
|
|
1453
|
+
* (payment-core fastspring.js), and the session id in that path is a credential: the session's own document,
|
|
1454
|
+
* which anyone holding the id can read with no cookie, carries the order and the session's token. On every
|
|
1455
|
+
* host under onfastspring.com the segment after /session/, in any case or after an escaped slash, reads
|
|
1456
|
+
* {session}.
|
|
1457
|
+
*/
|
|
1458
|
+
const FASTSPRING_HOST = /(?:^|\.)onfastspring\.com\.?$/i;
|
|
1459
|
+
const FASTSPRING_SESSION = /(\/session(?:\/|%2f))[^/]*/gi;
|
|
1460
|
+
function maskFastspringSession(path) {
|
|
1461
|
+
return path.replace(FASTSPRING_SESSION, '$1{session}');
|
|
1462
|
+
}
|
|
1223
1463
|
/**
|
|
1224
1464
|
* The processors whose card request carries its own credential in the URL path, with the hosts
|
|
1225
1465
|
* that send it, the text that names those hosts anywhere else (a string that is not a URL, or a
|
|
@@ -1273,6 +1513,18 @@ const REVOLUT_HOST = /^(?:sandbox-)?(?:merchant|checkout|merchant-mgmt)\.revolut
|
|
|
1273
1513
|
function maskRevolutToken(path) {
|
|
1274
1514
|
return maskUuids(path, '{token}');
|
|
1275
1515
|
}
|
|
1516
|
+
/**
|
|
1517
|
+
* ComGate's gateway pages name the payment by its transaction id in their paths (pay1.comgate.cz/<id>,
|
|
1518
|
+
* /specify/card2/<id>, payment-core processors/comgate.js), and the id alone opens that payment's gateway page, its
|
|
1519
|
+
* amount and its merchant. Its entry link names the id in the query, which redactUrl drops. On every host ComGate
|
|
1520
|
+
* owns, any id of its shape, in any case and with each hyphen in any spelling HYPHEN reads, reads {payment}. The
|
|
1521
|
+
* pattern reads a percent escape as one unit and keeps it, as maskUuids does.
|
|
1522
|
+
*/
|
|
1523
|
+
const COMGATE_HOST = /(?:^|\.)comgate\.cz\.?$/i;
|
|
1524
|
+
const COMGATE_ID = new RegExp(`%(?:25)*[0-9a-f]{2}|[a-z0-9]{4}${HYPHEN}[a-z0-9]{4}${HYPHEN}[a-z0-9]{4}`, 'gi');
|
|
1525
|
+
function maskComgateId(path) {
|
|
1526
|
+
return path.replace(COMGATE_ID, (token) => (token.startsWith('%') ? token : '{payment}'));
|
|
1527
|
+
}
|
|
1276
1528
|
/**
|
|
1277
1529
|
* Paytrail names its payment in the path of every route of its card method
|
|
1278
1530
|
* (/payments/<transaction>/click-to-pay/loading-and-redirect, and its success and cancel routes), and the same id
|
|
@@ -1377,10 +1629,12 @@ const PATH_CREDENTIALS = [
|
|
|
1377
1629
|
{ host: PRZELEWY24_HOST, named: /przelewy24\.pl/i, mask: maskPrzelewy24Token },
|
|
1378
1630
|
{ host: REVOLUT_HOST, named: /(?:merchant|checkout|merchant-mgmt)\.revolut\.com/i, mask: maskRevolutToken },
|
|
1379
1631
|
{ host: SUMUP_API_HOST, named: /api\.sumup\.com/i, mask: maskSumupCheckout },
|
|
1632
|
+
{ host: COMGATE_HOST, named: /comgate\.cz/i, mask: maskComgateId },
|
|
1380
1633
|
{ host: PAYTRAIL_HOST, named: /paytrail\.com/i, mask: maskPaytrailTransaction },
|
|
1381
1634
|
{ host: PAYMENT_HIGHWAY_HOST, named: /(?:paymenthighway\.io|sph-test-solinor\.com)/i, mask: maskPaymentHighwaySession },
|
|
1382
1635
|
{ host: TWOC2P_PAGE_HOST, named: /pgw-ui\.2c2p\.com/i, mask: maskTwoC2pToken, pathMask: maskTwoC2pPath },
|
|
1383
1636
|
{ host: QUICKPAY_HOST, named: /quickpay\.net/i, mask: maskQuickpaySession },
|
|
1637
|
+
{ host: FASTSPRING_HOST, named: /onfastspring\.com/i, mask: maskFastspringSession },
|
|
1384
1638
|
];
|
|
1385
1639
|
/**
|
|
1386
1640
|
* A URL as it may appear in an error message: origin + path only. A paused
|
|
@@ -1418,6 +1672,8 @@ const PATH_CREDENTIALS = [
|
|
|
1418
1672
|
* https://pgw-ui.2c2p.com/payment/4.1/{token}.
|
|
1419
1673
|
* On QuickPay's host the window's session reads `{session}`:
|
|
1420
1674
|
* https://payment.quickpay.net/{session}/3ds_auth.
|
|
1675
|
+
* On FastSpring's store hosts the session id reads `{session}`:
|
|
1676
|
+
* https://daisydisk.onfastspring.com/session/{session}/payment.
|
|
1421
1677
|
* A string that is not a URL keeps the part before any `?` or `#`, with the
|
|
1422
1678
|
* same segments masked for every processor whose host it names.
|
|
1423
1679
|
* A URL is read by its host only when the parser finds one. Text before a URL
|
|
@@ -1526,6 +1782,18 @@ const PADDLE_VGS_PREPARATION_REFUSALS = [
|
|
|
1526
1782
|
* `unsupported_checkout`, which PREPARATION_REFUSALS names for every checkout. Only for a Wix checkout.
|
|
1527
1783
|
*/
|
|
1528
1784
|
const WIX_PAYMENTS_PREPARATION_REFUSALS = ['processor_unavailable', 'live_host_on_sandbox_row', 'merchant_profile_mismatch'];
|
|
1785
|
+
/**
|
|
1786
|
+
* The API's refusals of a Paygent preparation, before any preparation exists, that prepareCheckout passes on
|
|
1787
|
+
* by name: Paygent is off for the company (`processor_unavailable`), the environment is not the client's test
|
|
1788
|
+
* or live mode, a live checkout's currency is not the one the reviewed merchant charges in
|
|
1789
|
+
* (`unsupported_currency`), and the price is zero (`amount_required`): Paygent's token request names no
|
|
1790
|
+
* amount, so the price the agent sends is the one the cardholder approves. A page and name no reviewed pin
|
|
1791
|
+
* lists answers `unsupported_checkout`, which PREPARATION_REFUSALS names for every checkout. The API holds the
|
|
1792
|
+
* pins, not this build, so a merchant a later release reviews needs no new SDK. Only for a Paygent checkout.
|
|
1793
|
+
*/
|
|
1794
|
+
const PAYGENT_PREPARATION_REFUSALS = [
|
|
1795
|
+
'processor_unavailable', 'sandbox_host_on_live_row', 'live_host_on_sandbox_row', 'unsupported_currency', 'amount_required',
|
|
1796
|
+
];
|
|
1529
1797
|
/**
|
|
1530
1798
|
* The API's refusals of a HiPay preparation, before any preparation exists, that prepareCheckout surfaces by name:
|
|
1531
1799
|
* the company is not turned on for HiPay (`processor_unavailable`), the environment is not the caller's key mode's
|
|
@@ -1754,7 +2022,7 @@ export class VaultClient {
|
|
|
1754
2022
|
// cybersource_sa_token_create, gravy_session_fields_put,
|
|
1755
2023
|
// moneris_hosted_tokenization, paddle_vgs_cards, paddle_tokenex_pay,
|
|
1756
2024
|
// redsys_hosted_payment_page, lyra_krypton_payment, payfast_card_pay,
|
|
1757
|
-
// quickpay_hosted_payment_page) and
|
|
2025
|
+
// quickpay_hosted_payment_page, tokenex_iframe_tokenize, among others) and
|
|
1758
2026
|
// the caller's company is one Agentcard turned that processor on for, so this
|
|
1759
2027
|
// build arms Fiserv's card capture, GMO's getToken, CyberSource Flex's card
|
|
1760
2028
|
// fields, Juspay's card flow, Datatrans's Secure Fields card post, the
|
|
@@ -1948,7 +2216,9 @@ export class VaultClient {
|
|
|
1948
2216
|
* The facts hold only while the cardholder pays with that card. `psp` names the processor
|
|
1949
2217
|
* whose card form the agent types into (the recognizer's name, `worldline_mycheckout`): a
|
|
1950
2218
|
* processor that needs its own number for the card's network gets it, and every other gets the
|
|
1951
|
-
* network's default.
|
|
2219
|
+
* network's default. On a TokenEx iframe (`tokenex_iframe`, JetBlue) the number is one TokenEx's frame
|
|
2220
|
+
* reads as the card's own network, Visa, Mastercard or American Express, and any other network has no
|
|
2221
|
+
* placeholder there. Throws PlaceholderCardError when there is no such card, its network has no
|
|
1952
2222
|
* placeholder, or it carries no expiry.
|
|
1953
2223
|
*/
|
|
1954
2224
|
async placeholderCard(input) {
|
|
@@ -2114,6 +2384,8 @@ export class VaultClient {
|
|
|
2114
2384
|
throw fail(secureAcceptanceRefusal);
|
|
2115
2385
|
const secureAcceptanceProfileId = secureAcceptance ? secureAcceptanceCheckoutProfile({ environment: input.environment,
|
|
2116
2386
|
profileId: input.secureAcceptanceProfileId, merchantOrigin: input.merchantOrigin }) : null;
|
|
2387
|
+
// A live Paygent checkout's merchant is the API's to judge against the reviewed pins it holds, before anyone is
|
|
2388
|
+
// asked to approve (PAYGENT_PREPARATION_REFUSALS); test mode needs no pin.
|
|
2117
2389
|
const timeoutMs = input.timeoutMs ?? 15 * 60_000;
|
|
2118
2390
|
if (!Number.isInteger(timeoutMs) || timeoutMs <= 0 || timeoutMs > 2_147_483_647)
|
|
2119
2391
|
throw fail('timeout_invalid');
|
|
@@ -2233,6 +2505,7 @@ export class VaultClient {
|
|
|
2233
2505
|
|| (input.psp === EPROTECT_PSP && EPROTECT_PREPARATION_REFUSALS.includes(error.code))
|
|
2234
2506
|
|| (input.psp === PADDLE_VGS_PSP && PADDLE_VGS_PREPARATION_REFUSALS.includes(error.code))
|
|
2235
2507
|
|| (input.psp === WIX_PAYMENTS_PSP && WIX_PAYMENTS_PREPARATION_REFUSALS.includes(error.code))
|
|
2508
|
+
|| (input.psp === PAYGENT_PSP && PAYGENT_PREPARATION_REFUSALS.includes(error.code))
|
|
2236
2509
|
|| (input.psp === HIPAY_PSP && HIPAY_PREPARATION_REFUSALS.includes(error.code))
|
|
2237
2510
|
|| (!!flexContext && CYBERSOURCE_FLEX_PREPARATION_REFUSALS.includes(error.code))))
|
|
2238
2511
|
|| (input.psp === 'fiserv' && error.status === 502 && error.code === 'cse_key_unavailable')))
|
|
@@ -2488,10 +2761,14 @@ export class VaultClient {
|
|
|
2488
2761
|
: `checkout authorization is POST-only; got ${method} for ${redactUrl(input.request.url)}. ` +
|
|
2489
2762
|
'A non-POST tokenizer needs recognizer support before it can be intercepted.');
|
|
2490
2763
|
}
|
|
2491
|
-
// A GET card request is its URL alone. This build completes
|
|
2492
|
-
// eProtect's registration exactly as payment-core admits it, with no body
|
|
2764
|
+
// A GET card request is its URL alone. This build completes two kinds: Worldpay
|
|
2765
|
+
// eProtect's registration exactly as payment-core admits it, with no body, and a
|
|
2766
|
+
// hosted page's entry link exactly as its rule admits it (hosted-entry.ts), with no
|
|
2767
|
+
// body, which the cardholder's device takes over.
|
|
2493
2768
|
const eprotect = rec?.psp === EPROTECT_PSP;
|
|
2494
|
-
|
|
2769
|
+
const hostedGetEntry = mode === 'hosted_form' && !!rec && isHostedGetEntry(rec.psp, input.request.url)
|
|
2770
|
+
&& (input.request.body === undefined || input.request.body === null || input.request.body === '');
|
|
2771
|
+
if (cardMethod === 'GET' && !(eprotect && isEprotectRegistration(input.request)) && !hostedGetEntry) {
|
|
2495
2772
|
throw new Error(`this ${rec?.psp} GET is not a card registration this version of @agent-cards/checkout can complete: ${redactUrl(input.request.url)}.`);
|
|
2496
2773
|
}
|
|
2497
2774
|
// A CyberSource Secure Acceptance token create (always prepared: its recognizer requires it). The
|
|
@@ -2517,11 +2794,30 @@ export class VaultClient {
|
|
|
2517
2794
|
// amount_required.
|
|
2518
2795
|
if (!hasAmount && rec && AGENT_AMOUNT_PROCESSORS.includes(rec.psp))
|
|
2519
2796
|
throw new AmountRequiredError(rec.psp);
|
|
2520
|
-
// A
|
|
2521
|
-
//
|
|
2522
|
-
//
|
|
2523
|
-
|
|
2524
|
-
|
|
2797
|
+
// A Przelewy24 pay wall charges the amount the merchant's server registered with Przelewy24 under the link's
|
|
2798
|
+
// token, so a zero pair would show the cardholder no price at all: it is refused like a missing one.
|
|
2799
|
+
// A Moneris, a HiPay or a Pay. token, or a GoDaddy Payments nonce, is charged later for whatever the merchant's
|
|
2800
|
+
// server decides, a Payfast pay, a PayMongo attach, a 2C2P Payment, a Revolut card payment or a SumUp card request
|
|
2801
|
+
// charges the total its payment, checkout session, payment token, order or checkout already holds, and a PayPal
|
|
2802
|
+
// order's amount was fixed by the merchant's server and never shown in the confirm, and a GoPay card post charges
|
|
2803
|
+
// the total GoPay's payment already holds, so a zero pair would show the cardholder no price at all: it is refused
|
|
2804
|
+
// like a missing one.
|
|
2805
|
+
// A ComGate payment charges the total ComGate already holds for it, the same way, and a Webpay transaction the
|
|
2806
|
+
// total the merchant's server bound to it, and a Payrexx page charges its own
|
|
2807
|
+
// total after the frame's token.
|
|
2808
|
+
// A Moneris token is charged later for whatever the merchant's server decides, and a Payfast pay, a
|
|
2809
|
+
// PayMongo attach or a MakeCommerce card post charges the total its payment, checkout session or
|
|
2810
|
+
// transaction already holds, so a zero pair would show the cardholder no price at all: it is refused
|
|
2811
|
+
// like a missing one. So is a TokenEx iframe token, which the merchant's server charges later for its own order.
|
|
2812
|
+
// A Clover token is charged later for whatever the merchant's server decides, as a Moneris one is.
|
|
2813
|
+
// A Toast payment method is charged by the page's own confirm for whatever its intent says, the same way.
|
|
2814
|
+
if (hasAmount && rec && rec.psp === PRZELEWY24_PSP && Number(input.amount) <= 0)
|
|
2815
|
+
throw new AmountRequiredError(rec.psp);
|
|
2816
|
+
if (hasAmount && rec && (rec.psp === MONERIS_PSP || rec.psp === PAYFAST_PSP || rec.psp === PAYMONGO_PSP || rec.psp === HIPAY_PSP || rec.psp === TWOC2P_PSP
|
|
2817
|
+
|| rec.psp === REVOLUT_PSP || rec.psp === PAY_NL_PSP || rec.psp === PAYPAL_PSP
|
|
2818
|
+
|| rec.psp === GOPAY_PSP || rec.psp === COMGATE_PSP || rec.psp === MAKECOMMERCE_PSP
|
|
2819
|
+
|| rec.psp === POYNT_PSP || rec.psp === TOKENEX_IFRAME_PSP || rec.psp === TRANSBANK_PSP || rec.psp === SUMUP_PSP
|
|
2820
|
+
|| rec.psp === CLOVER_PSP || rec.psp === PAYREXX_PSP || rec.psp === TOAST_PSP) && Number(input.amount) <= 0)
|
|
2525
2821
|
throw new AmountRequiredError(rec.psp);
|
|
2526
2822
|
// Nor is the agent's pair tied to the page by anything but the total the
|
|
2527
2823
|
// page shows: without it the cardholder could approve one figure while the
|
|
@@ -2857,6 +3153,10 @@ export class VaultClient {
|
|
|
2857
3153
|
// source.encryptionData and removes nothing (fiserv.ts); any other shape is not one to act on.
|
|
2858
3154
|
if (rec.psp === 'fiserv' && !isFiservSubstitution(sub))
|
|
2859
3155
|
throw new PaymentOutcomeUnknownError(authorizationId, 'cse_substitutions_malformed');
|
|
3156
|
+
// A Toast approval carries exactly one sealed card, cardData at `card`, and removes nothing
|
|
3157
|
+
// (payment-core toast-seal.js substituteToastCardData checks the rest at the swap).
|
|
3158
|
+
if (rec.psp === TOAST_PSP && !isToastSubstitution(sub))
|
|
3159
|
+
throw new PaymentOutcomeUnknownError(authorizationId, 'cse_substitutions_malformed');
|
|
2860
3160
|
// `remove`: sibling keys the API says to drop with the swap (Adyen's
|
|
2861
3161
|
// `brand`, stamped by adyen-web from the agent's dummy digits). Absent
|
|
2862
3162
|
// on an older API; anything but a list of names is refused, since a
|
|
@@ -3027,6 +3327,18 @@ export class VaultClient {
|
|
|
3027
3327
|
response.headers = answer.headers;
|
|
3028
3328
|
response.body = answer.body;
|
|
3029
3329
|
}
|
|
3330
|
+
if (rec?.psp === PAYGENT_PSP) {
|
|
3331
|
+
// Paygent's answer enters the page only as payment-core rebuilds an approved token from its members
|
|
3332
|
+
// (paygent.js paygentAnswerOutcome: HTTP 200, result 0000 and a token in shape), under a JSON content
|
|
3333
|
+
// type. Anything else, a refusal Paygent answers inside HTTP 200 included, is never handed over as an
|
|
3334
|
+
// approval; the card may have left the device, so the outcome is unknown rather than declined.
|
|
3335
|
+
const judged = paygentAnswerOutcome(response.status, response.body, null);
|
|
3336
|
+
if (judged.outcome !== 'approved' || typeof judged.body !== 'string')
|
|
3337
|
+
throw new PaymentOutcomeUnknownError(authorizationId, 'paygent_token_unconfirmed');
|
|
3338
|
+
response.status = 200;
|
|
3339
|
+
response.headers = { 'content-type': 'application/json' };
|
|
3340
|
+
response.body = judged.body;
|
|
3341
|
+
}
|
|
3030
3342
|
if (rec?.psp === PRIMER_PSP) {
|
|
3031
3343
|
// Primer's answer enters the page only as one single-use card token rebuilt from
|
|
3032
3344
|
// payment-core's allow-list, under a JSON content type (primer.ts primerTokenAnswer).
|
|
@@ -3039,6 +3351,18 @@ export class VaultClient {
|
|
|
3039
3351
|
response.headers = answer.headers;
|
|
3040
3352
|
response.body = answer.body;
|
|
3041
3353
|
}
|
|
3354
|
+
if (rec?.psp === TOKENEX_IFRAME_PSP) {
|
|
3355
|
+
// TokenEx's answer enters the frame only as TokenEx's 200 with a sound token, rebuilt from payment-core's
|
|
3356
|
+
// allow-list under a JSON content type (tokenex-iframe.ts tokenexIframePageAnswer); the frame adds its own
|
|
3357
|
+
// cardType. Anything else is never handed over: the device may already have sent the card, so the
|
|
3358
|
+
// outcome is unknown rather than declined.
|
|
3359
|
+
const answer = tokenexIframePageAnswer(response.status, response.body);
|
|
3360
|
+
if (!answer)
|
|
3361
|
+
throw new PaymentOutcomeUnknownError(authorizationId, 'tokenex_iframe_token_unconfirmed');
|
|
3362
|
+
response.status = answer.status;
|
|
3363
|
+
response.headers = answer.headers;
|
|
3364
|
+
response.body = answer.body;
|
|
3365
|
+
}
|
|
3042
3366
|
let processorContext;
|
|
3043
3367
|
if (mercadoContext) {
|
|
3044
3368
|
try {
|
|
@@ -3069,8 +3393,9 @@ export class VaultClient {
|
|
|
3069
3393
|
chargedAmount: typeof s.charged_amount === 'number' ? s.charged_amount : null,
|
|
3070
3394
|
chargedCurrency: typeof s.charged_currency === 'string' ? s.charged_currency : null,
|
|
3071
3395
|
chargedKind: s.charged_kind === 'captured' || s.charged_kind === 'authorized' || s.charged_kind === 'none' ? s.charged_kind : null,
|
|
3072
|
-
// An EBANX token approval
|
|
3073
|
-
|
|
3396
|
+
// An EBANX token approval, or a Payrexx card request whose answer started 3-D Secure, that covers the sends
|
|
3397
|
+
// after it names until when (ebanx.ts EbanxFollowUps, payrexx.ts PayrexxFollowUps).
|
|
3398
|
+
...((rec?.psp === 'ebanx' || rec?.psp === PAYREXX_PSP) && typeof s.follow_ups?.until === 'string' && Number.isFinite(Date.parse(s.follow_ups.until))
|
|
3074
3399
|
? { followUpsUntil: s.follow_ups.until } : {}),
|
|
3075
3400
|
...amountAuthority,
|
|
3076
3401
|
...execution,
|
|
@@ -3283,6 +3608,20 @@ export class VaultClient {
|
|
|
3283
3608
|
* an answer that carried the card, or `options.until` (the approval's window, plus the API's own clocks) passed.
|
|
3284
3609
|
*/
|
|
3285
3610
|
async sendEbanxFollowUp(authorizationId, request, options = {}) {
|
|
3611
|
+
return this.sendFollowUp(authorizationId, request, options, (reason, cardSent) => new EbanxFollowUpError(reason, cardSent, authorizationId));
|
|
3612
|
+
}
|
|
3613
|
+
/**
|
|
3614
|
+
* Send one of Payrexx's 3-D Secure sends an approved Payrexx card request covers (payrexx.ts says which: the card
|
|
3615
|
+
* frame's authentication after the ACS method and its post-authentication after the bank's challenge, bound to that
|
|
3616
|
+
* approval) from the cardholder's device, and resolve with Payrexx's answer for the paused request, exactly as
|
|
3617
|
+
* sendEbanxFollowUp does for EBANX: the same create, reads and clocks, and no prompt. Rejects with a
|
|
3618
|
+
* PayrexxFollowUpError naming why there is no answer and whether the card may have reached Payrexx.
|
|
3619
|
+
*/
|
|
3620
|
+
async sendPayrexxFollowUp(authorizationId, request, options = {}) {
|
|
3621
|
+
return this.sendFollowUp(authorizationId, request, options, (reason, cardSent) => new PayrexxFollowUpError(reason, cardSent, authorizationId));
|
|
3622
|
+
}
|
|
3623
|
+
/** The send behind sendEbanxFollowUp and sendPayrexxFollowUp; `fail` builds the processor's error for a send with no answer. */
|
|
3624
|
+
async sendFollowUp(authorizationId, request, options, fail) {
|
|
3286
3625
|
if (!/^cauth_[A-Za-z0-9_-]{1,128}$/.test(authorizationId))
|
|
3287
3626
|
throw new Error('Invalid authorization ID.');
|
|
3288
3627
|
const base = `/v2/checkout/authorizations/${authorizationId}/follow_ups`;
|
|
@@ -3296,16 +3635,16 @@ export class VaultClient {
|
|
|
3296
3635
|
catch (error) {
|
|
3297
3636
|
// A refusal (a 4xx answer) stored no send, so nothing left. Any other failure (a 5xx, a timeout, a dropped
|
|
3298
3637
|
// connection, an unreadable 201) may have come after the API stored the send, which the device then sends:
|
|
3299
|
-
// the card may reach
|
|
3638
|
+
// the card may reach the processor, and for an EBANX confirm that is a payment whose outcome is unknown.
|
|
3300
3639
|
const refused = error instanceof CheckoutApiError && error.status >= 400 && error.status < 500;
|
|
3301
3640
|
const reason = error instanceof CheckoutApiError
|
|
3302
3641
|
? (typeof error.details.reason === 'string' && /^[a-z_]{1,64}$/.test(error.details.reason) ? error.details.reason : error.code ?? `http_${error.status}`)
|
|
3303
3642
|
: 'follow_up_unavailable';
|
|
3304
|
-
throw
|
|
3643
|
+
throw fail(reason, !refused);
|
|
3305
3644
|
}
|
|
3306
3645
|
const id = created?.id;
|
|
3307
3646
|
if (typeof id !== 'string' || !/^cfu_[0-9a-f]{24}$/.test(id) || created.authorization !== authorizationId) {
|
|
3308
|
-
throw
|
|
3647
|
+
throw fail('follow_up_unrecognized', true);
|
|
3309
3648
|
}
|
|
3310
3649
|
// Reads the API refused in a row as too many (429): each waits twice as long as the last before the next read.
|
|
3311
3650
|
let limited = 0;
|
|
@@ -3317,14 +3656,14 @@ export class VaultClient {
|
|
|
3317
3656
|
}
|
|
3318
3657
|
catch (error) {
|
|
3319
3658
|
if (signal.aborted)
|
|
3320
|
-
throw
|
|
3659
|
+
throw fail('answer_late', true);
|
|
3321
3660
|
if (error instanceof CheckoutApiError && error.status === 429)
|
|
3322
3661
|
limited += 1;
|
|
3323
3662
|
}
|
|
3324
3663
|
if (read?.status === 'answered') {
|
|
3325
3664
|
const r = read.response;
|
|
3326
3665
|
if (!r || !Number.isInteger(r.status) || r.status < 200 || r.status > 599 || typeof r.body !== 'string' || !r.headers || typeof r.headers !== 'object') {
|
|
3327
|
-
throw
|
|
3666
|
+
throw fail('follow_up_unrecognized', true);
|
|
3328
3667
|
}
|
|
3329
3668
|
const headers = {};
|
|
3330
3669
|
for (const [name, value] of Object.entries(r.headers))
|
|
@@ -3334,10 +3673,10 @@ export class VaultClient {
|
|
|
3334
3673
|
}
|
|
3335
3674
|
if (read?.status === 'failed' || read?.status === 'expired') {
|
|
3336
3675
|
const reason = typeof read.failure_reason === 'string' && /^[a-z_]{1,64}$/.test(read.failure_reason) ? read.failure_reason : read.status;
|
|
3337
|
-
throw
|
|
3676
|
+
throw fail(reason, read.card_sent !== false);
|
|
3338
3677
|
}
|
|
3339
3678
|
if (signal.aborted || Date.now() >= deadline)
|
|
3340
|
-
throw
|
|
3679
|
+
throw fail('answer_late', true);
|
|
3341
3680
|
const wait = limited === 0 ? EBANX_FOLLOW_UP_READ_MS : Math.min(EBANX_FOLLOW_UP_BACKOFF_MAX_MS, 1000 * 2 ** (limited - 1));
|
|
3342
3681
|
await interruptibleSleep(Math.max(1, Math.min(wait, deadline - Date.now())), signal);
|
|
3343
3682
|
}
|
|
@@ -3898,3 +4237,14 @@ function interruptibleSleep(ms, signal) {
|
|
|
3898
4237
|
signal?.addEventListener('abort', done, { once: true });
|
|
3899
4238
|
});
|
|
3900
4239
|
}
|
|
4240
|
+
/** A served Toast set: exactly { cardData } at `card`, a 256-byte base64 value, with nothing to remove. */
|
|
4241
|
+
function isToastSubstitution(sub) {
|
|
4242
|
+
if (!sub || typeof sub !== 'object')
|
|
4243
|
+
return false;
|
|
4244
|
+
const { at, fields, remove } = sub;
|
|
4245
|
+
if (at !== 'card' || !fields || typeof fields !== 'object' || Array.isArray(fields))
|
|
4246
|
+
return false;
|
|
4247
|
+
const keys = Object.keys(fields);
|
|
4248
|
+
return keys.length === 1 && keys[0] === 'cardData' && isToastCiphertext(fields.cardData)
|
|
4249
|
+
&& (remove === undefined || (Array.isArray(remove) && remove.length === 0));
|
|
4250
|
+
}
|