@agent-cards/sdk 0.23.11 → 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/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
@@ -16,8 +16,12 @@
16
16
  * CyberSource's reply 30 times, a second apart, from Save (Arrow Electronics), too soon for an
17
17
  * approval after Save. Its environments are its two hosts: secureacceptance (production) and
18
18
  * testsecureacceptance (sandbox).
19
+ *
20
+ * paddle_vgs: prepared only. Paddle's checkout frame aborts its card request to its VGS card vault 10
21
+ * seconds after Pay and sends it once more, too soon for an approval after Pay. Its environments are its
22
+ * two card URLs: vault.paddle.com (production) and sandbox-vault.paddle.com (sandbox).
19
23
  */
20
- 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';
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';
21
25
  export type PreparationEnvironment = 'production' | 'sandbox' | 'shared';
22
26
  /**
23
27
  * How the approved card reaches the processor: the device's own request (token), or
@@ -39,8 +43,11 @@ export declare function preparationRequestMethod(psp: string): 'GET' | 'POST';
39
43
  * sending it, and a Pay button that runs the tokenization again sends the same placeholder and
40
44
  * fields (WooCommerce's Bambora plugin shows the timeout, clears its token fields and lets Pay be
41
45
  * clicked again). The cardholder's device answers with a token that pays nothing until the
42
- * merchant's server charges it. Every other prepared checkout still answers only the request it
43
- * was bound to.
46
+ * merchant's server charges it. Paddle VGS too: Paddle's frame aborts its card request to its VGS
47
+ * card vault 10 seconds after sending it and sends the same body under the same bearer once more, and
48
+ * the device's answer is the stored card's id, which pays nothing until Paddle's /pay names it. Only
49
+ * that repeat, byte for byte under the same bearer, is answered (cdp.ts paddleVgsRetryKey).
50
+ * Every other prepared checkout still answers only the request it was bound to.
44
51
  */
45
52
  export declare function preparedApprovalAnswersRetry(psp: string): boolean;
46
53
  /**
@@ -50,7 +57,8 @@ export declare function preparedApprovalAnswersRetry(psp: string): boolean;
50
57
  * Flex's: flex.cybersource.com is live, testflex.cybersource.com is test, and each has
51
58
  * its own signing keys. A Verifone preparation pays in the environment of the key its reviewed
52
59
  * profile pins. CyberSource Secure Acceptance's are separate hosts too: secureacceptance is
53
- * live, testsecureacceptance is test.
60
+ * live, testsecureacceptance is test. So are Paddle VGS's two card URLs: vault is live,
61
+ * sandbox-vault is test.
54
62
  */
55
63
  export declare function validPreparationEnvironment(psp: string, environment: string): boolean;
56
64
  /**
@@ -13,6 +13,7 @@ import { eprotectPreparationUrl, isPreparedEprotectRequest } from './eprotect.ge
13
13
  import { amadeusEnvironment, amadeusPreparationUrls, amadeusRequestRefusal, isPreparedAmadeusRequest } from './amadeus-checkout.generated.js';
14
14
  import { isPreparedVerifoneRequest } from './verifone.js';
15
15
  import { isPreparedSecureAcceptanceRequest, secureAcceptanceEnvironment, secureAcceptancePreparationUrl, secureAcceptanceRequestRefusal, } from './cybersource-secure-acceptance.generated.js';
16
+ import { PADDLE_VGS_CARDS_URLS, isPreparedPaddleVgsRequest } from './paddle-vgs.generated.js';
16
17
  export function preparationMode(psp) {
17
18
  return psp === 'adyen' || psp === 'fiserv' || psp === 'verifone' ? 'cse' : 'token';
18
19
  }
@@ -30,11 +31,14 @@ export function preparationRequestMethod(psp) {
30
31
  * sending it, and a Pay button that runs the tokenization again sends the same placeholder and
31
32
  * fields (WooCommerce's Bambora plugin shows the timeout, clears its token fields and lets Pay be
32
33
  * clicked again). The cardholder's device answers with a token that pays nothing until the
33
- * merchant's server charges it. Every other prepared checkout still answers only the request it
34
- * was bound to.
34
+ * merchant's server charges it. Paddle VGS too: Paddle's frame aborts its card request to its VGS
35
+ * card vault 10 seconds after sending it and sends the same body under the same bearer once more, and
36
+ * the device's answer is the stored card's id, which pays nothing until Paddle's /pay names it. Only
37
+ * that repeat, byte for byte under the same bearer, is answered (cdp.ts paddleVgsRetryKey).
38
+ * Every other prepared checkout still answers only the request it was bound to.
35
39
  */
