@agent-cards/sdk 0.23.14 → 0.23.15

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.
@@ -0,0 +1,126 @@
1
+ /**
2
+ * PayPal's Card Fields and PayPal's guest card form, which this SDK refuses for every company until a
3
+ * synced recognizer takes the request.
4
+ *
5
+ * Read 2026-10-15 from PayPal's public bundles and card-free page reads (WooCommerce PayPal Payments
6
+ * 4.1.3 checkouts on PayPal JS SDK 5.0.582, card-field client 1.270.0; web-sdk v6 6.68.0):
7
+ *
8
+ * Card Fields v5 (components=card-fields): each field is a frame on www.paypal.com/smart/card-field,
9
+ * and at submit the number frame sends the typed card as plaintext JSON, a same-origin XHR:
10
+ * POST https://www.paypal.com/v2/checkout/orders/<order id>/confirm-payment-source
11
+ * {"payment_source":{"card":{"number","expiry":"YYYY-MM","security_code","name"?,"billing_address"?}}}
12
+ * Card Fields v6 (web-sdk v6): the same request, sent to api-m.paypal.com.
13
+ * Card Fields saving a card with no purchase: POST https://www.paypal.com/graphql?UpdateVaultSetupToken,
14
+ * the card at variables.paymentSource.card.
15
+ * PayPal's guest card form, behind the black "Debit or Credit Card" button: PayPal's checkout page
16
+ * sends the card as POST https://www.paypal.com/graphql?OnboardGuestMutation, the number at
17
+ * variables.card.cardNumber.
18
+ * PayPal's sandbox serves each on www.sandbox.paypal.com and api-m.sandbox.paypal.com.
19
+ *
20
+ * An SDK without this guard let the agent's placeholder card reach PayPal there, and PayPal declined it.
21
+ * Both adapters arm these paths whatever the synced registry serves, and the pre-claim gate
22
+ * (paypalCardFormsPreClaim, pre-claim.ts) judges each POST:
23
+ * - a Card Fields confirm whose body carries a number at payment_source.card.number aborts, reason
24
+ * `unsupported_paypal_card_fields`, unless the synced registry recognizes it as a card request: an
25
+ * org Agentcard turned PayPal Card Fields on for pauses and pays that confirm through the recognizer,
26
+ * which judges the body itself;
27
+ * - a GraphQL operation carrying a card aborts whatever the registry serves, since no recognizer pays
28
+ * one: the guest form's card as `unsupported_paypal_guest_checkout`, a card saved with no purchase as
29
+ * `unsupported_paypal_card_fields`;
30
+ * - a body with no card (a PayPal wallet confirm, a saved card, every other GraphQL operation the
31
+ * buttons send) continues untouched;
32
+ * - a confirm the adapter could not read aborts that request alone, since nothing proves it carries no
33
+ * card. A GraphQL post it could not read aborts only when its query names one of the two card
34
+ * operations: PayPal's buttons send many others, and failing them would break PayPal's own wallet.
35
+ * Each abort holds the attachment as every unsupported refusal does: the page's next submit would carry
36
+ * the card again, so another way to pay needs a new attachment.
37
+ *
38
+ * A guest form PayPal opens in a popup window runs in another page, which an attachment covers only when
39
+ * the agent attaches the SDK to that page as well. PayPal's BIN lookup (/v2/payments/find-bin-details)
40
+ * reads the first digits the agent types and pays nothing; neither adapter intercepts it.
41
+ */
42
+ import { paypalHostedFieldsBody } from './paypal-hosted-fields.js';
43
+ export const PAYPAL_CARD_FIELDS_REASON = 'unsupported_paypal_card_fields';
44
+ export const PAYPAL_GUEST_CHECKOUT_REASON = 'unsupported_paypal_guest_checkout';
45
+ /** The hosts Card Fields sends its confirm to: v5 on www, v6 on api-m, live and sandbox. */
46
+ export const PAYPAL_CARD_FIELDS_HOSTS = Object.freeze(['www.paypal.com', 'www.sandbox.paypal.com',
47
+ 'api-m.paypal.com', 'api-m.sandbox.paypal.com']);
48
+ /** The hosts of PayPal's own pages, whose GraphQL endpoint takes the guest form's card and a card saved with no purchase. */
49
+ export const PAYPAL_GRAPHQL_HOSTS = Object.freeze(['www.paypal.com', 'www.sandbox.paypal.com']);
50
+ const CONFIRM_PATH = /^\/v2\/checkout\/orders\/[^/]+\/confirm-payment-source\/?$/;
51
+ const GRAPHQL_PATH = /^\/graphql\/?$/;
52
+ /** The two GraphQL operations that carry a card, as PayPal's clients name them in the query string. */
53
+ const CARD_OPERATIONS = ['OnboardGuestMutation', 'UpdateVaultSetupToken'];
54
+ /**
55
+ * CDP Fetch patterns, armed on every attachment: the confirm on its four hosts and GraphQL on PayPal's
56
+ * two page hosts. Chrome sends the canonical URL, whose host is lower case, and the trailing `*` keeps a
57
+ * query or a trailing slash paused. Nothing else on www.paypal.com is armed.
58
+ */
59
+ export const PAYPAL_CARD_FORM_PATTERNS = Object.freeze([
60
+ ...PAYPAL_CARD_FIELDS_HOSTS.map((host) => `https://${host}/v2/checkout/orders/*/confirm-payment-source*`),
61
+ ...PAYPAL_GRAPHQL_HOSTS.map((host) => `https://${host}/graphql*`),
62
+ ]);
63
+ function paypalUrl(url, hosts, path) {
64
+ let parsed;
65
+ try {
66
+ parsed = new URL(url);
67
+ }
68
+ catch {
69
+ return null;
70
+ }
71
+ return parsed.protocol === 'https:' && !parsed.port && !parsed.username && !parsed.password
72
+ && hosts.includes(parsed.hostname.toLowerCase()) && path.test(parsed.pathname) ? parsed : null;
73
+ }
74
+ /** Whether this URL is the Card Fields confirm on one of its four hosts (https, standard port, no credentials). */
75
+ export function isPaypalCardFieldsConfirm(url) {
76
+ return paypalUrl(url, PAYPAL_CARD_FIELDS_HOSTS, CONFIRM_PATH) !== null;
77
+ }
78
+ /** Whether this URL is GraphQL on one of PayPal's two page hosts. */
79
+ export function isPaypalGraphql(url) {
80
+ return paypalUrl(url, PAYPAL_GRAPHQL_HOSTS, GRAPHQL_PATH) !== null;
81
+ }
82
+ /** Whether this URL is any request the guard judges, for the Playwright route's predicate. */
83
+ export function isPaypalCardFormUrl(url) {
84
+ return isPaypalCardFieldsConfirm(url) || isPaypalGraphql(url);
85
+ }
86
+ /** Whether a GraphQL URL's query names one of the two card operations (`/graphql?OnboardGuestMutation`). */
87
+ export function paypalGraphqlNamesCardOperation(url) {
88
+ const parsed = paypalUrl(url, PAYPAL_GRAPHQL_HOSTS, GRAPHQL_PATH);
89
+ return parsed !== null && CARD_OPERATIONS.some((name) => parsed.searchParams.has(name));
90
+ }
91
+ const record = (value) => !!value && typeof value === 'object' && !Array.isArray(value);
92
+ const present = (value) => value !== undefined && value !== null && value !== '';
93
+ /**
94
+ * What a GraphQL post carries, read as PayPal reads it (the last of a repeated key wins in both), for one
95
+ * operation or a batch of them: 'guest_card' when an operation's variables.card.cardNumber holds anything
96
+ * but null or an empty string, 'saved_card' when an operation's variables.paymentSource.card is an object,
97
+ * 'no_card' for any other JSON object or array of them, and 'unreadable' for a body the adapter could not
98
+ * read (null), an empty one, or one that is neither.
99
+ */
100
+ export function paypalGraphqlCard(body) {
101
+ if (typeof body !== 'string' || body === '')
102
+ return 'unreadable';
103
+ let parsed;
104
+ try {
105
+ parsed = JSON.parse(body);
106
+ }
107
+ catch {
108
+ return 'unreadable';
109
+ }
110
+ const operations = Array.isArray(parsed) ? parsed : [parsed];
111
+ if (operations.length === 0 || !operations.every(record))
112
+ return 'unreadable';
113
+ let carried = 'no_card';
114
+ for (const operation of operations) {
115
+ const variables = operation.variables;
116
+ if (!record(variables))
117
+ continue;
118
+ if (record(variables.card) && present(variables.card.cardNumber))
119
+ return 'guest_card';
120
+ if (record(variables.paymentSource) && record(variables.paymentSource.card))
121
+ carried = 'saved_card';
122
+ }
123
+ return carried;
124
+ }
125
+ /** What a Card Fields confirm carries: its body is the Orders v2 shape Hosted Fields also sends. */
126
+ export const paypalCardFieldsBody = paypalHostedFieldsBody;
@@ -252,6 +252,7 @@ export declare function isArmedCreditguardCall(vault: {
252
252
  export declare const ekashuPreClaim: PreClaimGate;
253
253
  export declare const redsysPreClaim: PreClaimGate;
254
254
  export declare const paypalHostedFieldsPreClaim: PreClaimGate;
255
+ export declare const paypalCardFormsPreClaim: PreClaimGate;
255
256
  export declare const cybersourceSecureAcceptancePreClaim: PreClaimGate;
256
257
  export declare const worldlineMyCheckoutPreClaim: PreClaimGate;
257
258
  export declare const paddleVgsPayPreClaim: PreClaimGate;
package/dist/pre-claim.js CHANGED
@@ -16,8 +16,9 @@
16
16
  // card-saving bodies, WSPay's confirm URL, which also carries bodies with
17
17
  // no new card, Shiji STS's getTokenId and getCvvId, which also carry
18
18
  // requests the vault must never pay, PayPal's legacy Hosted Fields
19
- // confirm and CyberSource Secure Acceptance's Silent Order POST, whose card
20
- // no Vault mode takes over, Worldline MyCheckout's card form, held to the
19
+ // confirm, PayPal's Card Fields and guest card form, and CyberSource Secure
20
+ // Acceptance's Silent Order POST, whose card no Vault mode takes over until a
21
+ // recognizer does, Worldline MyCheckout's card form, held to the
21
22
  // card product its page chose, and EBANX's 3-D Secure calls and hosted confirm,
22
23
  // which send the card again after its token and leave only from the
23
24
  // cardholder's device, under the token request's approval.
@@ -80,6 +81,7 @@ import { classifyVerifoneProfileRequest, verifoneTemplatedUrl } from './verifone
80
81
  import { ekashuEntryUrl, isEkashuEntry, isEkashuHost } from './ekashu.js';
81
82
  import { isRedsysEntry, isRedsysHost, redsysEntryUrl } from './redsys.js';
82
83
  import { PAYPAL_HOSTED_FIELDS_REASON, isPaypalHostedFieldsConfirm, paypalHostedFieldsBody } from './paypal-hosted-fields.js';
84
+ import { PAYPAL_CARD_FIELDS_REASON, PAYPAL_GUEST_CHECKOUT_REASON, isPaypalCardFieldsConfirm, isPaypalGraphql, paypalCardFieldsBody, paypalGraphqlCard, paypalGraphqlNamesCardOperation, } from './paypal-card-forms.js';
83
85
  import { CYBERSOURCE_SECURE_ACCEPTANCE_REASON, CYBERSOURCE_SECURE_ACCEPTANCE_RECIPIENT, cybersourceSecureAcceptanceRequest, isCybersourceSecureAcceptanceUrl, isSecureAcceptanceFramePost, } from './cybersource-secure-acceptance.js';
84
86
  import { isWorldlineMyCheckoutEndpoint } from './worldline-mycheckout.js';
85
87
  import { paddleVgsPayEventUrl } from './paddle-vgs-pay.js';
@@ -1671,6 +1673,60 @@ async function paypalHostedFieldsUnreadable(ctx) {
1671
1673
  await ctx.effects.abort();
1672
1674
  return { kind: 'abort' };
1673
1675
  }
1676
+ // PayPal's Card Fields and guest card form (paypal-card-forms.ts). Every company's adapters hand each
1677
+ // POST to the Card Fields confirm (www and api-m, live and sandbox) and to GraphQL on www (live and
1678
+ // sandbox) to this gate:
1679
+ // - a confirm with a card number at payment_source.card.number aborts as an unsupported checkout,
1680
+ // reason `unsupported_paypal_card_fields`, with no claim, create or prompt, unless the synced
1681
+ // registry recognizes it as a card request: then it defers, and the PayPal Card Fields lane pauses
1682
+ // it for the cardholder's approval, judging the body itself;
1683
+ // - a GraphQL operation with a card aborts the same way whatever the registry serves:
1684
+ // `unsupported_paypal_guest_checkout` for the guest form's variables.card.cardNumber,
1685
+ // `unsupported_paypal_card_fields` for a card saved with no purchase (variables.paymentSource.card);
1686
+ // - a confirm the adapter could not read, and a GraphQL post it could not read whose query names one
1687
+ // of the two card operations, abort that request alone with a `blocked` event,
1688
+ // `unreadable_paypal_request`, and do not hold the attachment;
1689
+ // - anything else (a PayPal wallet confirm, a saved card, the buttons' other GraphQL operations, a GET
1690
+ // or a preflight) defers, and the arbiter continues it untouched.
1691
+ // Synchronous until it must abort, like every gate. Exported for the gate registry's tests.
1692
+ export const paypalCardFormsPreClaim = (ctx) => {
1693
+ const { url, method } = ctx.request;
1694
+ if (String(method).toUpperCase() !== 'POST')
1695
+ return undefined;
1696
+ const confirm = isPaypalCardFieldsConfirm(url);
1697
+ if (!confirm && !isPaypalGraphql(url))
1698
+ return undefined;
1699
+ if (confirm && ctx.vault.isCardRequest(url, method))
1700
+ return undefined;
1701
+ let body;
1702
+ // A reader that throws (Playwright's postData() can) proves nothing either way.
1703
+ try {
1704
+ body = ctx.request.body;
1705
+ }
1706
+ catch {
1707
+ body = null;
1708
+ }
1709
+ if (confirm) {
1710
+ const carried = paypalCardFieldsBody(body);
1711
+ if (carried === 'no_card')
1712
+ return undefined;
1713
+ return carried === 'card' ? paypalCardFormAbort(ctx, PAYPAL_CARD_FIELDS_REASON) : paypalHostedFieldsUnreadable(ctx);
1714
+ }
1715
+ const carried = paypalGraphqlCard(body);
1716
+ if (carried === 'guest_card')
1717
+ return paypalCardFormAbort(ctx, PAYPAL_GUEST_CHECKOUT_REASON);
1718
+ if (carried === 'saved_card')
1719
+ return paypalCardFormAbort(ctx, PAYPAL_CARD_FIELDS_REASON);
1720
+ return carried === 'unreadable' && paypalGraphqlNamesCardOperation(url) ? paypalHostedFieldsUnreadable(ctx) : undefined;
1721
+ };
1722
+ async function paypalCardFormAbort(ctx, reason) {
1723
+ const { url, method } = ctx.request;
1724
+ ctx.preparationGate.invalidate('unsupported_checkout');
1725
+ ctx.lifecycle.unsupported(reason);
1726
+ ctx.onEvent?.({ type: 'unsupported_checkout', detail: { url: eventUrl(ctx.vault, url), method, reason } });
1727
+ await ctx.effects.abort();
1728
+ return { kind: 'abort' };
1729
+ }
1674
1730
  // CyberSource Secure Acceptance's Silent Order POST (cybersource-secure-acceptance.ts). The merchant's
1675
1731
  // page posts the card the agent typed, beside the fields its server signed, to a /silent/ path on
1676
1732
  // CyberSource's Secure Acceptance hosts, which no Vault mode takes over, so every company's adapters
@@ -2092,7 +2148,7 @@ async function unsupportedContinue(ctx) {
2092
2148
  * after Stripe is never offered a step Stripe claimed: Stripe answers, aborts or
2093
2149
  * pauses each one itself. */
2094
2150
  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, redsysPreClaim, paypalHostedFieldsPreClaim,
2095
- cybersourceSecureAcceptancePreClaim, worldlineMyCheckoutPreClaim, paddleVgsPayPreClaim, ebanxPreClaim];
2151
+ paypalCardFormsPreClaim, cybersourceSecureAcceptancePreClaim, worldlineMyCheckoutPreClaim, paddleVgsPayPreClaim, ebanxPreClaim];
2096
2152
  /** Run each gate in order; the first to return a decision wins. If none does,
2097
2153
  * the terminal arbiter decides between continue, abort (unsupported) and pause.
2098
2154
  * Returns synchronously while every gate defers synchronously (an ordinary card
@@ -20,8 +20,12 @@
20
20
  * paddle_vgs: prepared only. Paddle's checkout frame aborts its card request to its VGS card vault 10
21
21
  * seconds after Pay and sends it once more, too soon for an approval after Pay. Its environments are its
22
22
  * two card URLs: vault.paddle.com (production) and sandbox-vault.paddle.com (sandbox).
23
+ *
24
+ * wix_payments: prepared only. Wix's payment widget fails the payment 30 seconds after it asks Wix
25
+ * Cashier's card frame for a token, too soon for an approval after Pay. Its one environment is
26
+ * production: Wix's tokenizer has no test host.
23
27
  */
24
- export type PreparationProcessor = 'square' | 'braintree' | 'worldpay' | 'bambora' | 'mercado_pago' | 'recurly' | 'spreedly' | 'adyen' | 'checkout_com' | 'paysafe' | 'fiserv' | 'cybersource_flex' | 'aci' | 'creditguard' | 'worldpay_eprotect' | 'amadeus_checkout' | 'verifone' | 'cybersource_secure_acceptance' | 'paddle_vgs';
28
+ export type PreparationProcessor = 'square' | 'braintree' | 'worldpay' | 'bambora' | 'mercado_pago' | 'recurly' | 'spreedly' | 'adyen' | 'checkout_com' | 'paysafe' | 'fiserv' | 'cybersource_flex' | 'aci' | 'creditguard' | 'worldpay_eprotect' | 'amadeus_checkout' | 'verifone' | 'cybersource_secure_acceptance' | 'paddle_vgs' | 'wix_payments';
25
29
  export type PreparationEnvironment = 'production' | 'sandbox' | 'shared';
26
30
  /**
27
31
  * How the approved card reaches the processor: the device's own request (token), or
@@ -14,6 +14,7 @@ import { amadeusEnvironment, amadeusPreparationUrls, amadeusRequestRefusal, isPr
14
14
  import { isPreparedVerifoneRequest } from './verifone.js';
15
15
  import { isPreparedSecureAcceptanceRequest, secureAcceptanceEnvironment, secureAcceptancePreparationUrl, secureAcceptanceRequestRefusal, } from './cybersource-secure-acceptance.generated.js';
16
16
  import { PADDLE_VGS_CARDS_URLS, isPreparedPaddleVgsRequest } from './paddle-vgs.generated.js';
17
+ import { WIX_PAYMENTS_TOKENIZE_URL, isPreparedWixPaymentsRequest } from './wix-payments.generated.js';
17
18
  export function preparationMode(psp) {
18
19
  return psp === 'adyen' || psp === 'fiserv' || psp === 'verifone' ? 'cse' : 'token';
19
20
  }
@@ -53,6 +54,9 @@ export function preparedApprovalAnswersRetry(psp) {
53
54
  export function validPreparationEnvironment(psp, environment) {
54
55
  if (psp === 'bambora' || psp === 'mercado_pago' || psp === 'recurly' || psp === 'spreedly')
55
56
  return environment === 'shared';
57
+ // Wix Payments has production alone: its tokenizer has no test host.
58
+ if (psp === 'wix_payments')
59
+ return environment === 'production';
56
60
  return ['square', 'braintree', 'worldpay', 'adyen', 'checkout_com', 'paysafe', 'fiserv', 'cybersource_flex', 'aci', 'creditguard', 'worldpay_eprotect', 'amadeus_checkout', 'verifone',
57
61
  'cybersource_secure_acceptance', 'paddle_vgs'].includes(psp) && ['production', 'sandbox'].includes(environment);
58
62
  }
@@ -99,6 +103,9 @@ export function preparationEndpoint(psp, environment) {
99
103
  // Paddle VGS: the environment's one card URL, the only URL the claim takes for it.
100
104
  if (psp === 'paddle_vgs')
101
105
  return PADDLE_VGS_CARDS_URLS[environment];
106
+ // Wix Payments: the tokenize URL, the only URL the claim takes for it.
107
+ if (psp === 'wix_payments')
108
+ return WIX_PAYMENTS_TOKENIZE_URL;
102
109
  // A Verifone endpoint is its reviewed profile's leg, never one per environment (verifone.ts).
103
110
  if (psp === 'verifone')
104
111
  throw new Error('unsupported_preparation_processor');
@@ -252,6 +259,10 @@ export function matchesPreparedRequest(psp, environment, requestUrl, method, bod
252
259
  // the JSON:API content type and the frame's body), the rule the API and the Vault bind with too.
253
260
  if (psp === 'paddle_vgs')
254
261
  return isPreparedPaddleVgsRequest(requestUrl, method.toUpperCase(), body ?? null, environment, headers ?? {});
262
+ // Wix Payments: production, the tokenize URL and payment-core's whole request rule (a JSON content type, the
263
+ // site's app instance and the card frame's body), the rule the API and the Vault bind with too.
264
+ if (psp === 'wix_payments')
265
+ return isPreparedWixPaymentsRequest(requestUrl, method.toUpperCase(), body ?? null, environment, headers ?? {});
255
266
  if (psp === 'creditguard')
256
267
  return matchesPreparedCreditguard(environment, requestUrl, body, headers, binding);
257
268
  // A Verifone request is judged only through the profile its preparation names (matchesPreparation).
@@ -1,6 +1,6 @@
1
1
  // @ts-nocheck
2
2
  // Generated from @agent-cards/payment-core. Do not edit.
3
- // artifact-sha256: 7d04123b22f392dd6a11848e8cf59b62efb1669527938a7dd1c83a34cb315621
3
+ // artifact-sha256: 1d05273d23008e96c38c3be306ab215212b654fc275b96cf11e6452dee60c0d7
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
@@ -666,10 +666,10 @@ var TYPED_ARRAY = Object.getPrototypeOf(Uint8Array.prototype);
666
666
  var typedArrayName = Object.getOwnPropertyDescriptor(TYPED_ARRAY, Symbol.toStringTag).get;
667
667
  function parseRsaPublicKey(key) {
668
668
  const spki = key && typeof key === "object" ? key.spki : void 0;
669
- const pin3 = key && typeof key === "object" ? key.sha256 : void 0;
669
+ const pin4 = key && typeof key === "object" ? key.sha256 : void 0;
670
670
  if (typeof spki !== "string" || spki.length > 1024 || spki.length % 4 !== 0 || !BASE64.test(spki))
671
671
  throw refuse6("key");
672
- if (typeof pin3 !== "string" || !SHA256_HEX.test(pin3))
672
+ if (typeof pin4 !== "string" || !SHA256_HEX.test(pin4))
673
673
  throw refuse6("key");
674
674
  let binary;
675
675
  try {
@@ -681,7 +681,7 @@ function parseRsaPublicKey(key) {
681
681
  if (btoa(binary) !== spki)
682
682
  throw refuse6("key");
683
683
  const der = Uint8Array.from(binary, (c) => c.charCodeAt(0));
684
- if (sha256Hex(der) !== pin3)
684
+ if (sha256Hex(der) !== pin4)
685
685
  throw refuse6("key");
686
686
  const outer = readElement(der, 0, 48);
687
687
  if (outer.end !== der.length || outer.start + RSA_ALGORITHM.length > der.length)
@@ -1241,10 +1241,93 @@ var LYRA_MERCHANT_PINS = /* @__PURE__ */ Object.freeze([
1241
1241
  hosts: Object.freeze(["api.systempay.fr"])
1242
1242
  })
1243
1243
  ]);
