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