36
40
  export function preparedApprovalAnswersRetry(psp) {
37
- return psp === 'bambora';
41
+ return psp === 'bambora' || psp === 'paddle_vgs';
38
42
  }
39
43
  /**
40
44
  * Shared endpoint processors cannot attest test/live mode from their URL or key prefix.
@@ -43,13 +47,14 @@ export function preparedApprovalAnswersRetry(psp) {
43
47
  * Flex's: flex.cybersource.com is live, testflex.cybersource.com is test, and each has
44
48
  * its own signing keys. A Verifone preparation pays in the environment of the key its reviewed
45
49
  * profile pins. CyberSource Secure Acceptance's are separate hosts too: secureacceptance is
46
- * live, testsecureacceptance is test.
50
+ * live, testsecureacceptance is test. So are Paddle VGS's two card URLs: vault is live,
51
+ * sandbox-vault is test.
47
52
  */
48
53
  export function validPreparationEnvironment(psp, environment) {
49
54
  if (psp === 'bambora' || psp === 'mercado_pago' || psp === 'recurly' || psp === 'spreedly')
50
55
  return environment === 'shared';
51
56
  return ['square', 'braintree', 'worldpay', 'adyen', 'checkout_com', 'paysafe', 'fiserv', 'cybersource_flex', 'aci', 'creditguard', 'worldpay_eprotect', 'amadeus_checkout', 'verifone',
52
- 'cybersource_secure_acceptance'].includes(psp) && ['production', 'sandbox'].includes(environment);
57
+ 'cybersource_secure_acceptance', 'paddle_vgs'].includes(psp) && ['production', 'sandbox'].includes(environment);
53
58
  }
