@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/pre-claim.js
CHANGED
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
// the vault can never pay, CyberSource Flex Microform's field iframes (whose
|
|
8
8
|
// capture context rides into the pause), Datatrans Secure Fields, whose
|
|
9
9
|
// card URL also carries card posts the device could never fill and card posts
|
|
10
|
-
// it already answered,
|
|
10
|
+
// it already answered, the TokenEx hosted iframe v3, whose card path carries the
|
|
11
|
+
// same two, and Pelecard's card submission URL, which also carries
|
|
11
12
|
// saved-token, wallet and keep-on-file bodies, Primer's
|
|
12
13
|
// /payment-instruments, which also carries requests the vault can never pay,
|
|
13
14
|
// Payrails, whose save needs the session key the page captured and whose
|
|
@@ -63,6 +64,7 @@ import { gmoRequestRefusal, isGmoTokenHost, isGmoTokenV1Request, recognizesGmo }
|
|
|
63
64
|
import { JUSPAY_PSP } from './juspay-gate.js';
|
|
64
65
|
import { datatransPauseDecision, isDatatransHost } from './datatrans.js';
|
|
65
66
|
import { DATATRANS_INVALID_REQUEST } from './datatrans.generated.js';
|
|
67
|
+
import { INVALID_TOKENEX_IFRAME_REQUEST, TOKENEX_IFRAME_PSP, isTokenexIframePath, servesTokenexIframe, tokenexIframePauseDecision, } from './tokenex-iframe.js';
|
|
66
68
|
import { isPelecardHost, pelecardRefusal } from './pelecard.js';
|
|
67
69
|
import { ACI_CHECKOUT_REUSED } from './aci.js';
|
|
68
70
|
import { ACI_EXPIRY_UNSTRIPPABLE } from './aci.generated.js';
|
|
@@ -79,8 +81,12 @@ import { cardEndpointStoppedPage, refusedNavigationOutcome } from './card-endpoi
|
|
|
79
81
|
import { PAYRAILS_HOLDING_REASONS, PAYRAILS_SESSION_MESSAGE, isPayrailsHost, isPayrailsPaymentUrl, isPayrailsStaticHost, payrailsKeyRefusal, payrailsPaymentRefusal, payrailsRequestKind, payrailsSaveRefusal, } from './payrails.js';
|
|
80
82
|
import { classifyVerifoneProfileRequest, verifoneTemplatedUrl } from './verifone.js';
|
|
81
83
|
import { ekashuEntryUrl, isEkashuEntry, isEkashuHost } from './ekashu.js';
|
|
84
|
+
import { isArmedTransbankUrl } from './transbank.js';
|
|
82
85
|
import { isRedsysEntry, isRedsysHost, redsysEntryUrl } from './redsys.js';
|
|
86
|
+
import { isArmedPrzelewy24Url, isPrzelewy24PayStep } from './przelewy24.js';
|
|
83
87
|
import { isQuickpayCardLookup, isQuickpayEntry, quickpayEntryUrl } from './quickpay.js';
|
|
88
|
+
import { isArmedComgateUrl } from './comgate.js';
|
|
89
|
+
import { isArmedEximbayUrl, isEximbayEntry } from './eximbay.js';
|
|
84
90
|
import { PAYPAL_HOSTED_FIELDS_REASON, isPaypalHostedFieldsConfirm, paypalHostedFieldsBody } from './paypal-hosted-fields.js';
|
|
85
91
|
import { PAYPAL_CARD_FIELDS_REASON, PAYPAL_GUEST_CHECKOUT_REASON, isPaypalCardFieldsConfirm, isPaypalGraphql, paypalCardFieldsBody, paypalGraphqlCard, paypalGraphqlNamesCardOperation, } from './paypal-card-forms.js';
|
|
86
92
|
import { CYBERSOURCE_SECURE_ACCEPTANCE_REASON, CYBERSOURCE_SECURE_ACCEPTANCE_RECIPIENT, cybersourceSecureAcceptanceRequest, isCybersourceSecureAcceptanceUrl, isSecureAcceptanceFramePost, } from './cybersource-secure-acceptance.js';
|
|
@@ -88,6 +94,7 @@ import { isWorldlineMyCheckoutEndpoint } from './worldline-mycheckout.js';
|
|
|
88
94
|
import { paddleVgsPayEventUrl } from './paddle-vgs-pay.js';
|
|
89
95
|
import { HIPAY_PSP, HIPAY_TOKEN_URLS, HIPAY_UPDATE_PATH, HIPAY_VAULT_HOSTS, hipayNumberOnlyRequest } from './hipay.generated.js';
|
|
90
96
|
import { EBANX_RESEND_REASON, EbanxFollowUpError, ebanxResendRefusalOf, isEbanxResendUrl, } from './ebanx.js';
|
|
97
|
+
import { PAYREXX_RESEND_REASON, PayrexxFollowUpError, isPayrexxResend, } from './payrexx.js';
|
|
91
98
|
/**
|
|
92
99
|
* The URL an event may name for a request. For an armed merchant profile's
|
|
93
100
|
* endpoint or a sibling of it: the endpoint origin, the path TEMPLATE and the
|
|
@@ -817,10 +824,10 @@ async function juspayUnsupported(ctx, reason) {
|
|
|
817
824
|
// card-on-file policy the API served with the recognizer (VaultClient.cardOnFilePolicy):
|
|
818
825
|
// - a sound TOKENIZE card post defers, and the arbiter pauses it for approval, unless the same
|
|
819
826
|
// frame's same form was already answered with an approved token (the repeat rule) and that
|
|
820
|
-
// approval is still the one being handed off (
|
|
827
|
+
// approval is still the one being handed off (handoffRepeatApproval): then that stored answer
|
|
821
828
|
// is fulfilled here, with no claim, create or prompt. Once the answer reached the page, the
|
|
822
829
|
// application's merchant resolver must first report the order still pending
|
|
823
|
-
// (
|
|
830
|
+
// (repeatAfterHandoff);
|
|
824
831
|
// - a card post the API would refuse at create aborts with a `blocked` event that names the
|
|
825
832
|
// API's own code, `invalid_datatrans_request` or `datatrans_card_on_file_refused` (the latter
|
|
826
833
|
// only while the served policy refuses card on file), before anything is claimed, created or
|
|
@@ -848,25 +855,26 @@ const datatransPreClaim = (ctx) => {
|
|
|
848
855
|
return undefined;
|
|
849
856
|
if (decision !== 'card')
|
|
850
857
|
return datatransBlock(ctx, decision);
|
|
851
|
-
const approval =
|
|
858
|
+
const approval = handoffRepeatApproval(ctx);
|
|
852
859
|
const repeat = approval === null ? null : ctx.datatrans?.answerFor(ctx.frame, url, ctx.request.body, approval) ?? null;
|
|
853
860
|
if (!repeat)
|
|
854
861
|
return undefined;
|
|
855
|
-
return ctx.lifecycle.getState().status === 'awaiting_merchant' ?
|
|
862
|
+
return ctx.lifecycle.getState().status === 'awaiting_merchant' ? repeatAfterHandoff(ctx, repeat, 'datatrans') : answerRepeat(ctx, repeat, 'datatrans');
|
|
856
863
|
};
|
|
857
864
|
/**
|
|
858
|
-
* The approval whose stored Datatrans answer a repeat may still receive, or null. A stored token
|
|
865
|
+
* The approval whose stored Datatrans or TokenEx answer a repeat may still receive, or null. A stored token
|
|
859
866
|
* answers a re-submit only while the approval it came from is still being handed off: the lifecycle
|
|
860
867
|
* names it and reads awaiting_approval (its answer is being delivered) or awaiting_merchant (the
|
|
861
868
|
* merchant has not confirmed yet), and nothing latched, tore down or cancelled the attachment. The
|
|
862
869
|
* order completing or failing, an unknown outcome, a decline, a hold for the cardholder, a retry
|
|
863
870
|
* after a merchant failure and a new authorization all end it, and the re-submit is then a new
|
|
864
|
-
* question for the terminal arbiter, which holds or pauses it like any card post:
|
|
865
|
-
* amount, so one approval never answers for a later purchase. Once the answer reached the
|
|
866
|
-
* (awaiting_merchant), the gate also reconciles before it answers (
|
|
867
|
-
*
|
|
871
|
+
* question for the terminal arbiter, which holds or pauses it like any card post: neither card post
|
|
872
|
+
* binds an amount, so one approval never answers for a later purchase. Once the answer reached the
|
|
873
|
+
* page (awaiting_merchant), the gate also reconciles before it answers (repeatAfterHandoff). A stored
|
|
874
|
+
* Datatrans answer also ends once its form goes quiet (DatatransRepeats, DATATRANS_REPEAT_IDLE_MS), and
|
|
875
|
+
* a stored TokenEx answer once its iframe key expires (TokenexIframeRepeats).
|
|
868
876
|
*/
|
|
869
|
-
function
|
|
877
|
+
function handoffRepeatApproval(ctx) {
|
|
870
878
|
if (ctx.terminal() || ctx.interceptionAborted() || ctx.lifecycleAborted())
|
|
871
879
|
return null;
|
|
872
880
|
const { status, authorizationId } = ctx.lifecycle.getState();
|
|
@@ -883,36 +891,82 @@ function datatransRepeatApproval(ctx) {
|
|
|
883
891
|
* arbiter, and so does every re-submit on an attachment with no resolver: a later purchase never
|
|
884
892
|
* receives an earlier token without the cardholder, and the paused post never waits on a stalled read.
|
|
885
893
|
*/
|
|
886
|
-
function
|
|
894
|
+
function repeatAfterHandoff(ctx, repeat, psp) {
|
|
887
895
|
if (!ctx.lifecycle.hasMerchantResolver())
|
|
888
896
|
return undefined;
|
|
889
|
-
return
|
|
897
|
+
return pendingRepeat(ctx, repeat, psp);
|
|
890
898
|
}
|
|
891
|
-
async function
|
|
899
|
+
async function pendingRepeat(ctx, repeat, psp) {
|
|
892
900
|
if (!await ctx.lifecycle.merchantOrderPending())
|
|
893
901
|
return undefined;
|
|
894
902
|
// The resolver answered asynchronously: the checkout must still be handing off this approval.
|
|
895
|
-
if (ctx.lifecycle.getState().status !== 'awaiting_merchant' ||
|
|
903
|
+
if (ctx.lifecycle.getState().status !== 'awaiting_merchant' || handoffRepeatApproval(ctx) !== repeat.authorizationId)
|
|
896
904
|
return undefined;
|
|
897
|
-
return
|
|
905
|
+
return answerRepeat(ctx, repeat, psp);
|
|
898
906
|
}
|
|
899
907
|
async function datatransBlock(ctx, reason) {
|
|
900
908
|
ctx.onEvent?.({ type: 'blocked', detail: reason });
|
|
901
909
|
await ctx.effects.abort();
|
|
902
910
|
return { kind: 'abort' };
|
|
903
911
|
}
|
|
904
|
-
|
|
912
|
+
/** Fulfill a repeat with the stored answer (`repeat_answered`), or abort it when the browser cannot take it (`<psp>_repeat_unanswered`). */
|
|
913
|
+
async function answerRepeat(ctx, repeat, psp) {
|
|
905
914
|
try {
|
|
906
915
|
await ctx.effects.answer(repeat.response);
|
|
907
916
|
}
|
|
908
917
|
catch {
|
|
909
|
-
ctx.onEvent?.({ type: 'blocked', detail:
|
|
918
|
+
ctx.onEvent?.({ type: 'blocked', detail: `${psp}_repeat_unanswered` });
|
|
910
919
|
await ctx.effects.abort();
|
|
911
920
|
return { kind: 'abort' };
|
|
912
921
|
}
|
|
913
|
-
ctx.onEvent?.({ type: 'repeat_answered', detail: { psp
|
|
922
|
+
ctx.onEvent?.({ type: 'repeat_answered', detail: { psp, authorizationId: repeat.authorizationId } });
|
|
914
923
|
return { kind: 'answer' };
|
|
915
924
|
}
|
|
925
|
+
// The TokenEx hosted iframe v3 (tokenex-iframe.ts), JetBlue's card form. TokenEx's card-number frame posts the
|
|
926
|
+
// whole card to its own document URL, and the same path takes posts the device could never fill (a cvvOnly frame,
|
|
927
|
+
// a query this lane has not read) and, after an approval, the frame's own re-sends. The recognizer matches only the
|
|
928
|
+
// exact card URL, so once the registry serves the TokenEx iframe (servesTokenexIframe), every POST on the frame's
|
|
929
|
+
// /iframe/v3 path is judged here, before the terminal arbiter would pause it, by payment-core's own pause decision:
|
|
930
|
+
// - the sound card post defers, and the arbiter pauses it for approval, unless the same frame already sent the
|
|
931
|
+
// same bytes and had them answered with an approved token, and that approval is still the one being handed off
|
|
932
|
+
// (handoffRepeatApproval): then that stored answer is fulfilled here, with no claim, create or prompt. Once the
|
|
933
|
+
// answer reached the page, the application's merchant resolver must first report the order still pending
|
|
934
|
+
// (repeatAfterHandoff). A post with another placeholder or CVV is a new question, as for Datatrans;
|
|
935
|
+
// - any other post on that path that carries a value in Data aborts with a `blocked` event that names the API's
|
|
936
|
+
// own code, invalid_tokenex_iframe_request, before anything is claimed, created or prompted. Like Datatrans's
|
|
937
|
+
// refusal it neither holds the attachment nor changes its state;
|
|
938
|
+
// - a post with nothing in Data carries no card and defers: on the exact URL it continues untouched through the
|
|
939
|
+
// recognizer's passWithoutCard, and anywhere else it continues as an unrecognized request.
|
|
940
|
+
// Nothing is decided without the registry: for an org the API did not serve the entry to, the gate defers and every
|
|
941
|
+
// TokenEx request continues as before. The frame's document GETs, BinLookup and Forter never reach it as a card post.
|
|
942
|
+
// Synchronous until an effect, so a card post pauses with no await. A check that throws blocks the request.
|
|
943
|
+
const tokenexIframePreClaim = (ctx) => {
|
|
944
|
+
const { url, method } = ctx.request;
|
|
945
|
+
if (method.toUpperCase() !== 'POST' || !isTokenexIframePath(url) || !servesTokenexIframe(ctx.vault))
|
|
946
|
+
return undefined;
|
|
947
|
+
let decision;
|
|
948
|
+
try {
|
|
949
|
+
decision = tokenexIframePauseDecision({ url, headers: ctx.request.headers, body: ctx.request.body });
|
|
950
|
+
}
|
|
951
|
+
catch {
|
|
952
|
+
decision = INVALID_TOKENEX_IFRAME_REQUEST;
|
|
953
|
+
}
|
|
954
|
+
if (decision === null)
|
|
955
|
+
return undefined;
|
|
956
|
+
if (decision !== 'card')
|
|
957
|
+
return tokenexIframeBlock(ctx, decision);
|
|
958
|
+
const approval = handoffRepeatApproval(ctx);
|
|
959
|
+
const repeat = approval === null ? null : ctx.tokenexIframe?.answerFor(ctx.frame, url, ctx.request.body, approval) ?? null;
|
|
960
|
+
if (!repeat)
|
|
961
|
+
return undefined;
|
|
962
|
+
return ctx.lifecycle.getState().status === 'awaiting_merchant'
|
|
963
|
+
? repeatAfterHandoff(ctx, repeat, TOKENEX_IFRAME_PSP) : answerRepeat(ctx, repeat, TOKENEX_IFRAME_PSP);
|
|
964
|
+
};
|
|
965
|
+
async function tokenexIframeBlock(ctx, reason) {
|
|
966
|
+
ctx.onEvent?.({ type: 'blocked', detail: reason });
|
|
967
|
+
await ctx.effects.abort();
|
|
968
|
+
return { kind: 'abort' };
|
|
969
|
+
}
|
|
916
970
|
// Pelecard Iframe/Redirect 2.0 (pelecard.ts). The payment page's card URL, POST
|
|
917
971
|
// /PaymentGW/Transaction on gateway21 or gateway20.pelecard.biz, also carries the wallet, Bit,
|
|
918
972
|
// PIN pad and bank tabs with no card, and card-shaped bodies the device could never fill: a saved
|
|
@@ -1574,6 +1628,23 @@ async function shijiStsCompanion(ctx, body) {
|
|
|
1574
1628
|
ctx.onEvent?.({ type: 'companion_continued', detail: { url: redactUrl(url), psp: 'shiji_sts', fields: ['cvv'] } });
|
|
1575
1629
|
return { kind: 'continue' };
|
|
1576
1630
|
}
|
|
1631
|
+
// Przelewy24's pay wall (przelewy24.ts). Once this build's registry serves Przelewy24's entry (a capability the
|
|
1632
|
+
// API serves only to a company Agentcard turned Przelewy24 on for), every request to the pay wall's two hosts
|
|
1633
|
+
// pauses. The merchant's entry, a GET of /trnRequest/<token> exactly, is no step of this gate's: it defers, and the
|
|
1634
|
+
// terminal arbiter pauses it for the claim flow like any card request, so the cardholder's device pays it. Every
|
|
1635
|
+
// step toward paying on the pay wall itself aborts as an unsupported checkout: every POST there (the card method's
|
|
1636
|
+
// post, the card form's post, a form posted to the entry's link) and the GET of the Saferpay resolver, which names
|
|
1637
|
+
// the typed card's type. So Przelewy24 never sees the card the agent typed. The agent's browser only reaches the
|
|
1638
|
+
// pay wall when the entry was not paused (an attachment made too late, say), and then only the cardholder's device
|
|
1639
|
+
// can pay. The pay wall's own pages, scripts and images are not this gate's and continue. With Przelewy24 not
|
|
1640
|
+
// served, the gate defers, and every request continues as before. Synchronous until it must abort, like every gate.
|
|
1641
|
+
// Exported for the gate registry's tests.
|
|
1642
|
+
export const przelewy24PreClaim = (ctx) => {
|
|
1643
|
+
const { url, method } = ctx.request;
|
|
1644
|
+
if (!isPrzelewy24PayStep(url, method) || !isArmedPrzelewy24Url(ctx.vault, url))
|
|
1645
|
+
return undefined;
|
|
1646
|
+
return processorPageAbort(ctx);
|
|
1647
|
+
};
|
|
1577
1648
|
// NMI's hosted payment page (ekashu.ts). Once this build's registry serves NMI's entry (a
|
|
1578
1649
|
// capability the API serves only to a company Agentcard turned NMI on for), every request to
|
|
1579
1650
|
// NMI's two hosts pauses, and this gate judges the POSTs:
|
|
@@ -1598,7 +1669,7 @@ export const ekashuPreClaim = (ctx) => {
|
|
|
1598
1669
|
return processorPageAbort(ctx);
|
|
1599
1670
|
};
|
|
1600
1671
|
// The stop for a card post into a processor's own page that only the cardholder's device may pay (NMI's, Redsys's,
|
|
1601
|
-
// QuickPay's).
|
|
1672
|
+
// QuickPay's, Eximbay's).
|
|
1602
1673
|
async function processorPageAbort(ctx) {
|
|
1603
1674
|
const { url, method } = ctx.request;
|
|
1604
1675
|
ctx.preparationGate.invalidate('unsupported_checkout');
|
|
@@ -1607,6 +1678,27 @@ async function processorPageAbort(ctx) {
|
|
|
1607
1678
|
await ctx.effects.abort();
|
|
1608
1679
|
return { kind: 'abort' };
|
|
1609
1680
|
}
|
|
1681
|
+
// Transbank's Webpay Plus (transbank.ts). Once this build's registry serves Webpay's entry (a capability the API
|
|
1682
|
+
// serves only to a company Agentcard turned Webpay on for), every request to Webpay's two hosts pauses, and this gate
|
|
1683
|
+
// judges them:
|
|
1684
|
+
// - the merchant's entry, the GET of initTransaction?token_ws=<token> exactly as the redirect writes it, is a GET
|
|
1685
|
+
// and not this gate's: the terminal arbiter pauses it for the claim flow like any card request, and the
|
|
1686
|
+
// cardholder's device pays it;
|
|
1687
|
+
// - every other request there but a GET (the token's one submit to init_transaction.cgi, Webpay's card lookup and
|
|
1688
|
+
// card check, the card post to bp_control.cgi, and an entry posted as a form) aborts as an unsupported checkout,
|
|
1689
|
+
// so the token is never spent in the agent's browser and Webpay never sees the card the agent typed. The agent's
|
|
1690
|
+
// browser only reaches Webpay's pages when the entry was not paused (an attachment made too late, an entry with
|
|
1691
|
+
// another parameter), and then only the cardholder's device can pay;
|
|
1692
|
+
// - another GET there (Webpay's scripts and images, or an entry written another way, whose page then submits the
|
|
1693
|
+
// token, which this gate aborts) continues.
|
|
1694
|
+
// With Webpay not served, the gate defers and every request continues as before. Synchronous until it must abort,
|
|
1695
|
+
// like every gate. Exported for the gate registry's tests.
|
|
1696
|
+
export const transbankPreClaim = (ctx) => {
|
|
1697
|
+
const { url, method } = ctx.request;
|
|
1698
|
+
if (String(method).toUpperCase() === 'GET' || !isArmedTransbankUrl(ctx.vault, url))
|
|
1699
|
+
return undefined;
|
|
1700
|
+
return processorPageAbort(ctx);
|
|
1701
|
+
};
|
|
1610
1702
|
// Redsys's hosted payment page (redsys.ts). Once this build's registry serves Redsys's entry (a
|
|
1611
1703
|
// capability the API serves only to a company Agentcard turned Redsys on for), every request to
|
|
1612
1704
|
// Redsys's hosts pauses, and this gate judges the POSTs:
|
|
@@ -1655,6 +1747,42 @@ export const quickpayPreClaim = (ctx) => {
|
|
|
1655
1747
|
return undefined;
|
|
1656
1748
|
return processorPageAbort(ctx);
|
|
1657
1749
|
};
|
|
1750
|
+
// ComGate's payment gateway (comgate.ts). Once this build's registry serves ComGate's entry (a capability the API serves
|
|
1751
|
+
// only to a company Agentcard turned ComGate on for), the gateway link pauses for the cardholder's device and every
|
|
1752
|
+
// request to ComGate's card hosts pauses too, and this gate judges those:
|
|
1753
|
+
// - every request but a GET (the gateway's card post and card check, the Web Checkout SDK's provider calls and card
|
|
1754
|
+
// check, the card frames' BIN lookup and their log) aborts as an unsupported checkout, so ComGate never sees the
|
|
1755
|
+
// card the agent typed. The agent's browser only reaches ComGate's card form when the link was not paused (an
|
|
1756
|
+
// attachment made too late, a link with another parameter, a shop on ComGate's Web Checkout SDK), and then only the
|
|
1757
|
+
// cardholder's device can pay;
|
|
1758
|
+
// - a GET there (the gateway's reads of the payment, the card frames' pages and scripts) is not this gate's and continues.
|
|
1759
|
+
// With ComGate not served, the gate defers and every request continues as before. Synchronous until it must abort, like
|
|
1760
|
+
// every gate. Exported for the gate registry's tests.
|
|
1761
|
+
export const comgatePreClaim = (ctx) => {
|
|
1762
|
+
const { url, method } = ctx.request;
|
|
1763
|
+
if (String(method).toUpperCase() === 'GET' || !isArmedComgateUrl(ctx.vault, url))
|
|
1764
|
+
return undefined;
|
|
1765
|
+
return processorPageAbort(ctx);
|
|
1766
|
+
};
|
|
1767
|
+
// Eximbay's payment window (eximbay.ts). Once this build's registry serves Eximbay's entry on a host (a capability the
|
|
1768
|
+
// API serves only to a company Agentcard turned Eximbay on for), every request to that host pauses, and this gate
|
|
1769
|
+
// judges the POSTs:
|
|
1770
|
+
// - the merchant's entry (the entry path exactly, no query) defers, and the terminal arbiter pauses it for the claim
|
|
1771
|
+
// flow like any card request: the cardholder's device pays it;
|
|
1772
|
+
// - every other POST to Eximbay (the step that opens the window, the window's amount query and its card lookup,
|
|
1773
|
+
// which carries the typed card's first eight digits, its card post, and its close and method posts) aborts as an
|
|
1774
|
+
// unsupported checkout, so Eximbay never sees the card the agent typed. The agent's browser only reaches Eximbay's
|
|
1775
|
+
// window when the entry was not paused (an attachment made too late, say), and then only the cardholder's device
|
|
1776
|
+
// can pay.
|
|
1777
|
+
// A GET (the window's scripts and images) is not this gate's and continues. With Eximbay not served, the entry is no
|
|
1778
|
+
// card request, the gate defers, and every request continues as before. Synchronous until it must abort, like every
|
|
1779
|
+
// gate. Exported for the gate registry's tests.
|
|
1780
|
+
export const eximbayPreClaim = (ctx) => {
|
|
1781
|
+
const { url, method } = ctx.request;
|
|
1782
|
+
if (String(method).toUpperCase() !== 'POST' || !isArmedEximbayUrl(ctx.vault, url) || isEximbayEntry(url, method))
|
|
1783
|
+
return undefined;
|
|
1784
|
+
return processorPageAbort(ctx);
|
|
1785
|
+
};
|
|
1658
1786
|
// PayPal's legacy Hosted Fields (paypal-hosted-fields.ts). Its tokenization frame posts the card the
|
|
1659
1787
|
// agent typed to the Orders v2 confirm-payment-source on cors.api.paypal.com (or its sandbox), which
|
|
1660
1788
|
// no Vault mode takes over, so every company's adapters hand each POST there to this gate:
|
|
@@ -2037,7 +2165,7 @@ async function hipayRefused(ctx, reason) {
|
|
|
2037
2165
|
// again. A checkout whose token request was already approved keeps that approval's state (lifecycle.unsupported),
|
|
2038
2166
|
// and its follow-ups stop (EbanxFollowUps.stop): a later request the approval would cover is refused too, so the
|
|
2039
2167
|
// page cannot finish 3-D Secure and pay through the confirm after a refusal. A failed send stops them the same way.
|
|
2040
|
-
// A send goes on only while the checkout itself names the remembered approval (
|
|
2168
|
+
// A send goes on only while the checkout itself names the remembered approval (followUpMaySend): it awaits the
|
|
2041
2169
|
// merchant, reports the bank's challenge, or holds that approval's answer for the page (awaiting_approval while
|
|
2042
2170
|
// the token's answer is on its way, since the adapters remember the approval before they hand the answer over, or
|
|
2043
2171
|
// ready_to_submit while the approval waits for the page's retry). A new token request awaiting the person, a new
|
|
@@ -2066,38 +2194,39 @@ export const ebanxPreClaim = (ctx) => {
|
|
|
2066
2194
|
catch {
|
|
2067
2195
|
body = null;
|
|
2068
2196
|
}
|
|
2069
|
-
const claim = ctx.ebanx && typeof ctx.vault.sendEbanxFollowUp === 'function' &&
|
|
2197
|
+
const claim = ctx.ebanx && typeof ctx.vault.sendEbanxFollowUp === 'function' && followUpMaySend(ctx, ctx.ebanx)
|
|
2070
2198
|
? ctx.ebanx.claim({ url, method: 'POST', headers: ctx.request.headers ?? {}, body }) : null;
|
|
2071
2199
|
return claim ? ebanxFollowUp(ctx, claim) : ebanxResendRefused(ctx, refusal);
|
|
2072
2200
|
};
|
|
2073
2201
|
/**
|
|
2074
2202
|
* The checkout states in which a send may go on under the remembered approval, an allowlist: the checkout awaits the
|
|
2075
|
-
* merchant, the application reported the bank's challenge (requestUserAction), the
|
|
2203
|
+
* merchant, the application reported the bank's challenge (requestUserAction), the approved answer is still on its way
|
|
2076
2204
|
* to the page (awaiting_approval: the page may send its first 3-D Secure call the moment the answer lands), or the
|
|
2077
2205
|
* approval waits for the page's retry (ready_to_submit). Every other state refuses, idle and the final ones among them.
|
|
2206
|
+
* The same for EBANX's sends and Payrexx's.
|
|
2078
2207
|
*/
|
|
2079
|
-
const
|
|
2208
|
+
const FOLLOW_UP_SENDING_STATES = new Set(['awaiting_merchant', 'requires_user_action', 'awaiting_approval', 'ready_to_submit']);
|
|
2080
2209
|
/**
|
|
2081
2210
|
* Whether a send may go on: nothing aborted the checkout, no reconciliation of the merchant's order is running (one
|
|
2082
2211
|
* that resolves failed while a confirm is out would let the application start another attempt), it is in one of
|
|
2083
|
-
*
|
|
2084
|
-
* person (awaiting_approval with no approval, or another one), a new
|
|
2085
|
-
* checkout and another approval's state all refuse.
|
|
2212
|
+
* FOLLOW_UP_SENDING_STATES, and its state names the approval the attachment remembered (`followUps`, EBANX's or
|
|
2213
|
+
* Payrexx's). A new card request awaiting the person (awaiting_approval with no approval, or another one), a new
|
|
2214
|
+
* attempt after a merchant failure, a prepared checkout and another approval's state all refuse.
|
|
2086
2215
|
*/
|
|
2087
|
-
function
|
|
2088
|
-
if (ctx.lifecycleAborted() || ctx.terminal() || !
|
|
2216
|
+
function followUpMaySend(ctx, followUps) {
|
|
2217
|
+
if (ctx.lifecycleAborted() || ctx.terminal() || !followUps || ctx.lifecycle.isReconciling())
|
|
2089
2218
|
return false;
|
|
2090
|
-
const approval =
|
|
2219
|
+
const approval = followUps.authorizationId;
|
|
2091
2220
|
const state = ctx.lifecycle.getState();
|
|
2092
|
-
return approval !== null &&
|
|
2221
|
+
return approval !== null && FOLLOW_UP_SENDING_STATES.has(state.status) && state.authorizationId === approval;
|
|
2093
2222
|
}
|
|
2094
2223
|
/**
|
|
2095
2224
|
* The checkout's final merchant results: completed, failed, declined, timed out, cancelled, an unknown outcome, or an
|
|
2096
2225
|
* unsupported request. A 3-D Secure call, which charges nothing, that fails in one of them leaves that outcome alone.
|
|
2097
2226
|
*/
|
|
2098
|
-
const
|
|
2099
|
-
/** Whether the checkout already ended (
|
|
2100
|
-
const
|
|
2227
|
+
const FOLLOW_UP_FINAL_STATES = new Set(['completed', 'failed', 'declined', 'timed_out', 'cancelled', 'outcome_unknown', 'unsupported']);
|
|
2228
|
+
/** Whether the checkout already ended (FOLLOW_UP_FINAL_STATES): its state and reason stand, whatever a later 3-D Secure call does. */
|
|
2229
|
+
const followUpCheckoutEnded = (ctx) => FOLLOW_UP_FINAL_STATES.has(ctx.lifecycle.getState().status);
|
|
2101
2230
|
/**
|
|
2102
2231
|
* A confirm whose card may have reached EBANX and whose answer the page never got: only a payment the merchant
|
|
2103
2232
|
* already confirmed (completed) stands, and every other state of this approval turns unknown, since EBANX may have
|
|
@@ -2138,7 +2267,7 @@ async function ebanxUnreadable(ctx) {
|
|
|
2138
2267
|
* (CheckoutLifecycle.followUpStarted): meanwhile reconcile() and retryAfterMerchantFailure() throw, so the application
|
|
2139
2268
|
* starts no other attempt while a confirm may pay. A confirm that may have left keeps only a payment the merchant
|
|
2140
2269
|
* confirmed (completed); any other state of its approval turns unknown (ebanxConfirmUnconfirmed). Any other send keeps
|
|
2141
|
-
* a final outcome (
|
|
2270
|
+
* a final outcome (FOLLOW_UP_FINAL_STATES) with its reason.
|
|
2142
2271
|
*/
|
|
2143
2272
|
async function ebanxFollowUp(ctx, claim) {
|
|
2144
2273
|
const settle = ctx.lifecycle.followUpStarted();
|
|
@@ -2164,7 +2293,7 @@ async function sendEbanxFollowUp(ctx, claim) {
|
|
|
2164
2293
|
ctx.onEvent?.({ type: 'ebanx_follow_up_failed', detail: { ...detail, reason, cardSent } });
|
|
2165
2294
|
if (cardSent && claim.kind === 'confirm')
|
|
2166
2295
|
ebanxConfirmUnconfirmed(ctx, claim.authorizationId, 'ebanx_confirm_unconfirmed');
|
|
2167
|
-
else if (!
|
|
2296
|
+
else if (!followUpCheckoutEnded(ctx))
|
|
2168
2297
|
ctx.lifecycle.unsupported(EBANX_RESEND_REASON);
|
|
2169
2298
|
await ctx.effects.abort();
|
|
2170
2299
|
return { kind: 'abort' };
|
|
@@ -2179,7 +2308,7 @@ async function sendEbanxFollowUp(ctx, claim) {
|
|
|
2179
2308
|
ctx.onEvent?.({ type: 'ebanx_follow_up_failed', detail: { ...detail, reason: 'handoff_failed', cardSent: true } });
|
|
2180
2309
|
if (claim.kind === 'confirm')
|
|
2181
2310
|
ebanxConfirmUnconfirmed(ctx, claim.authorizationId, 'ebanx_follow_up_handoff_failed');
|
|
2182
|
-
else if (!
|
|
2311
|
+
else if (!followUpCheckoutEnded(ctx))
|
|
2183
2312
|
ctx.lifecycle.unsupported(EBANX_RESEND_REASON);
|
|
2184
2313
|
await ctx.effects.abort();
|
|
2185
2314
|
return { kind: 'abort' };
|
|
@@ -2187,6 +2316,95 @@ async function sendEbanxFollowUp(ctx, claim) {
|
|
|
2187
2316
|
ctx.onEvent?.({ type: 'ebanx_follow_up_answered', detail: { ...detail, status: answer.status } });
|
|
2188
2317
|
return { kind: 'answer' };
|
|
2189
2318
|
}
|
|
2319
|
+
// Payrexx Pay's card frame after its card request (payrexx.ts, payment-core payrexx.js). When Payrexx answers the card
|
|
2320
|
+
// request by starting 3-D Secure, the frame posts the card again to its own URL for the authentication after the ACS
|
|
2321
|
+
// method and the post-authentication after the bank's challenge. On a page this SDK is attached to that card is the
|
|
2322
|
+
// agent's placeholder, and the frame's URL is the card request's, so this gate judges each such post before the
|
|
2323
|
+
// card-request arbiter would pause it as a new card request:
|
|
2324
|
+
// - one the approved card request on this attachment covers (PayrexxFollowUps.claim) goes to the cardholder's device
|
|
2325
|
+
// under that approval (payrexxFollowUp below), and the frame receives Payrexx's answer to the real card;
|
|
2326
|
+
// - any other aborts as an unsupported checkout, reason `unsupported_payrexx_card_resend`, with no claim, create or
|
|
2327
|
+
// prompt, so Payrexx never receives the placeholder as a card, and the attachment holds. A checkout whose card
|
|
2328
|
+
// request was already approved keeps that approval's state, and its sends stop (PayrexxFollowUps.stop), as
|
|
2329
|
+
// EBANX's do. A send goes on only while the checkout itself names the remembered approval (followUpMaySend).
|
|
2330
|
+
// The card request itself, a wallet's body with no card value, a body the adapter could not read and every other
|
|
2331
|
+
// request defer: the arbiter pauses the card request for its recognizer and judges the rest (card-fields.js).
|
|
2332
|
+
// Synchronous until it must abort or send, like every gate. Exported for the gate registry's tests.
|
|
2333
|
+
export const payrexxPreClaim = (ctx) => {
|
|
2334
|
+
const { url, method } = ctx.request;
|
|
2335
|
+
if (!isPayrexxResend(url, method, () => ctx.request.body))
|
|
2336
|
+
return undefined;
|
|
2337
|
+
let body;
|
|
2338
|
+
try {
|
|
2339
|
+
body = ctx.request.body;
|
|
2340
|
+
}
|
|
2341
|
+
catch {
|
|
2342
|
+
body = null;
|
|
2343
|
+
}
|
|
2344
|
+
const claim = ctx.payrexx && typeof ctx.vault.sendPayrexxFollowUp === 'function' && followUpMaySend(ctx, ctx.payrexx)
|
|
2345
|
+
? ctx.payrexx.claim({ url, method: 'POST', headers: ctx.request.headers ?? {}, body }) : null;
|
|
2346
|
+
return claim ? payrexxFollowUp(ctx, claim) : payrexxResendRefused(ctx);
|
|
2347
|
+
};
|
|
2348
|
+
async function payrexxResendRefused(ctx) {
|
|
2349
|
+
const { url, method } = ctx.request;
|
|
2350
|
+
ctx.preparationGate.invalidate('unsupported_checkout');
|
|
2351
|
+
ctx.payrexx?.stop();
|
|
2352
|
+
ctx.lifecycle.unsupported(PAYREXX_RESEND_REASON);
|
|
2353
|
+
ctx.onEvent?.({ type: 'unsupported_checkout', detail: { url: eventUrl(ctx.vault, url), method, reason: PAYREXX_RESEND_REASON, step: 'three_d_secure' } });
|
|
2354
|
+
await ctx.effects.abort();
|
|
2355
|
+
return { kind: 'abort' };
|
|
2356
|
+
}
|
|
2357
|
+
/**
|
|
2358
|
+
* A 3-D Secure send the approved card request covers: the cardholder's device sends it with the real card
|
|
2359
|
+
* (VaultClient.sendPayrexxFollowUp), and the frame receives Payrexx's answer, with no second prompt. The events name the
|
|
2360
|
+
* approval and the step, never the request, the card or Payrexx's answer. A send charges nothing: the merchant's page
|
|
2361
|
+
* charges only the token a later answer names, at its own validateForm. So a send that gets no answer, or whose answer
|
|
2362
|
+
* the frame never receives, leaves no payment behind it: the frame's request fails, the attachment holds as the
|
|
2363
|
+
* refusal above does (a final outcome keeps its state and reason), and nothing more is sent. While a send is out it
|
|
2364
|
+
* counts as work in flight (CheckoutLifecycle.followUpStarted), so the application starts no other attempt over it.
|
|
2365
|
+
*/
|
|
2366
|
+
async function payrexxFollowUp(ctx, claim) {
|
|
2367
|
+
const settle = ctx.lifecycle.followUpStarted();
|
|
2368
|
+
try {
|
|
2369
|
+
return await sendPayrexxFollowUp(ctx, claim);
|
|
2370
|
+
}
|
|
2371
|
+
finally {
|
|
2372
|
+
settle();
|
|
2373
|
+
}
|
|
2374
|
+
}
|
|
2375
|
+
async function sendPayrexxFollowUp(ctx, claim) {
|
|
2376
|
+
const detail = { authorizationId: claim.authorizationId, step: claim.kind };
|
|
2377
|
+
ctx.onEvent?.({ type: 'payrexx_follow_up_requested', detail });
|
|
2378
|
+
let answer;
|
|
2379
|
+
try {
|
|
2380
|
+
answer = await ctx.vault.sendPayrexxFollowUp(claim.authorizationId, claim.request, { signal: ctx.lifecycle.abort.signal, until: claim.until });
|
|
2381
|
+
}
|
|
2382
|
+
catch (error) {
|
|
2383
|
+
claim.settle(null);
|
|
2384
|
+
ctx.payrexx?.stop();
|
|
2385
|
+
const reason = error instanceof PayrexxFollowUpError ? error.reason : 'follow_up_unavailable';
|
|
2386
|
+
const cardSent = error instanceof PayrexxFollowUpError ? error.cardSent : true;
|
|
2387
|
+
ctx.onEvent?.({ type: 'payrexx_follow_up_failed', detail: { ...detail, reason, cardSent } });
|
|
2388
|
+
if (!followUpCheckoutEnded(ctx))
|
|
2389
|
+
ctx.lifecycle.unsupported(PAYREXX_RESEND_REASON);
|
|
2390
|
+
await ctx.effects.abort();
|
|
2391
|
+
return { kind: 'abort' };
|
|
2392
|
+
}
|
|
2393
|
+
claim.settle({ status: answer.status, text: answer.body });
|
|
2394
|
+
try {
|
|
2395
|
+
await ctx.effects.answer({ status: answer.status, headers: answer.headers, body: answer.body });
|
|
2396
|
+
}
|
|
2397
|
+
catch {
|
|
2398
|
+
ctx.payrexx?.stop();
|
|
2399
|
+
ctx.onEvent?.({ type: 'payrexx_follow_up_failed', detail: { ...detail, reason: 'handoff_failed', cardSent: true } });
|
|
2400
|
+
if (!followUpCheckoutEnded(ctx))
|
|
2401
|
+
ctx.lifecycle.unsupported(PAYREXX_RESEND_REASON);
|
|
2402
|
+
await ctx.effects.abort();
|
|
2403
|
+
return { kind: 'abort' };
|
|
2404
|
+
}
|
|
2405
|
+
ctx.onEvent?.({ type: 'payrexx_follow_up_answered', detail: { ...detail, status: answer.status } });
|
|
2406
|
+
return { kind: 'answer' };
|
|
2407
|
+
}
|
|
2190
2408
|
// The terminal arbiter, always last. A request that no gate claimed and that is
|
|
2191
2409
|
// neither a native Stripe step nor a recognized card request continues, unless
|
|
2192
2410
|
// it matches a declared payment endpoint we do not support (then it aborts as an
|
|
@@ -2220,8 +2438,8 @@ async function unsupportedContinue(ctx) {
|
|
|
2220
2438
|
* arbiter runs after all of them and is not part of this list. A gate appended
|
|
2221
2439
|
* after Stripe is never offered a step Stripe claimed: Stripe answers, aborts or
|
|
2222
2440
|
* pauses each one itself. */
|
|
2223
|
-
export const PSP_PRE_CLAIM_GATES = [cardEndpointResendPreClaim, cardEndpointPreClaim, mercadoPreClaim, juspayPreClaim, braintreePreClaim, stripePreClaim, merchantHostedPreClaim, verifonePreClaim, fiservPreClaim, gmoPreClaim, cybersourceFlexPreClaim, datatransPreClaim, pelecardPreClaim, aciPreClaim, creditguardPreClaim, primerPreClaim, payrailsPreClaim, convergePreClaim, eprotectPreClaim, wspayPreClaim, shijiStsPreClaim, ekashuPreClaim, redsysPreClaim, quickpayPreClaim, paypalHostedFieldsPreClaim,
|
|
2224
|
-
paypalCardFormsPreClaim, cybersourceSecureAcceptancePreClaim, worldlineMyCheckoutPreClaim, paddleVgsPayPreClaim, ebanxPreClaim, hipayPreClaim];
|
|
2441
|
+
export const PSP_PRE_CLAIM_GATES = [cardEndpointResendPreClaim, cardEndpointPreClaim, mercadoPreClaim, juspayPreClaim, braintreePreClaim, stripePreClaim, merchantHostedPreClaim, verifonePreClaim, fiservPreClaim, gmoPreClaim, cybersourceFlexPreClaim, datatransPreClaim, tokenexIframePreClaim, pelecardPreClaim, aciPreClaim, creditguardPreClaim, primerPreClaim, payrailsPreClaim, convergePreClaim, eprotectPreClaim, wspayPreClaim, shijiStsPreClaim, ekashuPreClaim, redsysPreClaim, quickpayPreClaim, comgatePreClaim, przelewy24PreClaim, transbankPreClaim, eximbayPreClaim, paypalHostedFieldsPreClaim,
|
|
2442
|
+
paypalCardFormsPreClaim, cybersourceSecureAcceptancePreClaim, worldlineMyCheckoutPreClaim, paddleVgsPayPreClaim, ebanxPreClaim, payrexxPreClaim, hipayPreClaim];
|
|
2225
2443
|
/** Run each gate in order; the first to return a decision wins. If none does,
|
|
2226
2444
|
* the terminal arbiter decides between continue, abort (unsupported) and pause.
|
|
2227
2445
|
* Returns synchronously while every gate defers synchronously (an ordinary card
|
|
@@ -2174,6 +2174,64 @@
|
|
|
2174
2174
|
"hostname_pattern": "^assets\\.braintreegateway\\.com$",
|
|
2175
2175
|
"pathname_pattern": "^\\/web\\/[0-9]+\\.[0-9]+\\.[0-9]+-payments-sdk(?:-[a-z]+)?\\/html\\/hosted-fields-(?:input-frame|payments-sdk-tokenization-frame)(?:\\.min)?\\.html$"
|
|
2176
2176
|
},
|
|
2177
|
+
{
|
|
2178
|
+
"id": "paypal-card-fields-v5",
|
|
2179
|
+
"psp": "paypal",
|
|
2180
|
+
"kinds": [
|
|
2181
|
+
"frame",
|
|
2182
|
+
"iframe"
|
|
2183
|
+
],
|
|
2184
|
+
"origin": "https://www.paypal.com",
|
|
2185
|
+
"path_pattern": "/smart/card-field",
|
|
2186
|
+
"sources": [
|
|
2187
|
+
{
|
|
2188
|
+
"kind": "primary_source",
|
|
2189
|
+
"reference": "https://www.paypal.com/sdk/js?components=card-fields",
|
|
2190
|
+
"reviewed_on": "2026-10-15"
|
|
2191
|
+
},
|
|
2192
|
+
{
|
|
2193
|
+
"kind": "primary_source",
|
|
2194
|
+
"reference": "https://developer.paypal.com/docs/checkout/advanced/integrate/",
|
|
2195
|
+
"reviewed_on": "2026-10-15"
|
|
2196
|
+
},
|
|
2197
|
+
{
|
|
2198
|
+
"kind": "saved_observation",
|
|
2199
|
+
"reference": "lake-antoine-park-2026-10-15:paypal-card-field-frames",
|
|
2200
|
+
"reviewed_on": "2026-10-15"
|
|
2201
|
+
}
|
|
2202
|
+
],
|
|
2203
|
+
"hostname_pattern": "^www\\.paypal\\.com$",
|
|
2204
|
+
"pathname_pattern": "^\\/smart\\/card-field$"
|
|
2205
|
+
},
|
|
2206
|
+
{
|
|
2207
|
+
"id": "paypal-card-fields-v6",
|
|
2208
|
+
"psp": "paypal",
|
|
2209
|
+
"kinds": [
|
|
2210
|
+
"frame",
|
|
2211
|
+
"iframe"
|
|
2212
|
+
],
|
|
2213
|
+
"origin": "https://www.paypal.com",
|
|
2214
|
+
"path_pattern": "/web-sdk/v6/card-fields-content",
|
|
2215
|
+
"sources": [
|
|
2216
|
+
{
|
|
2217
|
+
"kind": "primary_source",
|
|
2218
|
+
"reference": "https://www.paypal.com/sdk/js?components=card-fields",
|
|
2219
|
+
"reviewed_on": "2026-10-15"
|
|
2220
|
+
},
|
|
2221
|
+
{
|
|
2222
|
+
"kind": "primary_source",
|
|
2223
|
+
"reference": "https://developer.paypal.com/docs/checkout/advanced/integrate/",
|
|
2224
|
+
"reviewed_on": "2026-10-15"
|
|
2225
|
+
},
|
|
2226
|
+
{
|
|
2227
|
+
"kind": "saved_observation",
|
|
2228
|
+
"reference": "lake-antoine-park-2026-10-15:paypal-card-field-frames",
|
|
2229
|
+
"reviewed_on": "2026-10-15"
|
|
2230
|
+
}
|
|
2231
|
+
],
|
|
2232
|
+
"hostname_pattern": "^www\\.paypal\\.com$",
|
|
2233
|
+
"pathname_pattern": "^\\/web-sdk\\/v6\\/card-fields-content$"
|
|
2234
|
+
},
|
|
2177
2235
|
{
|
|
2178
2236
|
"id": "cybersource-secure-acceptance-silent-order-post",
|
|
2179
2237
|
"psp": "cybersource_secure_acceptance",
|
|
@@ -467,6 +467,8 @@
|
|
|
467
467
|
"gravy-secure-fields-script",
|
|
468
468
|
"gravy-secure-fields-script-sandbox",
|
|
469
469
|
"paypal-hosted-fields-payments-sdk",
|
|
470
|
+
"paypal-card-fields-v5",
|
|
471
|
+
"paypal-card-fields-v6",
|
|
470
472
|
"cybersource-secure-acceptance-silent-order-post",
|
|
471
473
|
"fiserv-commercehub-payment-fields",
|
|
472
474
|
"fiserv-commercehub-checkout-script",
|
|
@@ -1101,6 +1103,8 @@
|
|
|
1101
1103
|
"gravy-secure-fields-script",
|
|
1102
1104
|
"gravy-secure-fields-script-sandbox",
|
|
1103
1105
|
"paypal-hosted-fields-payments-sdk",
|
|
1106
|
+
"paypal-card-fields-v5",
|
|
1107
|
+
"paypal-card-fields-v6",
|
|
1104
1108
|
"cybersource-secure-acceptance-silent-order-post",
|
|
1105
1109
|
"fiserv-commercehub-payment-fields",
|
|
1106
1110
|
"fiserv-commercehub-checkout-script",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Generated from @agent-cards/payment-core. Do not edit.
|
|
2
|
-
// source-sha256:
|
|
2
|
+
// source-sha256: 639ffa689e8dfcf093eeba7e34b86c570e53ab17970976ae462a41fd2120d753
|
|
3
3
|
import { CHECKOUT_PREFLIGHT_CAPABILITIES } from './preflight-capabilities.generated.js';
|
|
4
4
|
/** Advisory page discovery. These rules never admit a payment destination. */
|
|
5
5
|
export const CHECKOUT_PREFLIGHT_VERSION = '1';
|
|
@@ -87,6 +87,20 @@ RULES.push({ id: 'paypal-hosted-fields-payments-sdk', psp: 'paypal', kinds: ['fr
|
|
|
87
87
|
paypalHostedFieldsSource('https://www.paypal.com/sdk/js?components=hosted-fields'),
|
|
88
88
|
{ kind: 'saved_observation', reference: 'planet-golf-uk-2026-10-06:paypal-hosted-fields-payments-sdk-frames', reviewed_on: '2026-10-07' }],
|
|
89
89
|
variant_unverified: 'PayPal legacy Hosted Fields frames send the card to the Orders v2 confirm-payment-source on cors.api.paypal.com; no Vault mode takes that request over.' });
|
|
90
|
+
// PayPal Card Fields (its JS SDK's components=card-fields, v5, and web-sdk v6), reviewed 2026-10-15 from
|
|
91
|
+
// WooCommerce PayPal Payments 4.1.3 checkouts (PayPal JS SDK 5.0.582, card-field client 1.270.0) and PayPal's
|
|
92
|
+
// public web-sdk v6 6.68.0 bundles, with no card typed. v5 renders each card field as a frame on
|
|
93
|
+
// www.paypal.com/smart/card-field (?type=number, expiry, cvv or name), and v6 as
|
|
94
|
+
// www.paypal.com/web-sdk/v6/card-fields-content. Each sends the card to the Orders v2 confirm-payment-source,
|
|
95
|
+
// which payment-core's paypal entry takes while it ships dark, so the frames name paypal and the result
|
|
96
|
+
// stays unknown until the lane is served to every client.
|
|
97
|
+
const paypalCardFieldsSource = (reference) => ({ kind: 'primary_source', reference, reviewed_on: '2026-10-15' });
|
|
98
|
+
const PAYPAL_CARD_FIELDS_SOURCES = [paypalCardFieldsSource('https://www.paypal.com/sdk/js?components=card-fields'),
|
|
99
|
+
paypalCardFieldsSource('https://developer.paypal.com/docs/checkout/advanced/integrate/'),
|
|
100
|
+
{ kind: 'saved_observation', reference: 'lake-antoine-park-2026-10-15:paypal-card-field-frames', reviewed_on: '2026-10-15' }];
|
|
101
|
+
RULES.push({ id: 'paypal-card-fields-v5', psp: 'paypal', kinds: ['frame', 'iframe'], hostname: /^www\.paypal\.com$/, pathname: /^\/smart\/card-field$/,
|
|
102
|
+
origin: 'https://www.paypal.com', path_pattern: '/smart/card-field', sources: PAYPAL_CARD_FIELDS_SOURCES }, { id: 'paypal-card-fields-v6', psp: 'paypal', kinds: ['frame', 'iframe'], hostname: /^www\.paypal\.com$/, pathname: /^\/web-sdk\/v6\/card-fields-content$/,
|
|
103
|
+
origin: 'https://www.paypal.com', path_pattern: '/web-sdk/v6/card-fields-content', sources: PAYPAL_CARD_FIELDS_SOURCES });
|
|
90
104
|
// CyberSource Secure Acceptance's Silent Order POST (the Secure Acceptance Checkout API), reviewed 2026-10-08 from
|
|
91
105
|
// CyberSource's endpoint table and Arrow Electronics' public bundles (no card was sent). The merchant's own page
|
|
92
106
|
// posts the card fields, unsigned, beside the fields its server signed, to a /silent/ path on CyberSource's Secure
|
|
@@ -308,7 +322,7 @@ RULES.push({ id: 'worldline-mycheckout-page', psp: 'worldline_mycheckout', kinds
|
|
|
308
322
|
origin: 'https://payment.pay1.checkout.worldline-solutions.com', path_pattern: '/checkout/{payment-product-details/}:hosted_checkout', sources: worldlineSources }, { id: 'worldline-mycheckout-card-form', psp: 'worldline_mycheckout', kinds: ['form'], hostname: /^payment\.pay1\.checkout\.worldline-solutions\.com$/, pathname: /^\/checkout$/,
|
|
309
323
|
origin: 'https://payment.pay1.checkout.worldline-solutions.com', path_pattern: '/checkout', sources: worldlineSources });
|
|
310
324
|
// A processor that ships dark (its definition declares a capability) is left out of the bundled
|
|
311
|
-
// catalog. Datatrans's discovery rules wait with it: they join RULES only once KNOWN_PSPS, the
|
|
325
|
+
// catalog. Datatrans's and the TokenEx iframe's discovery rules wait with it: they join RULES only once KNOWN_PSPS, the
|
|
312
326
|
// bundled catalog's processors, lists its psp. Until then its assets are not evidence, so a page
|
|
313
327
|
// that loads them, alone or beside another processor's SDK, reads exactly as it did before the
|
|
314
328
|
// processor existed. (The dark GMO, CyberSource Flex, Juspay, Pelecard, ACI, CreditGuard,
|
|
@@ -326,6 +340,16 @@ for (const [suffix, hostname, origin] of [['', /^pay\.datatrans\.com$/, 'https:/
|
|
|
326
340
|
sources: [datatransSource('https://pay.datatrans.com/upp/payment/js/secure-fields-2.0.0.js'), datatransSource('https://docs.pci-proxy.com/docs/secure-fields-quick-start')] }, { id: `datatrans-secure-fields-frame${suffix}`, psp: 'datatrans', kinds: ['document', 'frame', 'iframe'], hostname, pathname: /^\/upp\/payment\/SecureFields\/paymentField$/, origin, path_pattern: '/upp/payment/SecureFields/paymentField',
|
|
327
341
|
sources: [datatransSource('https://pay.datatrans.com/upp/payment/SecureFields/field.js'), datatransSource('https://docs.datatrans.ch/docs/secure-fields')] });
|
|
328
342
|
}
|
|
343
|
+
// TokenEx hosted iframe v3, read 2026-10-18 from JetBlue's checkout: the parent library
|
|
344
|
+
// (htp.tokenex.com/Iframe/Iframe-v3.min.js) and the iframe document it loads (/iframe/v3?<query>), on the
|
|
345
|
+
// live host and its test twin. Dark behind tokenex_iframe_tokenize, so it waits with Datatrans.
|
|
346
|
+
const tokenexSource = (reference) => ({ kind: 'primary_source', reference, reviewed_on: '2026-10-18' });
|
|
347
|
+
for (const [suffix, hostname, origin] of [['', /^htp\.tokenex\.com$/, 'https://htp.tokenex.com'],
|
|
348
|
+
['-test', /^test-htp\.tokenex\.com$/, 'https://test-htp.tokenex.com']]) {
|
|
349
|
+
SERVED_ONLY_RULES.push({ id: `tokenex-iframe-v3-script${suffix}`, psp: 'tokenex_iframe', kinds: ['script'], hostname, pathname: /^\/Iframe\/Iframe-v3(?:\.min)?\.js$/, origin, path_pattern: '/Iframe/Iframe-v3{,.min}.js',
|
|
350
|
+
sources: [tokenexSource('https://htp.tokenex.com/Iframe/Iframe-v3.min.js')] }, { id: `tokenex-iframe-v3-frame${suffix}`, psp: 'tokenex_iframe', kinds: ['document', 'frame', 'iframe'], hostname, pathname: /^\/iframe\/v3$/, origin, path_pattern: '/iframe/v3',
|
|
351
|
+
sources: [tokenexSource('https://htp.tokenex.com/Iframe/v3.min.js?version=latest')] });
|
|
352
|
+
}
|
|
329
353
|
RULES.push(...SERVED_ONLY_RULES.filter(rule => KNOWN_PSPS.includes(rule.psp)));
|
|
330
354
|
// Keep sandbox evidence truthful instead of labelling it with a production origin.
|
|
331
355
|
for (const [baseId, suffix, hostname, origin] of [
|