@agent-cards/checkout 0.19.0 → 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -753
- package/cdp.d.ts +1 -0
- package/cdp.js +2 -0
- package/index.d.ts +1 -0
- package/index.js +2 -0
- package/package.json +33 -33
- package/playwright.d.ts +1 -0
- package/playwright.js +2 -0
- package/preflight.d.ts +1 -0
- package/preflight.js +2 -0
- package/CHANGELOG.md +0 -132
- package/PREFLIGHT.md +0 -312
- package/dist/adyen-merchant-hosted.generated.d.ts +0 -277
- package/dist/adyen-merchant-hosted.generated.js +0 -1902
- package/dist/adyen.generated.d.ts +0 -24
- package/dist/adyen.generated.js +0 -64
- package/dist/attachment.d.ts +0 -11
- package/dist/attachment.js +0 -50
- package/dist/braintree.d.ts +0 -2
- package/dist/braintree.generated.d.ts +0 -10
- package/dist/braintree.generated.js +0 -302
- package/dist/braintree.js +0 -2
- package/dist/builtin-registry.generated.d.ts +0 -2
- package/dist/builtin-registry.generated.js +0 -1
- package/dist/card-fields.generated.d.ts +0 -3
- package/dist/card-fields.generated.js +0 -46
- package/dist/cdp.d.ts +0 -192
- package/dist/cdp.js +0 -2393
- package/dist/checkout-com.generated.d.ts +0 -4
- package/dist/checkout-com.generated.js +0 -183
- package/dist/client.d.ts +0 -849
- package/dist/client.js +0 -1754
- package/dist/cse-body.d.ts +0 -25
- package/dist/cse-body.js +0 -41
- package/dist/fiserv.d.ts +0 -65
- package/dist/fiserv.generated.d.ts +0 -73
- package/dist/fiserv.generated.js +0 -830
- package/dist/fiserv.js +0 -104
- package/dist/hosted-form.d.ts +0 -44
- package/dist/hosted-form.js +0 -78
- package/dist/index.d.ts +0 -18
- package/dist/index.js +0 -10
- package/dist/lifecycle.d.ts +0 -179
- package/dist/lifecycle.js +0 -395
- package/dist/mercado-checkout.d.ts +0 -20
- package/dist/mercado-checkout.generated.d.ts +0 -52
- package/dist/mercado-checkout.generated.js +0 -198
- package/dist/mercado-checkout.js +0 -108
- package/dist/merchant-handoff.d.ts +0 -54
- package/dist/merchant-handoff.js +0 -100
- package/dist/merchant-hosted.d.ts +0 -140
- package/dist/merchant-hosted.js +0 -170
- package/dist/merchant-total-watch.d.ts +0 -115
- package/dist/merchant-total-watch.js +0 -268
- package/dist/merchant-total.d.ts +0 -257
- package/dist/merchant-total.js +0 -383
- package/dist/owned-shop.generated.d.ts +0 -24
- package/dist/owned-shop.generated.js +0 -108
- package/dist/paysafe.generated.d.ts +0 -12
- package/dist/paysafe.generated.js +0 -87
- package/dist/playwright.d.ts +0 -3
- package/dist/playwright.js +0 -3
- package/dist/pre-claim.d.ts +0 -123
- package/dist/pre-claim.js +0 -386
- package/dist/preflight-capabilities.generated.d.ts +0 -1253
- package/dist/preflight-capabilities.generated.js +0 -1929
- package/dist/preflight-catalog.json +0 -4727
- package/dist/preflight-playwright.d.ts +0 -34
- package/dist/preflight-playwright.js +0 -355
- package/dist/preflight-schemas.json +0 -1122
- package/dist/preflight.d.ts +0 -1
- package/dist/preflight.generated.d.ts +0 -1965
- package/dist/preflight.generated.js +0 -570
- package/dist/preflight.js +0 -2
- package/dist/preparation.d.ts +0 -38
- package/dist/preparation.js +0 -191
- package/dist/prepared-processor.d.ts +0 -43
- package/dist/prepared-processor.js +0 -172
- package/dist/recurly.generated.d.ts +0 -1
- package/dist/recurly.generated.js +0 -87
- package/dist/registry.d.ts +0 -121
- package/dist/registry.js +0 -310
- package/dist/spreedly.generated.d.ts +0 -10
- package/dist/spreedly.generated.js +0 -332
- package/dist/stripe-checkout.d.ts +0 -81
- package/dist/stripe-checkout.generated.d.ts +0 -82
- package/dist/stripe-checkout.generated.js +0 -1093
- package/dist/stripe-checkout.js +0 -140
- package/dist/substitute.d.ts +0 -38
- package/dist/substitute.js +0 -23
- package/dist/substitutions.generated.d.ts +0 -11
- package/dist/substitutions.generated.js +0 -818
- package/examples/existing-browser.mjs +0 -63
- package/examples/preflight/classify-direct.mjs +0 -21
- package/examples/preflight/classify-kernel.mjs +0 -30
- package/examples/preflight/inspect-browser.mjs +0 -44
- package/examples/preflight/kernel-native/README.md +0 -112
- package/examples/preflight/kernel-native/documented-adapters.json +0 -113
- package/examples/preflight/kernel-native/inventory.json +0 -233
- package/examples/preflight/kernel-native/qualification.mjs +0 -182
- package/examples/preflight/kernel-profile.empty.json +0 -11
- package/examples/preflight/mollie-hosted.observations.json +0 -23
- package/examples/preflight/mollie-hosted.result.json +0 -103
- package/examples/preflight/stripe-script.direct.result.json +0 -92
- package/examples/preflight/stripe-script.observations.json +0 -16
- package/examples/preflight/stripe-script.result.json +0 -87
|
@@ -1,1093 +0,0 @@
|
|
|
1
|
-
// @ts-nocheck
|
|
2
|
-
// Generated from @agent-cards/payment-core. Do not edit.
|
|
3
|
-
// artifact-sha256: 9edf78da59b3bdfe310b4422e06d333cd164497cdb4070db59ffed348492198a
|
|
4
|
-
// src/deep-freeze.js
|
|
5
|
-
function deepFreeze(value) {
|
|
6
|
-
if (value && typeof value === "object" && !Object.isFrozen(value)) {
|
|
7
|
-
for (const child of Object.values(value))
|
|
8
|
-
deepFreeze(child);
|
|
9
|
-
Object.freeze(value);
|
|
10
|
-
}
|
|
11
|
-
return value;
|
|
12
|
-
}
|
|
13
|
-
// src/definitions.js
|
|
14
|
-
var PROCESSOR_DEFINITIONS = deepFreeze([
|
|
15
|
-
{
|
|
16
|
-
psp: "spreedly",
|
|
17
|
-
// Native iframe new-card tokenization only; spreedly.js pins the query and body.
|
|
18
|
-
match: "core\\.spreedly\\.com/v1/payment_methods/restricted\\.json",
|
|
19
|
-
hosts: ["^core\\.spreedly\\.com$"],
|
|
20
|
-
encoding: "json",
|
|
21
|
-
passthroughHeaders: ["^spreedly-environment-key$"]
|
|
22
|
-
},
|
|
23
|
-
{
|
|
24
|
-
psp: "shopify",
|
|
25
|
-
match: "(checkout\\.pci\\.shopifyinc\\.com|deposit\\.[a-z0-9-]+\\.shopifycs\\.com)/sessions",
|
|
26
|
-
hosts: ["^checkout\\.pci\\.shopifyinc\\.com$", "^deposit\\.[a-z0-9-]+\\.shopifycs\\.com$"],
|
|
27
|
-
encoding: "json",
|
|
28
|
-
passthroughHeaders: ["^shopify-identification-signature$"]
|
|
29
|
-
},
|
|
30
|
-
{
|
|
31
|
-
psp: "stripe",
|
|
32
|
-
// Every endpoint Stripe.js sends a card to, read from its own controller
|
|
33
|
-
// bundle: `tokens` (createToken), `payment_methods` (createPaymentMethod),
|
|
34
|
-
// `sources` (createSource, the older API), `confirmation_tokens`
|
|
35
|
-
// (createConfirmationToken: the page collects the card and confirms on its
|
|
36
|
-
// own server), and the intent confirmations that carry the card inline as
|
|
37
|
-
// `payment_method_data[card][number]` (confirmPayment, confirmSetup). The
|
|
38
|
-
// modern Payment Element mostly confirms inline or through a confirmation
|
|
39
|
-
// token, so matching the tokenizers alone never paused it. Observed live on
|
|
40
|
-
// Anthropic's own billing page (platform.claude.com/settings/billing),
|
|
41
|
-
// which posts to /v1/setup_intents/{seti_...}/confirm, the "save a card"
|
|
42
|
-
// flow; /v1/payment_intents/{pi_...}/confirm is the same shape for "pay
|
|
43
|
-
// now". Stripe Checkout Sessions confirm at
|
|
44
|
-
// /v1/payment_pages/{cs_...}/confirm: Checkout Elements (initCheckout)
|
|
45
|
-
// sends the card there inline, while hosted and embedded Checkout send only
|
|
46
|
-
// the payment method they created first. The id segment is matched
|
|
47
|
-
// narrowly (seti_/pi_/cs_ + word chars) rather than `.*` so the pattern
|
|
48
|
-
// cannot be widened by a crafted path. A client that does not check
|
|
49
|
-
// cardFields is served the pattern it knew before sources, confirmation
|
|
50
|
-
// tokens and Checkout Sessions joined (recognition.js recognizersForClient).
|
|
51
|
-
match: "api\\.stripe\\.com/v1/(payment_methods|tokens|sources|confirmation_tokens|setup_intents/seti_\\w+/confirm|payment_intents/pi_\\w+/confirm|payment_pages/cs_(?:test|live)_\\w+/confirm)",
|
|
52
|
-
hosts: ["^api\\.stripe\\.com$"],
|
|
53
|
-
encoding: "form",
|
|
54
|
-
// Elements' own surface markers; without these Stripe rejects the surface.
|
|
55
|
-
passthroughHeaders: ["^authorization$", "^stripe-version$", "^x-stripe-client-user-agent$"],
|
|
56
|
-
// The same confirmations also run WITHOUT a card: a page that created the
|
|
57
|
-
// method first confirms with `payment_method=pm_...`. Only a body that
|
|
58
|
-
// carries one of these fields is a card request (card-fields.js). Any
|
|
59
|
-
// other request on these endpoints has no card to approve, so it is
|
|
60
|
-
// refused (requestWithoutCard) instead of paused for the cardholder,
|
|
61
|
-
// unless passWithoutCard below names its endpoint.
|
|
62
|
-
cardFields: ["card[number]", "payment_method_data[card][number]"],
|
|
63
|
-
// A confirmation token that wraps a saved method, a source for a bank, or a
|
|
64
|
-
// Checkout Session confirm paying with the method hosted or embedded
|
|
65
|
-
// Checkout created first carries no card and was never ours: it continues
|
|
66
|
-
// untouched, as it did before these endpoints were recognized.
|
|
67
|
-
passWithoutCard: "api\\.stripe\\.com/v1/(sources|confirmation_tokens|payment_pages/cs_(?:test|live)_\\w+/confirm)"
|
|
68
|
-
},
|
|
69
|
-
{
|
|
70
|
-
psp: "braintree",
|
|
71
|
-
match: "payments(\\.sandbox)?\\.braintree-api\\.com/graphql",
|
|
72
|
-
hosts: ["^payments\\.braintree-api\\.com$", "^payments\\.sandbox\\.braintree-api\\.com$"],
|
|
73
|
-
sandboxHosts: ["^payments\\.sandbox\\.braintree-api\\.com$"],
|
|
74
|
-
encoding: "json",
|
|
75
|
-
passthroughHeaders: ["^authorization$", "^braintree-version$"]
|
|
76
|
-
},
|
|
77
|
-
{
|
|
78
|
-
psp: "checkout_com",
|
|
79
|
-
// Flow 1.226.0's card controller selects api or card-acquisition-gateway
|
|
80
|
-
// for the same card-token body and Bearer public-key Authorization.
|
|
81
|
-
// Native source: checkout-web-components.checkout.com/pages/1.226.0/index.html.
|
|
82
|
-
// Endpoint recognition does not establish consumer-wallet, CVV-only,
|
|
83
|
-
// reusable-token, 3DS or merchant payment completion compatibility.
|
|
84
|
-
match: "(api|card-acquisition-gateway)(\\.sandbox)?\\.checkout\\.com/tokens",
|
|
85
|
-
hosts: [
|
|
86
|
-
"^api\\.checkout\\.com$",
|
|
87
|
-
"^api\\.sandbox\\.checkout\\.com$",
|
|
88
|
-
"^card-acquisition-gateway\\.checkout\\.com$",
|
|
89
|
-
"^card-acquisition-gateway\\.sandbox\\.checkout\\.com$"
|
|
90
|
-
],
|
|
91
|
-
sandboxHosts: ["^api\\.sandbox\\.checkout\\.com$", "^card-acquisition-gateway\\.sandbox\\.checkout\\.com$"],
|
|
92
|
-
encoding: "json",
|
|
93
|
-
passthroughHeaders: ["^authorization$"]
|
|
94
|
-
},
|
|
95
|
-
// NOT here, on purpose: VGS Collect (Very Good Security; Wolt's add-a-card
|
|
96
|
-
// call through `<tenant>.<live|sandbox>.verygoodproxy.com`). Served for a
|
|
97
|
-
// few hours on 2026-09-03 and withdrawn the same night: the proxy aliases
|
|
98
|
-
// only submissions that originate in VGS's own iframe (its Collect session
|
|
99
|
-
// handshake), so a byte-identical replay from the cardholder's device, 4 s
|
|
100
|
-
// after capture, with the iframe's own headers and origin, is forwarded
|
|
101
|
-
// un-aliased and the merchant answers 400 "Failed to process card". A
|
|
102
|
-
// recognizer that pauses a call the vault cannot finish only blocks the
|
|
103
|
-
// agent's browser from adding a card at all, so the entry is gone from
|
|
104
|
-
// every registry; the page keeps its on-file wording machinery for the
|
|
105
|
-
// merchant that does allow it.
|
|
106
|
-
{
|
|
107
|
-
psp: "adyen",
|
|
108
|
-
encryptedCardFields: { number: "encryptedCardNumber", month: "encryptedExpiryMonth", year: "encryptedExpiryYear", cvc: "encryptedSecurityCode" },
|
|
109
|
-
// The Sessions-flow payments call only (adyen-web's makePayment): the
|
|
110
|
-
// card rides in paymentMethod as four JWE blobs the vault page produces
|
|
111
|
-
// under the merchant's Adyen public key (services/checkout-cse-adyen.ts),
|
|
112
|
-
// and the runtime substitutes them into the paused body. binLookup,
|
|
113
|
-
// analytics and paymentDetails share these hosts, carry no card, and must
|
|
114
|
-
// never raise an approval; the hosts test pins them refused. The host
|
|
115
|
-
// alternation covers the regional live hosts (live-us, live-au, live-apse,
|
|
116
|
-
// live-in, live-nea): `checkoutshopper-[a-z]+` never matched those, so
|
|
117
|
-
// merchants on them were silently never paused.
|
|
118
|
-
match: "(checkoutshopper-(test|live(-[a-z]+)?)\\.adyen\\.com|([a-z0-9-]+\\.)*adyenpayments\\.com)/checkoutshopper/v1/sessions/[A-Za-z0-9_-]+/payments",
|
|
119
|
-
hosts: ["^checkoutshopper-(test|live(-[a-z]+)?)\\.adyen\\.com$", "^([a-z0-9-]+\\.)*adyenpayments\\.com$"],
|
|
120
|
-
// The sandbox marker matches ONLY the TEST hostname, a subset of the first
|
|
121
|
-
// host source (which matches checkoutshopper-test and every checkoutshopper-live
|
|
122
|
-
// regional host). checkoutshopper-live* and *.adyenpayments.com are live.
|
|
123
|
-
// Load-bearing for Adyen although Adyen does not pin its environment: the
|
|
124
|
-
// Adyen TEST platform rule (backend.js adyenLivePlatform) reads this marker
|
|
125
|
-
// on the server's authorization create and cardholder legs
|
|
126
|
-
// (services/checkout-cse-adyen.ts in the backend), on the device and in the
|
|
127
|
-
// enclave. Dropping or widening it moves which payments count as Adyen TEST.
|
|
128
|
-
sandboxHosts: ["^checkoutshopper-test\\.adyen\\.com$"],
|
|
129
|
-
encoding: "json",
|
|
130
|
-
passthroughHeaders: [],
|
|
131
|
-
clientSideEncrypted: true,
|
|
132
|
-
// kept: older SDKs read this and refuse locally
|
|
133
|
-
mode: "cse",
|
|
134
|
-
cse: {
|
|
135
|
-
at: "paymentMethod",
|
|
136
|
-
fields: ["encryptedCardNumber", "encryptedExpiryMonth", "encryptedExpiryYear", "encryptedSecurityCode"],
|
|
137
|
-
// adyen-web stamps `paymentMethod.brand` from the digits typed into its
|
|
138
|
-
// Card component, i.e. the agent's dummy. Proven on Adyen TEST
|
|
139
|
-
// (2026-09-02): a Mastercard dummy swapped for the vault's Visa
|
|
140
|
-
// ciphertext was refused 422/109 "Invalid variant"; without `brand`
|
|
141
|
-
// Adyen reads it off the decrypted PAN and authorises.
|
|
142
|
-
remove: ["brand"]
|
|
143
|
-
}
|
|
144
|
-
},
|
|
145
|
-
{
|
|
146
|
-
psp: "tranzila",
|
|
147
|
-
// Tranzila's hosted form (direct.tranzila.com/<terminal>/iframenew.php,
|
|
148
|
-
// embedded by the merchant page) submits the card as a top-level form
|
|
149
|
-
// POST to /process/n/ (directng: /process), application/x-www-form-
|
|
150
|
-
// urlencoded, with the card in ccno/expmonth/expyear/mycvv and the
|
|
151
|
-
// Israeli ID number in myid. The paused request is a Document navigation,
|
|
152
|
-
// not an XHR: the runtime cannot resume it with the real card, so the
|
|
153
|
-
// cardholder's device rebuilds and submits the SAME form itself, top-level
|
|
154
|
-
// from the vault origin (mode hosted_form), and the runtime resolves the
|
|
155
|
-
// paused navigation with a synthetic page once the device says the form
|
|
156
|
-
// left. The iframe page itself and Tranzila's /done/ result page share
|
|
157
|
-
// these hosts, carry no card, and are refused by `match`. Served only to
|
|
158
|
-
// SDKs that ask for hosted_form on sync: an older SDK would arm these
|
|
159
|
-
// hosts, pause the navigation, get a 400 and abort the frame.
|
|
160
|
-
match: "(direct\\.tranzila\\.com/process/n/|directng\\.tranzila\\.com/process/?)",
|
|
161
|
-
hosts: ["^direct\\.tranzila\\.com$", "^directng\\.tranzila\\.com$"],
|
|
162
|
-
encoding: "form",
|
|
163
|
-
passthroughHeaders: [],
|
|
164
|
-
mode: "hosted_form",
|
|
165
|
-
hostedForm: {
|
|
166
|
-
required: ["sum", "supplier"],
|
|
167
|
-
card: { number: "ccno", month: "expmonth", year: "expyear", cvc: "mycvv", nationalId: "myid" },
|
|
168
|
-
amount: { sum: "sum", currency: "currency" },
|
|
169
|
-
// supplier (the terminal) + benid (the iframe session) + the amount:
|
|
170
|
-
// one submission, whatever nonce the form regenerated on a re-click.
|
|
171
|
-
dedupe: ["supplier", "benid", "sum", "currency"],
|
|
172
|
-
// One plain sale, reporting to the merchant the person sees: tranmode
|
|
173
|
-
// A (or absent) only (V verifies, K charges a token, C refunds, D
|
|
174
|
-
// forces), no TranzilaTK in either direction (`1` asks for a reusable
|
|
175
|
-
// token back, a token charges one), and every *_url_address on the
|
|
176
|
-
// merchant's host (Tranzila redirects the device there and POSTs the
|
|
177
|
-
// result there with the card's last four, expiry and ID number).
|
|
178
|
-
sale: { key: "tranmode", values: ["A"] },
|
|
179
|
-
token: "TranzilaTK",
|
|
180
|
-
callbackUrls: ["success_url_address", "fail_url_address", "notify_url_address"]
|
|
181
|
-
}
|
|
182
|
-
},
|
|
183
|
-
// ── Verified tokenizers (plaintext PAN in the body) ───────────────────────
|
|
184
|
-
// Each entry below was checked against the PSP's own shipped browser SDK or a
|
|
185
|
-
// live probe, then run through resolveRecognizerForUrl with positive, sibling
|
|
186
|
-
// and spoof URLs. Two rules the vectors keep proving, worth restating before
|
|
187
|
-
// anyone edits one of these:
|
|
188
|
-
// 1. Every dot in `hosts` is escaped. `^api.mercadopago.com$` admits the
|
|
189
|
-
// REGISTRABLE domain apiamercadopago.com; the escape is the allowlist.
|
|
190
|
-
// 2. `hosts` is pinned to literal labels, never widened to a wildcard label
|
|
191
|
-
// (`[a-z0-9-]+\.psp\.com`). Several of these vendors run wildcard DNS,
|
|
192
|
-
// so "the hostname resolves" is not evidence that it receives cards.
|
|
193
|
-
{
|
|
194
|
-
psp: "square",
|
|
195
|
-
// PAN@ card_data.number. POST /v2/card-nonce (URL always carries ?_=&version=).
|
|
196
|
-
match: "pci-connect\\.squareup(?:sandbox)?\\.com/v2/card-nonce",
|
|
197
|
-
hosts: ["^pci-connect\\.squareup\\.com$", "^pci-connect\\.squareupsandbox\\.com$"],
|
|
198
|
-
sandboxHosts: ["^pci-connect\\.squareupsandbox\\.com$"],
|
|
199
|
-
encoding: "json",
|
|
200
|
-
passthroughHeaders: []
|
|
201
|
-
// auth = client_id in body; no auth header
|
|
202
|
-
},
|
|
203
|
-
{
|
|
204
|
-
psp: "authorize_net",
|
|
205
|
-
// Accept.js. PAN@ securePaymentContainerRequest.data.token.cardNumber.
|
|
206
|
-
// Auth is body-level (merchantAuthentication: apiLoginID + clientKey) — nothing to replay.
|
|
207
|
-
// NOTE: prod is api2, not api. `api.authorize.net` is the server-side AIM/API host and is
|
|
208
|
-
// deliberately NOT allowlisted — a browser never posts a PAN there. Legacy SIM/DPM
|
|
209
|
-
// (secure2.authorize.net/gateway/transact.dll) is excluded for the same reason plus: it is
|
|
210
|
-
// retired, and it is a top-level form POST rather than an XHR.
|
|
211
|
-
// Coverage caveat: AcceptUI (the hosted lightbox) renders its fields inside an
|
|
212
|
-
// accept.authorize.net iframe, so that POST is cross-origin to the merchant page and an
|
|
213
|
-
// in-page hook cannot observe it. This entry covers the Accept.js custom-fields path, which
|
|
214
|
-
// does run in the merchant page. /xml/v1/request.api is Authorize.Net's single monolithic
|
|
215
|
-
// endpoint (request type is chosen by the body root key), so the path pin admits sibling
|
|
216
|
-
// request types on the same URL — the body, not the URL, is what distinguishes them.
|
|
217
|
-
match: "(api2|apitest)\\.authorize\\.net/xml/v1/request\\.api",
|
|
218
|
-
hosts: ["^api2\\.authorize\\.net$", "^apitest\\.authorize\\.net$"],
|
|
219
|
-
sandboxHosts: ["^apitest\\.authorize\\.net$"],
|
|
220
|
-
encoding: "json",
|
|
221
|
-
// JSON body; Content-Type is sometimes text/plain (preflight dodge), replayed as-is
|
|
222
|
-
passthroughHeaders: []
|
|
223
|
-
},
|
|
224
|
-
{
|
|
225
|
-
psp: "worldpay",
|
|
226
|
-
// Access Worldpay. PAN@ root key `cardNumber`. Accept is load-bearing:
|
|
227
|
-
// the two paths need DIFFERENT vendor media types; `application/json` → HTTP 406.
|
|
228
|
-
match: "(try\\.)?access\\.worldpay\\.com/(sessions/card|verifiedTokens/sessions)",
|
|
229
|
-
hosts: ["^access\\.worldpay\\.com$", "^try\\.access\\.worldpay\\.com$"],
|
|
230
|
-
sandboxHosts: ["^try\\.access\\.worldpay\\.com$"],
|
|
231
|
-
// try.* is Worldpay's try/test host
|
|
232
|
-
encoding: "json",
|
|
233
|
-
passthroughHeaders: ["^accept$"]
|
|
234
|
-
},
|
|
235
|
-
{
|
|
236
|
-
psp: "nuvei",
|
|
237
|
-
// Safecharge Fields. PAN@ paymentOption.card.cardNumber.
|
|
238
|
-
// THREE PAN-bearing endpoints, not one. Nuvei picks between them from
|
|
239
|
-
// `sourceApplication`, and the covered one was the WRONG half of that
|
|
240
|
-
// branch: WEB_SDK (merchant holds the raw PAN in their own page) posts
|
|
241
|
-
// cardTokenization.do, while WEB_SDK_FIELDS (hosted fields, the
|
|
242
|
-
// PCI-conscious integration) posts websdk/initPaymentWithCardTokenization.do
|
|
243
|
-
// and clientPayment.do. Confirmed against sfc_inner_card.min.js, whose
|
|
244
|
-
// initPayment() opens initPaymentWithCardTokenization.do unconditionally
|
|
245
|
-
// with no branch back to the covered path.
|
|
246
|
-
match: "(secure|ppp-test)\\.safecharge\\.com/ppp/api/v1/(?:cardTokenization|clientPayment|websdk/initPaymentWithCardTokenization)\\.do",
|
|
247
|
-
hosts: ["^secure\\.safecharge\\.com$", "^ppp-test\\.safecharge\\.com$"],
|
|
248
|
-
sandboxHosts: ["^ppp-test\\.safecharge\\.com$"],
|
|
249
|
-
encoding: "json",
|
|
250
|
-
passthroughHeaders: []
|
|
251
|
-
},
|
|
252
|
-
// bluesnap: DELIBERATELY NOT SHIPPED — it cannot be intercepted at all. Its tokenizer is a PUT, and isCardRequest
|
|
253
|
-
// gates on POST — but accepting PUT is not enough on its own. hpf.js fires the
|
|
254
|
-
// same token PUT SEVERAL TIMES per checkout (blur and submit each send a delta
|
|
255
|
-
// body), so intercepting it would raise one approval prompt per fire and hand the
|
|
256
|
-
// device partial card data. An entry that cannot actually be intercepted does not
|
|
257
|
-
// belong in an allowlist that advertises interception: it reads as support we do
|
|
258
|
-
// not have, and on the CDP path it arms Fetch.enable, gets refused by the POST
|
|
259
|
-
// gate, and is then continued straight to the PSP while the integrator believes
|
|
260
|
-
// the card was protected. Ship it when the multi-fire semantics are handled.
|
|
261
|
-
{
|
|
262
|
-
psp: "airwallex",
|
|
263
|
-
// PAN@ payment_method.card.number (payment_intents/confirm,
|
|
264
|
-
// payment_consents/verify) / card.number (payment_methods/create).
|
|
265
|
-
// ⚠ reference/bin/lookup is EXCLUDED. It egresses the PAN in an `x-pan` request
|
|
266
|
-
// HEADER, and it is a POST — so matching it would not merely "pause" it, it would
|
|
267
|
-
// intercept it and raise a real approval prompt for a bin lookup whose body holds
|
|
268
|
-
// no card. Handling header-borne PANs needs its own design; until then this
|
|
269
|
-
// header-aware handling, not the body cardFieldPath path. `x-pan` is deliberately NOT a
|
|
270
|
-
// passthrough header, so today the replay carries no PAN at all (inert, never a leak).
|
|
271
|
-
// `card/tokenize` was dropped from the candidate: an OPTIONS preflight on BOTH pci-api
|
|
272
|
-
// hosts returns the same 404 as a bogus control path, while real POST-only routes
|
|
273
|
-
// (payment_methods/create, payment_consents/create) answer 200 + Allow-Methods: POST.
|
|
274
|
-
// The trailing (\?|$) is a no-op server-side (pathname has no query) — it is the
|
|
275
|
-
// right-edge boundary for the SDK's UNANCHORED client-side test of the full URL.
|
|
276
|
-
match: "(checkout(\\.sandbox)?\\.airwallex\\.com|pci-api(\\.sandbox)?\\.airwallex\\.com)/api/v1/pa/(payment_intents/[^/?#]+/confirm|payment_consents/[^/?#]+/verify|payment_methods/create)(\\?|$)",
|
|
277
|
-
hosts: [
|
|
278
|
-
"^checkout\\.airwallex\\.com$",
|
|
279
|
-
"^checkout\\.sandbox\\.airwallex\\.com$",
|
|
280
|
-
"^pci-api\\.airwallex\\.com$",
|
|
281
|
-
"^pci-api\\.sandbox\\.airwallex\\.com$"
|
|
282
|
-
],
|
|
283
|
-
sandboxHosts: ["^checkout\\.sandbox\\.airwallex\\.com$", "^pci-api\\.sandbox\\.airwallex\\.com$"],
|
|
284
|
-
encoding: "json",
|
|
285
|
-
passthroughHeaders: ["^client-secret$", "^authorization$", "^x-auth-token$", "^x-on-behalf-of$", "^x-api-version$"]
|
|
286
|
-
},
|
|
287
|
-
{
|
|
288
|
-
psp: "rapyd",
|
|
289
|
-
// PAN@ required_fields.number (hosted collect page) OR payment_method.fields.number
|
|
290
|
-
// (checkout page — it renders on checkout.rapyd.net/?token=checkout_… and posts cross-origin to
|
|
291
|
-
// api.rapyd.net). The checkout-id segment is pinned to the documented `checkout_` prefix: a bare
|
|
292
|
-
// [^/?#]+ also admitted /v1/checkout/collect and the encoded-slash smuggle
|
|
293
|
-
// /v1/checkout/checkout_x%2Fadmin. ⚠ /v1/checkout/{id} is ALSO the documented GET Retrieve
|
|
294
|
-
// Checkout Page (signature-authed, server-to-server, no card) — the POST gate is what splits them.
|
|
295
|
-
match: "(api|sandboxapi)\\.rapyd\\.net/v1/(?:hosted/collect/card/[^/?#]+/payment_method|checkout/checkout_[A-Za-z0-9]+)(?:[?#]|$)",
|
|
296
|
-
hosts: ["^api\\.rapyd\\.net$", "^sandboxapi\\.rapyd\\.net$"],
|
|
297
|
-
sandboxHosts: ["^sandboxapi\\.rapyd\\.net$"],
|
|
298
|
-
encoding: "json",
|
|
299
|
-
passthroughHeaders: []
|
|
300
|
-
// auth rides the opaque hosted-page / checkout token in the URL
|
|
301
|
-
},
|
|
302
|
-
{
|
|
303
|
-
psp: "dlocal",
|
|
304
|
-
// Smart Fields cvault. PAN@ pan. Do NOT match /cvault/cvv/store (CVV only).
|
|
305
|
-
// Hosts are two literal labels (no wildcard/`[a-z]+` label), so there is no
|
|
306
|
-
// multi-tenant surface: `ppmcc` is dlocal-operated, not merchant-registrable.
|
|
307
|
-
match: "ppmcc(-sandbox)?\\.dlocal\\.com/cvault/credit-card/temporal",
|
|
308
|
-
hosts: ["^ppmcc\\.dlocal\\.com$", "^ppmcc-sandbox\\.dlocal\\.com$"],
|
|
309
|
-
sandboxHosts: ["^ppmcc-sandbox\\.dlocal\\.com$"],
|
|
310
|
-
encoding: "json",
|
|
311
|
-
passthroughHeaders: ["^x-fields-api-key$", "^x-uow$", "^x-dlocal-infrav2$"]
|
|
312
|
-
},
|
|
313
|
-
{
|
|
314
|
-
psp: "ebanx",
|
|
315
|
-
compose: { card_due_date: "MM/YYYY" },
|
|
316
|
-
// PAN@ card.card_number. POST /ws/token. Body content-type is text/plain
|
|
317
|
-
// (CORS-preflight dodge) — replay it verbatim; encoding:json describes the body shape.
|
|
318
|
-
// The two domains SHARE one `/ws/token` tail on purpose: a bare top-level `|` (the shape
|
|
319
|
-
// this entry arrived in) degrades to `(^A)|(B$)` under any caller that anchors without
|
|
320
|
-
// re-grouping, admitting both `api.ebanx.com/ws/tokenizer_admin` and
|
|
321
|
-
// `evil.com/api-diamond.ebanxpay.com/ws/token`. Keep the alternation wrapped.
|
|
322
|
-
//
|
|
323
|
-
// `api.ebanxpay.com` / `sandbox.ebanxpay.com` are the hosts a STOCK integration
|
|
324
|
-
// actually uses: every published EBANX JS SDK (1.6.0 through 1.11.0, verified
|
|
325
|
-
// against the live bundles) hardcodes
|
|
326
|
-
// path: () => config.isLive() ? 'https://api.ebanxpay.com/' : 'https://sandbox.ebanxpay.com/'
|
|
327
|
-
// Without them we recognized only the `.ebanx.com` and `api-diamond` variants, so
|
|
328
|
-
// an ordinary EBANX checkout was invisible to us and the cardholder was never asked.
|
|
329
|
-
match: "(?:(?:customer-frontier|customer|api-local-latam|api|sandbox-local-latam|sandbox)\\.ebanx\\.com|(?:api-diamond|api|sandbox)\\.ebanxpay\\.com)/ws/token",
|
|
330
|
-
hosts: [
|
|
331
|
-
"^customer\\.ebanx\\.com$",
|
|
332
|
-
"^customer-frontier\\.ebanx\\.com$",
|
|
333
|
-
"^api\\.ebanx\\.com$",
|
|
334
|
-
"^sandbox\\.ebanx\\.com$",
|
|
335
|
-
"^api-local-latam\\.ebanx\\.com$",
|
|
336
|
-
"^sandbox-local-latam\\.ebanx\\.com$",
|
|
337
|
-
"^api-diamond\\.ebanxpay\\.com$",
|
|
338
|
-
"^api\\.ebanxpay\\.com$",
|
|
339
|
-
"^sandbox\\.ebanxpay\\.com$"
|
|
340
|
-
],
|
|
341
|
-
sandboxHosts: ["^sandbox\\.ebanx\\.com$", "^sandbox-local-latam\\.ebanx\\.com$", "^sandbox\\.ebanxpay\\.com$"],
|
|
342
|
-
encoding: "json",
|
|
343
|
-
passthroughHeaders: []
|
|
344
|
-
},
|
|
345
|
-
{
|
|
346
|
-
psp: "mercado_pago",
|
|
347
|
-
// PAN@ card_number. POST api.mercadopago.com/v1/card_tokens — the ONLY
|
|
348
|
-
// PAN egress in sdk.mercadopago.com/js/v2 (verified against the live bundle). No sandbox host:
|
|
349
|
-
// test mode is the TEST-* public_key. Auth + attribution ride the QUERY (the SDK's
|
|
350
|
-
// defaultQueries appends public_key, locale, js_version, referer), so replay the URL verbatim
|
|
351
|
-
// and never give `match` a trailing `$` — the SDK tests it unanchored against the raw URL.
|
|
352
|
-
// Same endpoint also serves saved-card {card_id,security_code} (no PAN) → check body before swap.
|
|
353
|
-
// (?![\w/-]) pins the exact endpoint: the `/card_tokens/{id}` sibling is a PUT carrying only
|
|
354
|
-
// {security_code,expiration_*} (no PAN), and the guard stops the SDK pausing it or /{id}/clone.
|
|
355
|
-
match: "api\\.mercadopago\\.com/v1/card_tokens(?![\\w/-])",
|
|
356
|
-
hosts: ["^api\\.mercadopago\\.com$"],
|
|
357
|
-
encoding: "json",
|
|
358
|
-
passthroughHeaders: ["^x-product-id$"]
|
|
359
|
-
// SDK sets X-Product-Id on the tokenize POST
|
|
360
|
-
},
|
|
361
|
-
{
|
|
362
|
-
psp: "payu",
|
|
363
|
-
// GPO Secure Forms. PAN@ card.number.
|
|
364
|
-
match: "(secure\\.payu\\.com|(merch-prod|secure)\\.snd\\.payu\\.com)/api/front/tokens",
|
|
365
|
-
hosts: ["^secure\\.payu\\.com$", "^(merch-prod|secure)\\.snd\\.payu\\.com$"],
|
|
366
|
-
sandboxHosts: ["^(merch-prod|secure)\\.snd\\.payu\\.com$"],
|
|
367
|
-
// .snd. is PayU's sandbox
|
|
368
|
-
encoding: "json",
|
|
369
|
-
// Native checkout attaches a bearer to tokenization; preserve it on replay.
|
|
370
|
-
passthroughHeaders: ["^authorization$"]
|
|
371
|
-
},
|
|
372
|
-
{
|
|
373
|
-
psp: "razorpay",
|
|
374
|
-
// PAN@ card[number] (form). Body carries risk/fingerprint fields — replay
|
|
375
|
-
// body verbatim, swap only card[*]. Auth = ?key_id= (or ?x_entity_id=) in the URL.
|
|
376
|
-
// THREE sinks, all POST, all carrying the SAME payload object. checkout-frame.js builds
|
|
377
|
-
// them as ib(r,"payments/create/ajax") and ib(r,"payments/create/"+(fees?"fees":"checkout")):
|
|
378
|
-
// /ajax XHR, standard flow
|
|
379
|
-
// /checkout form-POST redirect flow
|
|
380
|
-
// /fees form-POST, customer-fee-bearer merchants
|
|
381
|
-
// `fees` is NOT optional: it is the same card body behind a boolean (vS(this.r, t.fees)),
|
|
382
|
-
// so dropping it silently un-intercepts every fee-bearer merchant.
|
|
383
|
-
// The optional `standard_checkout/` segment appears when a session_token is present
|
|
384
|
-
// (builder: api + "v1/" + "standard_checkout" + "/" + path).
|
|
385
|
-
// NOT matched, deliberately: the pg_router shape (api + "pg_router/" + "v1/" +
|
|
386
|
-
// "standard_checkout" + path — note the MISSING slash before the path). Every ib() call
|
|
387
|
-
// site passes only 2 args, so that flag is always false and the shape is unreachable for
|
|
388
|
-
// card paths. If Razorpay ever enables it we go inert (fail-closed), never leaky.
|
|
389
|
-
// NOT allowlisted, deliberately: api-<region>.razorpay.com. checkout.js only builds that
|
|
390
|
-
// when the merchant page sets Razorpay.config.api="" — page-controlled input must never
|
|
391
|
-
// widen a card allowlist.
|
|
392
|
-
match: "api\\.razorpay\\.com/v1/(?:standard_checkout/)?payments/create/(?:ajax|checkout|fees)",
|
|
393
|
-
hosts: ["^api\\.razorpay\\.com$"],
|
|
394
|
-
encoding: "form",
|
|
395
|
-
passthroughHeaders: ["^x-razorpay-sessionid$", "^x-customer-access-token$"]
|
|
396
|
-
},
|
|
397
|
-
{
|
|
398
|
-
psp: "mollie",
|
|
399
|
-
// Components v1+v2. PAN@ cardNumber. No sandbox host (body `testmode` boolean).
|
|
400
|
-
match: "api\\.cc\\.mollie\\.com/v1/card-tokens",
|
|
401
|
-
hosts: ["^api\\.cc\\.mollie\\.com$"],
|
|
402
|
-
encoding: "json",
|
|
403
|
-
passthroughHeaders: [],
|
|
404
|
-
compose: { cardExpiryDate: "MM/YY" }
|
|
405
|
-
},
|
|
406
|
-
{
|
|
407
|
-
psp: "paysafe",
|
|
408
|
-
// Paysafe.js. PAN@ card.cardNum, expiry@ card.cardExpiry.{month,year}.
|
|
409
|
-
// `paymenthub/v1/singleusepaymenthandles` was pinned from the docs and
|
|
410
|
-
// has NEVER been observed in a capture; every run of the live SDK posts to
|
|
411
|
-
// `/js/api/v1/tokenize` instead, from hosted.paysafe.com's own iframe. Both are kept:
|
|
412
|
-
// the SUPH path may be reachable on integrations we have not exercised.
|
|
413
|
-
// Auth travels as `x-paysafe-credentials` on the observed path (NOT `Authorization`,
|
|
414
|
-
// which the docs describe for SUPH), so both are declared — replaying the wrong one
|
|
415
|
-
// gets the tokenization rejected.
|
|
416
|
-
// Token boundary is `(?![\w/-])`, not `(?!/)`: the server anchors `match` with ^(?:…)$
|
|
417
|
-
// so either works there, but the SDK compiles `match` with `i` ALONE (unanchored), and
|
|
418
|
-
// `(?!/)` lets `…/singleusepaymenthandlesEVIL` satisfy the browser-side pause test.
|
|
419
|
-
// NOTE: legacy Paysafe.js v1 (/customervault/v1/singleusetokens, same two hosts) is
|
|
420
|
-
// deliberately NOT covered here — it needs its own verification pass before adding.
|
|
421
|
-
// Checkout 1.8 uses a separate hosted origin, with the same nested card shape.
|
|
422
|
-
// Verified from https://hosted.test.paysafe.com/checkout/1.8.0/main.bundle.js
|
|
423
|
-
// and its byte-identical production sibling (SHA-256 e9e111ff03073cc936a47674e8e7c5db4a6d1050e1b65bf5e20723116fd5080c).
|
|
424
|
-
// Its native LIVE/TEST environment map selects exactly hosted[.test].paysafe.com.
|
|
425
|
-
// Keep host/path pairs separate: details/log are not card requests;
|
|
426
|
-
// card/tokenize uses a saved paymentToken, and 3DS-specific paths are unverified.
|
|
427
|
-
match: "(?:api(\\.test)?\\.paysafe\\.com/(?:paymenthub/v1/singleusepaymenthandles|js/api/v1/tokenize)|hosted(\\.test)?\\.paysafe\\.com/checkout/api/v1/tokenize)(?![\\w/-])",
|
|
428
|
-
hosts: ["^api\\.paysafe\\.com$", "^api\\.test\\.paysafe\\.com$", "^hosted\\.paysafe\\.com$", "^hosted\\.test\\.paysafe\\.com$"],
|
|
429
|
-
sandboxHosts: ["^api\\.test\\.paysafe\\.com$", "^hosted\\.test\\.paysafe\\.com$"],
|
|
430
|
-
encoding: "json",
|
|
431
|
-
passthroughHeaders: ["^authorization$", "^x-paysafe-credentials$", "^correlationid$"]
|
|
432
|
-
},
|
|
433
|
-
{
|
|
434
|
-
psp: "recurly",
|
|
435
|
-
// PAN@ number (form-encoded POST). recurly.js hardcodes exactly TWO API bases:
|
|
436
|
-
// "https://api.recurly.com/js/v1" and "https://api.eu.recurly.com/js/v1". EU is auto-selected
|
|
437
|
-
// when the public key starts with `fra-`. No sandbox host: sandbox sites reuse api.recurly.com
|
|
438
|
-
// with a sandbox public key.
|
|
439
|
-
// WARNING: never widen these to [a-z0-9-]+\.recurly\.com. *.recurly.com is WILDCARD DNS (every
|
|
440
|
-
// merchant subdomain resolves to the same LB), so a broad host pattern is a card-exfil hole.
|
|
441
|
-
// (?![\w-]) excludes the live /js/v1/tokens sibling (iban/bacs/becs bank accounts + 3DS action
|
|
442
|
-
// results, no PAN); /js/v1/payment_methods/token is APM/wallet ({currency,amount}) and is
|
|
443
|
-
// excluded by the anchored path too.
|
|
444
|
-
// WARNING: the opt-in `cors:false` legacy path tokenizes over a JSONP GET, which the POST gate
|
|
445
|
-
// in isCardRequest cannot intercept. Default is `cors:true` (XHR POST), so this entry is live.
|
|
446
|
-
match: "api(\\.eu)?\\.recurly\\.com/js/v1/token(?![\\w-])",
|
|
447
|
-
hosts: ["^api\\.recurly\\.com$", "^api\\.eu\\.recurly\\.com$"],
|
|
448
|
-
encoding: "form",
|
|
449
|
-
passthroughHeaders: ["^recurly-credential-checkout-hostname$"]
|
|
450
|
-
// only in configure({hostname}) mode
|
|
451
|
-
},
|
|
452
|
-
{
|
|
453
|
-
psp: "moneris",
|
|
454
|
-
compose: { input_exp: "MMYY" },
|
|
455
|
-
// Hosted Tokenization. VERIFIED live against hpp_common.js on all six
|
|
456
|
-
// hosts: the iframe does `request.open('POST', 'request.php')` (XHR, relative to
|
|
457
|
-
// /HPPtoken/index.php) with Content-Type application/x-www-form-urlencoded and
|
|
458
|
-
// PAN@ input_data. It sets no auth header (the HT profile is the ?id= on the iframe
|
|
459
|
-
// URL), so passthroughHeaders is correctly empty. hpp_common.js references exactly
|
|
460
|
-
// one URL and one method, so there is no second tokenization path to cover.
|
|
461
|
-
// NOTE: do NOT add Moneris Checkout (chkt/) as a separate entry. It shares these
|
|
462
|
-
// hosts and resolveRecognizerForUrl .find()s on host first, so one would permanently
|
|
463
|
-
// shadow the other; chkt/ instead falls out at the path check (refused, not leaked).
|
|
464
|
-
match: "(www3|esqa|gateway|gatewayqa|gatewayt|gatewaydev)\\.moneris\\.com/HPPtoken/request\\.php",
|
|
465
|
-
hosts: [
|
|
466
|
-
"^www3\\.moneris\\.com$",
|
|
467
|
-
"^esqa\\.moneris\\.com$",
|
|
468
|
-
"^gateway\\.moneris\\.com$",
|
|
469
|
-
"^gatewayqa\\.moneris\\.com$",
|
|
470
|
-
"^gatewayt\\.moneris\\.com$",
|
|
471
|
-
"^gatewaydev\\.moneris\\.com$"
|
|
472
|
-
],
|
|
473
|
-
// Live: www3, gateway. Test/QA/dev: esqa, gatewayqa, gatewayt, gatewaydev.
|
|
474
|
-
sandboxHosts: ["^esqa\\.moneris\\.com$", "^gatewayqa\\.moneris\\.com$", "^gatewayt\\.moneris\\.com$", "^gatewaydev\\.moneris\\.com$"],
|
|
475
|
-
encoding: "form",
|
|
476
|
-
passthroughHeaders: []
|
|
477
|
-
},
|
|
478
|
-
{
|
|
479
|
-
psp: "bambora",
|
|
480
|
-
// Bambora NA / Worldline Custom Checkout. PAN@ number. POST. Content-Type is
|
|
481
|
-
// text/plain (preflight dodge) — replay verbatim; `encoding: 'json'` describes the body shape.
|
|
482
|
-
// No sandbox host: test mode rides the same host, decided by merchant id + API key.
|
|
483
|
-
// api.bam.shift4api.net is the post-migration host (Dec-2026 cutover); both hosts are pinned
|
|
484
|
-
// exact — never widen to the docs' *.bam.shift4api.net wildcard. api.na.bambora.com CNAMEs to
|
|
485
|
-
// the legacy beanstream.com origin; do NOT add www.beanstream.com, the browser SDK never calls it.
|
|
486
|
-
match: "(api\\.na\\.bambora\\.com|api\\.bam\\.shift4api\\.net)/scripts/tokenization/tokens",
|
|
487
|
-
hosts: ["^api\\.na\\.bambora\\.com$", "^api\\.bam\\.shift4api\\.net$"],
|
|
488
|
-
encoding: "json",
|
|
489
|
-
passthroughHeaders: []
|
|
490
|
-
},
|
|
491
|
-
{
|
|
492
|
-
psp: "global_payments",
|
|
493
|
-
// GlobalPayments.js, two gateways behind one PSP id. PAN@ card.number on
|
|
494
|
-
// both (verified in the shipped js.globalpay.com/v1/globalpayments.js, actionTokenize$3/$4).
|
|
495
|
-
// Heartland/Portico: prod api.heartlandportico.com/SecureSubmit.v1/api/token, sandbox
|
|
496
|
-
// cert.api2.heartlandportico.com/Hps.Exchange.PosGateway.Hpf.v1/api/token. The host/path
|
|
497
|
-
// pairing is ASYMMETRIC (prod is not api2, sandbox is not SecureSubmit.v1). That is the
|
|
498
|
-
// vendor's own domains$4/urls$4 table, not a typo, so do not "correct" it. Auth is ?api_key=
|
|
499
|
-
// in the URL, so there is nothing to replay in headers. api2.heartlandportico.com is
|
|
500
|
-
// deliberately absent: it is the server-side PHP SDK's endpoint, never a browser call.
|
|
501
|
-
// GP-API/UCP: apis[.sandbox][.eu].globalpay.com/ucp/[merchants/{id}/]payment-methods, auth via
|
|
502
|
-
// Authorization Bearer + X-GP-Version. All four hosts verified live and serving cross-origin
|
|
503
|
-
// POST. accounts/{id}/ is deliberately NOT accepted: the tokenizer only ever prefixes
|
|
504
|
-
// merchants/{id}/, so allowing it would widen the path with no endpoint behind it.
|
|
505
|
-
// Both gateways POST application/json, so isCardRequest's POST gate lets them through.
|
|
506
|
-
match: "(?:api\\.heartlandportico\\.com/SecureSubmit\\.v1/api/token|cert\\.api2\\.heartlandportico\\.com/Hps\\.Exchange\\.PosGateway\\.Hpf\\.v1/api/token|apis(?:\\.sandbox)?(?:\\.eu)?\\.globalpay\\.com/ucp/(?:merchants/[^/]+/)?payment-methods)",
|
|
507
|
-
hosts: [
|
|
508
|
-
"^api\\.heartlandportico\\.com$",
|
|
509
|
-
"^cert\\.api2\\.heartlandportico\\.com$",
|
|
510
|
-
"^apis\\.globalpay\\.com$",
|
|
511
|
-
"^apis\\.sandbox\\.globalpay\\.com$",
|
|
512
|
-
"^apis\\.eu\\.globalpay\\.com$",
|
|
513
|
-
"^apis\\.sandbox\\.eu\\.globalpay\\.com$"
|
|
514
|
-
],
|
|
515
|
-
// Heartland/Portico cert.* is the certification (sandbox) host; GP-API sandbox is apis.sandbox[.eu].
|
|
516
|
-
sandboxHosts: ["^cert\\.api2\\.heartlandportico\\.com$", "^apis\\.sandbox\\.globalpay\\.com$", "^apis\\.sandbox\\.eu\\.globalpay\\.com$"],
|
|
517
|
-
encoding: "json",
|
|
518
|
-
passthroughHeaders: ["^authorization$", "^x-gp-version$"]
|
|
519
|
-
},
|
|
520
|
-
// ── Encrypted on the device under a pinned key ────────────────────────────
|
|
521
|
-
{
|
|
522
|
-
psp: "fiserv",
|
|
523
|
-
// Fiserv Commerce Hub card capture from the Secure Data Capture (SDC) frame,
|
|
524
|
-
// the request Home Depot's OrangePay checkout sends (payment-fields.js and
|
|
525
|
-
// sdk-secure 3.8.14). The frame always encrypts: it seals the card with
|
|
526
|
-
// AES-256-GCM under a fresh key, wraps that key with RSA-OAEP-SHA256 under the
|
|
527
|
-
// public key the page passed it, and posts the envelope as
|
|
528
|
-
// source.encryptionData. It picks its endpoint by the page's auth type
|
|
529
|
-
// (iframe.js @85480): an AccessToken (Security Credentials) posts here, to
|
|
530
|
-
// card-capture; a Checkout Sessions JWT posts to /ch/checkouts/v1/payment-link
|
|
531
|
-
// instead. payment-link also carries the card, and it is left out on purpose:
|
|
532
|
-
// `match` names card-capture only, so a Sessions merchant's capture is never
|
|
533
|
-
// paused and fails as it does today. Fastlane (PaymentToken), ACH
|
|
534
|
-
// (PaymentCheck), gift and EBT captures share this URL, carry no Vault card,
|
|
535
|
-
// and the SDK continues them unpaused (fiserv.js isFiservPausableBody).
|
|
536
|
-
// The Wave 0 sandbox gate (2026-09-24, connect-cert, Fiserv simulator cards)
|
|
537
|
-
// settled the mode: card-capture refuses a plaintext source.card (400
|
|
538
|
-
// GATEWAY 302 "Missing or Invalid Encryption Data") and captures the frame's
|
|
539
|
-
// own envelope with no Origin, so the device builds that envelope under a
|
|
540
|
-
// key Agentcard pinned (fiserv-keys.js), the runtime writes its four members
|
|
541
|
-
// over the paused ones, and the capture continues from the agent's browser.
|
|
542
|
-
// The AccessToken the agent can read was refused on charges and tokens at
|
|
543
|
-
// Fiserv's edge in that sandbox. Every other Commerce Hub path is refused
|
|
544
|
-
// here: charges, tokens, security/keys, payment-link, and the non-production
|
|
545
|
-
// hosts (connect-dev, -qa, -uat, -test); resolveRecognizerForUrl also takes
|
|
546
|
-
// only the two endpoints written exactly (fiserv-environment.js). The
|
|
547
|
-
// envelope carries no timestamp or session binding, so it is approved by the
|
|
548
|
-
// cardholder every time: dark until an org is allowlisted, and autopilot
|
|
549
|
-
// refuses fiserv at all three gates.
|
|
550
|
-
capability: "fiserv_card_capture",
|
|
551
|
-
// A Fiserv card is sealed only for a capture the cardholder already approved
|
|
552
|
-
// through a prepare(): the backend refuses an unprepared create (409
|
|
553
|
-
// preparation_required), the internal buy loop never mints a Fiserv round,
|
|
554
|
-
// and the SDK refuses locally before it prompts.
|
|
555
|
-
preparationRequired: true,
|
|
556
|
-
match: "connect(?:-cert)?\\.fiservapis\\.com/ch/payments-vas/v1/card-capture(?![\\w/.-])",
|
|
557
|
-
hosts: ["^connect\\.fiservapis\\.com$", "^connect-cert\\.fiservapis\\.com$"],
|
|
558
|
-
sandboxHosts: ["^connect-cert\\.fiservapis\\.com$"],
|
|
559
|
-
// A live row reaches connect only and a test row connect-cert only, and
|
|
560
|
-
// connect-cert receives only documented test cards (device-replay.js).
|
|
561
|
-
hostEnvironmentPinned: true,
|
|
562
|
-
encoding: "json",
|
|
563
|
-
// The browser continues the capture with its own headers, so only the ones
|
|
564
|
-
// the backend checks against the body and the merchant are kept:
|
|
565
|
-
// Auth-Token-Type, the merchant and terminal ids, and the embedding frame's
|
|
566
|
-
// origin the SDC names (x-integration-origin, self-asserted). The
|
|
567
|
-
// AccessToken, the API key, Client-Request-Id and Timestamp are never stored.
|
|
568
|
-
passthroughHeaders: ["^auth-token-type$", "^x-integration-merchant-id$", "^x-integration-terminal-id$", "^x-integration-origin$"],
|
|
569
|
-
clientSideEncrypted: true,
|
|
570
|
-
mode: "cse",
|
|
571
|
-
cse: {
|
|
572
|
-
at: "source.encryptionData",
|
|
573
|
-
fields: ["keyId", "encryptedKey", "encryptionBlock", "aesIv"]
|
|
574
|
-
}
|
|
575
|
-
}
|
|
576
|
-
]);
|
|
577
|
-
var PSP_RECOGNIZERS = deepFreeze(PROCESSOR_DEFINITIONS.map(({ compose, encryptedCardFields, ...definition }) => definition));
|
|
578
|
-
var PSP_CLIENT_TABLE = deepFreeze(PROCESSOR_DEFINITIONS.map((definition) => ({
|
|
579
|
-
psp: definition.psp,
|
|
580
|
-
match: new RegExp(definition.match, "i"),
|
|
581
|
-
hosts: definition.hosts.map((source) => new RegExp(source, "i")),
|
|
582
|
-
encoding: definition.encoding,
|
|
583
|
-
headers: definition.passthroughHeaders.map((source) => new RegExp(source, "i")),
|
|
584
|
-
...definition.sandboxHosts ? { sandboxHosts: definition.sandboxHosts.map((source) => new RegExp(source, "i")) } : {},
|
|
585
|
-
...definition.hostEnvironmentPinned ? { hostEnvironmentPinned: true } : {},
|
|
586
|
-
...definition.mode ? { mode: definition.mode } : {},
|
|
587
|
-
...definition.clientSideEncrypted ? { clientSideEncrypted: true } : {},
|
|
588
|
-
...definition.compose ? { compose: definition.compose } : {},
|
|
589
|
-
...definition.encryptedCardFields ? { cse: { fields: definition.encryptedCardFields } } : {},
|
|
590
|
-
...definition.hostedForm ? { form: definition.hostedForm.card } : {}
|
|
591
|
-
})));
|
|
592
|
-
function buildBuiltinRegistry(recognizers) {
|
|
593
|
-
return recognizers.filter((definition) => !definition.capability).map(({ cse, hostedForm, ...definition }) => ({
|
|
594
|
-
...definition,
|
|
595
|
-
match: new RegExp(definition.match, "i"),
|
|
596
|
-
passthroughHeaders: definition.passthroughHeaders.map((source) => new RegExp(source, "i"))
|
|
597
|
-
}));
|
|
598
|
-
}
|
|
599
|
-
var BUILTIN_REGISTRY = deepFreeze(buildBuiltinRegistry(PSP_RECOGNIZERS));
|
|
600
|
-
var CHECKOUT_MODES = deepFreeze(["token", "cse", "hosted_form"]);
|
|
601
|
-
function recognizerByPsp(psp) {
|
|
602
|
-
return typeof psp === "string" ? PSP_CLIENT_TABLE.find((entry) => entry.psp === psp) ?? null : null;
|
|
603
|
-
}
|
|
604
|
-
function trustedClientRecognizer(pspOrEntry) {
|
|
605
|
-
const psp = typeof pspOrEntry === "string" ? pspOrEntry : pspOrEntry?.psp;
|
|
606
|
-
const definition = recognizerByPsp(psp);
|
|
607
|
-
if (!definition)
|
|
608
|
-
throw new Error("Refusing to pay: unknown payment processor.");
|
|
609
|
-
return definition;
|
|
610
|
-
}
|
|
611
|
-
// src/recognition.js
|
|
612
|
-
var PATTERN_WITHOUT_CARD_FIELDS = Object.freeze({
|
|
613
|
-
stripe: "api\\.stripe\\.com/v1/(payment_methods|tokens|setup_intents/seti_\\w+/confirm|payment_intents/pi_\\w+/confirm)"
|
|
614
|
-
});
|
|
615
|
-
var WITHOUT_CHECKOUT_SESSIONS = Object.freeze({
|
|
616
|
-
stripe: Object.freeze({
|
|
617
|
-
match: "api\\.stripe\\.com/v1/(payment_methods|tokens|sources|confirmation_tokens|setup_intents/seti_\\w+/confirm|payment_intents/pi_\\w+/confirm)",
|
|
618
|
-
passWithoutCard: "api\\.stripe\\.com/v1/(sources|confirmation_tokens)"
|
|
619
|
-
})
|
|
620
|
-
});
|
|
621
|
-
// src/request-policy.js
|
|
622
|
-
var HEADER_NEVER_FORWARD = /^(cookie|set-cookie|authorization2|proxy-authorization|host|content-length|origin|referer)$/i;
|
|
623
|
-
var ALLOWED_MEDIA_TYPES = {
|
|
624
|
-
json: ["application/json", "text/plain"],
|
|
625
|
-
form: ["application/x-www-form-urlencoded"]
|
|
626
|
-
};
|
|
627
|
-
function contentTypeFor(pspOrEntry, headers) {
|
|
628
|
-
const rec = trustedClientRecognizer(pspOrEntry);
|
|
629
|
-
const encoding = rec && rec.encoding === "form" ? "form" : "json";
|
|
630
|
-
const allowed = ALLOWED_MEDIA_TYPES[encoding];
|
|
631
|
-
const fallback = allowed[0];
|
|
632
|
-
const entry = Object.entries(headers || {}).find(([k]) => /^content-type$/i.test(k));
|
|
633
|
-
const raw = entry && entry[1];
|
|
634
|
-
if (typeof raw !== "string")
|
|
635
|
-
return fallback;
|
|
636
|
-
const base = raw.split(";")[0].trim().toLowerCase();
|
|
637
|
-
const worldpaySessionType = rec.psp === "worldpay" && encoding === "json" && base === "application/vnd.worldpay.sessions-v1.hal+json";
|
|
638
|
-
if (!allowed.includes(base) && !worldpaySessionType)
|
|
639
|
-
return fallback;
|
|
640
|
-
const charset = raw.match(/;\s*charset=([\w-]+)/i);
|
|
641
|
-
return charset ? `${base}; charset=${charset[1]}` : base;
|
|
642
|
-
}
|
|
643
|
-
function replayHeaders(pspOrEntry, captured = {}) {
|
|
644
|
-
const rec = trustedClientRecognizer(pspOrEntry);
|
|
645
|
-
const headers = { "content-type": contentTypeFor(rec, captured) };
|
|
646
|
-
for (const [name, value] of Object.entries(captured)) {
|
|
647
|
-
if (rec.headers.some((pattern) => pattern.test(name)) && !HEADER_NEVER_FORWARD.test(name))
|
|
648
|
-
headers[name] = value;
|
|
649
|
-
}
|
|
650
|
-
return headers;
|
|
651
|
-
}
|
|
652
|
-
var replayPolicy = Object.freeze({ contentTypeFor, HEADER_NEVER_FORWARD });
|
|
653
|
-
// src/stripe-checkout.js
|
|
654
|
-
var STRIPE_CHECKOUT_OPERATION = "stripe.device_checkout_confirm.v1";
|
|
655
|
-
var STRIPE_CHECKOUT_CONTEXT_HEADER = "x-agentcard-stripe-checkout-context";
|
|
656
|
-
var SESSION = /^cs_(?:test|live)_[A-Za-z0-9]{1,200}(?![\s\S])/;
|
|
657
|
-
var KEY = /^pk_(?:test|live)_[A-Za-z0-9_]{1,256}(?![\s\S])/;
|
|
658
|
-
var SYNTHETIC = /^pm_agentcard_checkout_[A-Za-z0-9_-]{16,80}(?![\s\S])/;
|
|
659
|
-
var STRIPE_CHECKOUT_VALIDATION_CODES = Object.freeze({
|
|
660
|
-
unclassified: "unclassified",
|
|
661
|
-
request_shape: "request_shape",
|
|
662
|
-
request_body_bounds: "request_body_bounds",
|
|
663
|
-
request_headers: "request_headers",
|
|
664
|
-
form_encoding: "form_encoding",
|
|
665
|
-
form_field_unknown: "form_field_unknown",
|
|
666
|
-
form_field_duplicate: "form_field_duplicate",
|
|
667
|
-
form_value_bounds: "form_value_bounds",
|
|
668
|
-
context_invalid: "context_invalid",
|
|
669
|
-
session_binding: "session_binding",
|
|
670
|
-
publishable_key: "publishable_key",
|
|
671
|
-
mode_binding: "mode_binding",
|
|
672
|
-
attribution_session: "attribution_session",
|
|
673
|
-
tokenization_fields: "tokenization_fields",
|
|
674
|
-
billing_email: "billing_email",
|
|
675
|
-
billing_binding: "billing_binding",
|
|
676
|
-
billing_conflict: "billing_conflict",
|
|
677
|
-
expected_amount: "expected_amount",
|
|
678
|
-
payment_method_type: "payment_method_type",
|
|
679
|
-
synthetic_payment_method: "synthetic_payment_method",
|
|
680
|
-
required_final_fields: "required_final_fields"
|
|
681
|
-
});
|
|
682
|
-
var validationCodes = /* @__PURE__ */ new WeakMap();
|
|
683
|
-
var reject = (code = "unclassified") => {
|
|
684
|
-
const error = new Error("stripe_checkout_rejected");
|
|
685
|
-
validationCodes.set(error, code);
|
|
686
|
-
throw error;
|
|
687
|
-
};
|
|
688
|
-
function stripeCheckoutValidationCode(error) {
|
|
689
|
-
const code = validationCodes.get(error);
|
|
690
|
-
return Object.values(STRIPE_CHECKOUT_VALIDATION_CODES).find((value) => value === code) ?? STRIPE_CHECKOUT_VALIDATION_CODES.unclassified;
|
|
691
|
-
}
|
|
692
|
-
var object = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
693
|
-
var opaque = (value) => typeof value === "string" && value.length <= 8192 && !/[\x00-\x1f\x7f]/.test(value);
|
|
694
|
-
var alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
|
695
|
-
function validateStripeCheckoutIdentity(value) {
|
|
696
|
-
if (!object(value) || !["test", "production"].includes(value.environment ?? "test") || value.environment === null || !["sessionId", "publishableKey", "livemode"].some((key) => Object.hasOwn(value, key)))
|
|
697
|
-
reject("mode_binding");
|
|
698
|
-
const environment = value.environment ?? "test", livemode = environment === "production";
|
|
699
|
-
const mode = livemode ? "live" : "test";
|
|
700
|
-
if (Object.hasOwn(value, "sessionId") && (typeof value.sessionId !== "string" || !SESSION.test(value.sessionId) || !value.sessionId.startsWith(`cs_${mode}_`)) || Object.hasOwn(value, "publishableKey") && (typeof value.publishableKey !== "string" || !KEY.test(value.publishableKey) || !value.publishableKey.startsWith(`pk_${mode}_`)) || Object.hasOwn(value, "livemode") && value.livemode !== livemode)
|
|
701
|
-
reject("mode_binding");
|
|
702
|
-
return Object.freeze({ environment, livemode });
|
|
703
|
-
}
|
|
704
|
-
var TEST_CARDS = ["4242424242424242", "4000000000000002", "4000002500003155"];
|
|
705
|
-
var SYNTHETIC_CARDS = [...TEST_CARDS, "4111111111111111"];
|
|
706
|
-
function validateStripeCheckoutCard(card, environment = "test") {
|
|
707
|
-
if (!["test", "production"].includes(environment) || !object(card) || typeof card.number !== "string" || typeof card.cvc !== "string" || !/^(?:0?[1-9]|1[0-2])(?![\s\S])/.test(String(card.month)) || !/^(?:\d{2}|\d{4})(?![\s\S])/.test(String(card.year)) || card.name !== void 0 && !opaque(card.name))
|
|
708
|
-
reject();
|
|
709
|
-
if (environment === "test") {
|
|
710
|
-
if (!TEST_CARDS.includes(card.number) || card.cvc !== "123")
|
|
711
|
-
reject();
|
|
712
|
-
}
|
|
713
|
-
else {
|
|
714
|
-
if (!/^\d{12,19}(?![\s\S])/.test(card.number) || /^(\d)\1+(?![\s\S])/.test(card.number) || !/^\d{3,4}(?![\s\S])/.test(card.cvc) || SYNTHETIC_CARDS.includes(card.number))
|
|
715
|
-
reject();
|
|
716
|
-
let sum = 0, double = false;
|
|
717
|
-
for (let i = card.number.length - 1; i >= 0; i--) {
|
|
718
|
-
let digit = Number(card.number[i]);
|
|
719
|
-
if (double) {
|
|
720
|
-
digit *= 2;
|
|
721
|
-
if (digit > 9)
|
|
722
|
-
digit -= 9;
|
|
723
|
-
}
|
|
724
|
-
sum += digit;
|
|
725
|
-
double = !double;
|
|
726
|
-
}
|
|
727
|
-
if (sum % 10 !== 0)
|
|
728
|
-
reject();
|
|
729
|
-
}
|
|
730
|
-
return true;
|
|
731
|
-
}
|
|
732
|
-
function isStripeCheckoutHostedMode(value) {
|
|
733
|
-
return value === "hosted" || value === "hosted_page";
|
|
734
|
-
}
|
|
735
|
-
function encodeAscii(value) {
|
|
736
|
-
let result = "", bits = 0, count = 0;
|
|
737
|
-
for (const char of value) {
|
|
738
|
-
bits = bits << 8 | char.charCodeAt(0);
|
|
739
|
-
count += 8;
|
|
740
|
-
while (count >= 6) {
|
|
741
|
-
count -= 6;
|
|
742
|
-
result += alphabet[bits >>> count & 63];
|
|
743
|
-
}
|
|
744
|
-
}
|
|
745
|
-
return result + (count ? alphabet[bits << 6 - count & 63] : "");
|
|
746
|
-
}
|
|
747
|
-
function context(value) {
|
|
748
|
-
if (!object(value) || Object.keys(value).length !== 3 || value.version !== 1 || !["version", "session_id", "synthetic_payment_method_id"].every((key) => Object.hasOwn(value, key)) || !SESSION.test(value.session_id) || !SYNTHETIC.test(value.synthetic_payment_method_id))
|
|
749
|
-
reject("context_invalid");
|
|
750
|
-
return { version: 1, session_id: value.session_id, synthetic_payment_method_id: value.synthetic_payment_method_id };
|
|
751
|
-
}
|
|
752
|
-
function encodeStripeCheckoutContext(value) {
|
|
753
|
-
return encodeAscii(JSON.stringify(context(value)));
|
|
754
|
-
}
|
|
755
|
-
function hasStripeCheckoutMarker(request) {
|
|
756
|
-
return object(request?.headers) && Object.keys(request.headers).some((key) => key.toLowerCase() === STRIPE_CHECKOUT_CONTEXT_HEADER);
|
|
757
|
-
}
|
|
758
|
-
function parseStripeCheckoutContext(request) {
|
|
759
|
-
const entries = Object.entries(request?.headers ?? {}).filter(([key]) => key.toLowerCase() === STRIPE_CHECKOUT_CONTEXT_HEADER);
|
|
760
|
-
if (!entries.length)
|
|
761
|
-
return null;
|
|
762
|
-
if (entries.length !== 1 || typeof entries[0][1] !== "string" || !/^[A-Za-z0-9_-]{1,1024}$/.test(entries[0][1]))
|
|
763
|
-
reject("context_invalid");
|
|
764
|
-
const encoded = entries[0][1];
|
|
765
|
-
let text = "", bits = 0, count = 0;
|
|
766
|
-
for (const char of encoded) {
|
|
767
|
-
bits = bits << 6 | alphabet.indexOf(char);
|
|
768
|
-
count += 6;
|
|
769
|
-
if (count >= 8) {
|
|
770
|
-
count -= 8;
|
|
771
|
-
text += String.fromCharCode(bits >>> count & 255);
|
|
772
|
-
}
|
|
773
|
-
}
|
|
774
|
-
let value;
|
|
775
|
-
try {
|
|
776
|
-
value = context(JSON.parse(text));
|
|
777
|
-
}
|
|
778
|
-
catch {
|
|
779
|
-
reject("context_invalid");
|
|
780
|
-
}
|
|
781
|
-
if (encodeStripeCheckoutContext(value) !== encoded)
|
|
782
|
-
reject("context_invalid");
|
|
783
|
-
return value;
|
|
784
|
-
}
|
|
785
|
-
function endpoint(raw) {
|
|
786
|
-
let url;
|
|
787
|
-
try {
|
|
788
|
-
url = new URL(raw);
|
|
789
|
-
}
|
|
790
|
-
catch {
|
|
791
|
-
return null;
|
|
792
|
-
}
|
|
793
|
-
if (url.origin !== "https://api.stripe.com" || url.port || url.username || url.password || url.search || url.hash)
|
|
794
|
-
return null;
|
|
795
|
-
return url;
|
|
796
|
-
}
|
|
797
|
-
function isStripeCheckoutEndpoint(raw) {
|
|
798
|
-
const url = endpoint(raw);
|
|
799
|
-
const match = url && /^\/v1\/payment_pages\/(cs_(?:test|live)_[A-Za-z0-9]+)\/confirm$/.exec(url.pathname);
|
|
800
|
-
return !!match && SESSION.test(match[1]);
|
|
801
|
-
}
|
|
802
|
-
var finalFields = /* @__PURE__ */ new Set([
|
|
803
|
-
"payment_method",
|
|
804
|
-
"key",
|
|
805
|
-
"expected_amount",
|
|
806
|
-
"expected_payment_method_type",
|
|
807
|
-
"payment_method_data[billing_details][email]",
|
|
808
|
-
"init_checksum",
|
|
809
|
-
"js_checksum",
|
|
810
|
-
"version",
|
|
811
|
-
"rv_timestamp",
|
|
812
|
-
"eid",
|
|
813
|
-
"link_brand",
|
|
814
|
-
"guid",
|
|
815
|
-
"muid",
|
|
816
|
-
"sid",
|
|
817
|
-
"passive_captcha_token",
|
|
818
|
-
"passive_captcha_ekey",
|
|
819
|
-
"pxcts",
|
|
820
|
-
"pxvid",
|
|
821
|
-
"px3",
|
|
822
|
-
...[
|
|
823
|
-
"checkout_config_id",
|
|
824
|
-
"checkout_session_id",
|
|
825
|
-
"client_session_id",
|
|
826
|
-
"merchant_integration_source",
|
|
827
|
-
"merchant_integration_version",
|
|
828
|
-
"payment_method_selection_flow"
|
|
829
|
-
].map((key) => `client_attribution_metadata[${key}]`),
|
|
830
|
-
...["shipping_rate_amount", "subtotal", "total_discount_amount", "total_exclusive_tax", "total_inclusive_tax"].map((key) => `last_displayed_line_item_group_details[${key}]`)
|
|
831
|
-
]);
|
|
832
|
-
var addressFields = ["city", "country", "line1", "line2", "postal_code", "state"];
|
|
833
|
-
var tokenFields = /* @__PURE__ */ new Set([
|
|
834
|
-
"type",
|
|
835
|
-
"key",
|
|
836
|
-
"card[number]",
|
|
837
|
-
"card[cvc]",
|
|
838
|
-
"card[exp_month]",
|
|
839
|
-
"card[exp_year]",
|
|
840
|
-
"billing_details[name]",
|
|
841
|
-
"billing_details[email]",
|
|
842
|
-
"billing_details[phone]",
|
|
843
|
-
"allow_redisplay",
|
|
844
|
-
...addressFields.map((key) => `billing_details[address][${key}]`),
|
|
845
|
-
"guid",
|
|
846
|
-
"muid",
|
|
847
|
-
"sid",
|
|
848
|
-
"payment_user_agent",
|
|
849
|
-
"referrer",
|
|
850
|
-
"time_on_page",
|
|
851
|
-
...[
|
|
852
|
-
"checkout_config_id",
|
|
853
|
-
"checkout_session_id",
|
|
854
|
-
"client_session_id",
|
|
855
|
-
"merchant_integration_source",
|
|
856
|
-
"merchant_integration_version",
|
|
857
|
-
"payment_method_selection_flow"
|
|
858
|
-
].map((key) => `client_attribution_metadata[${key}]`)
|
|
859
|
-
]);
|
|
860
|
-
function form(request, fields) {
|
|
861
|
-
if (!object(request) || request.method !== "POST" || typeof request.body !== "string")
|
|
862
|
-
reject("request_shape");
|
|
863
|
-
if (request.body.length > 2e4)
|
|
864
|
-
reject("request_body_bounds");
|
|
865
|
-
if (!object(request.headers) || Object.keys(request.headers).length > 40 || Object.values(request.headers).some((value) => !opaque(value)))
|
|
866
|
-
reject("request_headers");
|
|
867
|
-
const pairs = [];
|
|
868
|
-
const seen = /* @__PURE__ */ new Set();
|
|
869
|
-
for (const raw of request.body.split("&")) {
|
|
870
|
-
const index = raw.indexOf("=");
|
|
871
|
-
if (index < 1)
|
|
872
|
-
reject("form_encoding");
|
|
873
|
-
let key, value;
|
|
874
|
-
try {
|
|
875
|
-
key = decodeURIComponent(raw.slice(0, index).replace(/\+/g, " "));
|
|
876
|
-
value = decodeURIComponent(raw.slice(index + 1).replace(/\+/g, " "));
|
|
877
|
-
}
|
|
878
|
-
catch {
|
|
879
|
-
reject("form_encoding");
|
|
880
|
-
}
|
|
881
|
-
if (!fields.has(key))
|
|
882
|
-
reject("form_field_unknown");
|
|
883
|
-
if (seen.has(key))
|
|
884
|
-
reject("form_field_duplicate");
|
|
885
|
-
if (!opaque(value))
|
|
886
|
-
reject("form_value_bounds");
|
|
887
|
-
seen.add(key);
|
|
888
|
-
pairs.push({ key, value, raw });
|
|
889
|
-
}
|
|
890
|
-
return { pairs, values: new Map(pairs.map(({ key, value }) => [key, value])) };
|
|
891
|
-
}
|
|
892
|
-
function keyFor(request, values, expected) {
|
|
893
|
-
const authorization = Object.entries(request.headers).filter(([key2]) => key2.toLowerCase() === "authorization");
|
|
894
|
-
if (authorization.length > 1)
|
|
895
|
-
reject("publishable_key");
|
|
896
|
-
let headerKey;
|
|
897
|
-
if (authorization.length) {
|
|
898
|
-
const match = /^Bearer (pk_(?:test|live)_[A-Za-z0-9_]{1,256})$/.exec(authorization[0][1]);
|
|
899
|
-
if (!match)
|
|
900
|
-
reject("publishable_key");
|
|
901
|
-
headerKey = match[1];
|
|
902
|
-
}
|
|
903
|
-
const key = values.get("key") || headerKey;
|
|
904
|
-
if (!KEY.test(key) || headerKey && headerKey !== key || expected !== void 0 && expected !== key)
|
|
905
|
-
reject("publishable_key");
|
|
906
|
-
return key;
|
|
907
|
-
}
|
|
908
|
-
var billingEmail = (value) => value === null || typeof value === "string" && value.length > 0 && value.length <= 320 && opaque(value);
|
|
909
|
-
function captureStripeCheckoutBilling(request, options) {
|
|
910
|
-
const phase = classifyStripeCheckoutRequest(request, options);
|
|
911
|
-
if (phase?.phase !== "tokenization" || !SYNTHETIC.test(options?.syntheticPaymentMethodId))
|
|
912
|
-
reject("billing_binding");
|
|
913
|
-
const email = form(request, tokenFields).values.get("billing_details[email]") ?? null;
|
|
914
|
-
if (!billingEmail(email))
|
|
915
|
-
reject("billing_email");
|
|
916
|
-
return Object.freeze({
|
|
917
|
-
session_id: phase.session_id,
|
|
918
|
-
synthetic_payment_method_id: options.syntheticPaymentMethodId,
|
|
919
|
-
email
|
|
920
|
-
});
|
|
921
|
-
}
|
|
922
|
-
function attachStripeCheckoutBilling(request, captured, options) {
|
|
923
|
-
const phase = classifyStripeCheckoutRequest(request, options);
|
|
924
|
-
if (phase?.phase !== "final" || !object(captured) || Object.keys(captured).length !== 3 || !["session_id", "synthetic_payment_method_id", "email"].every((key) => Object.hasOwn(captured, key)) || captured.session_id !== phase.session_id || captured.synthetic_payment_method_id !== phase.synthetic_payment_method_id || !billingEmail(captured.email))
|
|
925
|
-
reject("billing_binding");
|
|
926
|
-
const field = "payment_method_data[billing_details][email]";
|
|
927
|
-
const existing = form(request, finalFields).values.get(field);
|
|
928
|
-
if (existing !== void 0 && existing !== captured.email)
|
|
929
|
-
reject("billing_conflict");
|
|
930
|
-
if (captured.email === null || existing !== void 0)
|
|
931
|
-
return { ...request };
|
|
932
|
-
const body = `${request.body}&${encodeURIComponent(field)}=${encodeURIComponent(captured.email)}`;
|
|
933
|
-
const result = { ...request, body };
|
|
934
|
-
classifyStripeCheckoutRequest(result, options);
|
|
935
|
-
return result;
|
|
936
|
-
}
|
|
937
|
-
function classifyStripeCheckoutRequest(request, options = {}) {
|
|
938
|
-
const url = endpoint(request?.url);
|
|
939
|
-
if (!url) {
|
|
940
|
-
if (hasStripeCheckoutMarker(request))
|
|
941
|
-
reject("context_invalid");
|
|
942
|
-
return null;
|
|
943
|
-
}
|
|
944
|
-
const final = isStripeCheckoutEndpoint(request.url);
|
|
945
|
-
const token = url.pathname === "/v1/payment_methods";
|
|
946
|
-
if (!final && !token) {
|
|
947
|
-
if (hasStripeCheckoutMarker(request))
|
|
948
|
-
reject("context_invalid");
|
|
949
|
-
return null;
|
|
950
|
-
}
|
|
951
|
-
if (token && options.sessionId === void 0 && !hasStripeCheckoutMarker(request))
|
|
952
|
-
return null;
|
|
953
|
-
const marker = parseStripeCheckoutContext(request);
|
|
954
|
-
const sessionId = final ? url.pathname.split("/")[3] : options.sessionId;
|
|
955
|
-
if (!SESSION.test(sessionId) || options.sessionId !== void 0 && options.sessionId !== sessionId || marker && marker.session_id !== sessionId)
|
|
956
|
-
reject("session_binding");
|
|
957
|
-
const { values } = form(request, final ? finalFields : tokenFields);
|
|
958
|
-
const key = keyFor(request, values, options.publishableKey);
|
|
959
|
-
validateStripeCheckoutIdentity({ environment: options.environment, sessionId, publishableKey: key });
|
|
960
|
-
const attributionSession = values.get("client_attribution_metadata[checkout_session_id]");
|
|
961
|
-
if (attributionSession !== void 0 && attributionSession !== sessionId)
|
|
962
|
-
reject("attribution_session");
|
|
963
|
-
if (token) {
|
|
964
|
-
const number = values.get("card[number]");
|
|
965
|
-
if (values.get("type") !== "card" || typeof number !== "string" || number.length > 64 || /[^0-9 -]/.test(number) || !["4111111111111111", "4242424242424242"].includes(number.replace(/[ -]/g, "")) || !/^\d{3}$/.test(values.get("card[cvc]") ?? "") || !/^(?:0?[1-9]|1[0-2])$/.test(values.get("card[exp_month]") ?? "") || !/^(?:\d{2}|\d{4})$/.test(values.get("card[exp_year]") ?? "") || values.has("allow_redisplay") && values.get("allow_redisplay") !== "unspecified")
|
|
966
|
-
reject("tokenization_fields");
|
|
967
|
-
return { phase: "tokenization", session_id: sessionId, publishable_key: key };
|
|
968
|
-
}
|
|
969
|
-
const amount = values.get("expected_amount");
|
|
970
|
-
const syntheticId = values.get("payment_method");
|
|
971
|
-
const email = values.get("payment_method_data[billing_details][email]");
|
|
972
|
-
if (email !== void 0 && !billingEmail(email))
|
|
973
|
-
reject("billing_email");
|
|
974
|
-
if (!/^[1-9]\d{0,11}$/.test(amount ?? "") || !Number.isSafeInteger(Number(amount)) || options.amountCents !== void 0 && options.amountCents !== Number(amount))
|
|
975
|
-
reject("expected_amount");
|
|
976
|
-
if (values.get("expected_payment_method_type") !== "card")
|
|
977
|
-
reject("payment_method_type");
|
|
978
|
-
if (!SYNTHETIC.test(syntheticId))
|
|
979
|
-
reject("synthetic_payment_method");
|
|
980
|
-
if (["init_checksum", "js_checksum", "version", "rv_timestamp"].some((key2) => !values.get(key2)))
|
|
981
|
-
reject("required_final_fields");
|
|
982
|
-
if (options.syntheticPaymentMethodId !== void 0 && options.syntheticPaymentMethodId !== syntheticId || marker && marker.synthetic_payment_method_id !== syntheticId)
|
|
983
|
-
reject("synthetic_payment_method");
|
|
984
|
-
return {
|
|
985
|
-
phase: "final",
|
|
986
|
-
session_id: sessionId,
|
|
987
|
-
publishable_key: key,
|
|
988
|
-
synthetic_payment_method_id: syntheticId,
|
|
989
|
-
amount_cents: Number(amount),
|
|
990
|
-
currency: "usd"
|
|
991
|
-
};
|
|
992
|
-
}
|
|
993
|
-
function createStripeCheckoutTokenizationStub(request, options) {
|
|
994
|
-
const phase = classifyStripeCheckoutRequest(request, options);
|
|
995
|
-
if (phase?.phase !== "tokenization" || !SYNTHETIC.test(options?.syntheticPaymentMethodId))
|
|
996
|
-
reject();
|
|
997
|
-
const { values } = form(request, tokenFields);
|
|
998
|
-
const address = Object.fromEntries(addressFields.filter((key) => values.has(`billing_details[address][${key}]`)).map((key) => [key, values.get(`billing_details[address][${key}]`)]));
|
|
999
|
-
const billing = Object.fromEntries(["name", "email", "phone"].filter((key) => values.has(`billing_details[${key}]`)).map((key) => [key, values.get(`billing_details[${key}]`)]));
|
|
1000
|
-
const year = values.get("card[exp_year]");
|
|
1001
|
-
return { status: 200, headers: { "content-type": "application/json" }, body: JSON.stringify({
|
|
1002
|
-
id: options.syntheticPaymentMethodId,
|
|
1003
|
-
object: "payment_method",
|
|
1004
|
-
type: "card",
|
|
1005
|
-
livemode: validateStripeCheckoutIdentity({ environment: options.environment, sessionId: phase.session_id }).livemode,
|
|
1006
|
-
billing_details: { ...billing, address },
|
|
1007
|
-
card: {
|
|
1008
|
-
brand: "visa",
|
|
1009
|
-
last4: "4242",
|
|
1010
|
-
exp_month: Number(values.get("card[exp_month]")),
|
|
1011
|
-
exp_year: Number(year.length === 2 ? `20${year}` : year),
|
|
1012
|
-
funding: "credit",
|
|
1013
|
-
country: "US"
|
|
1014
|
-
}
|
|
1015
|
-
}) };
|
|
1016
|
-
}
|
|
1017
|
-
var terminalFields = /* @__PURE__ */ new Set([
|
|
1018
|
-
"id",
|
|
1019
|
-
"object",
|
|
1020
|
-
"state",
|
|
1021
|
-
"status",
|
|
1022
|
-
"payment_status",
|
|
1023
|
-
"currency",
|
|
1024
|
-
"livemode",
|
|
1025
|
-
"session_id",
|
|
1026
|
-
"mode",
|
|
1027
|
-
"ui_mode",
|
|
1028
|
-
"success_url",
|
|
1029
|
-
"return_url"
|
|
1030
|
-
]);
|
|
1031
|
-
function parseStripeCheckoutResponse(raw, options) {
|
|
1032
|
-
let value;
|
|
1033
|
-
try {
|
|
1034
|
-
value = typeof raw === "string" ? JSON.parse(raw) : raw;
|
|
1035
|
-
validateStripeCheckoutIdentity({ environment: options?.environment, sessionId: options?.sessionId, livemode: value?.livemode });
|
|
1036
|
-
}
|
|
1037
|
-
catch {
|
|
1038
|
-
return null;
|
|
1039
|
-
}
|
|
1040
|
-
if (!object(value) || !SESSION.test(options?.sessionId) || value.session_id !== options.sessionId || Object.keys(value).some((key) => !terminalFields.has(key)) || !["id", "object", "state", "status", "payment_status", "currency", "livemode", "session_id", "mode", "ui_mode"].every((key) => Object.hasOwn(value, key)) || typeof value.id !== "string" || !/^[A-Za-z0-9_]{1,256}(?![\s\S])/.test(value.id) || value.object !== "checkout.session" || value.state !== "succeeded" || value.status !== "complete" || value.payment_status !== "paid" || value.currency !== "usd" || value.mode !== "payment" || !isStripeCheckoutHostedMode(value.ui_mode))
|
|
1041
|
-
return null;
|
|
1042
|
-
for (const key of ["success_url", "return_url"]) {
|
|
1043
|
-
if (!Object.hasOwn(value, key) || value[key] === null)
|
|
1044
|
-
continue;
|
|
1045
|
-
if (typeof value[key] !== "string" || value[key].length > 2048)
|
|
1046
|
-
return null;
|
|
1047
|
-
let url;
|
|
1048
|
-
try {
|
|
1049
|
-
url = new URL(value[key]);
|
|
1050
|
-
}
|
|
1051
|
-
catch {
|
|
1052
|
-
return null;
|
|
1053
|
-
}
|
|
1054
|
-
let parameters;
|
|
1055
|
-
try {
|
|
1056
|
-
parameters = decodeURIComponent(url.search + url.hash);
|
|
1057
|
-
}
|
|
1058
|
-
catch {
|
|
1059
|
-
return null;
|
|
1060
|
-
}
|
|
1061
|
-
if (url.protocol !== "https:" || url.username || url.password || options.returnOrigin !== void 0 && url.origin !== options.returnOrigin || /(?:client_secret|payment_method|secret_)/i.test(parameters))
|
|
1062
|
-
return null;
|
|
1063
|
-
}
|
|
1064
|
-
return { ...value };
|
|
1065
|
-
}
|
|
1066
|
-
function prepareStripeCheckoutReplay(request, card, options = {}) {
|
|
1067
|
-
const marker = parseStripeCheckoutContext(request);
|
|
1068
|
-
if (!marker)
|
|
1069
|
-
reject();
|
|
1070
|
-
const phase = classifyStripeCheckoutRequest(request, {
|
|
1071
|
-
...options,
|
|
1072
|
-
sessionId: marker.session_id,
|
|
1073
|
-
syntheticPaymentMethodId: marker.synthetic_payment_method_id
|
|
1074
|
-
});
|
|
1075
|
-
if (phase?.phase !== "final" || options.amountCents !== void 0 && phase.amount_cents !== options.amountCents || options.sessionId !== void 0 && options.sessionId !== marker.session_id || !validateStripeCheckoutCard(card, options.environment))
|
|
1076
|
-
reject();
|
|
1077
|
-
const replacements = {
|
|
1078
|
-
"payment_method_data[type]": "card",
|
|
1079
|
-
"payment_method_data[card][number]": card.number,
|
|
1080
|
-
"payment_method_data[card][cvc]": card.cvc,
|
|
1081
|
-
"payment_method_data[card][exp_month]": String(card.month),
|
|
1082
|
-
"payment_method_data[card][exp_year]": String(card.year),
|
|
1083
|
-
...card.name ? { "payment_method_data[billing_details][name]": card.name } : {}
|
|
1084
|
-
};
|
|
1085
|
-
const inline = Object.entries(replacements).map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`).join("&");
|
|
1086
|
-
const body = form(request, finalFields).pairs.map((pair) => pair.key === "payment_method" ? inline : pair.raw).join("&");
|
|
1087
|
-
return {
|
|
1088
|
-
psp: "stripe",
|
|
1089
|
-
request: { url: request.url, method: "POST", headers: replayHeaders("stripe", request.headers), body },
|
|
1090
|
-
where: Object.keys(replacements)
|
|
1091
|
-
};
|
|
1092
|
-
}
|
|
1093
|
-
export { STRIPE_CHECKOUT_CONTEXT_HEADER, STRIPE_CHECKOUT_OPERATION, STRIPE_CHECKOUT_VALIDATION_CODES, attachStripeCheckoutBilling, captureStripeCheckoutBilling, classifyStripeCheckoutRequest, createStripeCheckoutTokenizationStub, encodeStripeCheckoutContext, hasStripeCheckoutMarker, isStripeCheckoutEndpoint, isStripeCheckoutHostedMode, parseStripeCheckoutContext, parseStripeCheckoutResponse, prepareStripeCheckoutReplay, stripeCheckoutValidationCode, validateStripeCheckoutCard, validateStripeCheckoutIdentity };
|