54
59
  /**
55
60
  * ACI's prepared endpoint, the literal the API names (backend checkout-prepared-processor.ts): its
@@ -91,6 +96,9 @@ export function preparationEndpoint(psp, environment) {
91
96
  // The token-create URL on the environment's host, as the API names it: the one request the claim binds.
92
97
  if (psp === 'cybersource_secure_acceptance')
93
98
  return secureAcceptancePreparationUrl(environment);
99
+ // Paddle VGS: the environment's one card URL, the only URL the claim takes for it.
100
+ if (psp === 'paddle_vgs')
101
+ return PADDLE_VGS_CARDS_URLS[environment];
94
102
  // A Verifone endpoint is its reviewed profile's leg, never one per environment (verifone.ts).
95
103
  if (psp === 'verifone')
96
104
  throw new Error('unsupported_preparation_processor');
@@ -236,6 +244,10 @@ export function matchesPreparedRequest(psp, environment, requestUrl, method, bod
236
244
  // Flex gate verified the issuing field iframe's capture context against that JWE's key.
237
245
  if (psp === 'cybersource_flex')
238
246
  return isPreparedCybersourceFlexRequest(requestUrl, method.toUpperCase(), body ?? null, environment, headers);
247
+ // Paddle VGS: the environment's own card URL and payment-core's whole request rule (the checkout's bearer,
248
+ // the JSON:API content type and the frame's body), the rule the API and the Vault bind with too.
249
+ if (psp === 'paddle_vgs')
250
+ return isPreparedPaddleVgsRequest(requestUrl, method.toUpperCase(), body ?? null, environment, headers ?? {});
239
251
  if (psp === 'creditguard')
240
252
  return matchesPreparedCreditguard(environment, requestUrl, body, headers, binding);
241
253
  // 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: 3f5ac5b4125cab96cc0851e9b1fe705aa96635e1df05b9216e951140fb47cd43
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
@@ -348,8 +348,81 @@ function amadeusBodyKind(body) {
348
348
  return record2(value.data) && holdsPan(value.data.mopdata) ? "card" : "other";
349
349
  return "other";
350
350
  }
351
+ // src/paddle-vgs.js
352
+ var PADDLE_VGS_PSP = "paddle_vgs";
353
+ var PADDLE_VGS_CARDS_URLS = /* @__PURE__ */ Object.freeze({
354
+ production: "https://vault.paddle.com/cards",
355
+ sandbox: "https://sandbox-vault.paddle.com/cards"
356
+ });
357
+ var BODY_LIMIT2 = 4096;
358
+ var ROOT_KEYS2 = /* @__PURE__ */ Object.freeze(["data"]);
359
+ var DATA_KEYS = /* @__PURE__ */ Object.freeze(["type", "attributes"]);
360
+ var ATTRIBUTE_KEYS = /* @__PURE__ */ Object.freeze(["pan", "cvc", "exp_month", "exp_year", "cardholder"]);
361
+ var CARD_NUMBER4 = /^[0-9]{12,19}$/;
362
+ var SECURITY_CODE2 = /^[0-9]{3,4}$/;
363
+ var NAME_LIMIT = 256;
364
+ var CONTROL = /[\u0000-\u001f\u007f]/;
365
+ var cardLikeName = (name) => (name.match(/[0-9]/g) ?? []).length >= 12;
366
+ var record3 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
367
+ var sameKeys = (value, keys) => {
368
+ const own = Object.keys(value);
369
+ return own.length === keys.length && own.every((key, at) => key === keys[at]);
370
+ };
371
+ var refuse4 = (code) => Object.freeze({ verdict: "refuse", code });
372
+ var CARD4 = /* @__PURE__ */ Object.freeze({ verdict: "card" });
373
+ function paddleVgsEnvironment(url) {
374
+ if (url === PADDLE_VGS_CARDS_URLS.production)
375
+ return "production";
376
+ if (url === PADDLE_VGS_CARDS_URLS.sandbox)
377
+ return "sandbox";
378
+ return null;
379
+ }
380
+ function isPaddleVgsCardsUrl(url) {
381
+ return paddleVgsEnvironment(url) !== null;
382
+ }
383
+ function readBody2(body) {
384
+ if (typeof body !== "string" || body === "" || body.length > BODY_LIMIT2)
385
+ return null;
386
+ let value;
387
+ try {
388
+ value = JSON.parse(body);
389
+ if (!record3(value) || JSON.stringify(value) !== body)
390
+ return null;
391
+ }
392
+ catch {
393
+ return null;
394
+ }
395
+ return value;
396
+ }
397
+ function paddleVgsBodyVerdict(body) {
398
+ const value = readBody2(body);
399
+ if (value === null || !sameKeys(value, ROOT_KEYS2) || !record3(value.data) || !sameKeys(value.data, DATA_KEYS) || value.data.type !== "cards" || !record3(value.data.attributes) || !sameKeys(value.data.attributes, ATTRIBUTE_KEYS)) {
400
+ return refuse4("paddle_vgs_body_unsupported");
401
+ }
402
+ const card = value.data.attributes;
403
+ const holder = card.cardholder;
404
+ if (typeof card.pan !== "string" || !CARD_NUMBER4.test(card.pan) || typeof card.cvc !== "string" || !SECURITY_CODE2.test(card.cvc) || !Number.isInteger(card.exp_month) || card.exp_month < 1 || card.exp_month > 12 || !Number.isInteger(card.exp_year) || card.exp_year < 0 || card.exp_year > 99 || !record3(holder) || !sameKeys(holder, ["name"]) || typeof holder.name !== "string" || holder.name.length > NAME_LIMIT || CONTROL.test(holder.name) || cardLikeName(holder.name)) {
405
+ return refuse4("paddle_vgs_body_unsupported");
406
+ }
407
+ return CARD4;
408
+ }
409
+ var pin2 = (fields) => Object.freeze({
410
+ ...fields,
411
+ origins: Object.freeze([...fields.origins]),
412
+ sellerClaim: fields.sellerClaim ? Object.freeze({ ...fields.sellerClaim }) : null
413
+ });
414
+ var PADDLE_VGS_MERCHANT_PINS = /* @__PURE__ */ Object.freeze([
415
+ pin2({ id: "rectangle_pro", merchant: "Rectangle Pro", origins: ["https://rectangleapp.com"], sellerClaim: null })
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]);
351
419
  // src/card-fields.js