1244
+ // src/wix-payments.js
1245
+ var WIX_PAYMENTS_PSP = "wix_payments";
1246
+ var WIX_PAYMENTS_TOKENIZE_URL = "https://cashier-pci.wixapps.net/_api/payment-gateway-web/tokenize/v2/tokens";
1247
+ var BODY_LIMIT5 = 8192;
1248
+ var ROOT_KEYS3 = /* @__PURE__ */ Object.freeze(["cvv", "creditCard"]);
1249
+ var CARD_KEYS5 = /* @__PURE__ */ Object.freeze(["number", "expiration", "additionalInformation", "billingAddress"]);
1250
+ var EXPIRATION_KEYS = /* @__PURE__ */ Object.freeze(["month", "year"]);
1251
+ var HOLDER_KEYS = /* @__PURE__ */ Object.freeze(["holderName"]);
1252
+ var CARD_NUMBER6 = /^[0-9]{12,19}$/;
1253
+ var SECURITY_CODE5 = /^[0-9]{3,4}$/;
1254
+ var FIRST_YEAR = 2e3;
1255
+ var LAST_YEAR = 2099;
1256
+ var FIELD_NAME = /^[A-Za-z][A-Za-z0-9]{0,63}$/;
1257
+ var MAX_FIELDS = 32;
1258
+ var MAX_NESTED_FIELDS = 8;
1259
+ var TEXT_LIMIT = 256;
1260
+ var CONTROL4 = /[\u0000-\u001f\u007f]/;
1261
+ var record6 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
1262
+ var sameKeys3 = (value, keys) => {
1263
+ const own = Object.keys(value);
1264
+ return own.length === keys.length && own.every((key, at) => key === keys[at]);
1265
+ };
1266
+ var plainText = (value) => typeof value === "string" && value.length <= TEXT_LIMIT && !CONTROL4.test(value) && (value.match(/[0-9]/g) ?? []).length < 12;
1267
+ var refuse9 = (code) => Object.freeze({ verdict: "refuse", code });
1268
+ var CARD8 = /* @__PURE__ */ Object.freeze({ verdict: "card" });
1269
+ function isWixPaymentsTokenizeUrl(url) {
1270
+ return url === WIX_PAYMENTS_TOKENIZE_URL;
1271
+ }
1272
+ function readBody3(body) {
1273
+ if (typeof body !== "string" || body === "" || body.length > BODY_LIMIT5)
1274
+ return null;
1275
+ let value;
1276
+ try {
1277
+ value = JSON.parse(body);
1278
+ if (!record6(value) || JSON.stringify(value) !== body)
1279
+ return null;
1280
+ }
1281
+ catch {
1282
+ return null;
1283
+ }
1284
+ return value;
1285
+ }
1286
+ var billingText = (value) => value === null || plainText(value);
1287
+ function billingFields(value) {
1288
+ if (!record6(value))
1289
+ return false;
1290
+ const names = Object.keys(value);
1291
+ if (names.length > MAX_FIELDS)
1292
+ return false;
1293
+ return names.every((name) => {
1294
+ const field = value[name];
1295
+ if (!FIELD_NAME.test(name))
1296
+ return false;
1297
+ if (!record6(field))
1298
+ return billingText(field);
1299
+ const nested = Object.keys(field);
1300
+ return nested.length <= MAX_NESTED_FIELDS && nested.every((key) => FIELD_NAME.test(key) && billingText(field[key]));
1301
+ });
1302
+ }
1303
+ function wixPaymentsBodyVerdict(body) {
1304
+ const value = readBody3(body);
1305
+ if (value === null || !sameKeys3(value, ROOT_KEYS3) || !record6(value.creditCard) || !sameKeys3(value.creditCard, CARD_KEYS5)) {
1306
+ return refuse9("wix_payments_body_unsupported");
1307
+ }
1308
+ const card = value.creditCard;
1309
+ const expiration = card.expiration;
1310
+ const holder = card.additionalInformation;
1311
+ if (typeof value.cvv !== "string" || !SECURITY_CODE5.test(value.cvv) || typeof card.number !== "string" || !CARD_NUMBER6.test(card.number) || !record6(expiration) || !sameKeys3(expiration, EXPIRATION_KEYS) || !Number.isInteger(expiration.month) || expiration.month < 1 || expiration.month > 12 || !Number.isInteger(expiration.year) || expiration.year < FIRST_YEAR || expiration.year > LAST_YEAR || !record6(holder) || !sameKeys3(holder, HOLDER_KEYS) || !plainText(holder.holderName) || !billingFields(card.billingAddress)) {
1312
+ return refuse9("wix_payments_body_unsupported");
1313
+ }
1314
+ return CARD8;
1315
+ }
1316
+ var pin3 = (fields) => Object.freeze({ ...fields, origins: Object.freeze([...fields.origins]) });
1317
+ var WIX_PAYMENTS_MERCHANT_PINS = /* @__PURE__ */ Object.freeze([
1318
+ pin3({ id: "parahouse_magazine", merchant: "ParaHouse Magazine", origins: ["https://www.parahousemagazine.com"] }),
1319
+ pin3({ id: "psalms_and_potions", merchant: "Psalms & Potions", origins: ["https://www.psalmsandpotions.com"] }),
1320
+ pin3({ id: "alexandra_hagel", merchant: "Alexandra Hagel, LLC", origins: ["https://www.alexandrahagel.com"] })
1321
+ ]);
1244
1322
  // src/card-fields.js
