@agent-cards/sdk 0.23.2 → 0.23.3
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 +14 -0
- package/PREFLIGHT.md +2 -0
- package/README.md +14 -9
- package/dist/card-endpoint.d.ts +9 -1
- package/dist/card-endpoint.js +15 -2
- package/dist/cdp.js +19 -13
- package/dist/client.js +11 -8
- package/dist/lifecycle.d.ts +5 -3
- package/dist/lifecycle.js +4 -2
- package/dist/merchant-card-endpoints.generated.js +238 -136
- package/dist/paypal-hosted-fields.d.ts +52 -0
- package/dist/paypal-hosted-fields.js +80 -0
- package/dist/pre-claim.d.ts +1 -0
- package/dist/pre-claim.js +59 -6
- package/dist/preflight-catalog.json +37 -2
- package/dist/preflight-schemas.json +2 -0
- package/dist/preflight.generated.js +26 -4
- package/examples/preflight/kernel-native/inventory.json +1 -1
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -2,12 +2,26 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
- Stop PayPal's legacy Hosted Fields from sending the card your agent typed to PayPal's payment confirm. A checkout that loads PayPal's JS SDK with `components=hosted-fields`, such as Planet Golf UK's PayPal Checkout, takes the card in frames on `assets.braintreegateway.com` and sends it to `https://cors.api.paypal.com/v2/checkout/orders/{id}/confirm-payment-source` (`cors.api.sandbox.paypal.com` in PayPal's sandbox). No Agentcard payment takes that request over, and earlier releases let it through, so PayPal received the dummy card and declined it. Both adapters now fail that request in the browser for every company, whatever `syncRegistry()` served, and the controller reads `unsupported` with reason `unsupported_paypal_hosted_fields` and holds, like every `unsupported` reason: to pay another way on the same page, attach again. A confirm with no card number, such as a PayPal wallet payment, continues untouched. A confirm whose body the SDK cannot read is failed with a `blocked` event, `unreadable_paypal_request`, and the checkout is not held. A merchant that turns on PayPal's card installments also has the card number field send each typed number to PayPal's installment lookup, `/v1/credit/calculated-financing-options`; the SDK leaves that lookup alone, and it pays nothing. TypeScript: the controller's `unsupported()` reasons gain `unsupported_paypal_hosted_fields`; code that switches exhaustively over them needs the new case.
|
|
6
|
+
|
|
7
|
+
- Name PayPal's legacy Hosted Fields as PayPal in a preflight inspection, not as Braintree. Their card fields are braintree-web builds whose version carries `-payments-sdk` (`3.32.0-payments-sdk-dev` on Planet Golf UK), and inspection read the page as `braintree` with status `supported`. The frames now name `paypal`, and the result stays `unknown` with `processor_variant_unverified`. A braintree-web script from a `-payments-sdk` build is no longer Braintree evidence either, and a variant seen in several frames lists its limitation once. The catalog's `catalog_sha256` changes; `catalog_version` stays `2026-09-15.1`.
|
|
8
|
+
|
|
9
|
+
- Stop the card request AT&T's checkout sends today. AT&T's page posts the card to `/msapi/checkoutorch/v1/mfe/paymentInfo` with no query, and the adapters armed only the exporch form, `/msapi/checkoutexporch/v1/paymentInfo?cartId=`, which none of the checkouts Agentcard read live used, so the agent's dummy card reached AT&T. The adapters now pause the request AT&T sends and refuse it as `unsupported_checkout` while AT&T's profile is in `observe`. The exporch form is a profile of its own, `att_exporch_payment_info`, refused the same way, so a cart AT&T moves there is stopped too. AT&T's card-on-file step, which posts the typed card to `cardOnFile` on either base, is aborted in every status as `card_endpoint_refused` with reason `card_resend`, and so is the exporch form with no query, with its card-on-file step: AT&T's page sends both when its checkout names no cart id, and no payment can cover them. AT&T's profile reads the body its page builds: no `X-CSRF-TOKEN` on a guest checkout, `type` `creditcard` or `debitcard`, `statusCode` and `transactionId` as null, `cardSubType` one of `VISA`, `MASTERCARD`, `AMEX` or `DISCOVER`, a two-digit month and a four-digit year.
|
|
10
|
+
|
|
11
|
+
- Recognize Target Optical's card request as its checkout sends it today: a JSON `PUT` to `/wcs/resources/store/12001/cart/@self/addPayment` with the page's `nocache` query, the total in `piAmount` with five decimals (`"49.42000"`). The profile described the form post Target Optical's older checkout sent, which no request matches now, so the adapters let the dummy card reach Target Optical. The profile stays in observe: both adapters now pause that request and refuse it as `unsupported_checkout` before any authorization, let the saved-card body to the same URL through untouched, and abort the card save a signed-in shopper's page sends to `/wcs/resources/store/12001/mypayments` in every status. They also abort, in every status, a post to `/AjaxOrderChangeServicePIAddTO`, the form endpoint the older checkout used, which they refused before and which the storefront still declares in a script it serves, so moving the profile to the `PUT` leaves no path where the dummy card reaches Target Optical. `attachToPlaywright` routes a reviewed card endpoint's request by every method the profiles name; it asked only for `POST`, so a `PUT` card request or resend never reached its gates. A card endpoint create names the profile's own method in `request.method`; an API that reads every card endpoint request as a `POST` refuses a `PUT` create with `invalid_request`, so nothing is charged. TypeScript: `CardEndpointProfile.method` can be `PUT`.
|
|
12
|
+
|
|
5
13
|
- Stop the card fields adyen-web 5 encrypts at a reviewed Adyen merchant profile. adyen-web 5 seals each field as an RSA-OAEP / A256CBC-HS512 JWE, where adyen-web 6 writes RSA-OAEP-256 / A256GCM, and the SDK read only adyen-web 6's form. So the card requests of Cathay Pacific's payment page and Dunelm's legacy checkout, and Cathay's charge request after its signing step, reached the merchant with the agent's dummy card. The adapters now read both forms as a card. Both profiles ship in `observe`, so the adapters abort each of these requests before it leaves the browser and report it as `merchant_profile_observed`. The Vault writes only adyen-web 6's form, so the API refuses a create for a card request in adyen-web 5's form with `cse_template_unsupported`, and nothing is charged.
|
|
6
14
|
|
|
7
15
|
- Abort the requests in which IHG's pay page sends the typed card outside its reviewed card request, in every status, as `card_endpoint_refused` with reason `card_resend`. The page posts the same booking to IHG's enrollment service while its pre-ticked "Enroll for free" box stays ticked, posts it on other paths for more than one room, a travel agent or Business Rewards, and at a hotel that asks for 3-D Secure first posts the number to two 3-D Secure calls. The SDK let each of these reach IHG with the agent's dummy card. A booking with the box unticked reaches IHG's reviewed card request, which the adapters refuse as before while IHG is in `observe`.
|
|
8
16
|
|
|
9
17
|
- Read IHG's booking at a hotel that asks for no security code as IHG's reviewed card request. At the two US hotels a live check visited, the pay page shows no security code field and sends `cvvNumber: {token: ""}`. The adapters read that as a card in a body IHG's profile did not review and refused it with reason `unrecognized_payment_endpoint`, so the booking could never pause for the cardholder. They now refuse it with reason `merchant_card_endpoint_not_enabled`, naming IHG's profile, while IHG is in `observe`, and once Agentcard enables IHG it pauses and asks the cardholder like any other IHG booking. A security code the hotel asks for is still read as three or four digits, and any other value there is refused as before.
|
|
10
18
|
|
|
19
|
+
- Read a hotel booking at IHG, Hilton or The Verb Hotel as the hotel's reviewed card request only at a rate the hotel charges in full at booking: IHG's Book Early & Save (`ratePlanCode` `IDAPF`) in the hotel's own currency, a Hilton rate guaranteed `FP` with a deposit, and The Verb Hotel's Non-Refundable rate (`14DAYAP`). The adapters now refuse a rate that holds the card and charges it at the stay, an IHG group rate, an IHG booking that names `hotels[0].bookingCurrency` (the shopper's currency, which IHG's page sends only when the hotel prices in another one) and a Hilton booking with its pre-ticked "Join Hilton Honors for free" left on (`variables.enrollment`) with reason `unrecognized_payment_endpoint` and an event that names only the endpoint, where they refused each as `merchant_card_endpoint_not_enabled` naming the profile. All three hotels stay in `observe`, so the adapters refuse every booking there before any authorization, as before. IHG's and Hilton's profiles now carry the card network codes their pages write (VS, MC, AX and DS at IHG; VI, MC, AX and DS at Hilton): a body that names another code is unrecognized, and once a hotel is enabled the cardholder's device writes the code of the real card's network and refuses a card on any other. Hilton's profile reads `charge_timing: 'at_request'`, since Hilton charges its prepaid rate at booking. Italo's profile no longer waits on how its page sends `expire`, which the live checks of Sep 30 and Oct 6 read as left out.
|
|
20
|
+
|
|
21
|
+
- Stop Universal Orlando's card request at the checkout it runs today. Universal moved its store from `www.universalorlando.com/web-store` to `store.universalorlando.com`, whose guest checkout posts the card to `secure-api.universalparks.com/instances/{guest}/carts/{cart}/precheckout`. The SDK's profile still described the web-store request, so it let this one through and the agent's dummy card reached Universal. The adapters now refuse it before it leaves the browser with reason `merchant_card_endpoint_not_enabled`, naming Universal's profile, while Universal is in `observe`. They also abort the store's two other posts of the typed card, a signed-in customer's precheckout and the Cardinal 3-D Secure token call Universal keeps off today, in every status, as `card_endpoint_refused` with reason `card_resend`. The retired web-store checkout still loads from a deep link such as `www.universalorlando.com/web-store/en/us/checkout`, and the adapters abort its two posts of the typed card the same way: a guest's `/instances/{guest}/commerce-checkout`, the request they refused before this release, and a signed-in customer's `/guests/{guest}/commerce-checkout`. Once Agentcard enables Universal, the API takes one payment per cart and refuses another create for it with `409 merchant_checkout_in_use`.
|
|
22
|
+
|
|
23
|
+
- 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.
|
|
24
|
+
|
|
11
25
|
## 0.23.0
|
|
12
26
|
|
|
13
27
|
### Upgrading from 0.22.0
|
package/PREFLIGHT.md
CHANGED
|
@@ -207,6 +207,8 @@ The bundled direct SDK profile declares processor support for `one_time` purchas
|
|
|
207
207
|
|
|
208
208
|
The catalog also carries reviewed hosted, regional, and versioned variants observed on live checkouts: Adyen secured fields on the regional live hosts and any release, the Square card element frame, dLocal Smart Fields releases, Mercado Pago secure fields and the guest Checkout Pro card form, and the Rapyd hosted checkout page. A variant the implemented adapter does not admit, such as the Tranzila terminal-root page, the Authorize.Net hosted payment form, or a Worldpay hosted payment page, identifies its processor and returns `unknown` with `processor_variant_unverified`. A new fingerprint never declares payment support by itself.
|
|
209
209
|
|
|
210
|
+
PayPal's legacy Hosted Fields (`paypal`) are identified from the card fields PayPal's JS SDK renders with `components=hosted-fields`: braintree-web builds whose version carries `-payments-sdk`, such as `3.32.0-payments-sdk-dev`, served from `assets.braintreegateway.com` as `hosted-fields-input-frame.min.html` and `hosted-fields-payments-sdk-tokenization-frame.min.html`. PayPal's frames send the card to PayPal, not to Braintree, so a page that shows them names `paypal` and returns `unknown` with `processor_variant_unverified`, and no braintree-web file from a `-payments-sdk` build counts as Braintree evidence. The SDK fails the card request those frames send; see the PayPal Hosted Fields row in the [README's processor table](./README.md#supported-processors).
|
|
211
|
+
|
|
210
212
|
Adyen's `supported` covers the Sessions payment request on Adyen's own hosts. Some Adyen merchants post the four encrypted fields to their own server instead, and their pages load the same secured fields, so an inspection identifies Adyen on both and cannot tell the two flows apart. The SDK pays a merchant's own endpoint only through a merchant profile Agentcard reviewed for it; the reviewed profiles (Dunelm and Cinemark) are in `observe`, so the SDK aborts their card requests and nothing is charged. The `actual_payment_request_validation` requirement is where that difference shows: check the paused request's host before treating an Adyen result as payable.
|
|
211
213
|
|
|
212
214
|
Authorize.Net Direct Post Method and SIM ship dark. The SDK pauses their `transact.dll` card form only for an org Agentcard enabled for them (capability `authorize_net_dpm_form`), and the bundled catalog leaves them out, so the table above does not list them and inspection answers for their forms exactly as it did before: the SIM hosted form on `secure.authorize.net` identifies Authorize.Net and returns `unknown` with `processor_variant_unverified`.
|
package/README.md
CHANGED
|
@@ -254,6 +254,7 @@ Coverage is specific to the processor request format, merchant setup, browser tr
|
|
|
254
254
|
| Checkout.com | supported |
|
|
255
255
|
| Mercado Pago | Card tokenization and prepared checkout are implemented. Guest Checkout Pro in Mexico also corrects the issuer for one native card association when the selected card has the same brand and type. One live MXN 40 Lotería Chida purchase with published SDK 0.10.0 completed automatically through Pay; the merchant confirmed paid status and PDF fulfillment. The historical SDK result remains unknown because the private receipt adapter rejected a relative download URL; a separate read with the corrected adapter confirms that same paid receipt. Independent processor capture/settlement and other country or integration paths remain unverified. |
|
|
256
256
|
| VGS Collect (Very Good Security; Wolt) | not supported: VGS's proxy aliases only submissions from its own iframe, so a replay from the cardholder's device is refused by the merchant (verified on Wolt, 2026-09-03). Not recognized, so the agent's browser is not paused there |
|
|
257
|
+
| PayPal Hosted Fields (the legacy card fields of PayPal's JS SDK; Planet Golf UK) | not supported: no Agentcard payment takes over the card these fields send to `cors.api.paypal.com`. The SDK fails that request for every company, so PayPal's payment confirm never receives the dummy card, and the controller reads `unsupported` with reason `unsupported_paypal_hosted_fields` and holds the attachment until you attach again; a PayPal wallet payment continues untouched |
|
|
257
258
|
| Adyen | supported (mode `cse`): the vault encrypts the card for Adyen on the cardholder's device and your browser sends it. Sessions flow: the paused request is `/checkoutshopper/v1/sessions/{id}/payments` on Adyen's own hosts. A merchant that posts the encrypted fields to its own server pauses only when Agentcard reviewed it as a merchant profile (Dunelm and Cinemark so far); both profiles are in `observe`, so their card requests are reported and aborted and nothing pays there yet (see "Adyen merchants that take the card on their own server"). Without a preparation, approval starts at Pay and must land before Adyen Web's own request timeout (observed at 60 seconds on Adyen Web 6.41 and 6.44; not enforced by the SDK); `prepare({ psp: 'adyen' })` moves the approval before Pay |
|
|
258
259
|
| ACI Worldwide COPYandPAY (Halfords) | dark launch: served only after `syncRegistry()` and only for an org Agentcard enabled; prepared checkout only. The card-number request is replayed from the cardholder's device, the page's own CVC and expiry requests are answered locally, and `/payment` continues once. See [Pay an ACI COPYandPAY checkout](#pay-an-aci-copyandpay-checkout) |
|
|
259
260
|
| Tranzila | supported (mode `hosted_form`): the cardholder finishes on Tranzila's own page; the paused form navigation resolves to a synthetic page, and you poll the merchant's order state |
|
|
@@ -424,21 +425,25 @@ mode; the difference matters if you drive `authorize()` yourself.
|
|
|
424
425
|
page's origin, the exact origin, path and query of the card request, the
|
|
425
426
|
session headers and cookies it needs, and the answer headers that may come
|
|
426
427
|
back. The adapters arm every profile's endpoint (`cardEndpointPatterns()`
|
|
427
|
-
lists the `Fetch.enable` patterns) and pause a
|
|
428
|
-
card request URL
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
428
|
+
lists the `Fetch.enable` patterns) and pause a request to exactly a
|
|
429
|
+
profile's card request URL, sent with the profile's own method (a POST, or
|
|
430
|
+
the PUT Target Optical's checkout sends), when it carries a card: anything
|
|
431
|
+
at the profile's card number field, or a Luhn-valid number of 12 to 19
|
|
432
|
+
digits anywhere in the body, however it is written. A body the adapter
|
|
433
|
+
cannot read counts as carrying one. A GET, a CORS preflight, another
|
|
434
|
+
method, a look-alike URL, or a request to the same URL with no card in it
|
|
435
|
+
(an IHG stay paid with points, a saved card, a room another SynXis hotel
|
|
436
|
+
guarantees with a token) continues untouched. While this
|
|
435
437
|
build's copy of a profile does not take payments (every profile is in
|
|
436
438
|
observe today) a card request is refused as `unsupported_checkout` before
|
|
437
439
|
any authorization exists. When the body is the request the profile
|
|
438
440
|
reviewed, the controller reads `unsupported` with reason
|
|
439
441
|
`merchant_card_endpoint_not_enabled` and the event names the profile; a
|
|
440
442
|
card in any other body reads reason `unrecognized_payment_endpoint`, and
|
|
441
|
-
the event names only the endpoint origin.
|
|
443
|
+
the event names only the endpoint origin. LookFantastic's checkout takes
|
|
444
|
+
the card in BR-DGE's card fields and then sends the masked card number
|
|
445
|
+
BR-DGE returns in its order, so the adapters refuse that order with reason
|
|
446
|
+
`unrecognized_payment_endpoint`. For a
|
|
442
447
|
profile that takes payments, the adapter captures the request's session:
|
|
443
448
|
the profile's own header names from the paused request, the endpoint's
|
|
444
449
|
cookie jar for a cookie profile (`Network.getCookies` or
|
package/dist/card-endpoint.d.ts
CHANGED
|
@@ -12,7 +12,8 @@ export interface CardEndpointProfile {
|
|
|
12
12
|
readonly frame: string | null;
|
|
13
13
|
/** The exact https origin the card request goes to. */
|
|
14
14
|
readonly endpoint: string;
|
|
15
|
-
|
|
15
|
+
/** A POST, or a PUT for a page that sends its card request as one (Target Optical's addPayment). */
|
|
16
|
+
readonly method: 'POST' | 'PUT';
|
|
16
17
|
readonly path: string;
|
|
17
18
|
readonly query: Readonly<Record<string, string>>;
|
|
18
19
|
/** json_api is a JSON object sent as application/vnd.api+json. */
|
|
@@ -130,6 +131,13 @@ export declare function cardEndpointById(id: string): CardEndpointProfile | null
|
|
|
130
131
|
export declare function cardEndpointPasses(profile: CardEndpointProfile, body: string | null, contentType: string | undefined): boolean;
|
|
131
132
|
/** The reviewed profile (any status) one of whose resend requests `url` and `method` are, or null: the adapters abort it. */
|
|
132
133
|
export declare function cardEndpointResendFor(url: string, method: string): CardEndpointProfile | null;
|
|
134
|
+
/**
|
|
135
|
+
* Whether a URL is any profile's card request or one of its resends, by any method the
|
|
136
|
+
* table names: the Playwright adapter's route filter, which sees the URL alone. The
|
|
137
|
+
* pre-claim gates then judge each routed request by its own method and body (a GET or a
|
|
138
|
+
* CORS preflight to the same URL defers), so routing a URL is never a decision to pause it.
|
|
139
|
+
*/
|
|
140
|
+
export declare function cardEndpointRouted(url: string): boolean;
|
|
133
141
|
/** The origins of the profiles whose merchant keeps its own record of the order: the only responses card-endpoint-facts.ts records. */
|
|
134
142
|
export declare function cardEndpointFactsOrigins(): readonly string[];
|
|
135
143
|
/**
|
package/dist/card-endpoint.js
CHANGED
|
@@ -11,8 +11,9 @@
|
|
|
11
11
|
// typed into, the exact origin, path and query of the card request, which session
|
|
12
12
|
// headers and cookies it needs, and which answer headers may travel back.
|
|
13
13
|
//
|
|
14
|
-
// The adapters arm every profile's endpoint. A
|
|
15
|
-
// request URL
|
|
14
|
+
// The adapters arm every profile's endpoint. A request that is exactly a profile's card
|
|
15
|
+
// request, its URL and its method (a POST, or Target Optical's PUT), pauses when it
|
|
16
|
+
// carries a card (cardEndpointCarriesCard); one that carries
|
|
16
17
|
// none, such as a stay paid with points or a card a processor's own fields tokenized,
|
|
17
18
|
// is the merchant's own business and continues untouched. While this build's copy of
|
|
18
19
|
// the profile does not take payments (observe, a fact only a partner HAR can pin, no
|
|
@@ -108,6 +109,18 @@ export function cardEndpointResendFor(url, method) {
|
|
|
108
109
|
return null;
|
|
109
110
|
}
|
|
110
111
|
}
|
|
112
|
+
// Every method a profile's card request or one of its resends is sent with (a POST, Target
|
|
113
|
+
// Optical's PUT addPayment and PUT card save), from this build's own table.
|
|
114
|
+
const ROUTED_METHODS = [...new Set(PROFILES.flatMap((profile) => [profile.method, ...(profile.resends ?? []).map((resend) => resend.method)]))];
|
|
115
|
+
/**
|
|
116
|
+
* Whether a URL is any profile's card request or one of its resends, by any method the
|
|
117
|
+
* table names: the Playwright adapter's route filter, which sees the URL alone. The
|
|
118
|
+
* pre-claim gates then judge each routed request by its own method and body (a GET or a
|
|
119
|
+
* CORS preflight to the same URL defers), so routing a URL is never a decision to pause it.
|
|
120
|
+
*/
|
|
121
|
+
export function cardEndpointRouted(url) {
|
|
122
|
+
return ROUTED_METHODS.some((method) => cardEndpointFor(url, method) !== null || cardEndpointResendFor(url, method) !== null);
|
|
123
|
+
}
|
|
111
124
|
/** The origins of the profiles whose merchant keeps its own record of the order: the only responses card-endpoint-facts.ts records. */
|
|
112
125
|
export function cardEndpointFactsOrigins() {
|
|
113
126
|
return [...new Set(PROFILES.filter((profile) => profile.facts).map((profile) => profile.endpoint))];
|
package/dist/cdp.js
CHANGED
|
@@ -12,6 +12,7 @@ import { GMO_TOKEN_V1_PATTERNS, gmoRequestShop, isGmoTokenV1Request, recognizesG
|
|
|
12
12
|
import { DatatransRepeats } from './datatrans.js';
|
|
13
13
|
import { SHIJI_STS_COMPANION_WINDOW_MS, ShijiStsCompanions, ShijiStsError, isShijiStsHost, shijiStsTokenContinuation, } from './shiji-sts.js';
|
|
14
14
|
import { isArmedEkashuUrl } from './ekashu.js';
|
|
15
|
+
import { PAYPAL_HOSTED_FIELDS_PATTERNS, isPaypalHostedFieldsConfirm } from './paypal-hosted-fields.js';
|
|
15
16
|
import { CheckoutAttachmentError, attachmentDeadline, attachmentFailure, withinAttachmentDeadline } from './attachment.js';
|
|
16
17
|
import { cseBody } from './cse-body.js';
|
|
17
18
|
import { STRIPE_SETUP_INTENT_CONFIRM, setupConfirmPayingWith } from './stripe-setup.js';
|
|
@@ -23,7 +24,7 @@ import { hostedFormSubmittedPage } from './hosted-form.js';
|
|
|
23
24
|
import { amadeusSendWindowMs, armSendWindow, failsAsNetworkError } from './amadeus.js';
|
|
24
25
|
import { CheckoutLifecycle, paymentEndpointGuards } from './lifecycle.js';
|
|
25
26
|
import { CaptureContexts, MicroformFrames, judgeMicroformFrameUrls, loadedCardMicroformFromUrls, refusedRequestFailure, } from './cybersource-flex.js';
|
|
26
|
-
import { CARD_ENDPOINT_REFUSED_STEP_ANSWER, CARD_ENDPOINT_WITHHELD_ANSWER, CardEndpointRefusedError, captureCardEndpointSession, cardEndpointAnswerHeaders, cardEndpointCookies, cardEndpointDetail,
|
|
27
|
+
import { CARD_ENDPOINT_REFUSED_STEP_ANSWER, CARD_ENDPOINT_WITHHELD_ANSWER, CardEndpointRefusedError, captureCardEndpointSession, cardEndpointAnswerHeaders, cardEndpointCookies, cardEndpointDetail, cardEndpointNavigationAnswer, cardEndpointPatterns, cardEndpointRouted, } from './card-endpoint.js';
|
|
27
28
|
import { CardEndpointFactsWatch } from './card-endpoint-facts.js';
|
|
28
29
|
import { PAYRAILS_GUARD_PATTERNS, PAYRAILS_INIT_BODY_LIMIT, PayrailsSessionCapture, isPayrailsHost, payrailsInitCandidate, recognizesPayrails, } from './payrails.js';
|
|
29
30
|
import { cardEndpointStoppedPage, refusedNavigationOutcome } from './card-endpoint-page.js';
|
|
@@ -621,13 +622,13 @@ export function withCorsHeaders(headers, cors) {
|
|
|
621
622
|
return out;
|
|
622
623
|
}
|
|
623
624
|
/**
|
|
624
|
-
* The request a card endpoint approval is asked for: `request` names the URL and
|
|
625
|
-
* nothing else, and the profile travels with the captured
|
|
626
|
-
* merchant_card_endpoint, the one member the API seals, with the merchant's
|
|
627
|
-
* record of the order for a profile that reads one.
|
|
625
|
+
* The request a card endpoint approval is asked for: `request` names the URL and the
|
|
626
|
+
* profile's own method and nothing else, and the profile travels with the captured
|
|
627
|
+
* session in merchant_card_endpoint, the one member the API seals, with the merchant's
|
|
628
|
+
* own cut record of the order for a profile that reads one.
|
|
628
629
|
*/
|
|
629
630
|
function cardEndpointRequest(profile, session, facts = null) {
|
|
630
|
-
return { url: session.url, method:
|
|
631
|
+
return { url: session.url, method: profile.method, headers: {}, body: '', merchant_card_endpoint: { profile: profile.id, session, ...(facts ? { facts } : {}) } };
|
|
631
632
|
}
|
|
632
633
|
/**
|
|
633
634
|
* The merchant's own record of the order for a paused card request of a profile that
|
|
@@ -1909,10 +1910,13 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
|
|
|
1909
1910
|
// merchant, and a request with no card in it continues untouched.
|
|
1910
1911
|
// Every Payrails host is intercepted only for an org the API served Payrails
|
|
1911
1912
|
// to, so the gate can abort a card-form payment (see payrails.ts).
|
|
1913
|
+
// PayPal's legacy Hosted Fields confirm is armed for every org, whatever the
|
|
1914
|
+
// registry serves: one that carries the typed card is paused only to be
|
|
1915
|
+
// refused, and one with no card continues (see paypal-hosted-fields.ts).
|
|
1912
1916
|
const urlPatterns = [...new Set([...(derived.length > 0 ? derived : FALLBACK_CARD_PATTERNS), ...guards.patterns,
|
|
1913
1917
|
...(stripeCheckout.isEnabled() ? ['https://api.stripe.com/*'] : []), MERCADO_CHECKOUT_PATTERN, ...merchantPatterns,
|
|
1914
1918
|
...(recognizesGmo(opts.vault) ? GMO_TOKEN_V1_PATTERNS : []), ...juspayCheckout.urlPatterns(), ...cardEndpointPatterns(),
|
|
1915
|
-
...(payrails ? PAYRAILS_GUARD_PATTERNS : [])])];
|
|
1919
|
+
...(payrails ? PAYRAILS_GUARD_PATTERNS : []), ...PAYPAL_HOSTED_FIELDS_PATTERNS])];
|
|
1916
1920
|
const arm = async (sessionId, resume = false, worker = false) => {
|
|
1917
1921
|
const key = sessionId ?? '__root__';
|
|
1918
1922
|
if (armed.has(key))
|
|
@@ -3173,17 +3177,19 @@ export async function attachToPlaywright(page, opts) {
|
|
|
3173
3177
|
// GMO token v1's JSONP GET, routed only for an org the API served GMO to (see attachToCdp).
|
|
3174
3178
|
|| (isGmoTokenV1Request(url.toString()) && recognizesGmo(opts.vault))
|
|
3175
3179
|
|| juspayCheckout.matches(url.toString())
|
|
3176
|
-
// Every reviewed card endpoint, in any status
|
|
3177
|
-
//
|
|
3178
|
-
|
|
3179
|
-
//
|
|
3180
|
-
||
|
|
3180
|
+
// Every reviewed card endpoint, in any status, and every request that sends the typed card to such a merchant
|
|
3181
|
+
// again, by any method the table names (Target Optical's addPayment and card save are PUTs). The pre-claim
|
|
3182
|
+
// gates hold only a card request that carries a card, refuse one this build does not pay, and refuse a resend
|
|
3183
|
+
// in every status.
|
|
3184
|
+
|| cardEndpointRouted(url.toString())
|
|
3181
3185
|
|| aci.matches(url.toString())
|
|
3182
3186
|
// Every Payrails host, routed only for an org the API served Payrails to (see attachToCdp).
|
|
3183
3187
|
|| (payrails !== null && isPayrailsHost(url.toString()))
|
|
3184
3188
|
// Every request to an NMI host whose entry is served (ekashu.ts), as attachToCdp arms the
|
|
3185
3189
|
// host: the card page's post and its 3-D Secure calls reach the NMI gate and fail.
|
|
3186
|
-
|| isArmedEkashuUrl(opts.vault, url.toString())
|
|
3190
|
+
|| isArmedEkashuUrl(opts.vault, url.toString())
|
|
3191
|
+
// PayPal's legacy Hosted Fields confirm, for every org, as attachToCdp arms it (paypal-hosted-fields.ts).
|
|
3192
|
+
|| isPaypalHostedFieldsConfirm(url.toString()), async (route) => {
|
|
3187
3193
|
const request = route.request();
|
|
3188
3194
|
// When this request paused, and the page it came from: the merchant's total is read as of now.
|
|
3189
3195
|
const pausedAt = Date.now();
|
package/dist/client.js
CHANGED
|
@@ -1787,9 +1787,10 @@ export class VaultClient {
|
|
|
1787
1787
|
// with that method, so a caller handing in another one would get it
|
|
1788
1788
|
// silently rewritten and the cardholder's device would replay something the
|
|
1789
1789
|
// caller never asked for. A direct caller deserves to be told, not surprised.
|
|
1790
|
-
|
|
1791
|
-
//
|
|
1792
|
-
const
|
|
1790
|
+
// A card endpoint has no recognizer: its card request is its profile's own method (a POST, or
|
|
1791
|
+
// Target Optical's PUT), which admitCardEndpoint already held the request to.
|
|
1792
|
+
const method = (input.request.method ?? cardEndpoint?.method ?? 'POST').toUpperCase();
|
|
1793
|
+
const cardMethod = rec ? recognizerRequestMethod(rec) : cardEndpoint ? cardEndpoint.method : 'POST';
|
|
1793
1794
|
if (method !== cardMethod) {
|
|
1794
1795
|
throw new Error(cardMethod === 'GET'
|
|
1795
1796
|
? `a ${rec?.psp} card registration is a GET; got ${method} for ${redactUrl(input.request.url)}.`
|
|
@@ -1854,7 +1855,7 @@ export class VaultClient {
|
|
|
1854
1855
|
...(input.cardId ? { cardId: input.cardId } : {}),
|
|
1855
1856
|
execution_mode: 'user_approval',
|
|
1856
1857
|
checkout_origin: cardEndpoint.session.page_origin,
|
|
1857
|
-
request: { url: cardEndpoint.session.url, method:
|
|
1858
|
+
request: { url: cardEndpoint.session.url, method: cardEndpoint.method, headers: {}, body: '' },
|
|
1858
1859
|
merchant_card_endpoint: { profile: cardEndpoint.profile, session: cardEndpoint.session, ...(cardEndpoint.facts ? { facts: cardEndpoint.facts } : {}) },
|
|
1859
1860
|
} : {
|
|
1860
1861
|
user: input.user,
|
|
@@ -2707,8 +2708,10 @@ export class VaultClient {
|
|
|
2707
2708
|
/**
|
|
2708
2709
|
* A card endpoint request, checked whole before any network call: a profile in this
|
|
2709
2710
|
* build's table; no other checkout context, Autopilot or preparation (the cardholder
|
|
2710
|
-
* approves every such payment); the agent's amount, the only authority here;
|
|
2711
|
-
* session
|
|
2711
|
+
* approves every such payment); the agent's amount, the only authority here; the
|
|
2712
|
+
* session's own URL with the profile's own method (a request that names none takes
|
|
2713
|
+
* the profile's, a POST or Target Optical's PUT); a session the API and the device
|
|
2714
|
+
* would take (payment-core
|
|
2712
2715
|
* merchantCardEndpointSessionRefusal: the profile's URL, headers, cookies, Referer and
|
|
2713
2716
|
* body, paused on its checkout page); for a merchant that keeps its own record of the
|
|
2714
2717
|
* order, that record, held to the agent's amount and currency, and refused for an order
|
|
@@ -2731,7 +2734,7 @@ function admitCardEndpoint(input) {
|
|
|
2731
2734
|
if (input.amount == null || typeof input.currency !== 'string' || !input.currency)
|
|
2732
2735
|
throw new CardEndpointRefusedError(profile.id, 'amount_required');
|
|
2733
2736
|
const session = named.session;
|
|
2734
|
-
if (!isRecord(session) || (input.request.method ??
|
|
2737
|
+
if (!isRecord(session) || (input.request.method ?? profile.method).toUpperCase() !== profile.method || input.request.url !== session.url) {
|
|
2735
2738
|
throw new CardEndpointRefusedError(profile.id, 'request_unsupported', 'the request is not the session\'s card request');
|
|
2736
2739
|
}
|
|
2737
2740
|
const refusal = cardEndpointSessionRefusal(profile, session);
|
|
@@ -2740,7 +2743,7 @@ function admitCardEndpoint(input) {
|
|
|
2740
2743
|
const facts = admitCardEndpointFacts(profile, session, named.facts, input);
|
|
2741
2744
|
if (!cardEndpointLive(profile))
|
|
2742
2745
|
throw new CardEndpointRefusedError(profile.id, 'not_enabled');
|
|
2743
|
-
return { profile: profile.id, session, ...(facts ? { facts } : {}) };
|
|
2746
|
+
return { profile: profile.id, method: profile.method, session, ...(facts ? { facts } : {}) };
|
|
2744
2747
|
}
|
|
2745
2748
|
/**
|
|
2746
2749
|
* The merchant's own record of the order, for a profile whose merchant keeps one
|
package/dist/lifecycle.d.ts
CHANGED
|
@@ -225,10 +225,12 @@ export declare class CheckoutLifecycle implements CheckoutController {
|
|
|
225
225
|
* unsupported_primer_request), a Payrails request its gate refuses
|
|
226
226
|
* (`unsupported_payrails_card_payment` and the card-save refusal codes), a
|
|
227
227
|
* Shiji STS request its gate refuses (shiji_sts_keep_on_file and the other
|
|
228
|
-
* shiji-sts.ts reasons),
|
|
229
|
-
* the cardholder's device can fill (processor_card_page: NMI's)
|
|
228
|
+
* shiji-sts.ts reasons), a post to a processor's own card page that only
|
|
229
|
+
* the cardholder's device can fill (processor_card_page: NMI's), or a card
|
|
230
|
+
* sent to PayPal's legacy Hosted Fields confirm, which no Vault mode takes
|
|
231
|
+
* over (unsupported_paypal_hosted_fields, paypal-hosted-fields.ts).
|
|
230
232
|
*/
|
|
231
|
-
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'): void;
|
|
233
|
+
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'): void;
|
|
232
234
|
/**
|
|
233
235
|
* A card request that only a prepared checkout pays (an Adyen merchant
|
|
234
236
|
* profile's, or a Fiserv card capture) arrived with no preparation on this
|
package/dist/lifecycle.js
CHANGED
|
@@ -201,8 +201,10 @@ export class CheckoutLifecycle {
|
|
|
201
201
|
* unsupported_primer_request), a Payrails request its gate refuses
|
|
202
202
|
* (`unsupported_payrails_card_payment` and the card-save refusal codes), a
|
|
203
203
|
* Shiji STS request its gate refuses (shiji_sts_keep_on_file and the other
|
|
204
|
-
* shiji-sts.ts reasons),
|
|
205
|
-
* the cardholder's device can fill (processor_card_page: NMI's)
|
|
204
|
+
* shiji-sts.ts reasons), a post to a processor's own card page that only
|
|
205
|
+
* the cardholder's device can fill (processor_card_page: NMI's), or a card
|
|
206
|
+
* sent to PayPal's legacy Hosted Fields confirm, which no Vault mode takes
|
|
207
|
+
* over (unsupported_paypal_hosted_fields, paypal-hosted-fields.ts).
|
|
206
208
|
*/
|
|
207
209
|
unsupported(reason = 'unrecognized_payment_endpoint') {
|
|
208
210
|
this.held = true;
|