352
420
  function processorGate(recognizer, url, body) {
421
+ if (recognizer && recognizer.psp === PADDLE_VGS_PSP) {
422
+ if (url !== void 0 && !isPaddleVgsCardsUrl(url))
423
+ return { verdict: "refuse", code: "paddle_vgs_url_refused" };
424
+ return paddleVgsBodyVerdict(body);
425
+ }
353
426
  if (recognizer && recognizer.psp === GRAVY_PSP)
354
427
  return gravyBodyVerdict(body);
355
428
  if (recognizer && recognizer.psp === MONERIS_PSP) {
@@ -470,12 +543,12 @@ var KEEP_ON_FILE_REFUSAL = "Refusing to pay: this Primer payment would keep the
470
543
  var UNSUPPORTED_SDK_REFUSAL = "Refusing to pay: this checkout uses a Primer SDK Agentcard does not support yet. Nothing was sent.";
471
544
  var NETWORK_MISMATCH_REFUSAL = "Refusing to pay: this Primer payment names a card network that is not your card's. Nothing was sent.";
472
545
  var FILL_REFUSAL = "Could not fill the card fields for Primer. Nothing was sent.";
473
- var BODY_LIMIT2 = 16384;
546
+ var BODY_LIMIT3 = 16384;
474
547
  var RESPONSE_LIMIT = 32768;
475
- var ROOT_KEYS2 = ["paymentInstrument", "userDescription", "tokenType", "paymentFlow"];
548
+ var ROOT_KEYS3 = ["paymentInstrument", "userDescription", "tokenType", "paymentFlow"];
476
549
  var INSTRUMENT_KEYS = ["number", "cvv", "expirationMonth", "expirationYear", "cardholderName", "preferredNetwork"];
477
550
  var REQUIRED_INSTRUMENT_KEYS = ["number", "cvv", "expirationMonth", "expirationYear"];
478
- var record3 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
551
+ var record4 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
479
552
  var owns4 = (value, key) => Object.prototype.hasOwnProperty.call(value, key);
480
553
  var exact = (pattern, value) => typeof value === "string" && pattern.exec(value)?.[0] === value;
481
554
  var text = (value, max) => typeof value === "string" && value.length <= max && !/[\u0000-\u001f\u007f]/.test(value);
@@ -498,7 +571,7 @@ function isPrimerTokenizeUrl(url, method = "POST") {
498
571
  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;
499
572
  }
500
573
  function judgeBody(body) {
501
- if (typeof body !== "string" || body === "" || body.length > BODY_LIMIT2)
574
+ if (typeof body !== "string" || body === "" || body.length > BODY_LIMIT3)
502
575
  return null;
503
576
  let value;
504
577
  try {
@@ -507,12 +580,12 @@ function judgeBody(body) {
507
580
  catch {
508
581
  return null;
509
582
  }
510
- if (!record3(value) || !roundTrips(value, body))
583
+ if (!record4(value) || !roundTrips(value, body))
511
584
  return null;
512
- if (!Object.keys(value).every((key) => ROOT_KEYS2.includes(key)) || !owns4(value, "paymentInstrument"))
585
+ if (!Object.keys(value).every((key) => ROOT_KEYS3.includes(key)) || !owns4(value, "paymentInstrument"))
513
586
  return null;
514
587
  const instrument = value.paymentInstrument;
515
- if (!record3(instrument) || !Object.keys(instrument).every((key) => INSTRUMENT_KEYS.includes(key)) || !REQUIRED_INSTRUMENT_KEYS.every((key) => owns4(instrument, key)))
588
+ if (!record4(instrument) || !Object.keys(instrument).every((key) => INSTRUMENT_KEYS.includes(key)) || !REQUIRED_INSTRUMENT_KEYS.every((key) => owns4(instrument, key)))
516
589
  return null;
517
590
  if (!digits(instrument.number, 12, 19) || !digits(instrument.cvv, 3, 4) || !exact(MONTH, instrument.expirationMonth) || !exact(YEAR, instrument.expirationYear))
518
591
  return null;
@@ -540,7 +613,7 @@ var CLIENT_TOKEN = /^[A-Za-z0-9._~+/=-]{16,4096}$/;
540
613
  var REQUEST_ID = /^[A-Za-z0-9-]{1,64}$/;
541
614
  var API_VERSION = /^2\.[0-9]{1,2}$/;
542
615
  function readHeaders(headers) {
543
- if (!record3(headers))
616
+ if (!record4(headers))
544
617
  return null;
545
618
  const folded = /* @__PURE__ */ new Map();
546
619
  for (const [name, value] of Object.entries(headers)) {
@@ -587,7 +660,7 @@ function onTokenizePath(url) {
587
660
  }
588
661
  }
589
662
  function primerTokenizeRefusal(request) {
590
- if (!record3(request) || request.method !== "POST" || !isPrimerTokenizeUrl(request.url))
663
+ if (!record4(request) || request.method !== "POST" || !isPrimerTokenizeUrl(request.url))
591
664
  return "unsupported";
592
665
  return headersRefusal(request.headers) ?? primerBodyRefusal(request.body);
593
666
  }
@@ -602,7 +675,7 @@ function primerCardOnFileRefusal(url, options, policy = PRIMER_SESSION_CARD_ON_F
602
675
  return "unsupported";
603
676
  if (new URL(url).hostname !== LIVE_PCI_HOST)
604
677
  return null;
605
- return primerCardOnFileDisclosure(url, policy) && record3(options) && options.keepOnFile === true ? null : "keep_on_file";
678
+ return primerCardOnFileDisclosure(url, policy) && record4(options) && options.keepOnFile === true ? null : "keep_on_file";
606
679
  }
607
680
  function primerRefusalMessage(reason) {
608
681
  if (reason === "keep_on_file")
@@ -686,7 +759,7 @@ function readAnswer(body) {
686
759
  return null;
687
760
  try {
688
761
  const value = JSON.parse(body);
689
- return record3(value) ? value : null;
762
+ return record4(value) ? value : null;
690
763
  }
691
764
  catch {
692
765
  return null;
@@ -695,16 +768,16 @@ function readAnswer(body) {
695
768
  var tokenValue = (value) => exact(TOKEN, value) && !panLike(value);
696
769
  var optional = (value, key, check) => !owns4(value, key) || value[key] === null || check(value[key]);
697
770
  function cardDetails(data) {
698
- if (!record3(data) || !exact(/^[0-9]{4}$/, data.last4Digits))
771
+ if (!record4(data) || !exact(/^[0-9]{4}$/, data.last4Digits))
699
772
  return null;
700
- 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", record3))
773
+ 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", record4))
701
774
  return null;
702
775
  const safe = { last4Digits: data.last4Digits };
703
776
  for (const key of ["first6Digits", "expirationMonth", "expirationYear", "cardholderName", "network", "isNetworkTokenized"]) {
704
777
  if (owns4(data, key) && data[key] !== null)
705
778
  safe[key] = data[key];
706
779
  }
707
- if (record3(data.binData)) {
780
+ if (record4(data.binData)) {
708
781
  const bin = {};
709
782
  for (const key of BIN_KEYS) {
710
783
  if (!owns4(data.binData, key) || data.binData[key] === null)
@@ -720,7 +793,7 @@ function cardDetails(data) {
720
793
  function threeDsSummary(value) {
721
794
  if (value === void 0 || value === null)
722
795
  return null;
723
- if (!record3(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"))
796
+ if (!record4(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"))
724
797
  return void 0;
725
798
  const safe = { responseCode: value.responseCode };
726
799
  for (const key of ["reasonCode", "reasonText", "protocolVersion", "challengeIssued"]) {
@@ -748,7 +821,7 @@ function rebuildToken(value) {
748
821
  }
749
822
  function primerErrorId(body) {
750
823
  const value = readAnswer(body);
751
- const id = value && record3(value.error) ? value.error.errorId : null;
824
+ const id = value && record4(value.error) ? value.error.errorId : null;
752
825
  return exact(ERROR_ID, id) && !panLike(id) ? id : null;
753
826
  }
754
827
  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: 0abf1d62c20303c4496e1d26b64b1c36ad517919d23f2d14e3f0b6763694551f
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: ["^authorization$", "^x-paysafe-credentials$", "^correlationid$"]
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",
@@ -1418,6 +1431,33 @@ var PROCESSOR_DEFINITIONS = deepFreeze([
1418
1431
  // A checked "remember" asks Worldline to keep the card for later purchases.
1419
1432
  token: "remember"
1420
1433
  }
1434
+ },
1435
+ {
1436
+ psp: "paddle_vgs",
1437
+ // Paddle's checkout frame (buy.paddle.com) on a checkout whose card method names VGS as its
1438
+ // tokenizer: one JSON:API POST to /cards on vault.paddle.com, a VGS inbound proxy Paddle runs
1439
+ // (sandbox-vault.paddle.com in test), with the checkout's own bearer JWT. PAN@
1440
+ // data.attributes.pan, with cvc, exp_month (1 to 12) and exp_year (two digits) beside it, all
1441
+ // numbers but the pan and the cvc. paddle-vgs.js holds the request rule the pause decision, the
1442
+ // API's create, the prepared bind and the device all read: exactly one of the two URLs, the
1443
+ // frame's Content-Type and bearer, and the frame's body byte for byte. VGS answers with the
1444
+ // stored card's id, which the frame sends on to Paddle's /pay. The frame aborts this request
1445
+ // after 10 seconds and sends it once more, so the lane is prepared only (preparationRequired):
1446
+ // the cardholder approves before the agent clicks Pay. The card leaves the cardholder's device
1447
+ // by a direct fetch, since the proxy answers any origin. The checkouts that name TokenEx or
1448
+ // Spreedly send the card elsewhere and are not this entry.
1449
+ // Ships dark: served only to a client that declares `paddle_vgs_cards`, gated off for every org
1450
+ // until an operator lists one (`paddle_vgs:<org>`), and autopilot never sends this request
1451
+ // (autopilot.js DEVICE_REPLAY_REFUSED_OPERATIONS). The environment is pinned (S7):
1452
+ // sandbox-vault only on test rows and only with documented test cards, vault only on live rows.
1453
+ capability: "paddle_vgs_cards",
1454
+ match: "(?:sandbox-)?vault\\.paddle\\.com/cards",
1455
+ hosts: ["^vault\\.paddle\\.com$", "^sandbox-vault\\.paddle\\.com$"],
1456
+ sandboxHosts: ["^sandbox-vault\\.paddle\\.com$"],
1457
+ hostEnvironmentPinned: true,
1458
+ preparationRequired: true,
1459
+ encoding: "json",
1460
+ passthroughHeaders: ["^authorization$"]
1421
1461
  }
1422
1462
  ]);
1423
1463
  var PSP_RECOGNIZERS = deepFreeze(PROCESSOR_DEFINITIONS.map(({ compose, encryptedCardFields, deviceSteps, deviceEncrypt, ...definition }) => definition));
@@ -1520,6 +1560,17 @@ var pin = (fields) => Object.freeze({ ...fields, origins: Object.freeze([...fiel
1520
1560
  var MONERIS_MERCHANT_PINS = /* @__PURE__ */ Object.freeze([
1521
1561
  pin({ id: "jack_org_raisin", merchant: "Jack.org", origins: ["https://jack.akaraisin.com"], profileIds: ["htL7TR9H7ICYN2B"] })
1522
1562
  ]);
1563
+ // src/paddle-vgs.js
1564
+ var pin2 = (fields) => Object.freeze({
1565
+ ...fields,
1566
+ origins: Object.freeze([...fields.origins]),
1567
+ sellerClaim: fields.sellerClaim ? Object.freeze({ ...fields.sellerClaim }) : null
1568
+ });
1569
+ var PADDLE_VGS_MERCHANT_PINS = /* @__PURE__ */ Object.freeze([
1570
+ pin2({ id: "rectangle_pro", merchant: "Rectangle Pro", origins: ["https://rectangleapp.com"], sellerClaim: null })
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]);
1523
1574
  // src/cybersource-secure-acceptance.js
1524
1575
  var SA_BODY_LIMIT = 32 * 1024;
1525
1576
  var SA_REPLY_LIMIT = 64 * 1024;
@@ -1607,6 +1658,7 @@ var ALLOWED_MEDIA_TYPES = {
1607
1658
  jwe: ["application/jwt"]
1608
1659
  };
1609
1660
  var JWE_CONTENT_TYPE = "application/jwt; charset=utf-8";
1661
+ var PADDLE_VGS_MEDIA_TYPE = "application/vnd.api+json";
1610
1662
  function contentTypeFor(pspOrEntry, headers) {
1611
1663
  const rec = trustedClientRecognizer(pspOrEntry);
1612
1664
  if (rec.encoding === "jwe") {
@@ -1616,6 +1668,13 @@ function contentTypeFor(pspOrEntry, headers) {
1616
1668
  throw new Error("Refusing to pay: this processor takes its sealed card only as application/jwt.");
1617
1669
  return JWE_CONTENT_TYPE;
1618
1670
  }
1671
+ if (rec.psp === "paddle_vgs") {
1672
+ const captured = Object.entries(headers || {}).find(([k]) => /^content-type$/i.test(k));
1673
+ if (!captured || typeof captured[1] !== "string" || captured[1].trim().toLowerCase() !== PADDLE_VGS_MEDIA_TYPE) {
1674
+ throw new Error("Refusing to pay: this processor takes its card only as application/vnd.api+json.");
1675
+ }
1676
+ return PADDLE_VGS_MEDIA_TYPE;
1677
+ }
1619
1678
  const encoding = rec && rec.encoding === "form" ? "form" : "json";
1620
1679
  const allowed = ALLOWED_MEDIA_TYPES[encoding];
1621
1680
  const fallback = allowed[0];
@@ -1635,6 +1694,8 @@ function replayHeaders(pspOrEntry, captured = {}) {
1635
1694
  const headers = { "content-type": contentTypeFor(rec, captured) };
1636
1695
  if (rec.psp === "creditguard")
1637
1696
  headers.accept = "application/json";
1697
+ if (rec.psp === "paddle_vgs")
1698
+ headers.accept = PADDLE_VGS_MEDIA_TYPE;
1638
1699
  for (const [name, value] of Object.entries(captured)) {
1639
1700
  if (rec.headers.some((pattern) => pattern.test(name)) && !HEADER_NEVER_FORWARD.test(name))
1640
1701
  headers[name] = value;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-cards/sdk",
3
- "version": "0.23.11",
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",
@@ -27,7 +27,7 @@
27
27
  "_comment_build": "The public SDK keeps zero runtime dependencies. Its build vendors deterministic metadata and substitution artifacts from the internal payment core, and the voice streaming door from packages/voice/src/core (the phone package, @agent-cards/voice, ships it to phones); TypeScript remains pinned.",
28
28
  "build": "node ../payment-core/scripts/build.mjs && node scripts/generate-payment-core.mjs && node scripts/generate-voice-stream.mjs && npx -y -p typescript@5.9.3 tsc && node scripts/generate-preflight-contract.mjs",
29
29
  "prepublishOnly": "pnpm build",
30
- "test": "node test.mjs && node --test lifecycle.test.mjs merchant-abort.test.mjs preparation.test.mjs braintree.test.mjs pre-claim.test.mjs ekashu.test.mjs paypal-hosted-fields.test.mjs cybersource-secure-acceptance.test.mjs autopilot.test.mjs stripe-checkout.test.mjs stripe-setup.test.mjs stripe-legacy-checkout.test.mjs payment-core.test.mjs prepared-processor.test.mjs merchant-hosted.test.mjs merchant-total.test.mjs sandbox-declaration.test.mjs minimum-delay.test.mjs paysafe.test.mjs payu.test.mjs attachment.test.mjs worker-targets.test.mjs mercado-checkout.test.mjs mercado-polling.test.mjs amadeus.test.mjs pelecard.test.mjs aci.test.mjs primer.test.mjs payrails.test.mjs elavon-converge.test.mjs voice.test.mjs agentcard.test.mjs buy.test.mjs readme.test.mjs gmo.test.mjs cybersource-flex.test.mjs juspay-gate.test.mjs card-endpoint.test.mjs card-endpoint-fastspring.test.mjs card-endpoint-navigation.test.mjs merchant-session-probe.test.mjs datatrans.test.mjs authorize-net-dpm.test.mjs creditguard.test.mjs eprotect.test.mjs wspay.test.mjs shiji-sts.test.mjs worldline-mycheckout.test.mjs verifone.test.mjs gravy.test.mjs && node --test preflight-package.test.mjs preflight-collector.test.mjs && node --test kernel-native-qualification.test.mjs && node --test ../vault/scripts/recurly-validation/watch-duty-sdk-result.test.mjs",
30
+ "test": "node test.mjs && node --test lifecycle.test.mjs merchant-abort.test.mjs preparation.test.mjs braintree.test.mjs pre-claim.test.mjs ekashu.test.mjs paypal-hosted-fields.test.mjs cybersource-secure-acceptance.test.mjs autopilot.test.mjs stripe-checkout.test.mjs stripe-setup.test.mjs stripe-legacy-checkout.test.mjs payment-core.test.mjs prepared-processor.test.mjs merchant-hosted.test.mjs merchant-total.test.mjs sandbox-declaration.test.mjs minimum-delay.test.mjs paysafe.test.mjs payu.test.mjs attachment.test.mjs worker-targets.test.mjs mercado-checkout.test.mjs mercado-polling.test.mjs amadeus.test.mjs pelecard.test.mjs aci.test.mjs primer.test.mjs payrails.test.mjs elavon-converge.test.mjs voice.test.mjs agentcard.test.mjs buy.test.mjs readme.test.mjs gmo.test.mjs cybersource-flex.test.mjs juspay-gate.test.mjs card-endpoint.test.mjs card-endpoint-fastspring.test.mjs card-endpoint-navigation.test.mjs merchant-session-probe.test.mjs datatrans.test.mjs authorize-net-dpm.test.mjs creditguard.test.mjs eprotect.test.mjs wspay.test.mjs shiji-sts.test.mjs worldline-mycheckout.test.mjs verifone.test.mjs gravy.test.mjs paddle-vgs.test.mjs redact-url.test.mjs && node --test preflight-package.test.mjs preflight-collector.test.mjs && node --test kernel-native-qualification.test.mjs && node --test ../vault/scripts/recurly-validation/watch-duty-sdk-result.test.mjs",
31
31
  "test:browser": "node browser.test.mjs && node stripe-browser.test.mjs && node preparation-browser.test.mjs && node checkout-com-browser.test.mjs && node paysafe-preparation-browser.test.mjs && node owned-shop-browser.test.mjs && node spreedly-browser.test.mjs && node worker-browser.test.mjs && node amadeus-browser.test.mjs && node adyen-merchant-hosted-browser.test.mjs && node adyen-merchant-frames-browser.test.mjs && node fiserv-browser.test.mjs && node gmo-browser.test.mjs && node cybersource-flex-browser.test.mjs && node juspay-browser.test.mjs && node card-endpoint-browser.test.mjs && node datatrans-browser.test.mjs && node authorize-net-dpm-browser.test.mjs && node pelecard-browser.test.mjs && node aci-browser.test.mjs && node creditguard-browser.test.mjs && node primer-browser.test.mjs && node payrails-browser.test.mjs && node elavon-converge-browser.test.mjs && node eprotect-browser.test.mjs && node wspay-browser.test.mjs && node shiji-sts-browser.test.mjs && node worldline-mycheckout-browser.test.mjs && node paypal-hosted-fields-browser.test.mjs && node cybersource-secure-acceptance-browser.test.mjs && node gravy-browser.test.mjs",
32
32
  "check:payment-core": "node scripts/generate-payment-core.mjs --check",
33
33
  "check:voice-stream": "node scripts/generate-voice-stream.mjs --check",
@@ -49,5 +49,5 @@
49
49
  "engines": {
50
50
  "node": ">=22"
51
51
  },
52
- "license": "UNLICENSED"
52
+ "license": "SEE LICENSE IN LICENSE"
53
53
  }