1245
1323
  function processorGate(recognizer, url, body) {
1246
1324
  if (recognizer && recognizer.psp === LYRA_PSP)
1247
1325
  return lyraBodyVerdict(body, url);
1326
+ if (recognizer && recognizer.psp === WIX_PAYMENTS_PSP) {
1327
+ if (url !== void 0 && !isWixPaymentsTokenizeUrl(url))
1328
+ return { verdict: "refuse", code: "wix_payments_url_refused" };
1329
+ return wixPaymentsBodyVerdict(body);
1330
+ }
1248
1331
  if (recognizer && recognizer.psp === PADDLE_VGS_PSP) {
1249
1332
  if (url !== void 0 && !isPaddleVgsCardsUrl(url))
1250
1333
  return { verdict: "refuse", code: "paddle_vgs_url_refused" };
@@ -1381,12 +1464,12 @@ var KEEP_ON_FILE_REFUSAL = "Refusing to pay: this Primer payment would keep the
1381
1464
  var UNSUPPORTED_SDK_REFUSAL = "Refusing to pay: this checkout uses a Primer SDK Agentcard does not support yet. Nothing was sent.";
1382
1465
  var NETWORK_MISMATCH_REFUSAL = "Refusing to pay: this Primer payment names a card network that is not your card's. Nothing was sent.";
1383
1466
  var FILL_REFUSAL = "Could not fill the card fields for Primer. Nothing was sent.";
1384
- var BODY_LIMIT5 = 16384;
1467
+ var BODY_LIMIT6 = 16384;
1385
1468
  var RESPONSE_LIMIT = 32768;
1386
- var ROOT_KEYS3 = ["paymentInstrument", "userDescription", "tokenType", "paymentFlow"];
1469
+ var ROOT_KEYS4 = ["paymentInstrument", "userDescription", "tokenType", "paymentFlow"];
1387
1470
  var INSTRUMENT_KEYS = ["number", "cvv", "expirationMonth", "expirationYear", "cardholderName", "preferredNetwork"];
1388
1471
  var REQUIRED_INSTRUMENT_KEYS = ["number", "cvv", "expirationMonth", "expirationYear"];
1389
- var record6 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
1472
+ var record7 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
1390
1473
  var owns5 = (value, key) => Object.prototype.hasOwnProperty.call(value, key);
1391
1474
  var exact = (pattern, value) => typeof value === "string" && pattern.exec(value)?.[0] === value;
1392
1475
  var text2 = (value, max) => typeof value === "string" && value.length <= max && !/[\u0000-\u001f\u007f]/.test(value);
@@ -1409,7 +1492,7 @@ function isPrimerTokenizeUrl(url, method = "POST") {
1409
1492
  return parsed.href === url && parsed.protocol === "https:" && !parsed.port && !parsed.username && !parsed.password && !parsed.search && !parsed.hash && PRIMER_PCI_HOSTS.includes(parsed.hostname) && parsed.pathname === PRIMER_TOKENIZE_PATH;
1410
1493
  }
1411
1494
  function judgeBody2(body) {
1412
- if (typeof body !== "string" || body === "" || body.length > BODY_LIMIT5)
1495
+ if (typeof body !== "string" || body === "" || body.length > BODY_LIMIT6)
1413
1496
  return null;
1414
1497
  let value;
1415
1498
  try {
@@ -1418,12 +1501,12 @@ function judgeBody2(body) {
1418
1501
  catch {
1419
1502
  return null;
1420
1503
  }
1421
- if (!record6(value) || !roundTrips(value, body))
1504
+ if (!record7(value) || !roundTrips(value, body))
1422
1505
  return null;
1423
- if (!Object.keys(value).every((key) => ROOT_KEYS3.includes(key)) || !owns5(value, "paymentInstrument"))
1506
+ if (!Object.keys(value).every((key) => ROOT_KEYS4.includes(key)) || !owns5(value, "paymentInstrument"))
1424
1507
  return null;
1425
1508
  const instrument = value.paymentInstrument;
1426
- if (!record6(instrument) || !Object.keys(instrument).every((key) => INSTRUMENT_KEYS.includes(key)) || !REQUIRED_INSTRUMENT_KEYS.every((key) => owns5(instrument, key)))
1509
+ if (!record7(instrument) || !Object.keys(instrument).every((key) => INSTRUMENT_KEYS.includes(key)) || !REQUIRED_INSTRUMENT_KEYS.every((key) => owns5(instrument, key)))
1427
1510
  return null;
1428
1511
  if (!digits(instrument.number, 12, 19) || !digits(instrument.cvv, 3, 4) || !exact(MONTH3, instrument.expirationMonth) || !exact(YEAR3, instrument.expirationYear))
1429
1512
  return null;
@@ -1451,7 +1534,7 @@ var CLIENT_TOKEN = /^[A-Za-z0-9._~+/=-]{16,4096}$/;
1451
1534
  var REQUEST_ID = /^[A-Za-z0-9-]{1,64}$/;
1452
1535
  var API_VERSION = /^2\.[0-9]{1,2}$/;
1453
1536
  function readHeaders(headers) {
1454
- if (!record6(headers))
1537
+ if (!record7(headers))
1455
1538
  return null;
1456
1539
  const folded = /* @__PURE__ */ new Map();
1457
1540
  for (const [name, value] of Object.entries(headers)) {
@@ -1498,7 +1581,7 @@ function onTokenizePath(url) {
1498
1581
  }
1499
1582
  }
1500
1583
  function primerTokenizeRefusal(request) {
1501
- if (!record6(request) || request.method !== "POST" || !isPrimerTokenizeUrl(request.url))
1584
+ if (!record7(request) || request.method !== "POST" || !isPrimerTokenizeUrl(request.url))
1502
1585
  return "unsupported";
1503
1586
  return headersRefusal(request.headers) ?? primerBodyRefusal(request.body);
1504
1587
  }
@@ -1513,7 +1596,7 @@ function primerCardOnFileRefusal(url, options, policy = PRIMER_SESSION_CARD_ON_F
1513
1596
  return "unsupported";
1514
1597
  if (new URL(url).hostname !== LIVE_PCI_HOST)
1515
1598
  return null;
1516
- return primerCardOnFileDisclosure(url, policy) && record6(options) && options.keepOnFile === true ? null : "keep_on_file";
1599
+ return primerCardOnFileDisclosure(url, policy) && record7(options) && options.keepOnFile === true ? null : "keep_on_file";
1517
1600
  }
1518
1601
  function primerRefusalMessage(reason) {
1519
1602
  if (reason === "keep_on_file")
@@ -1597,7 +1680,7 @@ function readAnswer(body) {
1597
1680
  return null;
1598
1681
  try {
1599
1682
  const value = JSON.parse(body);
1600
- return record6(value) ? value : null;
1683
+ return record7(value) ? value : null;
1601
1684
  }
1602
1685
  catch {
1603
1686
  return null;
@@ -1606,16 +1689,16 @@ function readAnswer(body) {
1606
1689
  var tokenValue = (value) => exact(TOKEN2, value) && !panLike(value);
1607
1690
  var optional = (value, key, check) => !owns5(value, key) || value[key] === null || check(value[key]);
1608
1691
  function cardDetails(data) {
1609
- if (!record6(data) || !exact(/^[0-9]{4}$/, data.last4Digits))
1692
+ if (!record7(data) || !exact(/^[0-9]{4}$/, data.last4Digits))
1610
1693
  return null;
1611
- if (!optional(data, "first6Digits", (v) => exact(/^[0-9]{6}$/, v)) || !optional(data, "expirationMonth", (v) => exact(RESPONSE_MONTH, v)) || !optional(data, "expirationYear", (v) => exact(RESPONSE_YEAR, v)) || !optional(data, "cardholderName", (v) => plain(v, 256)) || !optional(data, "network", (v) => exact(NETWORK_NAME, v)) || !optional(data, "isNetworkTokenized", (v) => typeof v === "boolean") || !optional(data, "binData", record6))
1694
+ if (!optional(data, "first6Digits", (v) => exact(/^[0-9]{6}$/, v)) || !optional(data, "expirationMonth", (v) => exact(RESPONSE_MONTH, v)) || !optional(data, "expirationYear", (v) => exact(RESPONSE_YEAR, v)) || !optional(data, "cardholderName", (v) => plain(v, 256)) || !optional(data, "network", (v) => exact(NETWORK_NAME, v)) || !optional(data, "isNetworkTokenized", (v) => typeof v === "boolean") || !optional(data, "binData", record7))
1612
1695
  return null;
1613
1696
  const safe = { last4Digits: data.last4Digits };
1614
1697
  for (const key of ["first6Digits", "expirationMonth", "expirationYear", "cardholderName", "network", "isNetworkTokenized"]) {
1615
1698
  if (owns5(data, key) && data[key] !== null)
1616
1699
  safe[key] = data[key];
1617
1700
  }
1618
- if (record6(data.binData)) {
1701
+ if (record7(data.binData)) {
1619
1702
  const bin = {};
1620
1703
  for (const key of BIN_KEYS) {
1621
1704
  if (!owns5(data.binData, key) || data.binData[key] === null)
@@ -1631,7 +1714,7 @@ function cardDetails(data) {
1631
1714
  function threeDsSummary(value) {
1632
1715
  if (value === void 0 || value === null)
1633
1716
  return null;
1634
- if (!record6(value) || !exact(CODE, value.responseCode) || !optional(value, "reasonCode", (v) => exact(CODE, v)) || !optional(value, "reasonText", (v) => plain(v, 256)) || !optional(value, "protocolVersion", (v) => v === "" || exact(PROTOCOL, v)) || !optional(value, "challengeIssued", (v) => typeof v === "boolean"))
1717
+ if (!record7(value) || !exact(CODE, value.responseCode) || !optional(value, "reasonCode", (v) => exact(CODE, v)) || !optional(value, "reasonText", (v) => plain(v, 256)) || !optional(value, "protocolVersion", (v) => v === "" || exact(PROTOCOL, v)) || !optional(value, "challengeIssued", (v) => typeof v === "boolean"))
1635
1718
  return void 0;
1636
1719
  const safe = { responseCode: value.responseCode };
1637
1720
  for (const key of ["reasonCode", "reasonText", "protocolVersion", "challengeIssued"]) {
@@ -1659,7 +1742,7 @@ function rebuildToken(value) {
1659
1742
  }
1660
1743
  function primerErrorId(body) {
1661
1744
  const value = readAnswer(body);
1662
- const id = value && record6(value.error) ? value.error.errorId : null;
1745
+ const id = value && record7(value.error) ? value.error.errorId : null;
1663
1746
  return exact(ERROR_ID, id) && !panLike(id) ? id : null;
1664
1747
  }
1665
1748
  function matchesCard(details, card) {
@@ -1,6 +1,6 @@
1
1
  // @ts-nocheck
2
2
  // Generated from @agent-cards/payment-core. Do not edit.
3
- // artifact-sha256: eaa31e10104ec55a9914d5b874f25cb17e713de912b48ef41d7f987786e65785
3
+ // artifact-sha256: ce4887768a0ee59bbca5f329bce47a2831a24b06d7bea1d5517335f7afad75e0
4
4
  // src/deep-freeze.js
5
5
  function deepFreeze(value) {
6
6
  if (value && typeof value === "object" && !Object.isFrozen(value)) {
@@ -1660,6 +1660,31 @@ var PROCESSOR_DEFINITIONS = deepFreeze([
1660
1660
  passthroughHeaders: [],
1661
1661
  jsonCardFields: [["paymentForm", "pan"]],
1662
1662
  passWithoutCard: "api(?:\\.payzen\\.eu|\\.systempay\\.fr|-sogecommerce\\.societegenerale\\.eu|\\.lyra\\.com)/api-payment/V4/Charge/Public/ProcessPayment/2(?:;jsessionid=[A-Za-z0-9._-]{1,128})?"
1663
+ },
1664
+ {
1665
+ psp: "wix_payments",
1666
+ // Wix Cashier's card frame (cashier-pci.wixapps.net/card-form), which every Wix checkout mounts: for
1667
+ // Wix Payments and classic Stripe on Wix it posts the card as one JSON body to Wix's own tokenizer,
1668
+ // under the site's signed app instance. PAN@ creditCard.number, CVV@ cvv at the root, the expiry as
1669
+ // creditCard.expiration.month and .year (numbers, the year in four digits). wix-payments.js holds the
1670
+ // request rule the pause decision, the API's create, the prepared bind and the device all read: exactly
1671
+ // this URL (never /v2/credit-cards, which saves a card, or a saved card's token URL), a JSON
1672
+ // Content-Type, the instance, and the frame's body byte for byte. Wix's payment widget fails the
1673
+ // payment 30 seconds after it asks the frame for a token, so the lane is prepared only
1674
+ // (preparationRequired): the cardholder approves before the agent clicks Pay. The tokenizer answers no
1675
+ // browser origin, so the card leaves the cardholder's device only through the checkout relay. Wix's
1676
+ // other headers ride beside the instance, as the frame sends them. No sandbox host: a Wix site in test
1677
+ // mode answers inside the frame with a fake token and sends nothing, so the lane takes production only,
1678
+ // and the API refuses a test-mode client before any row (wix-payments.js wixPaymentsMerchantRefusal).
1679
+ // Ships dark: served only to a client that declares `wix_payments_tokenize`, gated off for every org
1680
+ // until an operator lists one (`wix_payments:<org>`), and autopilot never sends this request
1681
+ // (autopilot.js DEVICE_REPLAY_REFUSED_OPERATIONS).
1682
+ capability: "wix_payments_tokenize",
1683
+ match: "cashier-pci\\.wixapps\\.net/_api/payment-gateway-web/tokenize/v2/tokens",
1684
+ hosts: ["^cashier-pci\\.wixapps\\.net$"],
1685
+ preparationRequired: true,
1686
+ encoding: "json",
1687
+ passthroughHeaders: ["^authorization$", "^x-wix-client-artifact-id$", "^commonconfig$", "^x-wix-brand$", "^consent-policy$", "^x-wix-linguist$"]
1663
1688
  }
1664
1689
  ]);
1665
1690
  var PSP_RECOGNIZERS = deepFreeze(PROCESSOR_DEFINITIONS.map(({ compose, encryptedCardFields, deviceSteps, deviceEncrypt, ...definition }) => definition));
@@ -1817,6 +1842,13 @@ var LYRA_MERCHANT_PINS = /* @__PURE__ */ Object.freeze([
1817
1842
  hosts: Object.freeze(["api.systempay.fr"])
1818
1843
  })
1819
1844
  ]);
1845
+ // src/wix-payments.js
1846
+ var pin3 = (fields) => Object.freeze({ ...fields, origins: Object.freeze([...fields.origins]) });
1847
+ var WIX_PAYMENTS_MERCHANT_PINS = /* @__PURE__ */ Object.freeze([
1848
+ pin3({ id: "parahouse_magazine", merchant: "ParaHouse Magazine", origins: ["https://www.parahousemagazine.com"] }),
1849
+ pin3({ id: "psalms_and_potions", merchant: "Psalms & Potions", origins: ["https://www.psalmsandpotions.com"] }),
1850
+ pin3({ id: "alexandra_hagel", merchant: "Alexandra Hagel, LLC", origins: ["https://www.alexandrahagel.com"] })
1851
+ ]);
1820
1852
  // src/cybersource-secure-acceptance.js
1821
1853
  var SA_BODY_LIMIT = 32 * 1024;
1822
1854
  var SA_REPLY_LIMIT = 64 * 1024;
@@ -0,0 +1,42 @@
1
+ declare var WIX_PAYMENTS_PSP: string;
2
+ declare var WIX_PAYMENTS_CAPABILITY: string;
3
+ declare var WIX_PAYMENTS_OPERATION: string;
4
+ declare var WIX_PAYMENTS_HOST: string;
5
+ declare var WIX_PAYMENTS_TOKENIZE_URL: string;
6
+ declare var WIX_PAYMENTS_REFUSAL_CODES: readonly string[];
7
+ declare function isWixPaymentsTokenizeUrl(url: any): boolean;
8
+ declare function wixPaymentsBodyVerdict(body: any): Readonly<{
9
+ verdict: "refuse";
10
+ code: any;
11
+ }> | Readonly<{
12
+ verdict: "card";
13
+ }>;
14
+ declare function wixPaymentsRequestRefusal(request: any): any;
15
+ declare function isPreparedWixPaymentsRequest(url: any, method: any, body: any, environment: any, headers: any): boolean;
16
+ declare var WIX_PAYMENTS_MERCHANT_PINS: readonly any[];
17
+ declare function wixPaymentsMerchantPin(merchantOrigin: any, pins?: readonly any[]): any;
18
+ declare function wixPaymentsMerchantRefusal(environment: any, merchantOrigin: any, pins?: readonly any[]): "unsupported_environment" | "wix_payments_merchant_unreviewed" | null;
19
+ declare function wixPaymentsRefusalMessage(code: any): any;
20
+ declare function injectWixPaymentsCard(body: any, card: any): {
21
+ body: string;
22
+ where: string[];
23
+ };
24
+ declare function isWixPaymentsDeviceRequest(templateBody: any, deviceBody: any): boolean;
25
+ declare function wixPaymentsRefusalCode(text: any): string | null;
26
+ declare function wixPaymentsAnswerOutcome(status: any, text: any, card: any): {
27
+ outcome: string;
28
+ reason: string;
29
+ code?: undefined;
30
+ body?: undefined;
31
+ } | {
32
+ outcome: string;
33
+ code: string;
34
+ reason?: undefined;
35
+ body?: undefined;
36
+ } | {
37
+ outcome: string;
38
+ body: string;
39
+ reason?: undefined;
40
+ code?: undefined;
41
+ };
42
+ export { WIX_PAYMENTS_CAPABILITY, WIX_PAYMENTS_HOST, WIX_PAYMENTS_MERCHANT_PINS, WIX_PAYMENTS_OPERATION, WIX_PAYMENTS_PSP, WIX_PAYMENTS_REFUSAL_CODES, WIX_PAYMENTS_TOKENIZE_URL, injectWixPaymentsCard, isPreparedWixPaymentsRequest, isWixPaymentsDeviceRequest, isWixPaymentsTokenizeUrl, wixPaymentsAnswerOutcome, wixPaymentsBodyVerdict, wixPaymentsMerchantPin, wixPaymentsMerchantRefusal, wixPaymentsRefusalCode, wixPaymentsRefusalMessage, wixPaymentsRequestRefusal };