@agent-cards/sdk 0.23.13 → 0.23.14
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 +35 -0
- package/PREFLIGHT.md +1 -1
- package/README.md +52 -13
- package/dist/adyen-merchant-hosted.generated.js +2 -2
- package/dist/card-fields.generated.js +841 -3
- package/dist/cdp.js +68 -12
- package/dist/client.d.ts +87 -1
- package/dist/client.js +196 -12
- package/dist/creditguard.generated.d.ts +100 -2
- package/dist/creditguard.generated.js +333 -3
- package/dist/datatrans.generated.d.ts +19 -4
- package/dist/datatrans.generated.js +15 -3
- package/dist/ebanx.d.ts +107 -0
- package/dist/ebanx.generated.d.ts +78 -0
- package/dist/ebanx.generated.js +577 -0
- package/dist/ebanx.js +159 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -0
- package/dist/lifecycle.d.ts +28 -5
- package/dist/lifecycle.js +37 -5
- package/dist/paddle-tokenex.d.ts +56 -0
- package/dist/paddle-tokenex.generated.d.ts +67 -0
- package/dist/paddle-tokenex.generated.js +951 -0
- package/dist/paddle-tokenex.js +95 -0
- package/dist/pre-claim.d.ts +38 -1
- package/dist/pre-claim.js +291 -17
- package/dist/preparation.d.ts +13 -0
- package/dist/preparation.js +22 -0
- package/dist/prepared-processor.js +6 -2
- package/dist/primer.generated.js +876 -38
- package/dist/redsys.d.ts +42 -0
- package/dist/redsys.js +68 -0
- package/dist/stripe-checkout.generated.js +462 -9
- package/package.json +8 -5
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
// Paddle TokenEx, the card leg of a Paddle checkout whose frame names the tokenization provider "tokenex",
|
|
2
|
+
// as the SDK sees it (payment-core paddle-tokenex.js has the request's shape, the device encryption and the
|
|
3
|
+
// substitution). Paddle's frame RSA-encrypts the card number and the CVV in the page itself, each on its
|
|
4
|
+
// own, under the account-level TokenEx public key the checkout serves, and carries both inside one request:
|
|
5
|
+
// POST https://checkout-service.paddle.com/checkout/<id>/pay (Paddle Classic)
|
|
6
|
+
// POST https://checkout-service.paddle.com/transaction-checkout/<id>/pay (Paddle Billing)
|
|
7
|
+
// (sandbox-checkout-service.paddle.com in test). The agent's page encrypts a placeholder card, so the /pay
|
|
8
|
+
// names the placeholder's ciphertexts and the placeholder's first six, last four, expiry and brand. This
|
|
9
|
+
// build holds that /pay and writes over them the approved card's own: the two ciphertexts the cardholder's
|
|
10
|
+
// device produced under the key payment-core pins (never a key from a row, a request or a page) and the
|
|
11
|
+
// five metadata facts the device read from the real card. Every other byte goes as the frame wrote it
|
|
12
|
+
// (mode cse), so the full card number never reaches the agent's runtime.
|
|
13
|
+
//
|
|
14
|
+
// The generic Adyen swap writes only the named ciphertext path, not the five metadata facts a sibling of
|
|
15
|
+
// tokenex_card, so this lane is dispatched by psp to substitutePaddleTokenex instead, exactly as Shiji STS
|
|
16
|
+
// is dispatched for its own body root. A production checkout-service refuses the Vault's cross-origin /pay
|
|
17
|
+
// (403 at Cloudflare), so the device cannot send /pay itself and the lane rides in the agent's page.
|
|
18
|
+
//
|
|
19
|
+
// The registry entry ships dark (capability paddle_tokenex_pay). This build declares the capability on
|
|
20
|
+
// syncRegistry, and the API serves the recognizer only to an org Agentcard enabled for paddle_tokenex, so
|
|
21
|
+
// for every other org nothing here fires: isCardRequest never recognizes a TokenEx /pay and Paddle's
|
|
22
|
+
// requests go through untouched. Autopilot never pays this processor: the API, the authority and the
|
|
23
|
+
// enclave refuse it.
|
|
24
|
+
import { PADDLE_TOKENEX_FIELDS, PADDLE_TOKENEX_FACT_KEYS, paddleTokenexPayTarget, substitutePaddleTokenex } from './paddle-tokenex.generated.js';
|
|
25
|
+
/** The PSP id the API and the recognizer name this lane, re-exported so the client dispatches by it. */
|
|
26
|
+
export { PADDLE_TOKENEX_PSP } from './paddle-tokenex.generated.js';
|
|
27
|
+
/** The dark-launch capability a build declares when it fills a Paddle TokenEx /pay with the device's card. */
|
|
28
|
+
export const PADDLE_TOKENEX_CAPABILITY = 'paddle_tokenex_pay';
|
|
29
|
+
/** The holder the two ciphertexts are served at (the frame's tokenex_card), named once so both checks share it. */
|
|
30
|
+
const SERVED_AT = 'data.payment_method_data.card.tokenex_card';
|
|
31
|
+
const CIPHERTEXT_FIELDS = PADDLE_TOKENEX_FIELDS;
|
|
32
|
+
const FACT_KEYS = PADDLE_TOKENEX_FACT_KEYS;
|
|
33
|
+
const STRING_FACTS = ['card_first_six', 'card_last_four', 'card_brand'];
|
|
34
|
+
const NUMBER_FACTS = ['expiry_month', 'expiry_year'];
|
|
35
|
+
/**
|
|
36
|
+
* Whether an approved Paddle TokenEx authorization's served substitution is one this SDK can continue: JSON
|
|
37
|
+
* at the frame's tokenex_card holder, no sibling to remove, and exactly the two ciphertext fields, each a
|
|
38
|
+
* non-empty string and distinct from the other. substitutePaddleTokenex checks each against the pinned key
|
|
39
|
+
* for the /pay's environment; this is the shape gate the client runs before it acts.
|
|
40
|
+
*/
|
|
41
|
+
export function isPaddleTokenexServedSubstitution(sub) {
|
|
42
|
+
if (!sub || typeof sub !== 'object' || Array.isArray(sub))
|
|
43
|
+
return false;
|
|
44
|
+
const { encoding, at, fields, remove } = sub;
|
|
45
|
+
if (encoding !== 'json' || at !== SERVED_AT || !fields || typeof fields !== 'object' || Array.isArray(fields))
|
|
46
|
+
return false;
|
|
47
|
+
if (remove !== undefined && !(Array.isArray(remove) && remove.length === 0))
|
|
48
|
+
return false;
|
|
49
|
+
const named = fields;
|
|
50
|
+
const names = Object.keys(named);
|
|
51
|
+
if (names.length !== CIPHERTEXT_FIELDS.length || !CIPHERTEXT_FIELDS.every((name) => typeof named[name] === 'string' && named[name].length > 0))
|
|
52
|
+
return false;
|
|
53
|
+
return named.encrypted_card_number !== named.encrypted_cvv;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Whether the API's `paddle_tokenex_facts` beside an approved set is the five facts this SDK can write over
|
|
57
|
+
* the placeholder's: exactly the five keys, the three strings as strings and the two expiry parts as
|
|
58
|
+
* numbers. paddleTokenexSubstitutionOf checks the digits, range and network at the swap; this is the shape
|
|
59
|
+
* gate the client runs before it acts, so a malformed approval is refused before anything is written.
|
|
60
|
+
*/
|
|
61
|
+
export function isPaddleTokenexServedFacts(value) {
|
|
62
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
63
|
+
return false;
|
|
64
|
+
const named = value;
|
|
65
|
+
const names = Object.keys(named);
|
|
66
|
+
if (names.length !== FACT_KEYS.length || !FACT_KEYS.every((name) => name in named))
|
|
67
|
+
return false;
|
|
68
|
+
return STRING_FACTS.every((name) => typeof named[name] === 'string' && named[name].length > 0)
|
|
69
|
+
&& NUMBER_FACTS.every((name) => typeof named[name] === 'number' && Number.isInteger(named[name]));
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* The continuation of an approved Paddle TokenEx /pay: the paused body with the device's two ciphertexts
|
|
73
|
+
* written over the placeholder's at tokenex_card and the five facts over the placeholder's at metadata
|
|
74
|
+
* (payment-core substitutePaddleTokenex, which refuses a ciphertext that is not one under the pinned key
|
|
75
|
+
* for the /pay's environment, or one already in the paused body).
|
|
76
|
+
*
|
|
77
|
+
* Only into `approved`, the paused /pay the approval was created from: `url` and `body` must equal it byte
|
|
78
|
+
* for byte. The account-level TokenEx key is the same for every checkout of that seller, so the device's
|
|
79
|
+
* ciphertext could be decrypted in any later /pay of the same shape; binding it to the one request keeps
|
|
80
|
+
* this lane from ever submitting one checkout's ciphertext to another.
|
|
81
|
+
*
|
|
82
|
+
* Null when neither URL is a TokenEx /pay, so every other processor keeps the generic swap. Throws for any
|
|
83
|
+
* other body or URL, and for a substitution or facts the shape gates refuse, in each case before anything
|
|
84
|
+
* continues.
|
|
85
|
+
*/
|
|
86
|
+
export function paddleTokenexTokenContinuation(url, body, sub, facts, approved) {
|
|
87
|
+
if (paddleTokenexPayTarget(url) === null && paddleTokenexPayTarget(approved.url) === null)
|
|
88
|
+
return null;
|
|
89
|
+
if (url !== approved.url || body !== approved.body)
|
|
90
|
+
throw new Error('Refusing to substitute: the paused request is not the Paddle TokenEx /pay the cardholder approved.');
|
|
91
|
+
if (!isPaddleTokenexServedSubstitution(sub) || !isPaddleTokenexServedFacts(facts))
|
|
92
|
+
throw new Error('Refusing to substitute: the Paddle TokenEx approval is not two ciphertexts and five card facts.');
|
|
93
|
+
const substitution = { encrypted_card_number: sub.fields.encrypted_card_number, encrypted_cvv: sub.fields.encrypted_cvv, ...facts };
|
|
94
|
+
return { body: substitutePaddleTokenex(url, body, substitution) };
|
|
95
|
+
}
|
package/dist/pre-claim.d.ts
CHANGED
|
@@ -14,6 +14,7 @@ import { type PayrailsSessionCapture, type PayrailsSidecar } from './payrails.js
|
|
|
14
14
|
import { type VerifoneProfile } from './verifone.js';
|
|
15
15
|
import { type WorldlineMyCheckoutProducts } from './worldline-mycheckout.js';
|
|
16
16
|
import { type PaddleVgsPayGate } from './paddle-vgs-pay.js';
|
|
17
|
+
import { type EbanxFollowUps } from './ebanx.js';
|
|
17
18
|
/** The native Stripe step the gate claimed, or null when none was: exactly what StripeCheckoutGate.claim returns. */
|
|
18
19
|
type StripeStep = ReturnType<StripeCheckoutGate['claim']>;
|
|
19
20
|
/** A synthetic response a gate fulfills locally. The effect applies CORS and encoding. */
|
|
@@ -98,6 +99,20 @@ export interface PreClaimContext {
|
|
|
98
99
|
verifoneProfileOf?(url: string): VerifoneProfile | null;
|
|
99
100
|
cardOnFilePolicy?(url: string, method?: string): string | null;
|
|
100
101
|
withoutCard?(url: string, body: string | null | undefined): 'continue' | 'refuse' | null;
|
|
102
|
+
/** VaultClient.sendEbanxFollowUp: a send an approved EBANX token request covers, from the cardholder's device. */
|
|
103
|
+
sendEbanxFollowUp?(authorizationId: string, request: {
|
|
104
|
+
url: string;
|
|
105
|
+
method: 'POST';
|
|
106
|
+
headers: Record<string, string>;
|
|
107
|
+
body: string;
|
|
108
|
+
}, options?: {
|
|
109
|
+
signal?: AbortSignal;
|
|
110
|
+
until?: number;
|
|
111
|
+
}): Promise<{
|
|
112
|
+
status: number;
|
|
113
|
+
headers: Record<string, string>;
|
|
114
|
+
body: string;
|
|
115
|
+
}>;
|
|
101
116
|
};
|
|
102
117
|
guards: {
|
|
103
118
|
matches(url: string, method?: string): boolean;
|
|
@@ -117,11 +132,15 @@ export interface PreClaimContext {
|
|
|
117
132
|
eprotect?: EprotectGate;
|
|
118
133
|
readDocumentUrl: () => Promise<string>;
|
|
119
134
|
lifecycle: CheckoutLifecycle;
|
|
120
|
-
/**
|
|
135
|
+
/**
|
|
136
|
+
* isEngaged and observeUnprepared are called only for an armed merchant profile's card request or a Fiserv card capture.
|
|
137
|
+
* creditguardStanding, only for a request on a CreditGuard PPS host (PreparationGate.creditguardStanding); absent reads as 'none'.
|
|
138
|
+
*/
|
|
121
139
|
preparationGate: {
|
|
122
140
|
invalidate(reason: string): void;
|
|
123
141
|
isEngaged(): boolean;
|
|
124
142
|
observeUnprepared(): void;
|
|
143
|
+
creditguardStanding?(txId: string): 'none' | 'ready' | 'refuse';
|
|
125
144
|
};
|
|
126
145
|
effects: PreClaimEffects;
|
|
127
146
|
onEvent?: (event: PreClaimEvent) => void;
|
|
@@ -196,6 +215,12 @@ export interface PreClaimContext {
|
|
|
196
215
|
* checkout services are not this gate's.
|
|
197
216
|
*/
|
|
198
217
|
paddleVgsPay?: PaddleVgsPayGate;
|
|
218
|
+
/**
|
|
219
|
+
* The attachment's approved EBANX token request and the sends claimed under it (ebanx.ts EbanxFollowUps).
|
|
220
|
+
* Absent, or holding no approval whose read named a window, and every EBANX 3-D Secure call and card confirm
|
|
221
|
+
* is refused, as before the approval covered them.
|
|
222
|
+
*/
|
|
223
|
+
ebanx?: EbanxFollowUps;
|
|
199
224
|
}
|
|
200
225
|
/**
|
|
201
226
|
* The URL an event may name for a request. For an armed merchant profile's
|
|
@@ -214,11 +239,23 @@ export type PreClaimGate = (ctx: PreClaimContext) => PreClaimDecision | undefine
|
|
|
214
239
|
export declare const MERCHANT_BODY_UNREADABLE = "the paused request carries no body the SDK could read, so Agentcard cannot tell whether it carries a card";
|
|
215
240
|
export declare const verifonePreClaim: PreClaimGate;
|
|
216
241
|
export declare const fiservPreClaim: PreClaimGate;
|
|
242
|
+
/**
|
|
243
|
+
* Whether an adapter must hand this request to the pre-claim gates as a CreditGuard page's own call
|
|
244
|
+
* beside its submit: a companion endpoint (payment-core creditguardCompanionCall, as loosely as a
|
|
245
|
+
* request could spell one) while this build's registry serves CreditGuard. The CDP adapter pauses
|
|
246
|
+
* them already through the recognizer's host-wide URL patterns; attachToPlaywright's route predicate,
|
|
247
|
+
* which otherwise sees only the submit, asks this.
|
|
248
|
+
*/
|
|
249
|
+
export declare function isArmedCreditguardCall(vault: {
|
|
250
|
+
recognizesProcessor?(psp: string): boolean;
|
|
251
|
+
} | undefined, url: string): boolean;
|
|
217
252
|
export declare const ekashuPreClaim: PreClaimGate;
|
|
253
|
+
export declare const redsysPreClaim: PreClaimGate;
|
|
218
254
|
export declare const paypalHostedFieldsPreClaim: PreClaimGate;
|
|
219
255
|
export declare const cybersourceSecureAcceptancePreClaim: PreClaimGate;
|
|
220
256
|
export declare const worldlineMyCheckoutPreClaim: PreClaimGate;
|
|
221
257
|
export declare const paddleVgsPayPreClaim: PreClaimGate;
|
|
258
|
+
export declare const ebanxPreClaim: PreClaimGate;
|
|
222
259
|
/** The processor gates, in order. A new family adds one entry here; the terminal
|
|
223
260
|
* arbiter runs after all of them and is not part of this list. A gate appended
|
|
224
261
|
* after Stripe is never offered a step Stripe claimed: Stripe answers, aborts or
|
package/dist/pre-claim.js
CHANGED
|
@@ -15,10 +15,12 @@
|
|
|
15
15
|
// Converge's hpp/process URL, which also carries wallet, debit, follow-up and
|
|
16
16
|
// card-saving bodies, WSPay's confirm URL, which also carries bodies with
|
|
17
17
|
// no new card, Shiji STS's getTokenId and getCvvId, which also carry
|
|
18
|
-
// requests the vault must never pay,
|
|
18
|
+
// requests the vault must never pay, PayPal's legacy Hosted Fields
|
|
19
19
|
// confirm and CyberSource Secure Acceptance's Silent Order POST, whose card
|
|
20
|
-
// no Vault mode takes over,
|
|
21
|
-
// card product its page chose
|
|
20
|
+
// no Vault mode takes over, Worldline MyCheckout's card form, held to the
|
|
21
|
+
// card product its page chose, and EBANX's 3-D Secure calls and hosted confirm,
|
|
22
|
+
// which send the card again after its token and leave only from the
|
|
23
|
+
// cardholder's device, under the token request's approval.
|
|
22
24
|
// The first three used to be
|
|
23
25
|
// written inline in Fetch.requestPaused and again in the page.route handler, so
|
|
24
26
|
// a decision could drift between the two adapters and a new family had to be
|
|
@@ -63,7 +65,7 @@ import { DATATRANS_INVALID_REQUEST } from './datatrans.generated.js';
|
|
|
63
65
|
import { isPelecardHost, pelecardRefusal } from './pelecard.js';
|
|
64
66
|
import { ACI_CHECKOUT_REUSED } from './aci.js';
|
|
65
67
|
import { ACI_EXPIRY_UNSTRIPPABLE } from './aci.generated.js';
|
|
66
|
-
import { CREDITGUARD_PPS_HOSTS, classifyCreditguardRequest, creditguardNuveiRefusal, creditguardProcessRefusal } from './creditguard.generated.js';
|
|
68
|
+
import { CREDITGUARD_PPS_HOSTS, classifyCreditguardRequest, creditguardCompanionAnswer, creditguardCompanionCall, creditguardNuveiRefusal, creditguardProcessRefusal, creditguardProcessTxId, } from './creditguard.generated.js';
|
|
67
69
|
import { isPrimerPciHost, primerRequestVerdict } from './primer.js';
|
|
68
70
|
import { convergeRefusal, isConvergeHost } from './elavon-converge.js';
|
|
69
71
|
import { isWspayHost, wspayRefusal } from './wspay.js';
|
|
@@ -76,10 +78,12 @@ import { cardEndpointStoppedPage, refusedNavigationOutcome } from './card-endpoi
|
|
|
76
78
|
import { PAYRAILS_HOLDING_REASONS, PAYRAILS_SESSION_MESSAGE, isPayrailsHost, isPayrailsPaymentUrl, isPayrailsStaticHost, payrailsKeyRefusal, payrailsPaymentRefusal, payrailsRequestKind, payrailsSaveRefusal, } from './payrails.js';
|
|
77
79
|
import { classifyVerifoneProfileRequest, verifoneTemplatedUrl } from './verifone.js';
|
|
78
80
|
import { ekashuEntryUrl, isEkashuEntry, isEkashuHost } from './ekashu.js';
|
|
81
|
+
import { isRedsysEntry, isRedsysHost, redsysEntryUrl } from './redsys.js';
|
|
79
82
|
import { PAYPAL_HOSTED_FIELDS_REASON, isPaypalHostedFieldsConfirm, paypalHostedFieldsBody } from './paypal-hosted-fields.js';
|
|
80
83
|
import { CYBERSOURCE_SECURE_ACCEPTANCE_REASON, CYBERSOURCE_SECURE_ACCEPTANCE_RECIPIENT, cybersourceSecureAcceptanceRequest, isCybersourceSecureAcceptanceUrl, isSecureAcceptanceFramePost, } from './cybersource-secure-acceptance.js';
|
|
81
84
|
import { isWorldlineMyCheckoutEndpoint } from './worldline-mycheckout.js';
|
|
82
85
|
import { paddleVgsPayEventUrl } from './paddle-vgs-pay.js';
|
|
86
|
+
import { EBANX_RESEND_REASON, EbanxFollowUpError, ebanxResendRefusalOf, isEbanxResendUrl, } from './ebanx.js';
|
|
83
87
|
/**
|
|
84
88
|
* The URL an event may name for a request. For an armed merchant profile's
|
|
85
89
|
* endpoint or a sibling of it: the endpoint origin, the path TEMPLATE and the
|
|
@@ -1058,18 +1062,31 @@ async function aciRefuse(ctx, code) {
|
|
|
1058
1062
|
await ctx.effects.abort();
|
|
1059
1063
|
return { kind: 'abort' };
|
|
1060
1064
|
}
|
|
1061
|
-
// CreditGuard's hosted payment page (Hyp PPS), in
|
|
1065
|
+
// CreditGuard's hosted payment page (Hyp PPS), in three parts the generic card
|
|
1062
1066
|
// path cannot judge. Every other request on the PPS hosts (the page, its
|
|
1063
|
-
// transaction read,
|
|
1064
|
-
//
|
|
1065
|
-
//
|
|
1067
|
+
// transaction read, assets) and the stock new-card submit defer, synchronously,
|
|
1068
|
+
// and the terminal arbiter continues or pauses them; the pause then needs a
|
|
1069
|
+
// ready preparation for this page's transaction.
|
|
1070
|
+
// - The page's own calls beside the submit that carry the placeholder card:
|
|
1071
|
+
// its /card/details lookup and, on a 3-D Secure transaction, /cc/ddc/init,
|
|
1072
|
+
// /cg3ds/process/init and /cg3ds/process/outcome (payment-core
|
|
1073
|
+
// creditguardCompanionCall). Under a ready preparation for this page's own
|
|
1074
|
+
// transaction each is answered here, nothing sent: a foreign card, so the
|
|
1075
|
+
// page asks the agent for no Israeli ID; no device check; no challenge; and
|
|
1076
|
+
// an outcome that is not cancelled (creditguardCompanionAnswer). The page
|
|
1077
|
+
// then sends its submit, and the cardholder's device runs CreditGuard's 3-D
|
|
1078
|
+
// Secure with the real card before it sends its own. With no preparation
|
|
1079
|
+
// asked for, each goes on to CreditGuard as it always has; under any other
|
|
1080
|
+
// preparation, or in a shape the page does not send, it is aborted.
|
|
1066
1081
|
// - A /process submit the cardholder's device would not fill (a saved card,
|
|
1067
1082
|
// a wallet, "save my card", installments, DCC, an Israeli ID, a gift card,
|
|
1068
1083
|
// another method, an unreadable body or other headers) is aborted with the device's own
|
|
1069
1084
|
// reason, before any claim, create or prompt. A ready preparation stays
|
|
1070
|
-
// ready, so the page's corrected submit can still pay.
|
|
1071
|
-
//
|
|
1072
|
-
//
|
|
1085
|
+
// ready, so the page's corrected submit can still pay. Under a ready
|
|
1086
|
+
// preparation for its own transaction the encrypted submit (jwePayload)
|
|
1087
|
+
// defers like the plaintext one; the API holds it to the transaction's key.
|
|
1088
|
+
// - PPS runs Nuvei's 3-D Secure itself, sending the placeholder card to Nuvei
|
|
1089
|
+
// from inside its frame. The nuvei recognizer would pause that as a Nuvei
|
|
1073
1090
|
// payment. A request to a Nuvei host that a PPS document sent, or that any
|
|
1074
1091
|
// frame inside a PPS document sent, is aborted instead, the checkout
|
|
1075
1092
|
// becomes unsupported and any preparation is retired: the page's flow is
|
|
@@ -1100,14 +1117,84 @@ const creditguardPreClaim = (ctx) => {
|
|
|
1100
1117
|
// Any method reaches the /process check: payment-core blocks a non-POST, a query or an unreadable
|
|
1101
1118
|
// body there as well, so nothing but the stock POST ever continues to CreditGuard.
|
|
1102
1119
|
const verb = method.toUpperCase();
|
|
1103
|
-
const
|
|
1120
|
+
const companion = creditguardCompanionCall(url, verb, body ?? '');
|
|
1121
|
+
if (companion !== null)
|
|
1122
|
+
return creditguardCompanionPreClaim(ctx, companion);
|
|
1123
|
+
// The encrypted submit is the page's own only for a transaction that carries a key, which the API
|
|
1124
|
+
// reads; here it may defer only under a ready preparation for its own transaction.
|
|
1125
|
+
const txId = creditguardProcessTxId(url);
|
|
1126
|
+
const options = txId !== null && creditguardStanding(ctx, txId) === 'ready' ? { jwe: true } : undefined;
|
|
1127
|
+
const verdict = classifyCreditguardRequest(url, verb, body ?? '', options);
|
|
1104
1128
|
if (verdict === null)
|
|
1105
1129
|
return undefined;
|
|
1106
|
-
const refusal = creditguardProcessRefusal({ url, method: verb, headers: ctx.request.headers, body: body ?? '' },
|
|
1130
|
+
const refusal = creditguardProcessRefusal({ url, method: verb, headers: ctx.request.headers, body: body ?? '' }, options);
|
|
1107
1131
|
if (verdict === 'card' && refusal === null)
|
|
1108
1132
|
return undefined;
|
|
1109
1133
|
return creditguardBlock(ctx, refusal ?? 'malformed_body');
|
|
1110
1134
|
};
|
|
1135
|
+
/**
|
|
1136
|
+
* Whether an adapter must hand this request to the pre-claim gates as a CreditGuard page's own call
|
|
1137
|
+
* beside its submit: a companion endpoint (payment-core creditguardCompanionCall, as loosely as a
|
|
1138
|
+
* request could spell one) while this build's registry serves CreditGuard. The CDP adapter pauses
|
|
1139
|
+
* them already through the recognizer's host-wide URL patterns; attachToPlaywright's route predicate,
|
|
1140
|
+
* which otherwise sees only the submit, asks this.
|
|
1141
|
+
*/
|
|
1142
|
+
export function isArmedCreditguardCall(vault, url) {
|
|
1143
|
+
if (typeof vault?.recognizesProcessor !== 'function' || !vault.recognizesProcessor('creditguard'))
|
|
1144
|
+
return false;
|
|
1145
|
+
try {
|
|
1146
|
+
return creditguardCompanionCall(url, 'POST', '') !== null;
|
|
1147
|
+
}
|
|
1148
|
+
catch {
|
|
1149
|
+
return false;
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
/** The preparation's standing for a CreditGuard transaction (PreparationGate.creditguardStanding); 'none' where the context has none. */
|
|
1153
|
+
function creditguardStanding(ctx, txId) {
|
|
1154
|
+
try {
|
|
1155
|
+
return ctx.preparationGate.creditguardStanding?.(txId) ?? 'none';
|
|
1156
|
+
}
|
|
1157
|
+
catch {
|
|
1158
|
+
return 'refuse';
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
/**
|
|
1162
|
+
* A CreditGuard page's own call beside its submit (see the comment above): with no preparation asked
|
|
1163
|
+
* for it defers and goes on as before; under a ready preparation for its own transaction it is
|
|
1164
|
+
* answered here when it is the page's own call, in its own microtask's decision, and refused when it
|
|
1165
|
+
* is not; under any other preparation it is refused. A refused call never names a body field.
|
|
1166
|
+
*/
|
|
1167
|
+
function creditguardCompanionPreClaim(ctx, companion) {
|
|
1168
|
+
let engaged = true;
|
|
1169
|
+
try {
|
|
1170
|
+
engaged = ctx.preparationGate.isEngaged();
|
|
1171
|
+
}
|
|
1172
|
+
catch {
|
|
1173
|
+
engaged = true;
|
|
1174
|
+
}
|
|
1175
|
+
const standing = companion.txId !== null ? creditguardStanding(ctx, companion.txId) : engaged ? 'refuse' : 'none';
|
|
1176
|
+
if (standing === 'none')
|
|
1177
|
+
return undefined;
|
|
1178
|
+
if (standing !== 'ready')
|
|
1179
|
+
return creditguardBlock(ctx, 'preparation_mismatch');
|
|
1180
|
+
if (companion.refusal !== null || companion.step === null)
|
|
1181
|
+
return creditguardBlock(ctx, companion.refusal ?? 'not_companion_request');
|
|
1182
|
+
return creditguardCompanionAnswered(ctx, companion.step);
|
|
1183
|
+
}
|
|
1184
|
+
async function creditguardCompanionAnswered(ctx, step) {
|
|
1185
|
+
const answer = creditguardCompanionAnswer(step);
|
|
1186
|
+
if (answer && !ctx.interceptionAborted() && !ctx.lifecycleAborted() && !ctx.terminal()) {
|
|
1187
|
+
try {
|
|
1188
|
+
await ctx.effects.answer(answer);
|
|
1189
|
+
ctx.onEvent?.({ type: 'creditguard_call_answered', detail: { step } });
|
|
1190
|
+
return { kind: 'answer' };
|
|
1191
|
+
}
|
|
1192
|
+
catch { /* the page's request is gone; nothing was forwarded */ }
|
|
1193
|
+
}
|
|
1194
|
+
ctx.onEvent?.({ type: 'blocked', detail: { reason: 'creditguard_call_unanswered', step } });
|
|
1195
|
+
await ctx.effects.abort();
|
|
1196
|
+
return { kind: 'abort' };
|
|
1197
|
+
}
|
|
1111
1198
|
/**
|
|
1112
1199
|
* Who sent a request to a Nuvei host, as far as it decides the request:
|
|
1113
1200
|
* 1. The browser sets Origin and Referer, and no page can make either name another page's
|
|
@@ -1504,9 +1591,10 @@ export const ekashuPreClaim = (ctx) => {
|
|
|
1504
1591
|
return undefined;
|
|
1505
1592
|
if (isEkashuEntry(url, method))
|
|
1506
1593
|
return undefined;
|
|
1507
|
-
return
|
|
1594
|
+
return processorPageAbort(ctx);
|
|
1508
1595
|
};
|
|
1509
|
-
|
|
1596
|
+
// The stop for a card post into a processor's own page that only the cardholder's device may pay (NMI's, Redsys's).
|
|
1597
|
+
async function processorPageAbort(ctx) {
|
|
1510
1598
|
const { url, method } = ctx.request;
|
|
1511
1599
|
ctx.preparationGate.invalidate('unsupported_checkout');
|
|
1512
1600
|
ctx.lifecycle.unsupported('processor_card_page');
|
|
@@ -1514,6 +1602,30 @@ async function ekashuAbort(ctx) {
|
|
|
1514
1602
|
await ctx.effects.abort();
|
|
1515
1603
|
return { kind: 'abort' };
|
|
1516
1604
|
}
|
|
1605
|
+
// Redsys's hosted payment page (redsys.ts). Once this build's registry serves Redsys's entry (a
|
|
1606
|
+
// capability the API serves only to a company Agentcard turned Redsys on for), every request to
|
|
1607
|
+
// Redsys's hosts pauses, and this gate judges the POSTs:
|
|
1608
|
+
// - the merchant's entry (/sis/realizarPago or its /utf-8 twin on sis.redsys.es) defers, and the
|
|
1609
|
+
// terminal arbiter pauses it for the claim flow like any card request: the cardholder's device pays it;
|
|
1610
|
+
// - every other POST to Redsys (the card page's own post, the older page's 3-D Secure card lookup,
|
|
1611
|
+
// InSite's card post, and an entry on Redsys's test platform, which Agentcard pays nothing on and
|
|
1612
|
+
// whose page asks for a card) aborts as an unsupported checkout, and Redsys never sees the card the
|
|
1613
|
+
// agent typed. The agent's browser only reaches Redsys's card page when the entry was not paused (an
|
|
1614
|
+
// attachment made too late, say), and then only the cardholder's device can pay.
|
|
1615
|
+
// A GET (the card page's scripts and images) is not this gate's and continues. With Redsys not served,
|
|
1616
|
+
// the entry is no card request, the gate defers, and every request continues as before. Synchronous
|
|
1617
|
+
// until it must abort, like every gate. Exported for the gate registry's tests.
|
|
1618
|
+
export const redsysPreClaim = (ctx) => {
|
|
1619
|
+
const { url, method } = ctx.request;
|
|
1620
|
+
if (String(method).toUpperCase() !== 'POST' || !isRedsysHost(url))
|
|
1621
|
+
return undefined;
|
|
1622
|
+
const entry = redsysEntryUrl(url);
|
|
1623
|
+
if (!entry || !ctx.vault.isCardRequest(entry, 'POST'))
|
|
1624
|
+
return undefined;
|
|
1625
|
+
if (isRedsysEntry(url, method))
|
|
1626
|
+
return undefined;
|
|
1627
|
+
return processorPageAbort(ctx);
|
|
1628
|
+
};
|
|
1517
1629
|
// PayPal's legacy Hosted Fields (paypal-hosted-fields.ts). Its tokenization frame posts the card the
|
|
1518
1630
|
// agent typed to the Orders v2 confirm-payment-source on cors.api.paypal.com (or its sandbox), which
|
|
1519
1631
|
// no Vault mode takes over, so every company's adapters hand each POST there to this gate:
|
|
@@ -1784,6 +1896,168 @@ async function paddleVgsPayContinued(ctx, body) {
|
|
|
1784
1896
|
fields: ['card_first_six', 'card_last_four', 'expiry_month', 'expiry_year', 'card_brand'] } });
|
|
1785
1897
|
return { kind: 'continue' };
|
|
1786
1898
|
}
|
|
1899
|
+
// EBANX after its card token (ebanx.ts, payment-core ebanx.js). Once the cardholder's device has sent the card to
|
|
1900
|
+
// /ws/token, EBANX's hosted payment page and EBANX.js send the card the page holds again: its 3-D Secure calls post
|
|
1901
|
+
// the card, and the hosted page's confirm posts its code beside EBANX's token. On a page this SDK is attached to that
|
|
1902
|
+
// card is the agent's placeholder, so every company's adapters hand each POST there to this gate:
|
|
1903
|
+
// - one the approved token request on this attachment covers (EbanxFollowUps.claim) goes to the cardholder's
|
|
1904
|
+
// device under that approval (ebanxFollowUp below), and the page receives EBANX's answer to the real card;
|
|
1905
|
+
// - any other 3-D Secure call, or a confirm whose payment.card names a card code or number, aborts as an
|
|
1906
|
+
// unsupported checkout, reason `unsupported_ebanx_card_resend`, with no claim, create or prompt, so EBANX never
|
|
1907
|
+
// receives the placeholder as a card. The attachment then holds: the page's next try would send the placeholder
|
|
1908
|
+
// again. A checkout whose token request was already approved keeps that approval's state (lifecycle.unsupported),
|
|
1909
|
+
// and its follow-ups stop (EbanxFollowUps.stop): a later request the approval would cover is refused too, so the
|
|
1910
|
+
// page cannot finish 3-D Secure and pay through the confirm after a refusal. A failed send stops them the same way.
|
|
1911
|
+
// A send goes on only while the checkout itself names the remembered approval (ebanxMaySend): it awaits the
|
|
1912
|
+
// merchant, reports the bank's challenge, or holds that approval's answer for the page (awaiting_approval while
|
|
1913
|
+
// the token's answer is on its way, since the adapters remember the approval before they hand the answer over, or
|
|
1914
|
+
// ready_to_submit while the approval waits for the page's retry). A new token request awaiting the person, a new
|
|
1915
|
+
// attempt after a merchant failure (idle), another approval and every final merchant result refuse it, and
|
|
1916
|
+
// nothing more reaches the device;
|
|
1917
|
+
// - a confirm whose body the adapter could not read, an empty one, or one that is not a JSON object aborts that
|
|
1918
|
+
// request alone with a `blocked` event, `unreadable_ebanx_request`, and does not hold the attachment;
|
|
1919
|
+
// - any other request (a confirm for a bank transfer, cash or a wallet, -record) defers, and the arbiter continues
|
|
1920
|
+
// it untouched.
|
|
1921
|
+
// A request the synced registry recognizes as a card request defers too, so the arbiter pauses it for that
|
|
1922
|
+
// recognizer; no build serves one today. A GET or a preflight is not this gate's. Synchronous until it must abort or
|
|
1923
|
+
// send, like every gate. Exported for the gate registry's tests.
|
|
1924
|
+
export const ebanxPreClaim = (ctx) => {
|
|
1925
|
+
const { url, method } = ctx.request;
|
|
1926
|
+
if (String(method).toUpperCase() !== 'POST' || !isEbanxResendUrl(url) || ctx.vault.isCardRequest(url, method))
|
|
1927
|
+
return undefined;
|
|
1928
|
+
const refusal = ebanxResendRefusalOf(url, 'POST', () => ctx.request.body);
|
|
1929
|
+
if (refusal === null)
|
|
1930
|
+
return undefined;
|
|
1931
|
+
if (refusal === 'unreadable')
|
|
1932
|
+
return ebanxUnreadable(ctx);
|
|
1933
|
+
let body;
|
|
1934
|
+
try {
|
|
1935
|
+
body = ctx.request.body;
|
|
1936
|
+
}
|
|
1937
|
+
catch {
|
|
1938
|
+
body = null;
|
|
1939
|
+
}
|
|
1940
|
+
const claim = ctx.ebanx && typeof ctx.vault.sendEbanxFollowUp === 'function' && ebanxMaySend(ctx)
|
|
1941
|
+
? ctx.ebanx.claim({ url, method: 'POST', headers: ctx.request.headers ?? {}, body }) : null;
|
|
1942
|
+
return claim ? ebanxFollowUp(ctx, claim) : ebanxResendRefused(ctx, refusal);
|
|
1943
|
+
};
|
|
1944
|
+
/**
|
|
1945
|
+
* The checkout states in which a send may go on under the remembered approval, an allowlist: the checkout awaits the
|
|
1946
|
+
* merchant, the application reported the bank's challenge (requestUserAction), the token's answer is still on its way
|
|
1947
|
+
* to the page (awaiting_approval: the page may send its first 3-D Secure call the moment the answer lands), or the
|
|
1948
|
+
* approval waits for the page's retry (ready_to_submit). Every other state refuses, idle and the final ones among them.
|
|
1949
|
+
*/
|
|
1950
|
+
const EBANX_SENDING_STATES = new Set(['awaiting_merchant', 'requires_user_action', 'awaiting_approval', 'ready_to_submit']);
|
|
1951
|
+
/**
|
|
1952
|
+
* Whether a send may go on: nothing aborted the checkout, no reconciliation of the merchant's order is running (one
|
|
1953
|
+
* that resolves failed while a confirm is out would let the application start another attempt), it is in one of
|
|
1954
|
+
* EBANX_SENDING_STATES, and its state names the approval the attachment remembered. A new token request awaiting the
|
|
1955
|
+
* person (awaiting_approval with no approval, or another one), a new attempt after a merchant failure, a prepared
|
|
1956
|
+
* checkout and another approval's state all refuse.
|
|
1957
|
+
*/
|
|
1958
|
+
function ebanxMaySend(ctx) {
|
|
1959
|
+
if (ctx.lifecycleAborted() || ctx.terminal() || !ctx.ebanx || ctx.lifecycle.isReconciling())
|
|
1960
|
+
return false;
|
|
1961
|
+
const approval = ctx.ebanx.authorizationId;
|
|
1962
|
+
const state = ctx.lifecycle.getState();
|
|
1963
|
+
return approval !== null && EBANX_SENDING_STATES.has(state.status) && state.authorizationId === approval;
|
|
1964
|
+
}
|
|
1965
|
+
/**
|
|
1966
|
+
* The checkout's final merchant results: completed, failed, declined, timed out, cancelled, an unknown outcome, or an
|
|
1967
|
+
* unsupported request. A 3-D Secure call, which charges nothing, that fails in one of them leaves that outcome alone.
|
|
1968
|
+
*/
|
|
1969
|
+
const EBANX_FINAL_STATES = new Set(['completed', 'failed', 'declined', 'timed_out', 'cancelled', 'outcome_unknown', 'unsupported']);
|
|
1970
|
+
/** Whether the checkout already ended (EBANX_FINAL_STATES): its state and reason stand, whatever a later 3-D Secure call does. */
|
|
1971
|
+
const ebanxCheckoutEnded = (ctx) => EBANX_FINAL_STATES.has(ctx.lifecycle.getState().status);
|
|
1972
|
+
/**
|
|
1973
|
+
* A confirm whose card may have reached EBANX and whose answer the page never got: only a payment the merchant
|
|
1974
|
+
* already confirmed (completed) stands, and every other state of this approval turns unknown, since EBANX may have
|
|
1975
|
+
* taken the payment. No other attempt can start while the send is out (both adapters refuse a new card request then,
|
|
1976
|
+
* CheckoutLifecycle.followUpsInFlight), so the state names this approval; one that names another is left alone.
|
|
1977
|
+
*/
|
|
1978
|
+
function ebanxConfirmUnconfirmed(ctx, authorizationId, reason) {
|
|
1979
|
+
const state = ctx.lifecycle.getState();
|
|
1980
|
+
if (state.status !== 'completed' && state.authorizationId === authorizationId) {
|
|
1981
|
+
ctx.lifecycle.failed(new PaymentOutcomeUnknownError(authorizationId, reason), true);
|
|
1982
|
+
}
|
|
1983
|
+
}
|
|
1984
|
+
async function ebanxResendRefused(ctx, refusal) {
|
|
1985
|
+
const { url, method } = ctx.request;
|
|
1986
|
+
ctx.preparationGate.invalidate('unsupported_checkout');
|
|
1987
|
+
ctx.ebanx?.stop();
|
|
1988
|
+
ctx.lifecycle.unsupported(EBANX_RESEND_REASON);
|
|
1989
|
+
ctx.onEvent?.({ type: 'unsupported_checkout', detail: { url: eventUrl(ctx.vault, url), method, reason: EBANX_RESEND_REASON,
|
|
1990
|
+
step: refusal === 'three_ds_card' ? 'three_d_secure' : 'confirm' } });
|
|
1991
|
+
await ctx.effects.abort();
|
|
1992
|
+
return { kind: 'abort' };
|
|
1993
|
+
}
|
|
1994
|
+
async function ebanxUnreadable(ctx) {
|
|
1995
|
+
ctx.onEvent?.({ type: 'blocked', detail: 'unreadable_ebanx_request' });
|
|
1996
|
+
await ctx.effects.abort();
|
|
1997
|
+
return { kind: 'abort' };
|
|
1998
|
+
}
|
|
1999
|
+
/**
|
|
2000
|
+
* A send the approved token request covers: the cardholder's device sends it with the real card
|
|
2001
|
+
* (VaultClient.sendEbanxFollowUp), and the page receives EBANX's answer, with no second prompt. The events name the
|
|
2002
|
+
* approval and the step, never the request, the card or EBANX's answer. When no answer comes back:
|
|
2003
|
+
* - a confirm the card may have reached EBANX with ends `outcome_unknown` (ebanx_confirm_unconfirmed): EBANX may
|
|
2004
|
+
* have charged it, and only the merchant's order can say;
|
|
2005
|
+
* - anything else (a 3-D Secure call, which charges nothing, or a send that never left the device) holds the
|
|
2006
|
+
* attachment as the refusal above does, and the page shows EBANX's own payment error.
|
|
2007
|
+
* Either way the page's request fails, so it never waits on a send nobody will answer. A send runs outside the
|
|
2008
|
+
* card-request lifecycle, so it counts as work in flight from its claim until the page has its answer or it failed
|
|
2009
|
+
* (CheckoutLifecycle.followUpStarted): meanwhile reconcile() and retryAfterMerchantFailure() throw, so the application
|
|
2010
|
+
* starts no other attempt while a confirm may pay. A confirm that may have left keeps only a payment the merchant
|
|
2011
|
+
* confirmed (completed); any other state of its approval turns unknown (ebanxConfirmUnconfirmed). Any other send keeps
|
|
2012
|
+
* a final outcome (EBANX_FINAL_STATES) with its reason.
|
|
2013
|
+
*/
|
|
2014
|
+
async function ebanxFollowUp(ctx, claim) {
|
|
2015
|
+
const settle = ctx.lifecycle.followUpStarted();
|
|
2016
|
+
try {
|
|
2017
|
+
return await sendEbanxFollowUp(ctx, claim);
|
|
2018
|
+
}
|
|
2019
|
+
finally {
|
|
2020
|
+
settle();
|
|
2021
|
+
}
|
|
2022
|
+
}
|
|
2023
|
+
async function sendEbanxFollowUp(ctx, claim) {
|
|
2024
|
+
const detail = { authorizationId: claim.authorizationId, step: claim.kind };
|
|
2025
|
+
ctx.onEvent?.({ type: 'ebanx_follow_up_requested', detail });
|
|
2026
|
+
let answer;
|
|
2027
|
+
try {
|
|
2028
|
+
answer = await ctx.vault.sendEbanxFollowUp(claim.authorizationId, claim.request, { signal: ctx.lifecycle.abort.signal, until: claim.until });
|
|
2029
|
+
}
|
|
2030
|
+
catch (error) {
|
|
2031
|
+
claim.settle(null);
|
|
2032
|
+
ctx.ebanx?.stop();
|
|
2033
|
+
const reason = error instanceof EbanxFollowUpError ? error.reason : 'follow_up_unavailable';
|
|
2034
|
+
const cardSent = error instanceof EbanxFollowUpError ? error.cardSent : true;
|
|
2035
|
+
ctx.onEvent?.({ type: 'ebanx_follow_up_failed', detail: { ...detail, reason, cardSent } });
|
|
2036
|
+
if (cardSent && claim.kind === 'confirm')
|
|
2037
|
+
ebanxConfirmUnconfirmed(ctx, claim.authorizationId, 'ebanx_confirm_unconfirmed');
|
|
2038
|
+
else if (!ebanxCheckoutEnded(ctx))
|
|
2039
|
+
ctx.lifecycle.unsupported(EBANX_RESEND_REASON);
|
|
2040
|
+
await ctx.effects.abort();
|
|
2041
|
+
return { kind: 'abort' };
|
|
2042
|
+
}
|
|
2043
|
+
claim.settle({ status: answer.status, text: answer.body });
|
|
2044
|
+
try {
|
|
2045
|
+
await ctx.effects.answer({ status: answer.status, headers: answer.headers, body: answer.body });
|
|
2046
|
+
}
|
|
2047
|
+
catch {
|
|
2048
|
+
// EBANX answered the device and the page never received it: a confirm's charge is the merchant's to confirm.
|
|
2049
|
+
ctx.ebanx?.stop();
|
|
2050
|
+
ctx.onEvent?.({ type: 'ebanx_follow_up_failed', detail: { ...detail, reason: 'handoff_failed', cardSent: true } });
|
|
2051
|
+
if (claim.kind === 'confirm')
|
|
2052
|
+
ebanxConfirmUnconfirmed(ctx, claim.authorizationId, 'ebanx_follow_up_handoff_failed');
|
|
2053
|
+
else if (!ebanxCheckoutEnded(ctx))
|
|
2054
|
+
ctx.lifecycle.unsupported(EBANX_RESEND_REASON);
|
|
2055
|
+
await ctx.effects.abort();
|
|
2056
|
+
return { kind: 'abort' };
|
|
2057
|
+
}
|
|
2058
|
+
ctx.onEvent?.({ type: 'ebanx_follow_up_answered', detail: { ...detail, status: answer.status } });
|
|
2059
|
+
return { kind: 'answer' };
|
|
2060
|
+
}
|
|
1787
2061
|
// The terminal arbiter, always last. A request that no gate claimed and that is
|
|
1788
2062
|
// neither a native Stripe step nor a recognized card request continues, unless
|
|
1789
2063
|
// it matches a declared payment endpoint we do not support (then it aborts as an
|
|
@@ -1817,8 +2091,8 @@ async function unsupportedContinue(ctx) {
|
|
|
1817
2091
|
* arbiter runs after all of them and is not part of this list. A gate appended
|
|
1818
2092
|
* after Stripe is never offered a step Stripe claimed: Stripe answers, aborts or
|
|
1819
2093
|
* pauses each one itself. */
|
|
1820
|
-
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, paypalHostedFieldsPreClaim,
|
|
1821
|
-
cybersourceSecureAcceptancePreClaim, worldlineMyCheckoutPreClaim, paddleVgsPayPreClaim];
|
|
2094
|
+
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, paypalHostedFieldsPreClaim,
|
|
2095
|
+
cybersourceSecureAcceptancePreClaim, worldlineMyCheckoutPreClaim, paddleVgsPayPreClaim, ebanxPreClaim];
|
|
1822
2096
|
/** Run each gate in order; the first to return a decision wins. If none does,
|
|
1823
2097
|
* the terminal arbiter decides between continue, abort (unsupported) and pause.
|
|
1824
2098
|
* Returns synchronously while every gate defers synchronously (an ordinary card
|
package/dist/preparation.d.ts
CHANGED
|
@@ -28,6 +28,19 @@ export declare class PreparationGate {
|
|
|
28
28
|
assertDocument(): Promise<void>;
|
|
29
29
|
private readDocument;
|
|
30
30
|
isEngaged(): boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Where a CreditGuard hosted page's own call beside its submit (its card lookup, its 3-D Secure
|
|
33
|
+
* calls: pre-claim.ts creditguardCompanionPreClaim) stands against this attachment's one
|
|
34
|
+
* preparation, which it never claims:
|
|
35
|
+
* 'none' no preparation was asked for, so the call goes on as it did before the lane held any;
|
|
36
|
+
* 'ready' a ready CreditGuard preparation, still inside its window, framed the page of this very
|
|
37
|
+
* transaction: the SDK answers the call itself and the placeholder goes nowhere, and the
|
|
38
|
+
* same preparation admits the page's encrypted submit (jwePayload) for the claim to bind;
|
|
39
|
+
* 'refuse' anything else (a preparation still being made, spent, failed or expired, or one for
|
|
40
|
+
* another processor or transaction): the call is refused, so the placeholder never
|
|
41
|
+
* reaches CreditGuard on a page the cardholder did not approve.
|
|
42
|
+
*/
|
|
43
|
+
creditguardStanding(txId: string): 'none' | 'ready' | 'refuse';
|
|
31
44
|
/** Whether the one preparation was bound to a card request (claim() took it). */
|
|
32
45
|
isConsumed(): boolean;
|
|
33
46
|
/**
|
package/dist/preparation.js
CHANGED
|
@@ -2,6 +2,7 @@ import { CheckoutPreparationError } from './client.js';
|
|
|
2
2
|
import { amountRefusal, validAmountInput } from './client.js';
|
|
3
3
|
import { matchesPreparation, validPreparationEnvironment, preparationEndpoint, preparationBindsByShape, preparationRequestMethod, preparedRequestRefusal } from './prepared-processor.js';
|
|
4
4
|
import { SECURE_ACCEPTANCE_PSP, secureAcceptanceCheckoutProfile } from './cybersource-secure-acceptance.js';
|
|
5
|
+
import { creditguardPageProcessUrl, creditguardProcessTxId } from './creditguard.generated.js';
|
|
5
6
|
/** A local, one-use rendezvous. It never starts or retries a merchant request. */
|
|
6
7
|
export class PreparationGate {
|
|
7
8
|
opts;
|
|
@@ -216,6 +217,27 @@ export class PreparationGate {
|
|
|
216
217
|
}
|
|
217
218
|
}
|
|
218
219
|
isEngaged() { return this.state !== 'unused'; }
|
|
220
|
+
/**
|
|
221
|
+
* Where a CreditGuard hosted page's own call beside its submit (its card lookup, its 3-D Secure
|
|
222
|
+
* calls: pre-claim.ts creditguardCompanionPreClaim) stands against this attachment's one
|
|
223
|
+
* preparation, which it never claims:
|
|
224
|
+
* 'none' no preparation was asked for, so the call goes on as it did before the lane held any;
|
|
225
|
+
* 'ready' a ready CreditGuard preparation, still inside its window, framed the page of this very
|
|
226
|
+
* transaction: the SDK answers the call itself and the placeholder goes nowhere, and the
|
|
227
|
+
* same preparation admits the page's encrypted submit (jwePayload) for the claim to bind;
|
|
228
|
+
* 'refuse' anything else (a preparation still being made, spent, failed or expired, or one for
|
|
229
|
+
* another processor or transaction): the call is refused, so the placeholder never
|
|
230
|
+
* reaches CreditGuard on a page the cardholder did not approve.
|
|
231
|
+
*/
|
|
232
|
+
creditguardStanding(txId) {
|
|
233
|
+
if (this.state === 'unused')
|
|
234
|
+
return 'none';
|
|
235
|
+
const prepared = this.prepared;
|
|
236
|
+
if (this.state !== 'ready' || !prepared || prepared.psp !== 'creditguard' || !(Date.parse(prepared.expiresAt) > Date.now()))
|
|
237
|
+
return 'refuse';
|
|
238
|
+
const processUrl = typeof prepared.paymentPageUrl === 'string' ? creditguardPageProcessUrl(prepared.paymentPageUrl) : null;
|
|
239
|
+
return processUrl !== null && creditguardProcessTxId(processUrl) === txId ? 'ready' : 'refuse';
|
|
240
|
+
}
|
|
219
241
|
/** Whether the one preparation was bound to a card request (claim() took it). */
|
|
220
242
|
isConsumed() { return this.state === 'consumed'; }
|
|
221
243
|
/**
|