@agent-cards/sdk 0.23.12 → 0.23.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +3 -1
- package/LICENSE +38 -0
- package/README.md +1 -1
- package/dist/builtin-registry.generated.js +1 -1
- package/dist/card-fields.generated.js +3 -1
- package/dist/cdp.js +35 -3
- package/dist/client.d.ts +4 -1
- package/dist/client.js +9 -1
- package/dist/lifecycle.d.ts +11 -2
- package/dist/lifecycle.js +4 -1
- package/dist/paddle-vgs-pay.d.ts +55 -0
- package/dist/paddle-vgs-pay.js +115 -0
- package/dist/paddle-vgs.generated.d.ts +29 -1
- package/dist/paddle-vgs.generated.js +124 -5
- package/dist/paysafe.generated.d.ts +5 -7
- package/dist/paysafe.generated.js +50 -18
- package/dist/pre-claim.d.ts +8 -0
- package/dist/pre-claim.js +48 -1
- package/dist/primer.generated.js +3 -1
- package/dist/stripe-checkout.generated.js +18 -3
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
- Keep a Rapyd hosted page's id and a Stripe Checkout Session's id out of your events and error messages. Rapyd's checkout page sends the card to `/v1/checkout/checkout_<id>` on `api.rapyd.net` (a card page to `/v1/hosted/collect/card/hp_card_<id>/payment_method`) with no auth header, so the page's id is the only credential that request needs: anyone who holds it can read the page and post a card to it until it expires. Stripe Checkout confirms a session at `/v1/payment_pages/<session id>/confirm` with the merchant's publishable key, which is public, so the session id is what lets anyone confirm a payment on it. Earlier releases named both ids in the `card_request_paused` event and in the errors `authorize()` throws, so they reached your `onEvent` handler and every log that recorded the event or the error. `redactUrl`, every event and every error message now show them as `{token}` and `{session}`, as in `https://api.rapyd.net/v1/checkout/{token}` and `https://api.stripe.com/v1/payment_pages/{session}/confirm`. `redactUrl` also masks an ACI checkout id in the shapes it used to keep: a host with a trailing dot, an escaped or doubled slash, and a string that is not a URL. A PaymentIntent or SetupIntent id stays, since confirming one also needs its client_secret. No API or Vault release is needed.
|
|
6
6
|
|
|
7
|
+
- Pay a store on Paysafe Checkout's current build, 1.16.1. The loader Paysafe tells merchants to include, `checkout/v1/latest`, now runs version 1.16.1, and that build sends the card to `api.paysafe.com/checkout/api/v1/tokenize` (`api.test.paysafe.com` in its test environment), where 1.8.0 sent it to `hosted.paysafe.com`. Earlier releases did not recognize that request, so it was never paused and the agent's placeholder card reached Paysafe. The SDK now pauses it and keeps the three headers the build sends beside its credential: `X-Internal-Correlation-Id`, `X-App-Version` and `X-TransactionSource`. Paysafe.js latest sends the same three, and its replay keeps them too. `prepare({ psp: 'paysafe' })` binds a request only to a Checkout build Agentcard read, by the version the request names: 1.8.0 on `hosted.paysafe.com`, or 1.16.1 on `api.paysafe.com`, each with its own headers. A request from any other build stops with `checkout_changed`, and nothing is sent. An SDK that syncs its registry pauses the 1.16.1 request once the API release deploys; a prepared 1.16.1 checkout needs this release. Both need a Vault release after 0.6.2.
|
|
8
|
+
|
|
7
9
|
- Keep a Gravy checkout session out of your events and error messages. Gravy's Secure Fields send the card to `/checkout/sessions/<session id>/fields` on the merchant's Gravy host with no auth header, so the session id is the only credential that request needs: anyone who holds it can write card fields into the session until it expires. Earlier releases named the full URL in the `card_request_paused` event and in the error `authorize()` throws for a request in the wrong method, so the session id reached your `onEvent` handler and every log that recorded the event or the error. `redactUrl`, every event and every error message now show it as `{session}`, as in `https://api.<merchant>.gr4vy.app/checkout/sessions/{session}/fields`. No API or Vault release is needed.
|
|
8
10
|
|
|
9
11
|
- Pay FlixBus's checkout on Payrails web SDK 6.7.0. FlixBus's page now loads web SDK 6.7.0 in place of 5.50.0, and 6.7.0's Pay now button sends the card-form payment without the `enrollInstrumentToNetworkOffers` member that 5.50.0 sent as `false`. 0.23.11 required that member and aborted every such payment as `unsupported_payrails_card_payment` before creating anything, so no FlixBus payment reached an approval. A payment that names no `enrollInstrumentToNetworkOffers` now pauses as 5.50.0's does: Payrails reads a missing flag as `false`, and 6.7.0 sends no other member in its place. A payment that sets the flag `true` or keeps the card (`storeInstrument: true`, or `meta.order.processingType` `CardOnFile` or `UnscheduledCardOnFile`) still aborts as `payrails_card_on_file_consent_required`, and the flag in another case, in another place or as anything but a boolean aborts as `unsupported_payrails_card_payment`. A payment also needs the matching API and Vault releases.
|
|
@@ -96,7 +98,7 @@
|
|
|
96
98
|
|
|
97
99
|
- Read the card number on the D-EDGE card frame the way the frame groups each card network: 4-6-5 for an American Express number, 4-6-4 for a Diners Club number starting 36 or 300 to 305, and groups of four for any other. The SDK's copy of D-EDGE's rules read a grouped number only in groups of four, so an American Express or Diners Club placeholder as the frame groups it was not D-EDGE's reviewed card request, and the same number in groups of four, which the frame never posts, was. D-EDGE stays in `observe`, so its card post still gets the page that says Agentcard has not turned it on.
|
|
98
100
|
|
|
99
|
-
- Pay a Paddle checkout that sends the card to Paddle's card vault, from Agentcard's own account only for now (dark launch): Agentcard turns it on for another company once it has confirmed that a card stored for one Paddle seller cannot pay another. On a checkout whose card method names VGS, Paddle's checkout frame on `buy.paddle.com` posts the card to `https://vault.paddle.com/cards` (`https://sandbox-vault.paddle.com/cards` in test) and sends that request once more when it gets no answer within 10 seconds, too soon for an approval after Pay. So the card is paid only through `prepare({ psp: 'paddle_vgs', environment: 'production' })` (`'sandbox'` for Paddle's sandbox) on an attachment whose `cardId` names the card whose `placeholderCard()` facts you typed into Paddle's form; without one, `prepare()` fails with `card_required`, and the approval is locked to that card. The sync now also asks for `capabilities=paddle_vgs_cards`. Once the cardholder approves, the frame's request pauses, the cardholder's device sends the card to Paddle's vault itself, and the frame receives the stored card's id and nothing else; the frame's second request after its 10-second timeout, the same body under the same bearer, gets the same answer, and any other request is refused. Paddle's request names no amount, so the cardholder approves the `amount` and `currency` you pass. The lane pays Visa, Mastercard and American Express cards: `prepare()` fails with `paddle_vgs_card_unsupported` for a card on another network, and the create fails with `paddle_vgs_card_mismatch` when the expiry or placeholder you typed is not the card's. On Paddle's live vault, Agentcard pays only a seller it has reviewed, at the checkout page it reviewed (Rectangle Pro at `https://rectangleapp.com` today), and the approval names that seller and says it can keep the card on file, because the card stays stored in Paddle's vault. Paddle's own `/pay` call, which follows the stored card's id,
|
|
101
|
+
- Pay a Paddle checkout that sends the card to Paddle's card vault, from Agentcard's own account only for now (dark launch): Agentcard turns it on for another company once it has confirmed that a card stored for one Paddle seller cannot pay another. On a checkout whose card method names VGS, Paddle's checkout frame on `buy.paddle.com` posts the card to `https://vault.paddle.com/cards` (`https://sandbox-vault.paddle.com/cards` in test) and sends that request once more when it gets no answer within 10 seconds, too soon for an approval after Pay. So the card is paid only through `prepare({ psp: 'paddle_vgs', environment: 'production' })` (`'sandbox'` for Paddle's sandbox) on an attachment whose `cardId` names the card whose `placeholderCard()` facts you typed into Paddle's form; without one, `prepare()` fails with `card_required`, and the approval is locked to that card. The sync now also asks for `capabilities=paddle_vgs_cards`. Once the cardholder approves, the frame's request pauses, the cardholder's device sends the card to Paddle's vault itself, and the frame receives the stored card's id and nothing else; the frame's second request after its 10-second timeout, the same body under the same bearer, gets the same answer, and any other request is refused. Paddle's request names no amount, so the cardholder approves the `amount` and `currency` you pass. The lane pays Visa, Mastercard and American Express cards: `prepare()` fails with `paddle_vgs_card_unsupported` for a card on another network, and the create fails with `paddle_vgs_card_mismatch` when the expiry or placeholder you typed is not the card's. On Paddle's live vault, Agentcard pays only a seller it has reviewed, at the checkout page it reviewed (Rectangle Pro at `https://rectangleapp.com` today), and the approval names that seller and says it can keep the card on file, because the card stays stored in Paddle's vault. Paddle's own `/pay` call, which follows the stored card's id, names the card it pays by its first six and last four digits, expiry and brand, and Paddle's form reads those from the placeholder you typed. The SDK writes the approved card's own over them, the details the cardholder's device read from the card, and changes nothing else in that call. A `/pay` that names another stored card, comes from another frame, comes more than a minute after the id reached the frame, or follows the first one is aborted, and Paddle charges nothing. A Paddle checkout that sends the card through TokenEx or Spreedly is not paid. No live payment has gone through this route yet. A payment also needs the matching API and Vault releases.
|
|
100
102
|
|
|
101
103
|
## 0.23.0
|
|
102
104
|
|
package/LICENSE
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
Copyright (c) 2026 Agentcard Corporation. All rights reserved.
|
|
2
|
+
|
|
3
|
+
This software, including its source, compiled and minified code, models,
|
|
4
|
+
word lists, recognizers and data files (the "Software"), is proprietary to
|
|
5
|
+
Agentcard Corporation and is licensed, not sold.
|
|
6
|
+
|
|
7
|
+
You may install and run the Software only to use Agentcard's services, under
|
|
8
|
+
the terms you accepted when you signed up for them. If you integrate
|
|
9
|
+
Agentcard into an application, you may include the Software, unmodified and
|
|
10
|
+
as Agentcard publishes it, in that application and distribute the application
|
|
11
|
+
to your end users so they can use Agentcard's services through it; this
|
|
12
|
+
license binds every copy you distribute that way. Otherwise you may not, and
|
|
13
|
+
may not help or allow anyone else to:
|
|
14
|
+
|
|
15
|
+
1. copy, modify, translate or create derivative works of the Software, except
|
|
16
|
+
for copies made automatically when installing and running it or included
|
|
17
|
+
in an application as the paragraph above allows;
|
|
18
|
+
2. reverse engineer, decompile, disassemble, deobfuscate or otherwise try to
|
|
19
|
+
derive the source, models, thresholds, word lists, recognizers or data of
|
|
20
|
+
the Software, except to the extent applicable law expressly permits it
|
|
21
|
+
despite this restriction;
|
|
22
|
+
3. extract, scrape or reuse the Software's data, including its merchant,
|
|
23
|
+
processor and passphrase data, in any other product or service;
|
|
24
|
+
4. use the Software, or anything learned from studying it, to build, train,
|
|
25
|
+
tune or benchmark a product or service that competes with Agentcard, or to
|
|
26
|
+
defeat or imitate its security checks;
|
|
27
|
+
5. redistribute, sublicense, sell, rent or publish the Software apart from an
|
|
28
|
+
application as the paragraph above allows, or remove this notice from it.
|
|
29
|
+
|
|
30
|
+
Agentcard keeps dated fingerprints of the Software's data. A match in
|
|
31
|
+
another product or data set is evidence that it was copied from the
|
|
32
|
+
Software.
|
|
33
|
+
|
|
34
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
35
|
+
IMPLIED. IN NO EVENT SHALL AGENTCARD CORPORATION BE LIABLE FOR ANY CLAIM,
|
|
36
|
+
DAMAGES OR OTHER LIABILITY ARISING FROM THE SOFTWARE OR ITS USE.
|
|
37
|
+
|
|
38
|
+
Questions: legal@agentcard.sh
|
package/README.md
CHANGED
|
@@ -278,7 +278,7 @@ Coverage is specific to the processor request format, merchant setup, browser tr
|
|
|
278
278
|
| WSPay Components (WSPay's build of Monri Components) | dark launch: the card frame's confirm of a new card, after `vault.syncRegistry()`, with `amount` and `currency`, and only for an org Agentcard has enabled; for any other org the SDK leaves WSPay's requests alone. The cardholder's device sends the card to WSPay and the frame receives WSPay's answer. A confirm that asks WSPay to keep the card on file, as Valamar's does on every booking, is paid only as a hotel's EUR 1.00 card guarantee with a card the cardholder stored. Validated in local Chromium against a model of the card frame; no WSPay sandbox run or live merchant purchase yet. See [Pay a WSPay Components checkout](#pay-a-wspay-components-checkout) |
|
|
279
279
|
| Shiji STS (MyCheck Wallet V3; Wyndham's booking pages) | dark launch, mode `cse` under a pinned key, and only for an org Agentcard has enabled. The card token a hotel booking mints guarantees the stay, so the cardholder approves with a card they stored, under a line that says the hotel can keep it on file. See [Shiji STS](#shiji-sts-mycheck-wallet-v3) |
|
|
280
280
|
| Amadeus Checkout (Air France, KLM) | dark: only for an org Agentcard enabled, and only through `prepare({ psp: 'amadeus_checkout', cardId })`. The Checkout Web SDK's card add pauses; every other action on its URL continues. Air France and KLM keep the expiry in their own form, so the agent types the stored card's real expiry there and the checkout pays only with that card. Every Data Collection Page store is refused as `amadeus_dcp_field_config_unreviewed`. No live purchase yet. See "Prepare an Amadeus Checkout card step" |
|
|
281
|
-
| Paddle checkout (Paddle's card vault; Rectangle Pro) | dark launch: only in Agentcard's own account for now, after `vault.syncRegistry()`, and only through `prepare({ psp: 'paddle_vgs', environment: 'production' })` (`'sandbox'` for Paddle's sandbox) on an attachment whose `cardId` names the paying card; without one, `prepare()` fails with `card_required`. On a Paddle checkout whose card method names VGS, the frame's post to `vault.paddle.com/cards` pauses, the cardholder's device sends the card itself, and the frame receives the stored card's id; its second request after its 10-second timeout gets the same answer. Type `placeholderCard()`'s facts for that card into Paddle's form, and pass `amount` and `currency`, the total the cardholder approves. Only Visa, Mastercard and American Express cards are paid (`paddle_vgs_card_unsupported` otherwise), and a typed expiry or placeholder that is not the card's fails the create with `paddle_vgs_card_mismatch`. On the live vault only a reviewed seller at its reviewed page is paid (Rectangle Pro at `https://rectangleapp.com`), and the approval says that seller can keep the card on file. Paddle's `/pay`
|
|
281
|
+
| Paddle checkout (Paddle's card vault; Rectangle Pro) | dark launch: only in Agentcard's own account for now, after `vault.syncRegistry()`, and only through `prepare({ psp: 'paddle_vgs', environment: 'production' })` (`'sandbox'` for Paddle's sandbox) on an attachment whose `cardId` names the paying card; without one, `prepare()` fails with `card_required`. On a Paddle checkout whose card method names VGS, the frame's post to `vault.paddle.com/cards` pauses, the cardholder's device sends the card itself, and the frame receives the stored card's id; its second request after its 10-second timeout gets the same answer. Type `placeholderCard()`'s facts for that card into Paddle's form, and pass `amount` and `currency`, the total the cardholder approves. Only Visa, Mastercard and American Express cards are paid (`paddle_vgs_card_unsupported` otherwise), and a typed expiry or placeholder that is not the card's fails the create with `paddle_vgs_card_mismatch`. On the live vault only a reviewed seller at its reviewed page is paid (Rectangle Pro at `https://rectangleapp.com`), and the approval says that seller can keep the card on file. Paddle's `/pay` that follows names the approved card's first six and last four digits, expiry and brand: the SDK writes them over the ones Paddle's form read from the placeholder, and aborts a `/pay` that names another stored card or comes more than a minute later. A Paddle checkout that sends the card through TokenEx or Spreedly is not supported, and no live payment has gone through this route yet |
|
|
282
282
|
|
|
283
283
|
The recognizer list, and the status of each reviewed Adyen merchant profile on
|
|
284
284
|
your client, are fetched from the API at runtime (`agentcard.checkout.syncRegistry()`),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export const BUILTIN_REGISTRY = [{ "psp": "spreedly", "match": /core\.spreedly\.com\/v1\/payment_methods\/restricted\.json/i, "hosts": ["^core\\.spreedly\\.com$"], "encoding": "json", "passthroughHeaders": [/^spreedly-environment-key$/i] }, { "psp": "shopify", "match": /(checkout\.pci\.shopifyinc\.com|deposit\.[a-z0-9-]+\.shopifycs\.com)\/sessions/i, "hosts": ["^checkout\\.pci\\.shopifyinc\\.com$", "^deposit\\.[a-z0-9-]+\\.shopifycs\\.com$"], "encoding": "json", "passthroughHeaders": [/^shopify-identification-signature$/i] }, { "psp": "stripe", "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)/i, "hosts": ["^api\\.stripe\\.com$"], "encoding": "form", "passthroughHeaders": [/^authorization$/i, /^stripe-version$/i, /^x-stripe-client-user-agent$/i], "cardFields": ["card[number]", "payment_method_data[card][number]"], "passWithoutCard": "api\\.stripe\\.com/v1/(sources|confirmation_tokens|payment_pages/cs_(?:test|live)_\\w+/confirm)" }, { "psp": "braintree", "match": /payments(\.sandbox)?\.braintree-api\.com\/graphql/i, "hosts": ["^payments\\.braintree-api\\.com$", "^payments\\.sandbox\\.braintree-api\\.com$"], "sandboxHosts": ["^payments\\.sandbox\\.braintree-api\\.com$"], "encoding": "json", "passthroughHeaders": [/^authorization$/i, /^braintree-version$/i] }, { "psp": "checkout_com", "match": /(api|card-acquisition-gateway)(\.sandbox)?\.checkout\.com\/tokens/i, "hosts": ["^api\\.checkout\\.com$", "^api\\.sandbox\\.checkout\\.com$", "^card-acquisition-gateway\\.checkout\\.com$", "^card-acquisition-gateway\\.sandbox\\.checkout\\.com$"], "sandboxHosts": ["^api\\.sandbox\\.checkout\\.com$", "^card-acquisition-gateway\\.sandbox\\.checkout\\.com$"], "encoding": "json", "passthroughHeaders": [/^authorization$/i] }, { "psp": "adyen", "match": /(checkoutshopper-(test|live(-[a-z]+)?)\.adyen\.com|([a-z0-9-]+\.)*adyenpayments\.com)\/checkoutshopper\/v1\/sessions\/[A-Za-z0-9_-]+\/payments/i, "hosts": ["^checkoutshopper-(test|live(-[a-z]+)?)\\.adyen\\.com$", "^([a-z0-9-]+\\.)*adyenpayments\\.com$"], "sandboxHosts": ["^checkoutshopper-test\\.adyen\\.com$"], "encoding": "json", "passthroughHeaders": [], "clientSideEncrypted": true, "mode": "cse" }, { "psp": "tranzila", "match": /(direct\.tranzila\.com\/process\/n\/|directng\.tranzila\.com\/process\/?)/i, "hosts": ["^direct\\.tranzila\\.com$", "^directng\\.tranzila\\.com$"], "encoding": "form", "passthroughHeaders": [], "mode": "hosted_form" }, { "psp": "square", "match": /pci-connect\.squareup(?:sandbox)?\.com\/v2\/card-nonce/i, "hosts": ["^pci-connect\\.squareup\\.com$", "^pci-connect\\.squareupsandbox\\.com$"], "sandboxHosts": ["^pci-connect\\.squareupsandbox\\.com$"], "encoding": "json", "passthroughHeaders": [] }, { "psp": "authorize_net", "match": /(api2|apitest)\.authorize\.net\/xml\/v1\/request\.api/i, "hosts": ["^api2\\.authorize\\.net$", "^apitest\\.authorize\\.net$"], "sandboxHosts": ["^apitest\\.authorize\\.net$"], "encoding": "json", "passthroughHeaders": [] }, { "psp": "worldpay", "match": /(try\.)?access\.worldpay\.com\/(sessions\/card|verifiedTokens\/sessions)/i, "hosts": ["^access\\.worldpay\\.com$", "^try\\.access\\.worldpay\\.com$"], "sandboxHosts": ["^try\\.access\\.worldpay\\.com$"], "encoding": "json", "passthroughHeaders": [/^accept$/i] }, { "psp": "nuvei", "match": /(secure|ppp-test)\.safecharge\.com\/ppp\/api\/v1\/(?:cardTokenization|clientPayment|websdk\/initPaymentWithCardTokenization)\.do/i, "hosts": ["^secure\\.safecharge\\.com$", "^ppp-test\\.safecharge\\.com$"], "sandboxHosts": ["^ppp-test\\.safecharge\\.com$"], "encoding": "json", "passthroughHeaders": [], "jsonCardFields": [["paymentOption", "card", "cardNumber"], ["cardData", "cardNumber"]], "passWithoutCard": "(?:secure|ppp-test)\\.safecharge\\.com/ppp/api/v1/(?:clientPayment|websdk/initPaymentWithCardTokenization)\\.do" }, { "psp": "airwallex", "match": /(checkout(\.sandbox)?\.airwallex\.com|pci-api(\.sandbox)?\.airwallex\.com)\/api\/v1\/pa\/(payment_intents\/[^/?#]+\/confirm|payment_consents\/[^/?#]+\/verify|payment_methods\/create)(\?|$)/i, "hosts": ["^checkout\\.airwallex\\.com$", "^checkout\\.sandbox\\.airwallex\\.com$", "^pci-api\\.airwallex\\.com$", "^pci-api\\.sandbox\\.airwallex\\.com$"], "sandboxHosts": ["^checkout\\.sandbox\\.airwallex\\.com$", "^pci-api\\.sandbox\\.airwallex\\.com$"], "encoding": "json", "passthroughHeaders": [/^client-secret$/i, /^authorization$/i, /^x-auth-token$/i, /^x-on-behalf-of$/i, /^x-api-version$/i] }, { "psp": "rapyd", "match": /(api|sandboxapi)\.rapyd\.net\/v1\/(?:hosted\/collect\/card\/[^/?#]+\/payment_method|checkout\/checkout_[A-Za-z0-9]+)(?:[?#]|$)/i, "hosts": ["^api\\.rapyd\\.net$", "^sandboxapi\\.rapyd\\.net$"], "sandboxHosts": ["^sandboxapi\\.rapyd\\.net$"], "encoding": "json", "passthroughHeaders": [] }, { "psp": "dlocal", "match": /ppmcc(-sandbox)?\.dlocal\.com\/cvault\/credit-card\/temporal/i, "hosts": ["^ppmcc\\.dlocal\\.com$", "^ppmcc-sandbox\\.dlocal\\.com$"], "sandboxHosts": ["^ppmcc-sandbox\\.dlocal\\.com$"], "encoding": "json", "passthroughHeaders": [/^x-fields-api-key$/i, /^x-uow$/i, /^x-dlocal-infrav2$/i] }, { "psp": "ebanx", "match": /(?:(?:customer-frontier|customer|api-local-latam|api|sandbox-local-latam|sandbox)\.ebanx\.com|(?:api-diamond|api|sandbox)\.ebanxpay\.com)\/ws\/token/i, "hosts": ["^customer\\.ebanx\\.com$", "^customer-frontier\\.ebanx\\.com$", "^api\\.ebanx\\.com$", "^sandbox\\.ebanx\\.com$", "^api-local-latam\\.ebanx\\.com$", "^sandbox-local-latam\\.ebanx\\.com$", "^api-diamond\\.ebanxpay\\.com$", "^api\\.ebanxpay\\.com$", "^sandbox\\.ebanxpay\\.com$"], "sandboxHosts": ["^sandbox\\.ebanx\\.com$", "^sandbox-local-latam\\.ebanx\\.com$", "^sandbox\\.ebanxpay\\.com$"], "encoding": "json", "passthroughHeaders": [] }, { "psp": "mercado_pago", "match": /api\.mercadopago\.com\/v1\/card_tokens(?![\w/-])/i, "hosts": ["^api\\.mercadopago\\.com$"], "encoding": "json", "passthroughHeaders": [/^x-product-id$/i] }, { "psp": "payu", "match": /(secure\.payu\.com|(merch-prod|secure)\.snd\.payu\.com)\/api\/front\/tokens/i, "hosts": ["^secure\\.payu\\.com$", "^(merch-prod|secure)\\.snd\\.payu\\.com$"], "sandboxHosts": ["^(merch-prod|secure)\\.snd\\.payu\\.com$"], "encoding": "json", "passthroughHeaders": [/^authorization$/i] }, { "psp": "razorpay", "match": /api\.razorpay\.com\/v1\/(?:standard_checkout\/)?payments\/create\/(?:ajax|checkout|fees)/i, "hosts": ["^api\\.razorpay\\.com$"], "encoding": "form", "passthroughHeaders": [/^x-razorpay-sessionid$/i, /^x-customer-access-token$/i] }, { "psp": "mollie", "match": /api\.cc\.mollie\.com\/v1\/card-tokens/i, "hosts": ["^api\\.cc\\.mollie\\.com$"], "encoding": "json", "passthroughHeaders": [] }, { "psp": "paysafe", "match": /(?:api(\.test)?\.paysafe\.com\/(?:paymenthub\/v1\/singleusepaymenthandles|js\/api\/v1\/tokenize)|hosted(\.test)?\.paysafe\.com\/checkout\/api\/v1\/tokenize)(?![\w/-])/i, "hosts": ["^api\\.paysafe\\.com$", "^api\\.test\\.paysafe\\.com$", "^hosted\\.paysafe\\.com$", "^hosted\\.test\\.paysafe\\.com$"], "sandboxHosts": ["^api\\.test\\.paysafe\\.com$", "^hosted\\.test\\.paysafe\\.com$"], "encoding": "json", "passthroughHeaders": [/^authorization$/i, /^x-paysafe-credentials$/i, /^correlationid$/i] }, { "psp": "recurly", "match": /api(\.eu)?\.recurly\.com\/js\/v1\/token(?![\w-])/i, "hosts": ["^api\\.recurly\\.com$", "^api\\.eu\\.recurly\\.com$"], "encoding": "form", "passthroughHeaders": [/^recurly-credential-checkout-hostname$/i] }, { "psp": "bambora", "match": /(api\.na\.bambora\.com|api\.bam\.shift4api\.net)\/scripts\/tokenization\/tokens/i, "hosts": ["^api\\.na\\.bambora\\.com$", "^api\\.bam\\.shift4api\\.net$"], "encoding": "json", "passthroughHeaders": [] }, { "psp": "global_payments", "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)/i, "hosts": ["^api\\.heartlandportico\\.com$", "^cert\\.api2\\.heartlandportico\\.com$", "^apis\\.globalpay\\.com$", "^apis\\.sandbox\\.globalpay\\.com$", "^apis\\.eu\\.globalpay\\.com$", "^apis\\.sandbox\\.eu\\.globalpay\\.com$"], "sandboxHosts": ["^cert\\.api2\\.heartlandportico\\.com$", "^apis\\.sandbox\\.globalpay\\.com$", "^apis\\.sandbox\\.eu\\.globalpay\\.com$"], "encoding": "json", "passthroughHeaders": [/^authorization$/i, /^x-gp-version$/i] }];
|
|
1
|
+
export const BUILTIN_REGISTRY = [{ "psp": "spreedly", "match": /core\.spreedly\.com\/v1\/payment_methods\/restricted\.json/i, "hosts": ["^core\\.spreedly\\.com$"], "encoding": "json", "passthroughHeaders": [/^spreedly-environment-key$/i] }, { "psp": "shopify", "match": /(checkout\.pci\.shopifyinc\.com|deposit\.[a-z0-9-]+\.shopifycs\.com)\/sessions/i, "hosts": ["^checkout\\.pci\\.shopifyinc\\.com$", "^deposit\\.[a-z0-9-]+\\.shopifycs\\.com$"], "encoding": "json", "passthroughHeaders": [/^shopify-identification-signature$/i] }, { "psp": "stripe", "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)/i, "hosts": ["^api\\.stripe\\.com$"], "encoding": "form", "passthroughHeaders": [/^authorization$/i, /^stripe-version$/i, /^x-stripe-client-user-agent$/i], "cardFields": ["card[number]", "payment_method_data[card][number]"], "passWithoutCard": "api\\.stripe\\.com/v1/(sources|confirmation_tokens|payment_pages/cs_(?:test|live)_\\w+/confirm)" }, { "psp": "braintree", "match": /payments(\.sandbox)?\.braintree-api\.com\/graphql/i, "hosts": ["^payments\\.braintree-api\\.com$", "^payments\\.sandbox\\.braintree-api\\.com$"], "sandboxHosts": ["^payments\\.sandbox\\.braintree-api\\.com$"], "encoding": "json", "passthroughHeaders": [/^authorization$/i, /^braintree-version$/i] }, { "psp": "checkout_com", "match": /(api|card-acquisition-gateway)(\.sandbox)?\.checkout\.com\/tokens/i, "hosts": ["^api\\.checkout\\.com$", "^api\\.sandbox\\.checkout\\.com$", "^card-acquisition-gateway\\.checkout\\.com$", "^card-acquisition-gateway\\.sandbox\\.checkout\\.com$"], "sandboxHosts": ["^api\\.sandbox\\.checkout\\.com$", "^card-acquisition-gateway\\.sandbox\\.checkout\\.com$"], "encoding": "json", "passthroughHeaders": [/^authorization$/i] }, { "psp": "adyen", "match": /(checkoutshopper-(test|live(-[a-z]+)?)\.adyen\.com|([a-z0-9-]+\.)*adyenpayments\.com)\/checkoutshopper\/v1\/sessions\/[A-Za-z0-9_-]+\/payments/i, "hosts": ["^checkoutshopper-(test|live(-[a-z]+)?)\\.adyen\\.com$", "^([a-z0-9-]+\\.)*adyenpayments\\.com$"], "sandboxHosts": ["^checkoutshopper-test\\.adyen\\.com$"], "encoding": "json", "passthroughHeaders": [], "clientSideEncrypted": true, "mode": "cse" }, { "psp": "tranzila", "match": /(direct\.tranzila\.com\/process\/n\/|directng\.tranzila\.com\/process\/?)/i, "hosts": ["^direct\\.tranzila\\.com$", "^directng\\.tranzila\\.com$"], "encoding": "form", "passthroughHeaders": [], "mode": "hosted_form" }, { "psp": "square", "match": /pci-connect\.squareup(?:sandbox)?\.com\/v2\/card-nonce/i, "hosts": ["^pci-connect\\.squareup\\.com$", "^pci-connect\\.squareupsandbox\\.com$"], "sandboxHosts": ["^pci-connect\\.squareupsandbox\\.com$"], "encoding": "json", "passthroughHeaders": [] }, { "psp": "authorize_net", "match": /(api2|apitest)\.authorize\.net\/xml\/v1\/request\.api/i, "hosts": ["^api2\\.authorize\\.net$", "^apitest\\.authorize\\.net$"], "sandboxHosts": ["^apitest\\.authorize\\.net$"], "encoding": "json", "passthroughHeaders": [] }, { "psp": "worldpay", "match": /(try\.)?access\.worldpay\.com\/(sessions\/card|verifiedTokens\/sessions)/i, "hosts": ["^access\\.worldpay\\.com$", "^try\\.access\\.worldpay\\.com$"], "sandboxHosts": ["^try\\.access\\.worldpay\\.com$"], "encoding": "json", "passthroughHeaders": [/^accept$/i] }, { "psp": "nuvei", "match": /(secure|ppp-test)\.safecharge\.com\/ppp\/api\/v1\/(?:cardTokenization|clientPayment|websdk\/initPaymentWithCardTokenization)\.do/i, "hosts": ["^secure\\.safecharge\\.com$", "^ppp-test\\.safecharge\\.com$"], "sandboxHosts": ["^ppp-test\\.safecharge\\.com$"], "encoding": "json", "passthroughHeaders": [], "jsonCardFields": [["paymentOption", "card", "cardNumber"], ["cardData", "cardNumber"]], "passWithoutCard": "(?:secure|ppp-test)\\.safecharge\\.com/ppp/api/v1/(?:clientPayment|websdk/initPaymentWithCardTokenization)\\.do" }, { "psp": "airwallex", "match": /(checkout(\.sandbox)?\.airwallex\.com|pci-api(\.sandbox)?\.airwallex\.com)\/api\/v1\/pa\/(payment_intents\/[^/?#]+\/confirm|payment_consents\/[^/?#]+\/verify|payment_methods\/create)(\?|$)/i, "hosts": ["^checkout\\.airwallex\\.com$", "^checkout\\.sandbox\\.airwallex\\.com$", "^pci-api\\.airwallex\\.com$", "^pci-api\\.sandbox\\.airwallex\\.com$"], "sandboxHosts": ["^checkout\\.sandbox\\.airwallex\\.com$", "^pci-api\\.sandbox\\.airwallex\\.com$"], "encoding": "json", "passthroughHeaders": [/^client-secret$/i, /^authorization$/i, /^x-auth-token$/i, /^x-on-behalf-of$/i, /^x-api-version$/i] }, { "psp": "rapyd", "match": /(api|sandboxapi)\.rapyd\.net\/v1\/(?:hosted\/collect\/card\/[^/?#]+\/payment_method|checkout\/checkout_[A-Za-z0-9]+)(?:[?#]|$)/i, "hosts": ["^api\\.rapyd\\.net$", "^sandboxapi\\.rapyd\\.net$"], "sandboxHosts": ["^sandboxapi\\.rapyd\\.net$"], "encoding": "json", "passthroughHeaders": [] }, { "psp": "dlocal", "match": /ppmcc(-sandbox)?\.dlocal\.com\/cvault\/credit-card\/temporal/i, "hosts": ["^ppmcc\\.dlocal\\.com$", "^ppmcc-sandbox\\.dlocal\\.com$"], "sandboxHosts": ["^ppmcc-sandbox\\.dlocal\\.com$"], "encoding": "json", "passthroughHeaders": [/^x-fields-api-key$/i, /^x-uow$/i, /^x-dlocal-infrav2$/i] }, { "psp": "ebanx", "match": /(?:(?:customer-frontier|customer|api-local-latam|api|sandbox-local-latam|sandbox)\.ebanx\.com|(?:api-diamond|api|sandbox)\.ebanxpay\.com)\/ws\/token/i, "hosts": ["^customer\\.ebanx\\.com$", "^customer-frontier\\.ebanx\\.com$", "^api\\.ebanx\\.com$", "^sandbox\\.ebanx\\.com$", "^api-local-latam\\.ebanx\\.com$", "^sandbox-local-latam\\.ebanx\\.com$", "^api-diamond\\.ebanxpay\\.com$", "^api\\.ebanxpay\\.com$", "^sandbox\\.ebanxpay\\.com$"], "sandboxHosts": ["^sandbox\\.ebanx\\.com$", "^sandbox-local-latam\\.ebanx\\.com$", "^sandbox\\.ebanxpay\\.com$"], "encoding": "json", "passthroughHeaders": [] }, { "psp": "mercado_pago", "match": /api\.mercadopago\.com\/v1\/card_tokens(?![\w/-])/i, "hosts": ["^api\\.mercadopago\\.com$"], "encoding": "json", "passthroughHeaders": [/^x-product-id$/i] }, { "psp": "payu", "match": /(secure\.payu\.com|(merch-prod|secure)\.snd\.payu\.com)\/api\/front\/tokens/i, "hosts": ["^secure\\.payu\\.com$", "^(merch-prod|secure)\\.snd\\.payu\\.com$"], "sandboxHosts": ["^(merch-prod|secure)\\.snd\\.payu\\.com$"], "encoding": "json", "passthroughHeaders": [/^authorization$/i] }, { "psp": "razorpay", "match": /api\.razorpay\.com\/v1\/(?:standard_checkout\/)?payments\/create\/(?:ajax|checkout|fees)/i, "hosts": ["^api\\.razorpay\\.com$"], "encoding": "form", "passthroughHeaders": [/^x-razorpay-sessionid$/i, /^x-customer-access-token$/i] }, { "psp": "mollie", "match": /api\.cc\.mollie\.com\/v1\/card-tokens/i, "hosts": ["^api\\.cc\\.mollie\\.com$"], "encoding": "json", "passthroughHeaders": [] }, { "psp": "paysafe", "match": /(?:api(\.test)?\.paysafe\.com\/(?:paymenthub\/v1\/singleusepaymenthandles|js\/api\/v1\/tokenize|checkout\/api\/v1\/tokenize)|hosted(\.test)?\.paysafe\.com\/checkout\/api\/v1\/tokenize)(?![\w/-])/i, "hosts": ["^api\\.paysafe\\.com$", "^api\\.test\\.paysafe\\.com$", "^hosted\\.paysafe\\.com$", "^hosted\\.test\\.paysafe\\.com$"], "sandboxHosts": ["^api\\.test\\.paysafe\\.com$", "^hosted\\.test\\.paysafe\\.com$"], "encoding": "json", "passthroughHeaders": [/^authorization$/i, /^x-paysafe-credentials$/i, /^correlationid$/i, /^x-internal-correlation-id$/i, /^x-app-version$/i, /^x-transactionsource$/i] }, { "psp": "recurly", "match": /api(\.eu)?\.recurly\.com\/js\/v1\/token(?![\w-])/i, "hosts": ["^api\\.recurly\\.com$", "^api\\.eu\\.recurly\\.com$"], "encoding": "form", "passthroughHeaders": [/^recurly-credential-checkout-hostname$/i] }, { "psp": "bambora", "match": /(api\.na\.bambora\.com|api\.bam\.shift4api\.net)\/scripts\/tokenization\/tokens/i, "hosts": ["^api\\.na\\.bambora\\.com$", "^api\\.bam\\.shift4api\\.net$"], "encoding": "json", "passthroughHeaders": [] }, { "psp": "global_payments", "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)/i, "hosts": ["^api\\.heartlandportico\\.com$", "^cert\\.api2\\.heartlandportico\\.com$", "^apis\\.globalpay\\.com$", "^apis\\.sandbox\\.globalpay\\.com$", "^apis\\.eu\\.globalpay\\.com$", "^apis\\.sandbox\\.eu\\.globalpay\\.com$"], "sandboxHosts": ["^cert\\.api2\\.heartlandportico\\.com$", "^apis\\.sandbox\\.globalpay\\.com$", "^apis\\.sandbox\\.eu\\.globalpay\\.com$"], "encoding": "json", "passthroughHeaders": [/^authorization$/i, /^x-gp-version$/i] }];
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// @ts-nocheck
|
|
2
2
|
// Generated from @agent-cards/payment-core. Do not edit.
|
|
3
|
-
// artifact-sha256:
|
|
3
|
+
// artifact-sha256: 5c2d8f5671c901ef8162e61e21996671a0e6e9ca235467ea7f6267274f5c3ee1
|
|
4
4
|
// src/card-number-key.js
|
|
5
5
|
var CARD_NUMBER_KEY = /^(number|card_number|cardnumber|cardNum|card_num|pan)$/i;
|
|
6
6
|
// src/json-path.js
|
|
@@ -406,6 +406,8 @@ var pin2 = (fields) => Object.freeze({
|
|
|
406
406
|
var PADDLE_VGS_MERCHANT_PINS = /* @__PURE__ */ Object.freeze([
|
|
407
407
|
pin2({ id: "rectangle_pro", merchant: "Rectangle Pro", origins: ["https://rectangleapp.com"], sellerClaim: null })
|
|
408
408
|
]);
|
|
409
|
+
var FACT_KEYS = /* @__PURE__ */ Object.freeze(["card_first_six", "card_last_four", "expiry_month", "expiry_year", "card_brand"]);
|
|
410
|
+
var METADATA_KEYS = /* @__PURE__ */ Object.freeze(["cardholder_name", ...FACT_KEYS]);
|
|
409
411
|
// src/card-fields.js
|
|
410
412
|
function processorGate(recognizer, url, body) {
|
|
411
413
|
if (recognizer && recognizer.psp === PADDLE_VGS_PSP) {
|
package/dist/cdp.js
CHANGED
|
@@ -11,6 +11,7 @@ import { EprotectGate } from './eprotect.js';
|
|
|
11
11
|
import { dispatchPreClaim, eventUrl } from './pre-claim.js';
|
|
12
12
|
import { GMO_TOKEN_V1_PATTERNS, gmoRequestShop, isGmoTokenV1Request, recognizesGmo } from './gmo.js';
|
|
13
13
|
import { isPaddleVgsCardsUrl } from './paddle-vgs.generated.js';
|
|
14
|
+
import { PADDLE_VGS_PAY_PATTERNS, PaddleVgsPayGate, isPaddleCheckoutService, recognizesPaddleVgs } from './paddle-vgs-pay.js';
|
|
14
15
|
import { DatatransRepeats } from './datatrans.js';
|
|
15
16
|
import { SHIJI_STS_COMPANION_WINDOW_MS, ShijiStsCompanions, ShijiStsError, isShijiStsHost, shijiStsTokenContinuation, } from './shiji-sts.js';
|
|
16
17
|
import { isArmedEkashuUrl } from './ekashu.js';
|
|
@@ -1532,7 +1533,10 @@ function replayOf(result, timeoutMs) {
|
|
|
1532
1533
|
|| !response.headers || typeof response.headers !== 'object' || Array.isArray(response.headers)) {
|
|
1533
1534
|
throw new Error('An approved token read resumes with response, the processor\'s answer (voice.awaitResult carries it once the purchase finished).');
|
|
1534
1535
|
}
|
|
1535
|
-
|
|
1536
|
+
// The read's processor_context rides as it came: a Paddle VGS answer is handed to the page only with the
|
|
1537
|
+
// approved card's details its /pay names (paddle-vgs-pay.ts checks them before the hand-over).
|
|
1538
|
+
return { mode: 'token', authorizationId, status: response.status, headers: { ...response.headers }, body: response.body, amountVerified: null,
|
|
1539
|
+
...(result.processor_context !== undefined ? { processorContext: result.processor_context } : {}), ...execution };
|
|
1536
1540
|
}
|
|
1537
1541
|
/** The recogniser that claimed a paused request, by processor name, so a caller never re-matches the URL; null on a duck-typed vault without the read. */
|
|
1538
1542
|
function recognizerNameOf(opts, url, method) {
|
|
@@ -2019,6 +2023,10 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
|
|
|
2019
2023
|
// committing a new document (Page.frameNavigated, at the top of the event handler).
|
|
2020
2024
|
const shijiCompanions = new ShijiStsCompanions();
|
|
2021
2025
|
lifecycle.abort.signal.addEventListener('abort', () => shijiCompanions.release(), { once: true });
|
|
2026
|
+
// Paddle's /pay after the stored card's id this attachment handed a frame (paddle-vgs-pay.ts): its
|
|
2027
|
+
// checkout services are held only when the synced registry serves Paddle VGS, and a cancel drops the binding.
|
|
2028
|
+
const paddleVgsPay = new PaddleVgsPayGate(() => recognizesPaddleVgs(opts.vault));
|
|
2029
|
+
lifecycle.abort.signal.addEventListener('abort', () => paddleVgsPay.release(), { once: true });
|
|
2022
2030
|
const derived = typeof opts.vault?.cardUrlPatterns === 'function' ? opts.vault.cardUrlPatterns() : [];
|
|
2023
2031
|
// The endpoint and siblings of every merchant profile this build reviewed
|
|
2024
2032
|
// (merchant-hosted.ts), which a VaultClient arms from the start: a profile's card
|
|
@@ -2046,7 +2054,8 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
|
|
|
2046
2054
|
const urlPatterns = [...new Set([...(derived.length > 0 ? derived : FALLBACK_CARD_PATTERNS), ...guards.patterns,
|
|
2047
2055
|
...(stripeCheckout.isEnabled() ? ['https://api.stripe.com/*'] : []), MERCADO_CHECKOUT_PATTERN, ...merchantPatterns,
|
|
2048
2056
|
...(recognizesGmo(opts.vault) ? GMO_TOKEN_V1_PATTERNS : []), ...juspayCheckout.urlPatterns(), ...cardEndpointPatterns(),
|
|
2049
|
-
...(payrails ? PAYRAILS_GUARD_PATTERNS : []), ...PAYPAL_HOSTED_FIELDS_PATTERNS, ...CYBERSOURCE_SECURE_ACCEPTANCE_PATTERNS
|
|
2057
|
+
...(payrails ? PAYRAILS_GUARD_PATTERNS : []), ...PAYPAL_HOSTED_FIELDS_PATTERNS, ...CYBERSOURCE_SECURE_ACCEPTANCE_PATTERNS,
|
|
2058
|
+
...(recognizesPaddleVgs(opts.vault) ? PADDLE_VGS_PAY_PATTERNS : [])])];
|
|
2050
2059
|
const arm = async (sessionId, resume = false, worker = false) => {
|
|
2051
2060
|
const key = sessionId ?? '__root__';
|
|
2052
2061
|
if (armed.has(key))
|
|
@@ -2480,6 +2489,7 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
|
|
|
2480
2489
|
payrails,
|
|
2481
2490
|
shijiSts: { companions: shijiCompanions, page: () => cdpShijiPage(sessionId, frameId) },
|
|
2482
2491
|
worldlineMyCheckout: worldlineProducts,
|
|
2492
|
+
paddleVgsPay,
|
|
2483
2493
|
});
|
|
2484
2494
|
const preClaim = preClaimResult instanceof Promise ? await preClaimResult : preClaimResult;
|
|
2485
2495
|
if (preClaim.kind !== 'pause')
|
|
@@ -2901,6 +2911,15 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
|
|
|
2901
2911
|
// device_encrypt answer (Payrails' 201, which the device obtained) is
|
|
2902
2912
|
// handed over the same way.
|
|
2903
2913
|
const cors = corsDecision(deliverRequest.url, deliverRequest.headers);
|
|
2914
|
+
// A Paddle card request's answer is the stored card's id: the frame's /pay that follows continues only
|
|
2915
|
+
// with the approved card's details, bound to that frame before the id reaches it (paddle-vgs-pay.ts).
|
|
2916
|
+
// An answer that came with no details is not handed over: its /pay could only name the placeholder.
|
|
2917
|
+
try {
|
|
2918
|
+
paddleVgsPay.arm(activeRequest?.frameId || undefined, deliverRequest.url, replay);
|
|
2919
|
+
}
|
|
2920
|
+
catch {
|
|
2921
|
+
throw new PaymentOutcomeUnknownError(replay.authorizationId, 'paddle_vgs_card_facts_unavailable');
|
|
2922
|
+
}
|
|
2904
2923
|
await cdp.send('Fetch.fulfillRequest', {
|
|
2905
2924
|
requestId: deliverTo,
|
|
2906
2925
|
responseCode: replay.status,
|
|
@@ -3131,6 +3150,9 @@ export async function attachToPlaywright(page, opts) {
|
|
|
3131
3150
|
// a new document (the handlers below).
|
|
3132
3151
|
const shijiCompanions = new ShijiStsCompanions();
|
|
3133
3152
|
lifecycle.abort.signal.addEventListener('abort', () => shijiCompanions.release(), { once: true });
|
|
3153
|
+
// Paddle's /pay after the stored card's id this attachment handed a frame, as attachToCdp binds it.
|
|
3154
|
+
const paddleVgsPay = new PaddleVgsPayGate(() => recognizesPaddleVgs(opts.vault));
|
|
3155
|
+
lifecycle.abort.signal.addEventListener('abort', () => paddleVgsPay.release(), { once: true });
|
|
3134
3156
|
// An answer to a request the facts watch records began to arrive: the watch orders what the page sent after a
|
|
3135
3157
|
// record by this moment, since Playwright reports the answer's end after the page's next request. Listened to only
|
|
3136
3158
|
// once the page sent such a request, so a page that never meets a merchant with facts carries no extra listener.
|
|
@@ -3388,7 +3410,9 @@ export async function attachToPlaywright(page, opts) {
|
|
|
3388
3410
|
// PayPal's legacy Hosted Fields confirm, for every org, as attachToCdp arms it (paypal-hosted-fields.ts).
|
|
3389
3411
|
|| isPaypalHostedFieldsConfirm(url.toString())
|
|
3390
3412
|
// CyberSource Secure Acceptance's /silent/ paths, for every org, as attachToCdp arms them (cybersource-secure-acceptance.ts).
|
|
3391
|
-
|| isCybersourceSecureAcceptanceUrl(url.toString())
|
|
3413
|
+
|| isCybersourceSecureAcceptanceUrl(url.toString())
|
|
3414
|
+
// Paddle's checkout services, routed only for an org the API served Paddle VGS to (see attachToCdp).
|
|
3415
|
+
|| (isPaddleCheckoutService(url.toString()) && recognizesPaddleVgs(opts.vault)), async (route) => {
|
|
3392
3416
|
const request = route.request();
|
|
3393
3417
|
// When this request paused, and the page it came from: the merchant's total is read as of now.
|
|
3394
3418
|
const pausedAt = Date.now();
|
|
@@ -3485,6 +3509,7 @@ export async function attachToPlaywright(page, opts) {
|
|
|
3485
3509
|
payrails,
|
|
3486
3510
|
shijiSts: { companions: shijiCompanions, page: async () => (await playwrightShijiDocumentPage(request)) ?? `pw:none:${++playwrightShijiSeq}` },
|
|
3487
3511
|
worldlineMyCheckout: worldlineProducts,
|
|
3512
|
+
paddleVgsPay,
|
|
3488
3513
|
});
|
|
3489
3514
|
const preClaim = preClaimResult instanceof Promise ? await preClaimResult : preClaimResult;
|
|
3490
3515
|
if (preClaim.kind !== 'pause')
|
|
@@ -3847,6 +3872,13 @@ export async function attachToPlaywright(page, opts) {
|
|
|
3847
3872
|
// none; written here anyway (replacing a stale value) so a
|
|
3848
3873
|
// cross-origin answer is the same whichever adapter ran.
|
|
3849
3874
|
const cors = corsDecision(deliverRequest.url(), deliverRequest.headers());
|
|
3875
|
+
// See the same step in attachToCdp: a Paddle stored card's id reaches its frame only with its /pay bound.
|
|
3876
|
+
try {
|
|
3877
|
+
paddleVgsPay.arm(frameOf(deliverRequest), deliverRequest.url(), replay);
|
|
3878
|
+
}
|
|
3879
|
+
catch {
|
|
3880
|
+
throw new PaymentOutcomeUnknownError(replay.authorizationId, 'paddle_vgs_card_facts_unavailable');
|
|
3881
|
+
}
|
|
3850
3882
|
await deliverTo.fulfill({ status: replay.status, headers: withCorsHeaders(replay.headers, cors.headers), body: replay.body });
|
|
3851
3883
|
datatransRepeats.remember(frameOf(deliverRequest), deliverRequest.url(), deliverBody, replay);
|
|
3852
3884
|
assertRequestLive();
|
package/dist/client.d.ts
CHANGED
|
@@ -251,7 +251,10 @@ export interface ExecutionMetadata {
|
|
|
251
251
|
* this is the processor's answer to replay into the paused request.
|
|
252
252
|
*/
|
|
253
253
|
export interface TokenReplay extends ExecutionMetadata {
|
|
254
|
-
/**
|
|
254
|
+
/**
|
|
255
|
+
* Authenticated selected-card metadata; the processor response body stays unchanged. Paddle VGS: the
|
|
256
|
+
* approved card's first six and last four digits, expiry and brand, which the frame's /pay names.
|
|
257
|
+
*/
|
|
255
258
|
processorContext?: unknown;
|
|
256
259
|
/** Present only after validating the native TEST Checkout completion. */
|
|
257
260
|
checkoutSessionId?: string;
|
package/dist/client.js
CHANGED
|
@@ -14,7 +14,7 @@ import { creditguardPageProcessUrl, creditguardPpDomainHost, creditguardPreparat
|
|
|
14
14
|
import { CONVERGE_CAPABILITY } from './elavon-converge.js';
|
|
15
15
|
import { SHIJI_STS_CAPABILITY, SHIJI_STS_PSP, isShijiStsServedCompanion, isShijiStsServedSubstitution } from './shiji-sts.js';
|
|
16
16
|
import { AMADEUS_CHECKOUT_CAPABILITY, AMADEUS_CHECKOUT_PSP } from './amadeus-checkout.generated.js';
|
|
17
|
-
import { PADDLE_VGS_CAPABILITY, PADDLE_VGS_PSP } from './paddle-vgs.generated.js';
|
|
17
|
+
import { PADDLE_VGS_CAPABILITY, PADDLE_VGS_PSP, paddleVgsCardFactsOf } from './paddle-vgs.generated.js';
|
|
18
18
|
import { classifyStripeCheckoutRequest, encodeStripeCheckoutContext, hasStripeCheckoutMarker, parseStripeCheckoutContext, parseStripeCheckoutResponse, STRIPE_CHECKOUT_CONTEXT_HEADER, } from './stripe-checkout.generated.js';
|
|
19
19
|
import { VoiceModule } from './voice.js';
|
|
20
20
|
import { BuyModule } from './buy.js';
|
|
@@ -2620,6 +2620,14 @@ export class VaultClient {
|
|
|
2620
2620
|
throw new PaymentOutcomeUnknownError(authorizationId, 'mercado_checkout_metadata_invalid');
|
|
2621
2621
|
}
|
|
2622
2622
|
}
|
|
2623
|
+
else if (rec?.psp === PADDLE_VGS_PSP) {
|
|
2624
|
+
// Paddle VGS: the approved card's details the phone read from the card it stored at VGS, which the
|
|
2625
|
+
// frame's /pay names in place of the placeholder's (paddle-vgs-pay.ts). Exactly those five, or the
|
|
2626
|
+
// stored card's id never reaches the frame.
|
|
2627
|
+
processorContext = paddleVgsCardFactsOf(s.processor_context) ?? undefined;
|
|
2628
|
+
if (!processorContext)
|
|
2629
|
+
throw new PaymentOutcomeUnknownError(authorizationId, 'paddle_vgs_card_facts_unavailable');
|
|
2630
|
+
}
|
|
2623
2631
|
else if (s.processor_context !== undefined)
|
|
2624
2632
|
throw new PaymentOutcomeUnknownError(authorizationId, 'unexpected_processor_context');
|
|
2625
2633
|
return {
|
package/dist/lifecycle.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ import type { PayrailsUnsupportedReason } from './payrails.js';
|
|
|
4
4
|
import type { Substitutions } from './substitute.js';
|
|
5
5
|
import type { GmoUnsupportedReason } from './gmo.js';
|
|
6
6
|
import type { ShijiStsUnsupportedReason } from './shiji-sts.js';
|
|
7
|
+
import type { PaddleVgsPayRefusal } from './paddle-vgs-pay.js';
|
|
7
8
|
/** The application's resolver confirmed the payment for this checkout authorization. */
|
|
8
9
|
export interface MerchantPaymentConfirmation {
|
|
9
10
|
kind: 'merchant_payment';
|
|
@@ -91,6 +92,11 @@ export interface ResumeResult {
|
|
|
91
92
|
headers: Record<string, string>;
|
|
92
93
|
body: string;
|
|
93
94
|
};
|
|
95
|
+
/**
|
|
96
|
+
* The read's `processor_context`, as the API serves it. A Paddle VGS read carries the approved card's
|
|
97
|
+
* details here, which the frame's /pay names; without them the stored card's id is never handed to the page.
|
|
98
|
+
*/
|
|
99
|
+
processor_context?: unknown;
|
|
94
100
|
substitutions?: Substitutions;
|
|
95
101
|
submitted_at?: string;
|
|
96
102
|
execution_mode?: string;
|
|
@@ -239,9 +245,12 @@ export declare class CheckoutLifecycle implements CheckoutController {
|
|
|
239
245
|
* over (unsupported_paypal_hosted_fields, paypal-hosted-fields.ts), or a card
|
|
240
246
|
* a merchant's page posts to CyberSource Secure Acceptance's Silent Order
|
|
241
247
|
* POST, which no Vault mode takes over either
|
|
242
|
-
* (unsupported_cybersource_secure_acceptance, cybersource-secure-acceptance.ts)
|
|
248
|
+
* (unsupported_cybersource_secure_acceptance, cybersource-secure-acceptance.ts), or a
|
|
249
|
+
* Paddle /pay naming a stored card that its binding does not continue (the
|
|
250
|
+
* paddle-vgs-pay.ts reasons): once an approval exists the state keeps it, and only the
|
|
251
|
+
* hold changes.
|
|
243
252
|
*/
|
|
244
|
-
unsupported(reason?: 'unrecognized_payment_endpoint' | 'merchant_profile_observe' | 'merchant_request_unsupported' | GmoUnsupportedReason | 'microform_unsupported' | 'juspay_request_unsupported' | 'merchant_card_endpoint_not_enabled' | 'merchant_card_endpoint_resend_refused' | 'creditguard_three_d_secure' | 'unsupported_primer_sdk' | 'unsupported_primer_request' | PayrailsUnsupportedReason | ShijiStsUnsupportedReason | 'processor_card_page' | 'unsupported_paypal_hosted_fields' | 'unsupported_cybersource_secure_acceptance'): void;
|
|
253
|
+
unsupported(reason?: 'unrecognized_payment_endpoint' | 'merchant_profile_observe' | 'merchant_request_unsupported' | GmoUnsupportedReason | 'microform_unsupported' | 'juspay_request_unsupported' | 'merchant_card_endpoint_not_enabled' | 'merchant_card_endpoint_resend_refused' | 'creditguard_three_d_secure' | 'unsupported_primer_sdk' | 'unsupported_primer_request' | PayrailsUnsupportedReason | ShijiStsUnsupportedReason | 'processor_card_page' | 'unsupported_paypal_hosted_fields' | 'unsupported_cybersource_secure_acceptance' | PaddleVgsPayRefusal): void;
|
|
245
254
|
/**
|
|
246
255
|
* A card request that only a prepared checkout pays (an Adyen merchant
|
|
247
256
|
* profile's, or a Fiserv card capture) arrived with no preparation on this
|
package/dist/lifecycle.js
CHANGED
|
@@ -238,7 +238,10 @@ export class CheckoutLifecycle {
|
|
|
238
238
|
* over (unsupported_paypal_hosted_fields, paypal-hosted-fields.ts), or a card
|
|
239
239
|
* a merchant's page posts to CyberSource Secure Acceptance's Silent Order
|
|
240
240
|
* POST, which no Vault mode takes over either
|
|
241
|
-
* (unsupported_cybersource_secure_acceptance, cybersource-secure-acceptance.ts)
|
|
241
|
+
* (unsupported_cybersource_secure_acceptance, cybersource-secure-acceptance.ts), or a
|
|
242
|
+
* Paddle /pay naming a stored card that its binding does not continue (the
|
|
243
|
+
* paddle-vgs-pay.ts reasons): once an approval exists the state keeps it, and only the
|
|
244
|
+
* hold changes.
|
|
242
245
|
*/
|
|
243
246
|
unsupported(reason = 'unrecognized_payment_endpoint') {
|
|
244
247
|
this.held = true;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { ReplayResponse } from './client.js';
|
|
2
|
+
/**
|
|
3
|
+
* Why a /pay naming a stored card was aborted, reported as the `reason` of the `unsupported_checkout` event:
|
|
4
|
+
* paddle_vgs_pay_unbound this attachment handed no stored card's id to the frame that sent it
|
|
5
|
+
* paddle_vgs_pay_expired it came more than a minute after the id reached the frame
|
|
6
|
+
* paddle_vgs_pay_used the one /pay for that id already went
|
|
7
|
+
* paddle_vgs_pay_card_mismatch it names another stored card, or the other environment's checkout service
|
|
8
|
+
* paddle_vgs_pay_unsupported anything else payment-core's rule refuses: another shape, a card saved at
|
|
9
|
+
* Paddle for later, a network the frame chose from the typed card
|
|
10
|
+
*/
|
|
11
|
+
export type PaddleVgsPayRefusal = 'paddle_vgs_pay_unbound' | 'paddle_vgs_pay_expired' | 'paddle_vgs_pay_used' | 'paddle_vgs_pay_card_mismatch' | 'paddle_vgs_pay_unsupported';
|
|
12
|
+
/** The patterns attachToCdp holds Paddle's two checkout services under, only for an attachment the API served Paddle VGS to. */
|
|
13
|
+
export declare const PADDLE_VGS_PAY_PATTERNS: readonly string[];
|
|
14
|
+
/** Whether `url` is on one of Paddle's two checkout services: what attachToPlaywright routes, once served. */
|
|
15
|
+
export declare function isPaddleCheckoutService(url: string): boolean;
|
|
16
|
+
/** Whether the synced registry serves Paddle VGS's card request: an org Agentcard turned Paddle on for. */
|
|
17
|
+
export declare function recognizesPaddleVgs(vault: {
|
|
18
|
+
isCardRequest?: (url: string, method?: string) => boolean;
|
|
19
|
+
} | null | undefined): boolean;
|
|
20
|
+
/** The URL an event names for a /pay: its origin and path, with the checkout's id as {checkout}. */
|
|
21
|
+
export declare function paddleVgsPayEventUrl(url: string): string;
|
|
22
|
+
/**
|
|
23
|
+
* One attachment's /pay binding: at most one, to the stored card's id it last handed a Paddle frame. `arm` runs
|
|
24
|
+
* right before the id is handed over, `release` when the attachment is cancelled, and `judge` for every request
|
|
25
|
+
* to Paddle's checkout services. `served` says whether the synced registry serves Paddle VGS; it is asked only
|
|
26
|
+
* about a POST to /pay, so no other request reads the registry here.
|
|
27
|
+
*/
|
|
28
|
+
export declare class PaddleVgsPayGate {
|
|
29
|
+
private readonly served;
|
|
30
|
+
private readonly now;
|
|
31
|
+
private bound;
|
|
32
|
+
constructor(served: () => boolean, now?: () => number);
|
|
33
|
+
/** Whether the gate judges this request: a POST to /pay, on an attachment the API served Paddle VGS to. */
|
|
34
|
+
holds(url: string, method: string): boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Bind the next /pay of `frame`, the frame about to receive `replay` for its card request to `cardsUrl`, to the
|
|
37
|
+
* stored card's id in the replay and the approved card's facts the API served beside it. Does nothing for a
|
|
38
|
+
* request to anything but Paddle's card vault. Throws when the frame is unknown, or the replay carries no id or
|
|
39
|
+
* no facts payment-core reads: the id must then not reach the frame, since its /pay could only carry the
|
|
40
|
+
* placeholder's facts.
|
|
41
|
+
*/
|
|
42
|
+
arm(frame: unknown, cardsUrl: string, replay: ReplayResponse): void;
|
|
43
|
+
/** Drop the binding: no /pay continues with these facts after this. */
|
|
44
|
+
release(): void;
|
|
45
|
+
/**
|
|
46
|
+
* What becomes of a request to Paddle's checkout services: null when it is not a /pay naming a stored card (it
|
|
47
|
+
* continues untouched), the body to continue it with (payment-core paddleVgsPayVerdict), or why it is aborted.
|
|
48
|
+
* The binding answers one /pay: the first one from its frame continues or is refused, and either way ends it.
|
|
49
|
+
*/
|
|
50
|
+
judge(frame: unknown, url: string, method: string, body: string | null): {
|
|
51
|
+
body: string;
|
|
52
|
+
} | {
|
|
53
|
+
reason: PaddleVgsPayRefusal;
|
|
54
|
+
} | null;
|
|
55
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
// Paddle's /pay, the request that charges the card the cardholder's phone stored at VGS, as the SDK sees it
|
|
2
|
+
// (payment-core paddle-vgs.js has the request's shape and the rule).
|
|
3
|
+
//
|
|
4
|
+
// Once the SDK hands the stored card's id to Paddle's checkout frame, the frame posts /pay to its checkout
|
|
5
|
+
// service with that id beside card facts it read from the number typed into its own form: on this lane the
|
|
6
|
+
// agent's placeholder, so the placeholder's first six, last four and brand. This build holds that /pay and
|
|
7
|
+
// writes over those five values the approved card's own, which the phone read from the card it sent to VGS and
|
|
8
|
+
// the API served with the id (payment-core paddleVgsPayVerdict). Every other byte goes as the frame wrote it.
|
|
9
|
+
//
|
|
10
|
+
// The binding: the frame that received the stored card's id, the id, the vault's environment and the approved
|
|
11
|
+
// card's facts, for PADDLE_VGS_CARD_ID_WINDOW_MS after the hand-over, for one /pay. A /pay naming a stored card
|
|
12
|
+
// is aborted, so Paddle charges nothing, when this attachment handed no id to that frame
|
|
13
|
+
// (paddle_vgs_pay_unbound), after the window (paddle_vgs_pay_expired), after the one /pay already went
|
|
14
|
+
// (paddle_vgs_pay_used), or when payment-core's rule refuses it: another stored card or environment
|
|
15
|
+
// (paddle_vgs_pay_card_mismatch), or any other shape (paddle_vgs_pay_unsupported). A /pay naming no stored card
|
|
16
|
+
// (PayPal, a wallet) carries none of the cardholder's cards and continues untouched.
|
|
17
|
+
//
|
|
18
|
+
// Nothing is decided without the registry: an attachment the API did not serve Paddle VGS to never holds
|
|
19
|
+
// Paddle's checkout service, so its /pay continues untouched, exactly as before.
|
|
20
|
+
import { PADDLE_VGS_CARD_ID_WINDOW_MS, PADDLE_VGS_CARDS_URLS, paddleVgsCardFactsOf, paddleVgsEnvironment, paddleVgsPayCarriesStoredCard, paddleVgsPayTarget, paddleVgsPayVerdict, } from './paddle-vgs.generated.js';
|
|
21
|
+
const CHECKOUT_SERVICES = ['https://checkout-service.paddle.com', 'https://sandbox-checkout-service.paddle.com'];
|
|
22
|
+
/** The patterns attachToCdp holds Paddle's two checkout services under, only for an attachment the API served Paddle VGS to. */
|
|
23
|
+
export const PADDLE_VGS_PAY_PATTERNS = CHECKOUT_SERVICES.map((origin) => `${origin}/*`);
|
|
24
|
+
/** Whether `url` is on one of Paddle's two checkout services: what attachToPlaywright routes, once served. */
|
|
25
|
+
export function isPaddleCheckoutService(url) {
|
|
26
|
+
try {
|
|
27
|
+
return CHECKOUT_SERVICES.includes(new URL(url).origin);
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/** Whether the synced registry serves Paddle VGS's card request: an org Agentcard turned Paddle on for. */
|
|
34
|
+
export function recognizesPaddleVgs(vault) {
|
|
35
|
+
if (!vault || typeof vault.isCardRequest !== 'function')
|
|
36
|
+
return false;
|
|
37
|
+
try {
|
|
38
|
+
return vault.isCardRequest(PADDLE_VGS_CARDS_URLS.production, 'POST') || vault.isCardRequest(PADDLE_VGS_CARDS_URLS.sandbox, 'POST');
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/** The URL an event names for a /pay: its origin and path, with the checkout's id as {checkout}. */
|
|
45
|
+
export function paddleVgsPayEventUrl(url) {
|
|
46
|
+
return url.replace(/^(https:\/\/[^/]+\/(?:checkout|transaction-checkout)\/)[^/]+(\/pay)$/, '$1{checkout}$2');
|
|
47
|
+
}
|
|
48
|
+
const known = (frame) => frame !== undefined && frame !== null && frame !== '';
|
|
49
|
+
/**
|
|
50
|
+
* One attachment's /pay binding: at most one, to the stored card's id it last handed a Paddle frame. `arm` runs
|
|
51
|
+
* right before the id is handed over, `release` when the attachment is cancelled, and `judge` for every request
|
|
52
|
+
* to Paddle's checkout services. `served` says whether the synced registry serves Paddle VGS; it is asked only
|
|
53
|
+
* about a POST to /pay, so no other request reads the registry here.
|
|
54
|
+
*/
|
|
55
|
+
export class PaddleVgsPayGate {
|
|
56
|
+
served;
|
|
57
|
+
now;
|
|
58
|
+
bound = null;
|
|
59
|
+
constructor(served, now = Date.now) {
|
|
60
|
+
this.served = served;
|
|
61
|
+
this.now = now;
|
|
62
|
+
}
|
|
63
|
+
/** Whether the gate judges this request: a POST to /pay, on an attachment the API served Paddle VGS to. */
|
|
64
|
+
holds(url, method) {
|
|
65
|
+
return String(method).toUpperCase() === 'POST' && paddleVgsPayTarget(url) !== null && this.served();
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Bind the next /pay of `frame`, the frame about to receive `replay` for its card request to `cardsUrl`, to the
|
|
69
|
+
* stored card's id in the replay and the approved card's facts the API served beside it. Does nothing for a
|
|
70
|
+
* request to anything but Paddle's card vault. Throws when the frame is unknown, or the replay carries no id or
|
|
71
|
+
* no facts payment-core reads: the id must then not reach the frame, since its /pay could only carry the
|
|
72
|
+
* placeholder's facts.
|
|
73
|
+
*/
|
|
74
|
+
arm(frame, cardsUrl, replay) {
|
|
75
|
+
const environment = paddleVgsEnvironment(cardsUrl);
|
|
76
|
+
if (environment === null)
|
|
77
|
+
return;
|
|
78
|
+
this.release();
|
|
79
|
+
const token = replay.mode === undefined || replay.mode === 'token' ? replay : null;
|
|
80
|
+
const facts = token ? paddleVgsCardFactsOf(token.processorContext) : null;
|
|
81
|
+
let cardId = null;
|
|
82
|
+
try {
|
|
83
|
+
cardId = token ? JSON.parse(token.body)?.data?.id : null;
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
cardId = null;
|
|
87
|
+
}
|
|
88
|
+
if (!known(frame) || !facts || typeof cardId !== 'string')
|
|
89
|
+
throw new Error('paddle_vgs_card_facts_unavailable');
|
|
90
|
+
this.bound = { frame, environment, cardId, facts, until: this.now() + PADDLE_VGS_CARD_ID_WINDOW_MS, used: false };
|
|
91
|
+
}
|
|
92
|
+
/** Drop the binding: no /pay continues with these facts after this. */
|
|
93
|
+
release() {
|
|
94
|
+
this.bound = null;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* What becomes of a request to Paddle's checkout services: null when it is not a /pay naming a stored card (it
|
|
98
|
+
* continues untouched), the body to continue it with (payment-core paddleVgsPayVerdict), or why it is aborted.
|
|
99
|
+
* The binding answers one /pay: the first one from its frame continues or is refused, and either way ends it.
|
|
100
|
+
*/
|
|
101
|
+
judge(frame, url, method, body) {
|
|
102
|
+
if (!this.holds(url, method) || !paddleVgsPayCarriesStoredCard(body))
|
|
103
|
+
return null;
|
|
104
|
+
const bound = this.bound;
|
|
105
|
+
if (!bound || !known(frame) || frame !== bound.frame)
|
|
106
|
+
return { reason: 'paddle_vgs_pay_unbound' };
|
|
107
|
+
if (bound.used)
|
|
108
|
+
return { reason: 'paddle_vgs_pay_used' };
|
|
109
|
+
bound.used = true;
|
|
110
|
+
if (this.now() > bound.until)
|
|
111
|
+
return { reason: 'paddle_vgs_pay_expired' };
|
|
112
|
+
const verdict = paddleVgsPayVerdict({ url, method, body }, { environment: bound.environment, cardId: bound.cardId, facts: bound.facts });
|
|
113
|
+
return verdict.verdict === 'continue' ? { body: verdict.body } : { reason: verdict.code };
|
|
114
|
+
}
|
|
115
|
+
}
|
|
@@ -47,4 +47,32 @@ declare function paddleVgsAnswerOutcome(status: any, text: any, card: any): {
|
|
|
47
47
|
reason?: undefined;
|
|
48
48
|
code?: undefined;
|
|
49
49
|
};
|
|
50
|
-
|
|
50
|
+
declare var PADDLE_VGS_CARD_ID_WINDOW_MS: number;
|
|
51
|
+
declare function paddleVgsPayTarget(url: any): Readonly<{
|
|
52
|
+
environment: "production" | "sandbox";
|
|
53
|
+
line: "classic" | "billing";
|
|
54
|
+
}> | null;
|
|
55
|
+
declare function paddleVgsPayCarriesStoredCard(body: any): boolean;
|
|
56
|
+
declare function paddleVgsCardFacts(card: any): Readonly<{
|
|
57
|
+
card_first_six: string;
|
|
58
|
+
card_last_four: string;
|
|
59
|
+
expiry_month: number;
|
|
60
|
+
expiry_year: number;
|
|
61
|
+
card_brand: "visa" | "master" | "american_express";
|
|
62
|
+
}>;
|
|
63
|
+
declare function paddleVgsCardFactsOf(value: any): Readonly<{
|
|
64
|
+
card_first_six: any;
|
|
65
|
+
card_last_four: string;
|
|
66
|
+
expiry_month: any;
|
|
67
|
+
expiry_year: any;
|
|
68
|
+
card_brand: any;
|
|
69
|
+
}> | null;
|
|
70
|
+
declare function paddleVgsCardFactsAgree(facts: any, stored: any): boolean;
|
|
71
|
+
declare function paddleVgsPayVerdict(request: any, approved: any): Readonly<{
|
|
72
|
+
verdict: "refuse";
|
|
73
|
+
code: any;
|
|
74
|
+
}> | Readonly<{
|
|
75
|
+
verdict: "continue";
|
|
76
|
+
body: string;
|
|
77
|
+
}>;
|
|
78
|
+
export { PADDLE_VGS_CAPABILITY, PADDLE_VGS_CARDS_URLS, PADDLE_VGS_CARD_ID_WINDOW_MS, PADDLE_VGS_CARD_NETWORKS, PADDLE_VGS_CONTENT_TYPE, PADDLE_VGS_MERCHANT_PINS, PADDLE_VGS_OPERATION, PADDLE_VGS_PSP, PADDLE_VGS_REFUSAL_CODES, injectPaddleVgsCard, isPaddleVgsCardsUrl, isPreparedPaddleVgsRequest, paddleVgsAnswerOutcome, paddleVgsBodyVerdict, paddleVgsCardFacts, paddleVgsCardFactsAgree, paddleVgsCardFactsOf, paddleVgsCardRefusal, paddleVgsEnvironment, paddleVgsMerchantPin, paddleVgsMerchantRefusal, paddleVgsPayCarriesStoredCard, paddleVgsPayTarget, paddleVgsPayVerdict, paddleVgsRefusalCode, paddleVgsRefusalMessage, paddleVgsRequestRefusal, paddleVgsSellerClaimsPinned, paddleVgsStoredCardRefusal, paddleVgsTypedCardRefusal };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// @ts-nocheck
|
|
2
2
|
// Generated from @agent-cards/payment-core. Do not edit.
|
|
3
|
-
// artifact-sha256:
|
|
3
|
+
// artifact-sha256: f3437a15e11b72b9958179cc9610fdafb411cc5db76be536af62c08360a45575
|
|
4
4
|
// src/paddle-vgs.js
|
|
5
5
|
var PADDLE_VGS_PSP = "paddle_vgs";
|
|
6
6
|
var PADDLE_VGS_CAPABILITY = "paddle_vgs_cards";
|
|
@@ -254,12 +254,12 @@ function readAnswer(text) {
|
|
|
254
254
|
return null;
|
|
255
255
|
}
|
|
256
256
|
}
|
|
257
|
+
var isStoredCardId = (id) => typeof id === "string" && STORED_CARD_ID.test(id) && /[A-Za-z]/.test(id) && !PAN_LIKE.test(id);
|
|
257
258
|
function storedCardId(answer) {
|
|
258
259
|
const data = answer && answer.data;
|
|
259
|
-
if (!record(data) || data.type !== "cards"
|
|
260
|
+
if (!record(data) || data.type !== "cards")
|
|
260
261
|
return null;
|
|
261
|
-
|
|
262
|
-
return STORED_CARD_ID.test(id) && /[A-Za-z]/.test(id) && !PAN_LIKE.test(id) ? id : null;
|
|
262
|
+
return isStoredCardId(data.id) ? data.id : null;
|
|
263
263
|
}
|
|
264
264
|
var boundedCode = (value) => typeof value === "string" && REFUSAL_CODE.test(value) && (value.match(/[0-9]/g) ?? []).length <= MAX_CODE_DIGITS ? value : null;
|
|
265
265
|
function paddleVgsRefusalCode(text) {
|
|
@@ -286,4 +286,123 @@ function paddleVgsAnswerOutcome(status, text, card) {
|
|
|
286
286
|
return { outcome: "unknown", reason: "paddle_vgs_unsafe_response" };
|
|
287
287
|
return { outcome: "approved", body: JSON.stringify({ data: { type: "cards", id } }) };
|
|
288
288
|
}
|
|
289
|
-
|
|
289
|
+
var PADDLE_VGS_CARD_ID_WINDOW_MS = 6e4;
|
|
290
|
+
var PAY_URL = /^https:\/\/(sandbox-)?checkout-service\.paddle\.com\/(checkout|transaction-checkout)\/[A-Za-z0-9_-]{1,64}\/pay$/;
|
|
291
|
+
function paddleVgsPayTarget(url) {
|
|
292
|
+
const match = typeof url === "string" ? PAY_URL.exec(url) : null;
|
|
293
|
+
return match ? Object.freeze({ environment: match[1] ? "sandbox" : "production", line: match[2] === "checkout" ? "classic" : "billing" }) : null;
|
|
294
|
+
}
|
|
295
|
+
function paddleVgsPayCarriesStoredCard(body) {
|
|
296
|
+
if (typeof body !== "string")
|
|
297
|
+
return true;
|
|
298
|
+
try {
|
|
299
|
+
return JSON.stringify(JSON.parse(body)).includes('"vgs_card"');
|
|
300
|
+
}
|
|
301
|
+
catch {
|
|
302
|
+
return true;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
var FRAME_BRANDS = /* @__PURE__ */ Object.freeze(["visa", "master", "american_express", "discover", "diners_club", "jcb", "maestro", "dankort", "unionpay", "unknown"]);
|
|
306
|
+
var PAY_BRANDS = /* @__PURE__ */ Object.freeze({ visa: "visa", mastercard: "master", amex: "american_express" });
|
|
307
|
+
var FACT_KEYS = /* @__PURE__ */ Object.freeze(["card_first_six", "card_last_four", "expiry_month", "expiry_year", "card_brand"]);
|
|
308
|
+
var FIRST_SIX = /^[0-9]{6}$/;
|
|
309
|
+
var LAST_FOUR = /^[0-9]{4}$/;
|
|
310
|
+
var fullYear = (year) => {
|
|
311
|
+
const value = Number(year);
|
|
312
|
+
return Number.isInteger(value) && value >= 0 && value <= 99 ? 2e3 + value : value;
|
|
313
|
+
};
|
|
314
|
+
var expiryMonth = (month) => Number.isInteger(month) && month >= 1 && month <= 12;
|
|
315
|
+
var expiryYear = (year) => Number.isInteger(year) && year >= 2e3 && year <= 2099;
|
|
316
|
+
function paddleVgsCardFacts(card) {
|
|
317
|
+
const number = card ? String(card.number) : "";
|
|
318
|
+
const network = cardNetwork(number);
|
|
319
|
+
const month = card ? Number(card.month) : NaN;
|
|
320
|
+
const year = card ? fullYear(card.year) : NaN;
|
|
321
|
+
if (network === null || !expiryMonth(month) || !expiryYear(year))
|
|
322
|
+
throw new Error("Could not read this card's details for Paddle. Nothing was sent.");
|
|
323
|
+
return Object.freeze({ card_first_six: number.slice(0, 6), card_last_four: number.slice(-4), expiry_month: month, expiry_year: year, card_brand: PAY_BRANDS[network] });
|
|
324
|
+
}
|
|
325
|
+
function paddleVgsCardFactsOf(value) {
|
|
326
|
+
if (!record(value))
|
|
327
|
+
return null;
|
|
328
|
+
const keys = Object.keys(value);
|
|
329
|
+
if (keys.length !== FACT_KEYS.length || !FACT_KEYS.every((key) => keys.includes(key)))
|
|
330
|
+
return null;
|
|
331
|
+
const { card_first_six: firstSix, card_last_four: lastFour, expiry_month: month, expiry_year: year, card_brand: brand } = value;
|
|
332
|
+
const network = typeof firstSix === "string" && FIRST_SIX.test(firstSix) ? cardNetwork(`${firstSix}000000`) : null;
|
|
333
|
+
if (network === null || PAY_BRANDS[network] !== brand || typeof lastFour !== "string" || !LAST_FOUR.test(lastFour) || !expiryMonth(month) || !expiryYear(year))
|
|
334
|
+
return null;
|
|
335
|
+
return Object.freeze({ card_first_six: firstSix, card_last_four: lastFour, expiry_month: month, expiry_year: year, card_brand: brand });
|
|
336
|
+
}
|
|
337
|
+
function paddleVgsCardFactsAgree(facts, stored) {
|
|
338
|
+
const read = paddleVgsCardFactsOf(facts);
|
|
339
|
+
if (!read)
|
|
340
|
+
return false;
|
|
341
|
+
const card = record(stored) ? stored : {};
|
|
342
|
+
const network = Object.keys(PAY_BRANDS).find((name) => PAY_BRANDS[name] === read.card_brand);
|
|
343
|
+
const brand = storedBrand(card.brand);
|
|
344
|
+
return !(typeof card.last4 === "string" && LAST_FOUR.test(card.last4) && card.last4 !== read.card_last_four) && (brand === null || brand === network) && !(Number.isInteger(card.expiryMonth) && card.expiryMonth !== read.expiry_month) && !(Number.isInteger(card.expiryYear) && card.expiryYear % 100 !== read.expiry_year % 100);
|
|
345
|
+
}
|
|
346
|
+
var PAY_BODY_LIMIT = 16384;
|
|
347
|
+
var PAY_DATA_KEYS = /* @__PURE__ */ Object.freeze(["payment_method_type", "payment_method_data"]);
|
|
348
|
+
var PAY_CARD_KEYS = /* @__PURE__ */ Object.freeze(["metadata", "three_d_s", "vgs_card"]);
|
|
349
|
+
var METADATA_KEYS = /* @__PURE__ */ Object.freeze(["cardholder_name", ...FACT_KEYS]);
|
|
350
|
+
var BROWSER_INFO = /^[A-Za-z0-9+/]{1,4096}={0,2}$/;
|
|
351
|
+
var RADAR_TOKEN = /^[\x21-\x7e]{1,512}$/;
|
|
352
|
+
var COUNTRY_CODE = /^[A-Za-z]{2}$/;
|
|
353
|
+
var FRONTEND_HOST = /^https:\/\/[\x21-\x7e]{1,1024}$/;
|
|
354
|
+
var radarKeys = (value) => sameKeys(value, ["token", "country_code"]) || sameKeys(value, ["country_code", "token"]);
|
|
355
|
+
function readPay(body, line) {
|
|
356
|
+
if (typeof body !== "string" || body === "" || body.length > PAY_BODY_LIMIT)
|
|
357
|
+
return null;
|
|
358
|
+
let value;
|
|
359
|
+
try {
|
|
360
|
+
value = JSON.parse(body);
|
|
361
|
+
if (!record(value) || JSON.stringify(value) !== body)
|
|
362
|
+
return null;
|
|
363
|
+
}
|
|
364
|
+
catch {
|
|
365
|
+
return null;
|
|
366
|
+
}
|
|
367
|
+
const data = value.data;
|
|
368
|
+
if (!sameKeys(value, ["data"]) || !record(data) || !(sameKeys(data, PAY_DATA_KEYS) || sameKeys(data, [...PAY_DATA_KEYS, "checkout"])) || data.payment_method_type !== (line === "classic" ? "CARD" : "card"))
|
|
369
|
+
return null;
|
|
370
|
+
if (data.checkout !== void 0 && !(record(data.checkout) && sameKeys(data.checkout, ["frontend_host"]) && typeof data.checkout.frontend_host === "string" && FRONTEND_HOST.test(data.checkout.frontend_host)))
|
|
371
|
+
return null;
|
|
372
|
+
const method = data.payment_method_data;
|
|
373
|
+
const card = record(method) && sameKeys(method, ["card"]) ? method.card : null;
|
|
374
|
+
if (!record(card) || !(sameKeys(card, PAY_CARD_KEYS) || sameKeys(card, [...PAY_CARD_KEYS, "session_tracking"])))
|
|
375
|
+
return null;
|
|
376
|
+
const metadata = card.metadata;
|
|
377
|
+
if (!record(metadata) || !sameKeys(metadata, METADATA_KEYS) || typeof metadata.cardholder_name !== "string" || metadata.cardholder_name.length > NAME_LIMIT || CONTROL.test(metadata.cardholder_name) || cardLikeName(metadata.cardholder_name) || typeof metadata.card_first_six !== "string" || !FIRST_SIX.test(metadata.card_first_six) || typeof metadata.card_last_four !== "string" || !LAST_FOUR.test(metadata.card_last_four) || !expiryMonth(metadata.expiry_month) || !expiryYear(metadata.expiry_year) || !FRAME_BRANDS.includes(metadata.card_brand))
|
|
378
|
+
return null;
|
|
379
|
+
const details = card.three_d_s;
|
|
380
|
+
if (!record(details) || !sameKeys(details, ["browser_info"]) || typeof details.browser_info !== "string" || !BROWSER_INFO.test(details.browser_info))
|
|
381
|
+
return null;
|
|
382
|
+
if (!record(card.vgs_card) || !sameKeys(card.vgs_card, ["card_id"]) || !isStoredCardId(card.vgs_card.card_id))
|
|
383
|
+
return null;
|
|
384
|
+
if (card.session_tracking !== void 0) {
|
|
385
|
+
const tracking = card.session_tracking;
|
|
386
|
+
const radar = record(tracking) && sameKeys(tracking, ["stripe_radar"]) ? tracking.stripe_radar : null;
|
|
387
|
+
if (!record(radar) || !radarKeys(radar) || typeof radar.token !== "string" || !RADAR_TOKEN.test(radar.token) || typeof radar.country_code !== "string" || !COUNTRY_CODE.test(radar.country_code))
|
|
388
|
+
return null;
|
|
389
|
+
}
|
|
390
|
+
return value;
|
|
391
|
+
}
|
|
392
|
+
function paddleVgsPayVerdict(request, approved) {
|
|
393
|
+
const facts = record(approved) ? paddleVgsCardFactsOf(approved.facts) : null;
|
|
394
|
+
if (!facts || !isStoredCardId(approved.cardId) || approved.environment !== "production" && approved.environment !== "sandbox") {
|
|
395
|
+
return refuse("paddle_vgs_pay_unbound");
|
|
396
|
+
}
|
|
397
|
+
const target = record(request) && typeof request.method === "string" && request.method.toUpperCase() === "POST" ? paddleVgsPayTarget(request.url) : null;
|
|
398
|
+
const value = target ? readPay(request.body, target.line) : null;
|
|
399
|
+
if (!value)
|
|
400
|
+
return refuse("paddle_vgs_pay_unsupported");
|
|
401
|
+
const card = value.data.payment_method_data.card;
|
|
402
|
+
if (target.environment !== approved.environment || card.vgs_card.card_id !== approved.cardId)
|
|
403
|
+
return refuse("paddle_vgs_pay_card_mismatch");
|
|
404
|
+
for (const key of FACT_KEYS)
|
|
405
|
+
card.metadata[key] = facts[key];
|
|
406
|
+
return Object.freeze({ verdict: "continue", body: JSON.stringify(value) });
|
|
407
|
+
}
|
|
408
|
+
export { PADDLE_VGS_CAPABILITY, PADDLE_VGS_CARDS_URLS, PADDLE_VGS_CARD_ID_WINDOW_MS, PADDLE_VGS_CARD_NETWORKS, PADDLE_VGS_CONTENT_TYPE, PADDLE_VGS_MERCHANT_PINS, PADDLE_VGS_OPERATION, PADDLE_VGS_PSP, PADDLE_VGS_REFUSAL_CODES, injectPaddleVgsCard, isPaddleVgsCardsUrl, isPreparedPaddleVgsRequest, paddleVgsAnswerOutcome, paddleVgsBodyVerdict, paddleVgsCardFacts, paddleVgsCardFactsAgree, paddleVgsCardFactsOf, paddleVgsCardRefusal, paddleVgsEnvironment, paddleVgsMerchantPin, paddleVgsMerchantRefusal, paddleVgsPayCarriesStoredCard, paddleVgsPayTarget, paddleVgsPayVerdict, paddleVgsRefusalCode, paddleVgsRefusalMessage, paddleVgsRequestRefusal, paddleVgsSellerClaimsPinned, paddleVgsStoredCardRefusal, paddleVgsTypedCardRefusal };
|
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
export type PaysafePreparationEnvironment = 'production' | 'sandbox';
|
|
2
2
|
export declare function paysafePreparationUrl(environment: string): string;
|
|
3
|
-
/** Exact strings refuse credentials, queries, fragments and URL normalization. */
|
|
3
|
+
/** The environment of a reviewed build's card URL. Exact strings refuse credentials, queries, fragments and URL normalization. */
|
|
4
4
|
export declare function paysafeEnvironment(url: string): PaysafePreparationEnvironment | undefined;
|
|
5
|
-
/** Checkout
|
|
6
|
-
* consume only its new-card request, including native clientInfo and headers
|
|
7
|
-
* Saved cards, alternate APIs and 3DS-only requests
|
|
8
|
-
*
|
|
9
|
-
* e9e111ff03073cc936a47674e8e7c5db4a6d1050e1b65bf5e20723116fd5080c.
|
|
10
|
-
* The guard checks original bytes and never changes the request.
|
|
5
|
+
/** A reviewed Checkout build (BUILDS) starts a 40-second XHR when Pay is pressed. Prior approval may
|
|
6
|
+
* consume only its new-card request, including native clientInfo and headers, on the card URL that
|
|
7
|
+
* build sends to in the approved environment. Saved cards, alternate APIs and 3DS-only requests
|
|
8
|
+
* require separate review. The guard checks original bytes and never changes the request.
|
|
11
9
|
*/
|
|
12
10
|
export declare function isPreparedPaysafeRequest(url: string, method: string, body: string | null, environment: string, headers?: Record<string, string>): boolean;
|
|
@@ -1,10 +1,34 @@
|
|
|
1
1
|
// Generated from @agent-cards/payment-core. Do not edit.
|
|
2
|
-
// source-sha256:
|
|
2
|
+
// source-sha256: afa00cd57246e51361474c66c5ab4282bd5b39f0297fdf68616e164e19ed1741
|
|
3
3
|
import { readTokenizationJson } from './braintree.js';
|
|
4
|
+
/** The card URL a preparation names: Checkout 1.8.0's. The bind takes the exact URL of the build the request names (BUILDS). */
|
|
4
5
|
const ENDPOINTS = {
|
|
5
6
|
production: 'https://hosted.paysafe.com/checkout/api/v1/tokenize',
|
|
6
7
|
sandbox: 'https://hosted.test.paysafe.com/checkout/api/v1/tokenize',
|
|
7
8
|
};
|
|
9
|
+
/**
|
|
10
|
+
* The Paysafe Checkout builds whose new-card request a prior approval may consume, each read from
|
|
11
|
+
* Paysafe's public bundle. A request binds only to the build its own clientInfo.version names, on
|
|
12
|
+
* that build's URL and headers, so a build Paysafe ships later is refused, and nothing is sent,
|
|
13
|
+
* until it is read and listed here. The request moved inside 1.x: 1.8.0 to 1.12.0 post to
|
|
14
|
+
* hosted.paysafe.com with a correlationId header, 1.16.1 to api.paysafe.com with three other
|
|
15
|
+
* headers, so a version range would hold one build's request to another build's rules.
|
|
16
|
+
*
|
|
17
|
+
* 1.8.0: hosted.paysafe.com/checkout/1.8.0/main.bundle.js, SHA-256
|
|
18
|
+
* e9e111ff03073cc936a47674e8e7c5db4a6d1050e1b65bf5e20723116fd5080c; LIVE/TEST select hosted[.test].
|
|
19
|
+
* 1.16.1: the checkout/v1/latest loader (SHA-256 5fac65c4f7a74b45388ad3ce7c02282f05e832226792bebc417cd9ff191be4f0)
|
|
20
|
+
* names it and frames checkout/v1/latest/index.html. Its main.js (SHA-256
|
|
21
|
+
* 328c6443da930f3648826248afa3c342cdd07d04e49a78b0878dda187772bf80) posts to environment +
|
|
22
|
+
* "checkout/api/v1/tokenize", and its vendor.js (SHA-256 7348275bd07256e295f4b83b292c7fe5115dd7ac35d1b1fe3d26eda3c37d2835)
|
|
23
|
+
* maps LIVE/TEST to api[.test].paysafe.com and sets X-Internal-Correlation-Id, X-Paysafe-Credentials,
|
|
24
|
+
* X-App-Version (the clientInfo version) and X-TransactionSource (CheckoutV1 for paysafe.checkout).
|
|
25
|
+
* The TEST host serves the same bytes (read 2026-10-15).
|
|
26
|
+
*/
|
|
27
|
+
const BUILDS = [
|
|
28
|
+
{ version: '1.8.0', endpoints: ENDPOINTS, correlation: 'correlationid', fixed: {} },
|
|
29
|
+
{ version: '1.16.1', correlation: 'x-internal-correlation-id', fixed: { 'x-app-version': '1.16.1', 'x-transactionsource': 'CheckoutV1' },
|
|
30
|
+
endpoints: { production: 'https://api.paysafe.com/checkout/api/v1/tokenize', sandbox: 'https://api.test.paysafe.com/checkout/api/v1/tokenize' } },
|
|
31
|
+
];
|
|
8
32
|
const record = (value) => value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
9
33
|
const keys = (value, allowed) => record(value) && Object.keys(value).every(key => allowed.includes(key));
|
|
10
34
|
const exact = (pattern, value) => typeof value === 'string' && pattern.exec(value)?.[0] === value;
|
|
@@ -16,27 +40,31 @@ export function paysafePreparationUrl(environment) {
|
|
|
16
40
|
throw new Error('unsupported_preparation_environment');
|
|
17
41
|
return ENDPOINTS[environment];
|
|
18
42
|
}
|
|
19
|
-
/** Exact strings refuse credentials, queries, fragments and URL normalization. */
|
|
43
|
+
/** The environment of a reviewed build's card URL. Exact strings refuse credentials, queries, fragments and URL normalization. */
|
|
20
44
|
export function paysafeEnvironment(url) {
|
|
21
|
-
|
|
45
|
+
const build = BUILDS.find(candidate => url === candidate.endpoints.production || url === candidate.endpoints.sandbox);
|
|
46
|
+
return !build ? undefined : url === build.endpoints.production ? 'production' : 'sandbox';
|
|
22
47
|
}
|
|
23
48
|
const ADDRESS_FIELDS = ['city', 'country', 'zip', 'street', 'street2', 'state'];
|
|
24
49
|
function billing(value) {
|
|
25
50
|
return keys(value, ADDRESS_FIELDS) && Object.values(value).every(value => text(value))
|
|
26
51
|
&& (!own(value, 'country') || exact(/^[A-Z]{2}$/, value.country));
|
|
27
52
|
}
|
|
28
|
-
|
|
53
|
+
/** The reviewed build whose new-card body this is: never a saved card, a 3-D Secure request or another app's body. */
|
|
54
|
+
function freshCardBuild(value) {
|
|
29
55
|
if (!keys(value, ['card', 'clientInfo', 'billingAddress']) || !keys(value.card, ['cardNum', 'cardExpiry', 'cvv', 'holderName']))
|
|
30
|
-
return
|
|
56
|
+
return undefined;
|
|
31
57
|
const card = value.card;
|
|
32
58
|
if (!exact(/^[0-9]{12,19}$/, card.cardNum) || !exact(/^[0-9]{3,4}$/, card.cvv)
|
|
33
59
|
|| !keys(card.cardExpiry, ['month', 'year']) || !exact(/^(?:0[1-9]|1[0-2])$/, card.cardExpiry.month)
|
|
34
60
|
|| !exact(/^20[0-9]{2}$/, card.cardExpiry.year) || own(card, 'holderName') && !text(card.holderName)
|
|
35
61
|
|| own(value, 'billingAddress') && !billing(value.billingAddress))
|
|
36
|
-
return
|
|
62
|
+
return undefined;
|
|
37
63
|
const info = value.clientInfo;
|
|
38
|
-
|
|
39
|
-
|
|
64
|
+
if (!keys(info, ['correlationId', 'invocationId', 'version', 'appName'])
|
|
65
|
+
|| !uuid(info.correlationId) || !uuid(info.invocationId) || info.appName !== 'paysafe.checkout')
|
|
66
|
+
return undefined;
|
|
67
|
+
return BUILDS.find(build => build.version === info.version);
|
|
40
68
|
}
|
|
41
69
|
const BROWSER_HEADERS = new Set(['accept', 'accept-language', 'accept-encoding', 'content-length', 'origin', 'referer',
|
|
42
70
|
'user-agent', 'host', 'connection', 'cache-control', 'pragma', 'priority', 'dnt',
|
|
@@ -53,9 +81,11 @@ function singleUseCredential(value) {
|
|
|
53
81
|
return false;
|
|
54
82
|
}
|
|
55
83
|
}
|
|
56
|
-
|
|
84
|
+
/** The build's own headers, each once with its native value, and only headers a browser adds beside them. */
|
|
85
|
+
function nativeHeaders(headers, correlationId, build) {
|
|
57
86
|
if (!record(headers))
|
|
58
87
|
return false;
|
|
88
|
+
const native = ['content-type', 'x-paysafe-credentials', build.correlation, ...Object.keys(build.fixed)];
|
|
59
89
|
const parsed = new Map();
|
|
60
90
|
let bytes = 0;
|
|
61
91
|
for (const [name, value] of Object.entries(headers)) {
|
|
@@ -63,25 +93,27 @@ function nativeHeaders(headers, correlationId) {
|
|
|
63
93
|
if (parsed.size >= 64 || parsed.has(lower) || !exact(/^[A-Za-z0-9-]+$/, name) || !text(value, 8192)
|
|
64
94
|
|| (bytes += name.length + value.length) > 32768)
|
|
65
95
|
return false;
|
|
66
|
-
if (!
|
|
96
|
+
if (!native.includes(lower) && !BROWSER_HEADERS.has(lower)
|
|
67
97
|
&& !exact(/^sec-ch-ua(?:-[a-z0-9-]+)?$/, lower))
|
|
68
98
|
return false;
|
|
69
99
|
parsed.set(lower, value);
|
|
70
100
|
}
|
|
71
101
|
return exact(/^application\/json(?:;\s*charset=utf-8)?$/i, parsed.get('content-type'))
|
|
72
|
-
&& parsed.get(
|
|
102
|
+
&& parsed.get(build.correlation) === correlationId && singleUseCredential(parsed.get('x-paysafe-credentials'))
|
|
103
|
+
&& Object.entries(build.fixed).every(([name, value]) => parsed.get(name) === value);
|
|
73
104
|
}
|
|
74
|
-
/** Checkout
|
|
75
|
-
* consume only its new-card request, including native clientInfo and headers
|
|
76
|
-
* Saved cards, alternate APIs and 3DS-only requests
|
|
77
|
-
*
|
|
78
|
-
* e9e111ff03073cc936a47674e8e7c5db4a6d1050e1b65bf5e20723116fd5080c.
|
|
79
|
-
* The guard checks original bytes and never changes the request.
|
|
105
|
+
/** A reviewed Checkout build (BUILDS) starts a 40-second XHR when Pay is pressed. Prior approval may
|
|
106
|
+
* consume only its new-card request, including native clientInfo and headers, on the card URL that
|
|
107
|
+
* build sends to in the approved environment. Saved cards, alternate APIs and 3DS-only requests
|
|
108
|
+
* require separate review. The guard checks original bytes and never changes the request.
|
|
80
109
|
*/
|
|
81
110
|
export function isPreparedPaysafeRequest(url, method, body, environment, headers) {
|
|
82
111
|
if (method !== 'POST' || (environment !== 'production' && environment !== 'sandbox')
|
|
83
112
|
|| paysafeEnvironment(url) !== environment)
|
|
84
113
|
return false;
|
|
85
114
|
const value = readTokenizationJson(body);
|
|
86
|
-
|
|
115
|
+
if (!value || !record(value.clientInfo))
|
|
116
|
+
return false;
|
|
117
|
+
const build = freshCardBuild(value);
|
|
118
|
+
return !!build && url === build.endpoints[environment] && nativeHeaders(headers, value.clientInfo.correlationId, build);
|
|
87
119
|
}
|
package/dist/pre-claim.d.ts
CHANGED
|
@@ -13,6 +13,7 @@ import { type CardEndpointProfile } from './card-endpoint.js';
|
|
|
13
13
|
import { type PayrailsSessionCapture, type PayrailsSidecar } from './payrails.js';
|
|
14
14
|
import { type VerifoneProfile } from './verifone.js';
|
|
15
15
|
import { type WorldlineMyCheckoutProducts } from './worldline-mycheckout.js';
|
|
16
|
+
import { type PaddleVgsPayGate } from './paddle-vgs-pay.js';
|
|
16
17
|
/** The native Stripe step the gate claimed, or null when none was: exactly what StripeCheckoutGate.claim returns. */
|
|
17
18
|
type StripeStep = ReturnType<StripeCheckoutGate['claim']>;
|
|
18
19
|
/** A synthetic response a gate fulfills locally. The effect applies CORS and encoding. */
|
|
@@ -189,6 +190,12 @@ export interface PreClaimContext {
|
|
|
189
190
|
* Absent, no MyCheckout card submit is held to a product here.
|
|
190
191
|
*/
|
|
191
192
|
worldlineMyCheckout?: WorldlineMyCheckoutProducts;
|
|
193
|
+
/**
|
|
194
|
+
* The attachment's binding of Paddle's /pay to the stored card's id it handed a frame (paddle-vgs-pay.ts),
|
|
195
|
+
* judged by the request's frame (`frame`). Absent, or for an org the API did not serve Paddle VGS to, Paddle's
|
|
196
|
+
* checkout services are not this gate's.
|
|
197
|
+
*/
|
|
198
|
+
paddleVgsPay?: PaddleVgsPayGate;
|
|
192
199
|
}
|
|
193
200
|
/**
|
|
194
201
|
* The URL an event may name for a request. For an armed merchant profile's
|
|
@@ -211,6 +218,7 @@ export declare const ekashuPreClaim: PreClaimGate;
|
|
|
211
218
|
export declare const paypalHostedFieldsPreClaim: PreClaimGate;
|
|
212
219
|
export declare const cybersourceSecureAcceptancePreClaim: PreClaimGate;
|
|
213
220
|
export declare const worldlineMyCheckoutPreClaim: PreClaimGate;
|
|
221
|
+
export declare const paddleVgsPayPreClaim: PreClaimGate;
|
|
214
222
|
/** The processor gates, in order. A new family adds one entry here; the terminal
|
|
215
223
|
* arbiter runs after all of them and is not part of this list. A gate appended
|
|
216
224
|
* after Stripe is never offered a step Stripe claimed: Stripe answers, aborts or
|
package/dist/pre-claim.js
CHANGED
|
@@ -79,6 +79,7 @@ import { ekashuEntryUrl, isEkashuEntry, isEkashuHost } from './ekashu.js';
|
|
|
79
79
|
import { PAYPAL_HOSTED_FIELDS_REASON, isPaypalHostedFieldsConfirm, paypalHostedFieldsBody } from './paypal-hosted-fields.js';
|
|
80
80
|
import { CYBERSOURCE_SECURE_ACCEPTANCE_REASON, CYBERSOURCE_SECURE_ACCEPTANCE_RECIPIENT, cybersourceSecureAcceptanceRequest, isCybersourceSecureAcceptanceUrl, isSecureAcceptanceFramePost, } from './cybersource-secure-acceptance.js';
|
|
81
81
|
import { isWorldlineMyCheckoutEndpoint } from './worldline-mycheckout.js';
|
|
82
|
+
import { paddleVgsPayEventUrl } from './paddle-vgs-pay.js';
|
|
82
83
|
/**
|
|
83
84
|
* The URL an event may name for a request. For an armed merchant profile's
|
|
84
85
|
* endpoint or a sibling of it: the endpoint origin, the path TEMPLATE and the
|
|
@@ -1737,6 +1738,52 @@ async function worldlineMyCheckoutBlock(ctx, reason) {
|
|
|
1737
1738
|
await ctx.effects.abort();
|
|
1738
1739
|
return { kind: 'abort' };
|
|
1739
1740
|
}
|
|
1741
|
+
// Paddle's /pay (paddle-vgs-pay.ts), held only on an attachment the API served Paddle VGS to: a POST to /pay
|
|
1742
|
+
// that names a stored card continues with the approved card's first six, last four, expiry and brand written
|
|
1743
|
+
// over the ones the frame read from the agent's placeholder, when this attachment handed that frame the stored
|
|
1744
|
+
// card's id within the minute and this is its first /pay; otherwise it aborts as an unsupported checkout, with
|
|
1745
|
+
// the gate's reason, so Paddle never receives the real card's id beside the placeholder's facts. Anything else
|
|
1746
|
+
// on Paddle's checkout services (its create, the buyer's first step, 3-D Secure's follow-up, a PayPal or wallet
|
|
1747
|
+
// /pay naming no stored card) defers, and the terminal arbiter continues it untouched. Synchronous until the
|
|
1748
|
+
// continue or the abort.
|
|
1749
|
+
export const paddleVgsPayPreClaim = (ctx) => {
|
|
1750
|
+
const { url, method } = ctx.request;
|
|
1751
|
+
if (!ctx.paddleVgsPay?.holds(url, method))
|
|
1752
|
+
return undefined;
|
|
1753
|
+
let body;
|
|
1754
|
+
// A reader that throws (Playwright's postData() can) proves nothing: judged as a body that is not the frame's.
|
|
1755
|
+
try {
|
|
1756
|
+
body = ctx.request.body;
|
|
1757
|
+
}
|
|
1758
|
+
catch {
|
|
1759
|
+
body = null;
|
|
1760
|
+
}
|
|
1761
|
+
const judged = ctx.paddleVgsPay.judge(ctx.frame, url, method, body);
|
|
1762
|
+
if (judged === null)
|
|
1763
|
+
return undefined;
|
|
1764
|
+
return 'reason' in judged ? paddleVgsPayRefused(ctx, judged.reason) : paddleVgsPayContinued(ctx, judged.body);
|
|
1765
|
+
};
|
|
1766
|
+
async function paddleVgsPayRefused(ctx, reason) {
|
|
1767
|
+
const { url, method } = ctx.request;
|
|
1768
|
+
ctx.preparationGate.invalidate('unsupported_checkout');
|
|
1769
|
+
ctx.lifecycle.unsupported(reason);
|
|
1770
|
+
ctx.onEvent?.({ type: 'unsupported_checkout', detail: { url: paddleVgsPayEventUrl(url), method, reason } });
|
|
1771
|
+
await ctx.effects.abort();
|
|
1772
|
+
return { kind: 'abort' };
|
|
1773
|
+
}
|
|
1774
|
+
async function paddleVgsPayContinued(ctx, body) {
|
|
1775
|
+
try {
|
|
1776
|
+
await ctx.effects.continueRequest(body);
|
|
1777
|
+
}
|
|
1778
|
+
catch (error) {
|
|
1779
|
+
ctx.onEvent?.({ type: 'failed', detail: ctx.failureSummary(error) });
|
|
1780
|
+
await ctx.effects.abort().catch(() => { });
|
|
1781
|
+
return { kind: 'abort' };
|
|
1782
|
+
}
|
|
1783
|
+
ctx.onEvent?.({ type: 'companion_continued', detail: { url: paddleVgsPayEventUrl(ctx.request.url), psp: 'paddle_vgs',
|
|
1784
|
+
fields: ['card_first_six', 'card_last_four', 'expiry_month', 'expiry_year', 'card_brand'] } });
|
|
1785
|
+
return { kind: 'continue' };
|
|
1786
|
+
}
|
|
1740
1787
|
// The terminal arbiter, always last. A request that no gate claimed and that is
|
|
1741
1788
|
// neither a native Stripe step nor a recognized card request continues, unless
|
|
1742
1789
|
// it matches a declared payment endpoint we do not support (then it aborts as an
|
|
@@ -1771,7 +1818,7 @@ async function unsupportedContinue(ctx) {
|
|
|
1771
1818
|
* after Stripe is never offered a step Stripe claimed: Stripe answers, aborts or
|
|
1772
1819
|
* pauses each one itself. */
|
|
1773
1820
|
export const PSP_PRE_CLAIM_GATES = [cardEndpointResendPreClaim, cardEndpointPreClaim, mercadoPreClaim, juspayPreClaim, braintreePreClaim, stripePreClaim, merchantHostedPreClaim, verifonePreClaim, fiservPreClaim, gmoPreClaim, cybersourceFlexPreClaim, datatransPreClaim, pelecardPreClaim, aciPreClaim, creditguardPreClaim, primerPreClaim, payrailsPreClaim, convergePreClaim, eprotectPreClaim, wspayPreClaim, shijiStsPreClaim, ekashuPreClaim, paypalHostedFieldsPreClaim,
|
|
1774
|
-
cybersourceSecureAcceptancePreClaim, worldlineMyCheckoutPreClaim];
|
|
1821
|
+
cybersourceSecureAcceptancePreClaim, worldlineMyCheckoutPreClaim, paddleVgsPayPreClaim];
|
|
1775
1822
|
/** Run each gate in order; the first to return a decision wins. If none does,
|
|
1776
1823
|
* the terminal arbiter decides between continue, abort (unsupported) and pause.
|
|
1777
1824
|
* Returns synchronously while every gate defers synchronously (an ordinary card
|
package/dist/primer.generated.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// @ts-nocheck
|
|
2
2
|
// Generated from @agent-cards/payment-core. Do not edit.
|
|
3
|
-
// artifact-sha256:
|
|
3
|
+
// artifact-sha256: 80af165492668e00d651e9a039d3f24fe336bc3601dd5d2ab937a4b79bbb7ad8
|
|
4
4
|
// src/card-number-key.js
|
|
5
5
|
var CARD_NUMBER_KEY = /^(number|card_number|cardnumber|cardNum|card_num|pan)$/i;
|
|
6
6
|
// src/json-path.js
|
|
@@ -414,6 +414,8 @@ var pin2 = (fields) => Object.freeze({
|
|
|
414
414
|
var PADDLE_VGS_MERCHANT_PINS = /* @__PURE__ */ Object.freeze([
|
|
415
415
|
pin2({ id: "rectangle_pro", merchant: "Rectangle Pro", origins: ["https://rectangleapp.com"], sellerClaim: null })
|
|
416
416
|
]);
|
|
417
|
+
var FACT_KEYS = /* @__PURE__ */ Object.freeze(["card_first_six", "card_last_four", "expiry_month", "expiry_year", "card_brand"]);
|
|
418
|
+
var METADATA_KEYS = /* @__PURE__ */ Object.freeze(["cardholder_name", ...FACT_KEYS]);
|
|
417
419
|
// src/card-fields.js
|
|
418
420
|
function processorGate(recognizer, url, body) {
|
|
419
421
|
if (recognizer && recognizer.psp === PADDLE_VGS_PSP) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// @ts-nocheck
|
|
2
2
|
// Generated from @agent-cards/payment-core. Do not edit.
|
|
3
|
-
// artifact-sha256:
|
|
3
|
+
// artifact-sha256: 7e7a9b515128df56d149df50e0c275c7f6e8e522945ed818ca714ccac7eb1cd7
|
|
4
4
|
// src/deep-freeze.js
|
|
5
5
|
function deepFreeze(value) {
|
|
6
6
|
if (value && typeof value === "object" && !Object.isFrozen(value)) {
|
|
@@ -583,13 +583,26 @@ var PROCESSOR_DEFINITIONS = deepFreeze([
|
|
|
583
583
|
// Verified from https://hosted.test.paysafe.com/checkout/1.8.0/main.bundle.js
|
|
584
584
|
// and its byte-identical production sibling (SHA-256 e9e111ff03073cc936a47674e8e7c5db4a6d1050e1b65bf5e20723116fd5080c).
|
|
585
585
|
// Its native LIVE/TEST environment map selects exactly hosted[.test].paysafe.com.
|
|
586
|
+
// Checkout v1 latest (loader version 1.16.1; checkout/v1/latest/main.js SHA-256
|
|
587
|
+
// 328c6443da930f3648826248afa3c342cdd07d04e49a78b0878dda187772bf80) posts the same body to
|
|
588
|
+
// checkout/api/v1/tokenize on api[.test].paysafe.com, the hosts its vendor.js maps LIVE/TEST to.
|
|
589
|
+
// It and Paysafe.js latest set X-Internal-Correlation-Id, X-App-Version and X-TransactionSource
|
|
590
|
+
// on every card request, and Paysafe's CORS answer on all three API card paths allows them for
|
|
591
|
+
// any origin (2026-10-15), so a replay keeps them as the native request sends them.
|
|
586
592
|
// Keep host/path pairs separate: details/log are not card requests;
|
|
587
593
|
// card/tokenize uses a saved paymentToken, and 3DS-specific paths are unverified.
|
|
588
|
-
match: "(?:api(\\.test)?\\.paysafe\\.com/(?:paymenthub/v1/singleusepaymenthandles|js/api/v1/tokenize)|hosted(\\.test)?\\.paysafe\\.com/checkout/api/v1/tokenize)(?![\\w/-])",
|
|
594
|
+
match: "(?:api(\\.test)?\\.paysafe\\.com/(?:paymenthub/v1/singleusepaymenthandles|js/api/v1/tokenize|checkout/api/v1/tokenize)|hosted(\\.test)?\\.paysafe\\.com/checkout/api/v1/tokenize)(?![\\w/-])",
|
|
589
595
|
hosts: ["^api\\.paysafe\\.com$", "^api\\.test\\.paysafe\\.com$", "^hosted\\.paysafe\\.com$", "^hosted\\.test\\.paysafe\\.com$"],
|
|
590
596
|
sandboxHosts: ["^api\\.test\\.paysafe\\.com$", "^hosted\\.test\\.paysafe\\.com$"],
|
|
591
597
|
encoding: "json",
|
|
592
|
-
passthroughHeaders: [
|
|
598
|
+
passthroughHeaders: [
|
|
599
|
+
"^authorization$",
|
|
600
|
+
"^x-paysafe-credentials$",
|
|
601
|
+
"^correlationid$",
|
|
602
|
+
"^x-internal-correlation-id$",
|
|
603
|
+
"^x-app-version$",
|
|
604
|
+
"^x-transactionsource$"
|
|
605
|
+
]
|
|
593
606
|
},
|
|
594
607
|
{
|
|
595
608
|
psp: "recurly",
|
|
@@ -1556,6 +1569,8 @@ var pin2 = (fields) => Object.freeze({
|
|
|
1556
1569
|
var PADDLE_VGS_MERCHANT_PINS = /* @__PURE__ */ Object.freeze([
|
|
1557
1570
|
pin2({ id: "rectangle_pro", merchant: "Rectangle Pro", origins: ["https://rectangleapp.com"], sellerClaim: null })
|
|
1558
1571
|
]);
|
|
1572
|
+
var FACT_KEYS = /* @__PURE__ */ Object.freeze(["card_first_six", "card_last_four", "expiry_month", "expiry_year", "card_brand"]);
|
|
1573
|
+
var METADATA_KEYS = /* @__PURE__ */ Object.freeze(["cardholder_name", ...FACT_KEYS]);
|
|
1559
1574
|
// src/cybersource-secure-acceptance.js
|
|
1560
1575
|
var SA_BODY_LIMIT = 32 * 1024;
|
|
1561
1576
|
var SA_REPLY_LIMIT = 64 * 1024;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-cards/sdk",
|
|
3
|
-
"version": "0.23.
|
|
3
|
+
"version": "0.23.13",
|
|
4
4
|
"description": "Agentcard's SDK: one client, Agentcard, with two modules. checkout pauses a browser purchase and pays it with the person's own card; voice connects a person to your product once and approves a purchase by voice inside it. Card data never enters your process.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -49,5 +49,5 @@
|
|
|
49
49
|
"engines": {
|
|
50
50
|
"node": ">=22"
|
|
51
51
|
},
|
|
52
|
-
"license": "
|
|
52
|
+
"license": "SEE LICENSE IN LICENSE"
|
|
53
53
|
}
|