@agent-cards/sdk 0.23.12 → 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 +38 -1
- package/LICENSE +38 -0
- package/PREFLIGHT.md +1 -1
- package/README.md +53 -14
- package/dist/adyen-merchant-hosted.generated.js +2 -2
- package/dist/builtin-registry.generated.js +1 -1
- package/dist/card-fields.generated.js +843 -3
- package/dist/cdp.js +101 -13
- package/dist/client.d.ts +91 -2
- package/dist/client.js +205 -13
- 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 +35 -3
- package/dist/lifecycle.js +38 -3
- 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/paddle-vgs-pay.d.ts +55 -0
- package/dist/paddle-vgs-pay.js +115 -0
- package/dist/paddle-vgs.generated.d.ts +29 -1
- package/dist/paddle-vgs.generated.js +124 -5
- package/dist/paysafe.generated.d.ts +5 -7
- package/dist/paysafe.generated.js +50 -18
- package/dist/pre-claim.d.ts +46 -1
- package/dist/pre-claim.js +338 -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 +878 -38
- package/dist/redsys.d.ts +42 -0
- package/dist/redsys.js +68 -0
- package/dist/stripe-checkout.generated.js +479 -11
- package/package.json +9 -6
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { ReplayResponse } from './client.js';
|
|
2
|
+
/**
|
|
3
|
+
* Why a /pay naming a stored card was aborted, reported as the `reason` of the `unsupported_checkout` event:
|
|
4
|
+
* paddle_vgs_pay_unbound this attachment handed no stored card's id to the frame that sent it
|
|
5
|
+
* paddle_vgs_pay_expired it came more than a minute after the id reached the frame
|
|
6
|
+
* paddle_vgs_pay_used the one /pay for that id already went
|
|
7
|
+
* paddle_vgs_pay_card_mismatch it names another stored card, or the other environment's checkout service
|
|
8
|
+
* paddle_vgs_pay_unsupported anything else payment-core's rule refuses: another shape, a card saved at
|
|
9
|
+
* Paddle for later, a network the frame chose from the typed card
|
|
10
|
+
*/
|
|
11
|
+
export type PaddleVgsPayRefusal = 'paddle_vgs_pay_unbound' | 'paddle_vgs_pay_expired' | 'paddle_vgs_pay_used' | 'paddle_vgs_pay_card_mismatch' | 'paddle_vgs_pay_unsupported';
|
|
12
|
+
/** The patterns attachToCdp holds Paddle's two checkout services under, only for an attachment the API served Paddle VGS to. */
|
|
13
|
+
export declare const PADDLE_VGS_PAY_PATTERNS: readonly string[];
|
|
14
|
+
/** Whether `url` is on one of Paddle's two checkout services: what attachToPlaywright routes, once served. */
|
|
15
|
+
export declare function isPaddleCheckoutService(url: string): boolean;
|
|
16
|
+
/** Whether the synced registry serves Paddle VGS's card request: an org Agentcard turned Paddle on for. */
|
|
17
|
+
export declare function recognizesPaddleVgs(vault: {
|
|
18
|
+
isCardRequest?: (url: string, method?: string) => boolean;
|
|
19
|
+
} | null | undefined): boolean;
|
|
20
|
+
/** The URL an event names for a /pay: its origin and path, with the checkout's id as {checkout}. */
|
|
21
|
+
export declare function paddleVgsPayEventUrl(url: string): string;
|
|
22
|
+
/**
|
|
23
|
+
* One attachment's /pay binding: at most one, to the stored card's id it last handed a Paddle frame. `arm` runs
|
|
24
|
+
* right before the id is handed over, `release` when the attachment is cancelled, and `judge` for every request
|
|
25
|
+
* to Paddle's checkout services. `served` says whether the synced registry serves Paddle VGS; it is asked only
|
|
26
|
+
* about a POST to /pay, so no other request reads the registry here.
|
|
27
|
+
*/
|
|
28
|
+
export declare class PaddleVgsPayGate {
|
|
29
|
+
private readonly served;
|
|
30
|
+
private readonly now;
|
|
31
|
+
private bound;
|
|
32
|
+
constructor(served: () => boolean, now?: () => number);
|
|
33
|
+
/** Whether the gate judges this request: a POST to /pay, on an attachment the API served Paddle VGS to. */
|
|
34
|
+
holds(url: string, method: string): boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Bind the next /pay of `frame`, the frame about to receive `replay` for its card request to `cardsUrl`, to the
|
|
37
|
+
* stored card's id in the replay and the approved card's facts the API served beside it. Does nothing for a
|
|
38
|
+
* request to anything but Paddle's card vault. Throws when the frame is unknown, or the replay carries no id or
|
|
39
|
+
* no facts payment-core reads: the id must then not reach the frame, since its /pay could only carry the
|
|
40
|
+
* placeholder's facts.
|
|
41
|
+
*/
|
|
42
|
+
arm(frame: unknown, cardsUrl: string, replay: ReplayResponse): void;
|
|
43
|
+
/** Drop the binding: no /pay continues with these facts after this. */
|
|
44
|
+
release(): void;
|
|
45
|
+
/**
|
|
46
|
+
* What becomes of a request to Paddle's checkout services: null when it is not a /pay naming a stored card (it
|
|
47
|
+
* continues untouched), the body to continue it with (payment-core paddleVgsPayVerdict), or why it is aborted.
|
|
48
|
+
* The binding answers one /pay: the first one from its frame continues or is refused, and either way ends it.
|
|
49
|
+
*/
|
|
50
|
+
judge(frame: unknown, url: string, method: string, body: string | null): {
|
|
51
|
+
body: string;
|
|
52
|
+
} | {
|
|
53
|
+
reason: PaddleVgsPayRefusal;
|
|
54
|
+
} | null;
|
|
55
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
// Paddle's /pay, the request that charges the card the cardholder's phone stored at VGS, as the SDK sees it
|
|
2
|
+
// (payment-core paddle-vgs.js has the request's shape and the rule).
|
|
3
|
+
//
|
|
4
|
+
// Once the SDK hands the stored card's id to Paddle's checkout frame, the frame posts /pay to its checkout
|
|
5
|
+
// service with that id beside card facts it read from the number typed into its own form: on this lane the
|
|
6
|
+
// agent's placeholder, so the placeholder's first six, last four and brand. This build holds that /pay and
|
|
7
|
+
// writes over those five values the approved card's own, which the phone read from the card it sent to VGS and
|
|
8
|
+
// the API served with the id (payment-core paddleVgsPayVerdict). Every other byte goes as the frame wrote it.
|
|
9
|
+
//
|
|
10
|
+
// The binding: the frame that received the stored card's id, the id, the vault's environment and the approved
|
|
11
|
+
// card's facts, for PADDLE_VGS_CARD_ID_WINDOW_MS after the hand-over, for one /pay. A /pay naming a stored card
|
|
12
|
+
// is aborted, so Paddle charges nothing, when this attachment handed no id to that frame
|
|
13
|
+
// (paddle_vgs_pay_unbound), after the window (paddle_vgs_pay_expired), after the one /pay already went
|
|
14
|
+
// (paddle_vgs_pay_used), or when payment-core's rule refuses it: another stored card or environment
|
|
15
|
+
// (paddle_vgs_pay_card_mismatch), or any other shape (paddle_vgs_pay_unsupported). A /pay naming no stored card
|
|
16
|
+
// (PayPal, a wallet) carries none of the cardholder's cards and continues untouched.
|
|
17
|
+
//
|
|
18
|
+
// Nothing is decided without the registry: an attachment the API did not serve Paddle VGS to never holds
|
|
19
|
+
// Paddle's checkout service, so its /pay continues untouched, exactly as before.
|
|
20
|
+
import { PADDLE_VGS_CARD_ID_WINDOW_MS, PADDLE_VGS_CARDS_URLS, paddleVgsCardFactsOf, paddleVgsEnvironment, paddleVgsPayCarriesStoredCard, paddleVgsPayTarget, paddleVgsPayVerdict, } from './paddle-vgs.generated.js';
|
|
21
|
+
const CHECKOUT_SERVICES = ['https://checkout-service.paddle.com', 'https://sandbox-checkout-service.paddle.com'];
|
|
22
|
+
/** The patterns attachToCdp holds Paddle's two checkout services under, only for an attachment the API served Paddle VGS to. */
|
|
23
|
+
export const PADDLE_VGS_PAY_PATTERNS = CHECKOUT_SERVICES.map((origin) => `${origin}/*`);
|
|
24
|
+
/** Whether `url` is on one of Paddle's two checkout services: what attachToPlaywright routes, once served. */
|
|
25
|
+
export function isPaddleCheckoutService(url) {
|
|
26
|
+
try {
|
|
27
|
+
return CHECKOUT_SERVICES.includes(new URL(url).origin);
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/** Whether the synced registry serves Paddle VGS's card request: an org Agentcard turned Paddle on for. */
|
|
34
|
+
export function recognizesPaddleVgs(vault) {
|
|
35
|
+
if (!vault || typeof vault.isCardRequest !== 'function')
|
|
36
|
+
return false;
|
|
37
|
+
try {
|
|
38
|
+
return vault.isCardRequest(PADDLE_VGS_CARDS_URLS.production, 'POST') || vault.isCardRequest(PADDLE_VGS_CARDS_URLS.sandbox, 'POST');
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/** The URL an event names for a /pay: its origin and path, with the checkout's id as {checkout}. */
|
|
45
|
+
export function paddleVgsPayEventUrl(url) {
|
|
46
|
+
return url.replace(/^(https:\/\/[^/]+\/(?:checkout|transaction-checkout)\/)[^/]+(\/pay)$/, '$1{checkout}$2');
|
|
47
|
+
}
|
|
48
|
+
const known = (frame) => frame !== undefined && frame !== null && frame !== '';
|
|
49
|
+
/**
|
|
50
|
+
* One attachment's /pay binding: at most one, to the stored card's id it last handed a Paddle frame. `arm` runs
|
|
51
|
+
* right before the id is handed over, `release` when the attachment is cancelled, and `judge` for every request
|
|
52
|
+
* to Paddle's checkout services. `served` says whether the synced registry serves Paddle VGS; it is asked only
|
|
53
|
+
* about a POST to /pay, so no other request reads the registry here.
|
|
54
|
+
*/
|
|
55
|
+
export class PaddleVgsPayGate {
|
|
56
|
+
served;
|
|
57
|
+
now;
|
|
58
|
+
bound = null;
|
|
59
|
+
constructor(served, now = Date.now) {
|
|
60
|
+
this.served = served;
|
|
61
|
+
this.now = now;
|
|
62
|
+
}
|
|
63
|
+
/** Whether the gate judges this request: a POST to /pay, on an attachment the API served Paddle VGS to. */
|
|
64
|
+
holds(url, method) {
|
|
65
|
+
return String(method).toUpperCase() === 'POST' && paddleVgsPayTarget(url) !== null && this.served();
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Bind the next /pay of `frame`, the frame about to receive `replay` for its card request to `cardsUrl`, to the
|
|
69
|
+
* stored card's id in the replay and the approved card's facts the API served beside it. Does nothing for a
|
|
70
|
+
* request to anything but Paddle's card vault. Throws when the frame is unknown, or the replay carries no id or
|
|
71
|
+
* no facts payment-core reads: the id must then not reach the frame, since its /pay could only carry the
|
|
72
|
+
* placeholder's facts.
|
|
73
|
+
*/
|
|
74
|
+
arm(frame, cardsUrl, replay) {
|
|
75
|
+
const environment = paddleVgsEnvironment(cardsUrl);
|
|
76
|
+
if (environment === null)
|
|
77
|
+
return;
|
|
78
|
+
this.release();
|
|
79
|
+
const token = replay.mode === undefined || replay.mode === 'token' ? replay : null;
|
|
80
|
+
const facts = token ? paddleVgsCardFactsOf(token.processorContext) : null;
|
|
81
|
+
let cardId = null;
|
|
82
|
+
try {
|
|
83
|
+
cardId = token ? JSON.parse(token.body)?.data?.id : null;
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
cardId = null;
|
|
87
|
+
}
|
|
88
|
+
if (!known(frame) || !facts || typeof cardId !== 'string')
|
|
89
|
+
throw new Error('paddle_vgs_card_facts_unavailable');
|
|
90
|
+
this.bound = { frame, environment, cardId, facts, until: this.now() + PADDLE_VGS_CARD_ID_WINDOW_MS, used: false };
|
|
91
|
+
}
|
|
92
|
+
/** Drop the binding: no /pay continues with these facts after this. */
|
|
93
|
+
release() {
|
|
94
|
+
this.bound = null;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* What becomes of a request to Paddle's checkout services: null when it is not a /pay naming a stored card (it
|
|
98
|
+
* continues untouched), the body to continue it with (payment-core paddleVgsPayVerdict), or why it is aborted.
|
|
99
|
+
* The binding answers one /pay: the first one from its frame continues or is refused, and either way ends it.
|
|
100
|
+
*/
|
|
101
|
+
judge(frame, url, method, body) {
|
|
102
|
+
if (!this.holds(url, method) || !paddleVgsPayCarriesStoredCard(body))
|
|
103
|
+
return null;
|
|
104
|
+
const bound = this.bound;
|
|
105
|
+
if (!bound || !known(frame) || frame !== bound.frame)
|
|
106
|
+
return { reason: 'paddle_vgs_pay_unbound' };
|
|
107
|
+
if (bound.used)
|
|
108
|
+
return { reason: 'paddle_vgs_pay_used' };
|
|
109
|
+
bound.used = true;
|
|
110
|
+
if (this.now() > bound.until)
|
|
111
|
+
return { reason: 'paddle_vgs_pay_expired' };
|
|
112
|
+
const verdict = paddleVgsPayVerdict({ url, method, body }, { environment: bound.environment, cardId: bound.cardId, facts: bound.facts });
|
|
113
|
+
return verdict.verdict === 'continue' ? { body: verdict.body } : { reason: verdict.code };
|
|
114
|
+
}
|
|
115
|
+
}
|
|
@@ -47,4 +47,32 @@ declare function paddleVgsAnswerOutcome(status: any, text: any, card: any): {
|
|
|
47
47
|
reason?: undefined;
|
|
48
48
|
code?: undefined;
|
|
49
49
|
};
|
|
50
|
-
|
|
50
|
+
declare var PADDLE_VGS_CARD_ID_WINDOW_MS: number;
|
|
51
|
+
declare function paddleVgsPayTarget(url: any): Readonly<{
|
|
52
|
+
environment: "production" | "sandbox";
|
|
53
|
+
line: "classic" | "billing";
|
|
54
|
+
}> | null;
|
|
55
|
+
declare function paddleVgsPayCarriesStoredCard(body: any): boolean;
|
|
56
|
+
declare function paddleVgsCardFacts(card: any): Readonly<{
|
|
57
|
+
card_first_six: string;
|
|
58
|
+
card_last_four: string;
|
|
59
|
+
expiry_month: number;
|
|
60
|
+
expiry_year: number;
|
|
61
|
+
card_brand: "visa" | "master" | "american_express";
|
|
62
|
+
}>;
|
|
63
|
+
declare function paddleVgsCardFactsOf(value: any): Readonly<{
|
|
64
|
+
card_first_six: any;
|
|
65
|
+
card_last_four: string;
|
|
66
|
+
expiry_month: any;
|
|
67
|
+
expiry_year: any;
|
|
68
|
+
card_brand: any;
|
|
69
|
+
}> | null;
|
|
70
|
+
declare function paddleVgsCardFactsAgree(facts: any, stored: any): boolean;
|
|
71
|
+
declare function paddleVgsPayVerdict(request: any, approved: any): Readonly<{
|
|
72
|
+
verdict: "refuse";
|
|
73
|
+
code: any;
|
|
74
|
+
}> | Readonly<{
|
|
75
|
+
verdict: "continue";
|
|
76
|
+
body: string;
|
|
77
|
+
}>;
|
|
78
|
+
export { PADDLE_VGS_CAPABILITY, PADDLE_VGS_CARDS_URLS, PADDLE_VGS_CARD_ID_WINDOW_MS, PADDLE_VGS_CARD_NETWORKS, PADDLE_VGS_CONTENT_TYPE, PADDLE_VGS_MERCHANT_PINS, PADDLE_VGS_OPERATION, PADDLE_VGS_PSP, PADDLE_VGS_REFUSAL_CODES, injectPaddleVgsCard, isPaddleVgsCardsUrl, isPreparedPaddleVgsRequest, paddleVgsAnswerOutcome, paddleVgsBodyVerdict, paddleVgsCardFacts, paddleVgsCardFactsAgree, paddleVgsCardFactsOf, paddleVgsCardRefusal, paddleVgsEnvironment, paddleVgsMerchantPin, paddleVgsMerchantRefusal, paddleVgsPayCarriesStoredCard, paddleVgsPayTarget, paddleVgsPayVerdict, paddleVgsRefusalCode, paddleVgsRefusalMessage, paddleVgsRequestRefusal, paddleVgsSellerClaimsPinned, paddleVgsStoredCardRefusal, paddleVgsTypedCardRefusal };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// @ts-nocheck
|
|
2
2
|
// Generated from @agent-cards/payment-core. Do not edit.
|
|
3
|
-
// artifact-sha256:
|
|
3
|
+
// artifact-sha256: f3437a15e11b72b9958179cc9610fdafb411cc5db76be536af62c08360a45575
|
|
4
4
|
// src/paddle-vgs.js
|
|
5
5
|
var PADDLE_VGS_PSP = "paddle_vgs";
|
|
6
6
|
var PADDLE_VGS_CAPABILITY = "paddle_vgs_cards";
|
|
@@ -254,12 +254,12 @@ function readAnswer(text) {
|
|
|
254
254
|
return null;
|
|
255
255
|
}
|
|
256
256
|
}
|
|
257
|
+
var isStoredCardId = (id) => typeof id === "string" && STORED_CARD_ID.test(id) && /[A-Za-z]/.test(id) && !PAN_LIKE.test(id);
|
|
257
258
|
function storedCardId(answer) {
|
|
258
259
|
const data = answer && answer.data;
|
|
259
|
-
if (!record(data) || data.type !== "cards"
|
|
260
|
+
if (!record(data) || data.type !== "cards")
|
|
260
261
|
return null;
|
|
261
|
-
|
|
262
|
-
return STORED_CARD_ID.test(id) && /[A-Za-z]/.test(id) && !PAN_LIKE.test(id) ? id : null;
|
|
262
|
+
return isStoredCardId(data.id) ? data.id : null;
|
|
263
263
|
}
|
|
264
264
|
var boundedCode = (value) => typeof value === "string" && REFUSAL_CODE.test(value) && (value.match(/[0-9]/g) ?? []).length <= MAX_CODE_DIGITS ? value : null;
|
|
265
265
|
function paddleVgsRefusalCode(text) {
|
|
@@ -286,4 +286,123 @@ function paddleVgsAnswerOutcome(status, text, card) {
|
|
|
286
286
|
return { outcome: "unknown", reason: "paddle_vgs_unsafe_response" };
|
|
287
287
|
return { outcome: "approved", body: JSON.stringify({ data: { type: "cards", id } }) };
|
|
288
288
|
}
|
|
289
|
-
|
|
289
|
+
var PADDLE_VGS_CARD_ID_WINDOW_MS = 6e4;
|
|
290
|
+
var PAY_URL = /^https:\/\/(sandbox-)?checkout-service\.paddle\.com\/(checkout|transaction-checkout)\/[A-Za-z0-9_-]{1,64}\/pay$/;
|
|
291
|
+
function paddleVgsPayTarget(url) {
|
|
292
|
+
const match = typeof url === "string" ? PAY_URL.exec(url) : null;
|
|
293
|
+
return match ? Object.freeze({ environment: match[1] ? "sandbox" : "production", line: match[2] === "checkout" ? "classic" : "billing" }) : null;
|
|
294
|
+
}
|
|
295
|
+
function paddleVgsPayCarriesStoredCard(body) {
|
|
296
|
+
if (typeof body !== "string")
|
|
297
|
+
return true;
|
|
298
|
+
try {
|
|
299
|
+
return JSON.stringify(JSON.parse(body)).includes('"vgs_card"');
|
|
300
|
+
}
|
|
301
|
+
catch {
|
|
302
|
+
return true;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
var FRAME_BRANDS = /* @__PURE__ */ Object.freeze(["visa", "master", "american_express", "discover", "diners_club", "jcb", "maestro", "dankort", "unionpay", "unknown"]);
|
|
306
|
+
var PAY_BRANDS = /* @__PURE__ */ Object.freeze({ visa: "visa", mastercard: "master", amex: "american_express" });
|
|
307
|
+
var FACT_KEYS = /* @__PURE__ */ Object.freeze(["card_first_six", "card_last_four", "expiry_month", "expiry_year", "card_brand"]);
|
|
308
|
+
var FIRST_SIX = /^[0-9]{6}$/;
|
|
309
|
+
var LAST_FOUR = /^[0-9]{4}$/;
|
|
310
|
+
var fullYear = (year) => {
|
|
311
|
+
const value = Number(year);
|
|
312
|
+
return Number.isInteger(value) && value >= 0 && value <= 99 ? 2e3 + value : value;
|
|
313
|
+
};
|
|
314
|
+
var expiryMonth = (month) => Number.isInteger(month) && month >= 1 && month <= 12;
|
|
315
|
+
var expiryYear = (year) => Number.isInteger(year) && year >= 2e3 && year <= 2099;
|
|
316
|
+
function paddleVgsCardFacts(card) {
|
|
317
|
+
const number = card ? String(card.number) : "";
|
|
318
|
+
const network = cardNetwork(number);
|
|
319
|
+
const month = card ? Number(card.month) : NaN;
|
|
320
|
+
const year = card ? fullYear(card.year) : NaN;
|
|
321
|
+
if (network === null || !expiryMonth(month) || !expiryYear(year))
|
|
322
|
+
throw new Error("Could not read this card's details for Paddle. Nothing was sent.");
|
|
323
|
+
return Object.freeze({ card_first_six: number.slice(0, 6), card_last_four: number.slice(-4), expiry_month: month, expiry_year: year, card_brand: PAY_BRANDS[network] });
|
|
324
|
+
}
|
|
325
|
+
function paddleVgsCardFactsOf(value) {
|
|
326
|
+
if (!record(value))
|
|
327
|
+
return null;
|
|
328
|
+
const keys = Object.keys(value);
|
|
329
|
+
if (keys.length !== FACT_KEYS.length || !FACT_KEYS.every((key) => keys.includes(key)))
|
|
330
|
+
return null;
|
|
331
|
+
const { card_first_six: firstSix, card_last_four: lastFour, expiry_month: month, expiry_year: year, card_brand: brand } = value;
|
|
332
|
+
const network = typeof firstSix === "string" && FIRST_SIX.test(firstSix) ? cardNetwork(`${firstSix}000000`) : null;
|
|
333
|
+
if (network === null || PAY_BRANDS[network] !== brand || typeof lastFour !== "string" || !LAST_FOUR.test(lastFour) || !expiryMonth(month) || !expiryYear(year))
|
|
334
|
+
return null;
|
|
335
|
+
return Object.freeze({ card_first_six: firstSix, card_last_four: lastFour, expiry_month: month, expiry_year: year, card_brand: brand });
|
|
336
|
+
}
|
|
337
|
+
function paddleVgsCardFactsAgree(facts, stored) {
|
|
338
|
+
const read = paddleVgsCardFactsOf(facts);
|
|
339
|
+
if (!read)
|
|
340
|
+
return false;
|
|
341
|
+
const card = record(stored) ? stored : {};
|
|
342
|
+
const network = Object.keys(PAY_BRANDS).find((name) => PAY_BRANDS[name] === read.card_brand);
|
|
343
|
+
const brand = storedBrand(card.brand);
|
|
344
|
+
return !(typeof card.last4 === "string" && LAST_FOUR.test(card.last4) && card.last4 !== read.card_last_four) && (brand === null || brand === network) && !(Number.isInteger(card.expiryMonth) && card.expiryMonth !== read.expiry_month) && !(Number.isInteger(card.expiryYear) && card.expiryYear % 100 !== read.expiry_year % 100);
|
|
345
|
+
}
|
|
346
|
+
var PAY_BODY_LIMIT = 16384;
|
|
347
|
+
var PAY_DATA_KEYS = /* @__PURE__ */ Object.freeze(["payment_method_type", "payment_method_data"]);
|
|
348
|
+
var PAY_CARD_KEYS = /* @__PURE__ */ Object.freeze(["metadata", "three_d_s", "vgs_card"]);
|
|
349
|
+
var METADATA_KEYS = /* @__PURE__ */ Object.freeze(["cardholder_name", ...FACT_KEYS]);
|
|
350
|
+
var BROWSER_INFO = /^[A-Za-z0-9+/]{1,4096}={0,2}$/;
|
|
351
|
+
var RADAR_TOKEN = /^[\x21-\x7e]{1,512}$/;
|
|
352
|
+
var COUNTRY_CODE = /^[A-Za-z]{2}$/;
|
|
353
|
+
var FRONTEND_HOST = /^https:\/\/[\x21-\x7e]{1,1024}$/;
|
|
354
|
+
var radarKeys = (value) => sameKeys(value, ["token", "country_code"]) || sameKeys(value, ["country_code", "token"]);
|
|
355
|
+
function readPay(body, line) {
|
|
356
|
+
if (typeof body !== "string" || body === "" || body.length > PAY_BODY_LIMIT)
|
|
357
|
+
return null;
|
|
358
|
+
let value;
|
|
359
|
+
try {
|
|
360
|
+
value = JSON.parse(body);
|
|
361
|
+
if (!record(value) || JSON.stringify(value) !== body)
|
|
362
|
+
return null;
|
|
363
|
+
}
|
|
364
|
+
catch {
|
|
365
|
+
return null;
|
|
366
|
+
}
|
|
367
|
+
const data = value.data;
|
|
368
|
+
if (!sameKeys(value, ["data"]) || !record(data) || !(sameKeys(data, PAY_DATA_KEYS) || sameKeys(data, [...PAY_DATA_KEYS, "checkout"])) || data.payment_method_type !== (line === "classic" ? "CARD" : "card"))
|
|
369
|
+
return null;
|
|
370
|
+
if (data.checkout !== void 0 && !(record(data.checkout) && sameKeys(data.checkout, ["frontend_host"]) && typeof data.checkout.frontend_host === "string" && FRONTEND_HOST.test(data.checkout.frontend_host)))
|
|
371
|
+
return null;
|
|
372
|
+
const method = data.payment_method_data;
|
|
373
|
+
const card = record(method) && sameKeys(method, ["card"]) ? method.card : null;
|
|
374
|
+
if (!record(card) || !(sameKeys(card, PAY_CARD_KEYS) || sameKeys(card, [...PAY_CARD_KEYS, "session_tracking"])))
|
|
375
|
+
return null;
|
|
376
|
+
const metadata = card.metadata;
|
|
377
|
+
if (!record(metadata) || !sameKeys(metadata, METADATA_KEYS) || typeof metadata.cardholder_name !== "string" || metadata.cardholder_name.length > NAME_LIMIT || CONTROL.test(metadata.cardholder_name) || cardLikeName(metadata.cardholder_name) || typeof metadata.card_first_six !== "string" || !FIRST_SIX.test(metadata.card_first_six) || typeof metadata.card_last_four !== "string" || !LAST_FOUR.test(metadata.card_last_four) || !expiryMonth(metadata.expiry_month) || !expiryYear(metadata.expiry_year) || !FRAME_BRANDS.includes(metadata.card_brand))
|
|
378
|
+
return null;
|
|
379
|
+
const details = card.three_d_s;
|
|
380
|
+
if (!record(details) || !sameKeys(details, ["browser_info"]) || typeof details.browser_info !== "string" || !BROWSER_INFO.test(details.browser_info))
|
|
381
|
+
return null;
|
|
382
|
+
if (!record(card.vgs_card) || !sameKeys(card.vgs_card, ["card_id"]) || !isStoredCardId(card.vgs_card.card_id))
|
|
383
|
+
return null;
|
|
384
|
+
if (card.session_tracking !== void 0) {
|
|
385
|
+
const tracking = card.session_tracking;
|
|
386
|
+
const radar = record(tracking) && sameKeys(tracking, ["stripe_radar"]) ? tracking.stripe_radar : null;
|
|
387
|
+
if (!record(radar) || !radarKeys(radar) || typeof radar.token !== "string" || !RADAR_TOKEN.test(radar.token) || typeof radar.country_code !== "string" || !COUNTRY_CODE.test(radar.country_code))
|
|
388
|
+
return null;
|
|
389
|
+
}
|
|
390
|
+
return value;
|
|
391
|
+
}
|
|
392
|
+
function paddleVgsPayVerdict(request, approved) {
|
|
393
|
+
const facts = record(approved) ? paddleVgsCardFactsOf(approved.facts) : null;
|
|
394
|
+
if (!facts || !isStoredCardId(approved.cardId) || approved.environment !== "production" && approved.environment !== "sandbox") {
|
|
395
|
+
return refuse("paddle_vgs_pay_unbound");
|
|
396
|
+
}
|
|
397
|
+
const target = record(request) && typeof request.method === "string" && request.method.toUpperCase() === "POST" ? paddleVgsPayTarget(request.url) : null;
|
|
398
|
+
const value = target ? readPay(request.body, target.line) : null;
|
|
399
|
+
if (!value)
|
|
400
|
+
return refuse("paddle_vgs_pay_unsupported");
|
|
401
|
+
const card = value.data.payment_method_data.card;
|
|
402
|
+
if (target.environment !== approved.environment || card.vgs_card.card_id !== approved.cardId)
|
|
403
|
+
return refuse("paddle_vgs_pay_card_mismatch");
|
|
404
|
+
for (const key of FACT_KEYS)
|
|
405
|
+
card.metadata[key] = facts[key];
|
|
406
|
+
return Object.freeze({ verdict: "continue", body: JSON.stringify(value) });
|
|
407
|
+
}
|
|
408
|
+
export { PADDLE_VGS_CAPABILITY, PADDLE_VGS_CARDS_URLS, PADDLE_VGS_CARD_ID_WINDOW_MS, PADDLE_VGS_CARD_NETWORKS, PADDLE_VGS_CONTENT_TYPE, PADDLE_VGS_MERCHANT_PINS, PADDLE_VGS_OPERATION, PADDLE_VGS_PSP, PADDLE_VGS_REFUSAL_CODES, injectPaddleVgsCard, isPaddleVgsCardsUrl, isPreparedPaddleVgsRequest, paddleVgsAnswerOutcome, paddleVgsBodyVerdict, paddleVgsCardFacts, paddleVgsCardFactsAgree, paddleVgsCardFactsOf, paddleVgsCardRefusal, paddleVgsEnvironment, paddleVgsMerchantPin, paddleVgsMerchantRefusal, paddleVgsPayCarriesStoredCard, paddleVgsPayTarget, paddleVgsPayVerdict, paddleVgsRefusalCode, paddleVgsRefusalMessage, paddleVgsRequestRefusal, paddleVgsSellerClaimsPinned, paddleVgsStoredCardRefusal, paddleVgsTypedCardRefusal };
|
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
export type PaysafePreparationEnvironment = 'production' | 'sandbox';
|
|
2
2
|
export declare function paysafePreparationUrl(environment: string): string;
|
|
3
|
-
/** Exact strings refuse credentials, queries, fragments and URL normalization. */
|
|
3
|
+
/** The environment of a reviewed build's card URL. Exact strings refuse credentials, queries, fragments and URL normalization. */
|
|
4
4
|
export declare function paysafeEnvironment(url: string): PaysafePreparationEnvironment | undefined;
|
|
5
|
-
/** Checkout
|
|
6
|
-
* consume only its new-card request, including native clientInfo and headers
|
|
7
|
-
* Saved cards, alternate APIs and 3DS-only requests
|
|
8
|
-
*
|
|
9
|
-
* e9e111ff03073cc936a47674e8e7c5db4a6d1050e1b65bf5e20723116fd5080c.
|
|
10
|
-
* The guard checks original bytes and never changes the request.
|
|
5
|
+
/** A reviewed Checkout build (BUILDS) starts a 40-second XHR when Pay is pressed. Prior approval may
|
|
6
|
+
* consume only its new-card request, including native clientInfo and headers, on the card URL that
|
|
7
|
+
* build sends to in the approved environment. Saved cards, alternate APIs and 3DS-only requests
|
|
8
|
+
* require separate review. The guard checks original bytes and never changes the request.
|
|
11
9
|
*/
|
|
12
10
|
export declare function isPreparedPaysafeRequest(url: string, method: string, body: string | null, environment: string, headers?: Record<string, string>): boolean;
|
|
@@ -1,10 +1,34 @@
|
|
|
1
1
|
// Generated from @agent-cards/payment-core. Do not edit.
|
|
2
|
-
// source-sha256:
|
|
2
|
+
// source-sha256: afa00cd57246e51361474c66c5ab4282bd5b39f0297fdf68616e164e19ed1741
|
|
3
3
|
import { readTokenizationJson } from './braintree.js';
|
|
4
|
+
/** The card URL a preparation names: Checkout 1.8.0's. The bind takes the exact URL of the build the request names (BUILDS). */
|
|
4
5
|
const ENDPOINTS = {
|
|
5
6
|
production: 'https://hosted.paysafe.com/checkout/api/v1/tokenize',
|
|
6
7
|
sandbox: 'https://hosted.test.paysafe.com/checkout/api/v1/tokenize',
|
|
7
8
|
};
|
|
9
|
+
/**
|
|
10
|
+
* The Paysafe Checkout builds whose new-card request a prior approval may consume, each read from
|
|
11
|
+
* Paysafe's public bundle. A request binds only to the build its own clientInfo.version names, on
|
|
12
|
+
* that build's URL and headers, so a build Paysafe ships later is refused, and nothing is sent,
|
|
13
|
+
* until it is read and listed here. The request moved inside 1.x: 1.8.0 to 1.12.0 post to
|
|
14
|
+
* hosted.paysafe.com with a correlationId header, 1.16.1 to api.paysafe.com with three other
|
|
15
|
+
* headers, so a version range would hold one build's request to another build's rules.
|
|
16
|
+
*
|
|
17
|
+
* 1.8.0: hosted.paysafe.com/checkout/1.8.0/main.bundle.js, SHA-256
|
|
18
|
+
* e9e111ff03073cc936a47674e8e7c5db4a6d1050e1b65bf5e20723116fd5080c; LIVE/TEST select hosted[.test].
|
|
19
|
+
* 1.16.1: the checkout/v1/latest loader (SHA-256 5fac65c4f7a74b45388ad3ce7c02282f05e832226792bebc417cd9ff191be4f0)
|
|
20
|
+
* names it and frames checkout/v1/latest/index.html. Its main.js (SHA-256
|
|
21
|
+
* 328c6443da930f3648826248afa3c342cdd07d04e49a78b0878dda187772bf80) posts to environment +
|
|
22
|
+
* "checkout/api/v1/tokenize", and its vendor.js (SHA-256 7348275bd07256e295f4b83b292c7fe5115dd7ac35d1b1fe3d26eda3c37d2835)
|
|
23
|
+
* maps LIVE/TEST to api[.test].paysafe.com and sets X-Internal-Correlation-Id, X-Paysafe-Credentials,
|
|
24
|
+
* X-App-Version (the clientInfo version) and X-TransactionSource (CheckoutV1 for paysafe.checkout).
|
|
25
|
+
* The TEST host serves the same bytes (read 2026-10-15).
|
|
26
|
+
*/
|
|
27
|
+
const BUILDS = [
|
|
28
|
+
{ version: '1.8.0', endpoints: ENDPOINTS, correlation: 'correlationid', fixed: {} },
|
|
29
|
+
{ version: '1.16.1', correlation: 'x-internal-correlation-id', fixed: { 'x-app-version': '1.16.1', 'x-transactionsource': 'CheckoutV1' },
|
|
30
|
+
endpoints: { production: 'https://api.paysafe.com/checkout/api/v1/tokenize', sandbox: 'https://api.test.paysafe.com/checkout/api/v1/tokenize' } },
|
|
31
|
+
];
|
|
8
32
|
const record = (value) => value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
9
33
|
const keys = (value, allowed) => record(value) && Object.keys(value).every(key => allowed.includes(key));
|
|
10
34
|
const exact = (pattern, value) => typeof value === 'string' && pattern.exec(value)?.[0] === value;
|
|
@@ -16,27 +40,31 @@ export function paysafePreparationUrl(environment) {
|
|
|
16
40
|
throw new Error('unsupported_preparation_environment');
|
|
17
41
|
return ENDPOINTS[environment];
|
|
18
42
|
}
|
|
19
|
-
/** Exact strings refuse credentials, queries, fragments and URL normalization. */
|
|
43
|
+
/** The environment of a reviewed build's card URL. Exact strings refuse credentials, queries, fragments and URL normalization. */
|
|
20
44
|
export function paysafeEnvironment(url) {
|
|
21
|
-
|
|
45
|
+
const build = BUILDS.find(candidate => url === candidate.endpoints.production || url === candidate.endpoints.sandbox);
|
|
46
|
+
return !build ? undefined : url === build.endpoints.production ? 'production' : 'sandbox';
|
|
22
47
|
}
|
|
23
48
|
const ADDRESS_FIELDS = ['city', 'country', 'zip', 'street', 'street2', 'state'];
|
|
24
49
|
function billing(value) {
|
|
25
50
|
return keys(value, ADDRESS_FIELDS) && Object.values(value).every(value => text(value))
|
|
26
51
|
&& (!own(value, 'country') || exact(/^[A-Z]{2}$/, value.country));
|
|
27
52
|
}
|
|
28
|
-
|
|
53
|
+
/** The reviewed build whose new-card body this is: never a saved card, a 3-D Secure request or another app's body. */
|
|
54
|
+
function freshCardBuild(value) {
|
|
29
55
|
if (!keys(value, ['card', 'clientInfo', 'billingAddress']) || !keys(value.card, ['cardNum', 'cardExpiry', 'cvv', 'holderName']))
|
|
30
|
-
return
|
|
56
|
+
return undefined;
|
|
31
57
|
const card = value.card;
|
|
32
58
|
if (!exact(/^[0-9]{12,19}$/, card.cardNum) || !exact(/^[0-9]{3,4}$/, card.cvv)
|
|
33
59
|
|| !keys(card.cardExpiry, ['month', 'year']) || !exact(/^(?:0[1-9]|1[0-2])$/, card.cardExpiry.month)
|
|
34
60
|
|| !exact(/^20[0-9]{2}$/, card.cardExpiry.year) || own(card, 'holderName') && !text(card.holderName)
|
|
35
61
|
|| own(value, 'billingAddress') && !billing(value.billingAddress))
|
|
36
|
-
return
|
|
62
|
+
return undefined;
|
|
37
63
|
const info = value.clientInfo;
|
|
38
|
-
|
|
39
|
-
|
|
64
|
+
if (!keys(info, ['correlationId', 'invocationId', 'version', 'appName'])
|
|
65
|
+
|| !uuid(info.correlationId) || !uuid(info.invocationId) || info.appName !== 'paysafe.checkout')
|
|
66
|
+
return undefined;
|
|
67
|
+
return BUILDS.find(build => build.version === info.version);
|
|
40
68
|
}
|
|
41
69
|
const BROWSER_HEADERS = new Set(['accept', 'accept-language', 'accept-encoding', 'content-length', 'origin', 'referer',
|
|
42
70
|
'user-agent', 'host', 'connection', 'cache-control', 'pragma', 'priority', 'dnt',
|
|
@@ -53,9 +81,11 @@ function singleUseCredential(value) {
|
|
|
53
81
|
return false;
|
|
54
82
|
}
|
|
55
83
|
}
|
|
56
|
-
|
|
84
|
+
/** The build's own headers, each once with its native value, and only headers a browser adds beside them. */
|
|
85
|
+
function nativeHeaders(headers, correlationId, build) {
|
|
57
86
|
if (!record(headers))
|
|
58
87
|
return false;
|
|
88
|
+
const native = ['content-type', 'x-paysafe-credentials', build.correlation, ...Object.keys(build.fixed)];
|
|
59
89
|
const parsed = new Map();
|
|
60
90
|
let bytes = 0;
|
|
61
91
|
for (const [name, value] of Object.entries(headers)) {
|
|
@@ -63,25 +93,27 @@ function nativeHeaders(headers, correlationId) {
|
|
|
63
93
|
if (parsed.size >= 64 || parsed.has(lower) || !exact(/^[A-Za-z0-9-]+$/, name) || !text(value, 8192)
|
|
64
94
|
|| (bytes += name.length + value.length) > 32768)
|
|
65
95
|
return false;
|
|
66
|
-
if (!
|
|
96
|
+
if (!native.includes(lower) && !BROWSER_HEADERS.has(lower)
|
|
67
97
|
&& !exact(/^sec-ch-ua(?:-[a-z0-9-]+)?$/, lower))
|
|
68
98
|
return false;
|
|
69
99
|
parsed.set(lower, value);
|
|
70
100
|
}
|
|
71
101
|
return exact(/^application\/json(?:;\s*charset=utf-8)?$/i, parsed.get('content-type'))
|
|
72
|
-
&& parsed.get(
|
|
102
|
+
&& parsed.get(build.correlation) === correlationId && singleUseCredential(parsed.get('x-paysafe-credentials'))
|
|
103
|
+
&& Object.entries(build.fixed).every(([name, value]) => parsed.get(name) === value);
|
|
73
104
|
}
|
|
74
|
-
/** Checkout
|
|
75
|
-
* consume only its new-card request, including native clientInfo and headers
|
|
76
|
-
* Saved cards, alternate APIs and 3DS-only requests
|
|
77
|
-
*
|
|
78
|
-
* e9e111ff03073cc936a47674e8e7c5db4a6d1050e1b65bf5e20723116fd5080c.
|
|
79
|
-
* The guard checks original bytes and never changes the request.
|
|
105
|
+
/** A reviewed Checkout build (BUILDS) starts a 40-second XHR when Pay is pressed. Prior approval may
|
|
106
|
+
* consume only its new-card request, including native clientInfo and headers, on the card URL that
|
|
107
|
+
* build sends to in the approved environment. Saved cards, alternate APIs and 3DS-only requests
|
|
108
|
+
* require separate review. The guard checks original bytes and never changes the request.
|
|
80
109
|
*/
|
|
81
110
|
export function isPreparedPaysafeRequest(url, method, body, environment, headers) {
|
|
82
111
|
if (method !== 'POST' || (environment !== 'production' && environment !== 'sandbox')
|
|
83
112
|
|| paysafeEnvironment(url) !== environment)
|
|
84
113
|
return false;
|
|
85
114
|
const value = readTokenizationJson(body);
|
|
86
|
-
|
|
115
|
+
if (!value || !record(value.clientInfo))
|
|
116
|
+
return false;
|
|
117
|
+
const build = freshCardBuild(value);
|
|
118
|
+
return !!build && url === build.endpoints[environment] && nativeHeaders(headers, value.clientInfo.correlationId, build);
|
|
87
119
|
}
|