@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/client.js CHANGED
@@ -14,6 +14,7 @@ import { creditguardPageProcessUrl, creditguardPpDomainHost, creditguardPreparat
14
14
  import { CONVERGE_CAPABILITY } from './elavon-converge.js';
15
15
  import { SHIJI_STS_CAPABILITY, SHIJI_STS_PSP, isShijiStsServedCompanion, isShijiStsServedSubstitution } from './shiji-sts.js';
16
16
  import { AMADEUS_CHECKOUT_CAPABILITY, AMADEUS_CHECKOUT_PSP } from './amadeus-checkout.generated.js';
17
+ import { PADDLE_VGS_CAPABILITY, PADDLE_VGS_PSP, paddleVgsCardFactsOf } from './paddle-vgs.generated.js';
17
18
  import { classifyStripeCheckoutRequest, encodeStripeCheckoutContext, hasStripeCheckoutMarker, parseStripeCheckoutContext, parseStripeCheckoutResponse, STRIPE_CHECKOUT_CONTEXT_HEADER, } from './stripe-checkout.generated.js';
18
19
  import { VoiceModule } from './voice.js';
19
20
  import { BuyModule } from './buy.js';
@@ -185,8 +186,16 @@ export const SUPPORTED_REGISTRY_FEATURES = ['card_fields', 'checkout_sessions'];
185
186
  * the same terms: only to a build that lists it, for a company Agentcard turned
186
187
  * Moneris on for. A gated recognizer never sits in BUILTIN_REGISTRY, so it is paused
187
188
  * only after a sync.
189
+ *
190
+ * `paddle_vgs_cards`: the card request Paddle's checkout frame sends to its VGS card
191
+ * vault (vault.paddle.com/cards) on a checkout that names VGS, only through a prepared
192
+ * checkout: the frame aborts the request 10 seconds after Pay and sends it once more,
193
+ * and this build answers that second request from the same approval. The cardholder's
194
+ * device sends the card itself and the frame gets back the stored card's id alone.
195
+ * Served on the same terms: only to a build that lists it, for a company Agentcard
196
+ * turned Paddle on for.
188
197
  */
189
- export const SUPPORTED_CAPABILITIES = ['fiserv_card_capture', GMO_CAPABILITY, CYBERSOURCE_FLEX_CAPABILITY, JUSPAY_CAPABILITY, DATATRANS_CAPABILITY, 'authorize_net_dpm_form', PELECARD_CAPABILITY, ACI_CAPABILITY, 'creditguard_pps_process', PRIMER_CAPABILITY, PAYRAILS_CAPABILITY, CONVERGE_CAPABILITY, EPROTECT_CAPABILITY, WSPAY_CAPABILITY, SHIJI_STS_CAPABILITY, AMADEUS_CHECKOUT_CAPABILITY, 'worldline_mycheckout_form', 'verifone_card_capture', 'ekashu_hosted_payment_page', SECURE_ACCEPTANCE_CAPABILITY, 'gravy_session_fields_put', 'moneris_hosted_tokenization'];
198
+ export const SUPPORTED_CAPABILITIES = ['fiserv_card_capture', GMO_CAPABILITY, CYBERSOURCE_FLEX_CAPABILITY, JUSPAY_CAPABILITY, DATATRANS_CAPABILITY, 'authorize_net_dpm_form', PELECARD_CAPABILITY, ACI_CAPABILITY, 'creditguard_pps_process', PRIMER_CAPABILITY, PAYRAILS_CAPABILITY, CONVERGE_CAPABILITY, EPROTECT_CAPABILITY, WSPAY_CAPABILITY, SHIJI_STS_CAPABILITY, AMADEUS_CHECKOUT_CAPABILITY, 'worldline_mycheckout_form', 'verifone_card_capture', 'ekashu_hosted_payment_page', SECURE_ACCEPTANCE_CAPABILITY, 'gravy_session_fields_put', 'moneris_hosted_tokenization', PADDLE_VGS_CAPABILITY];
190
199
  /**
191
200
  * Processors whose paused request names no amount at all, so the pair the
192
201
  * agent passes is the amount the cardholder approves: authorize() refuses one
@@ -207,13 +216,15 @@ export const SUPPORTED_CAPABILITIES = ['fiserv_card_capture', GMO_CAPABILITY, CY
207
216
  * no more than that (the API's card rule). Moneris Hosted Tokenization's
208
217
  * request.php carries the card and no price: the merchant's server charges the
209
218
  * token later, so the agent's pair, above zero (MONERIS_PSP below), is the one
210
- * figure the approval shows.
219
+ * figure the approval shows. Paddle VGS's card request carries none either: Paddle
220
+ * charges the stored card at /pay for what the checkout says, so the agent's pair,
221
+ * which the prepared checkout carries, is the total the cardholder approves.
211
222
  */
212
223
  const MONERIS_PSP = 'moneris';
213
- const AGENT_AMOUNT_PROCESSORS = [GMO_PSP, PELECARD_PSP, WSPAY_PSP, SECURE_ACCEPTANCE_PSP, MONERIS_PSP];
224
+ const AGENT_AMOUNT_PROCESSORS = [GMO_PSP, PELECARD_PSP, WSPAY_PSP, SECURE_ACCEPTANCE_PSP, MONERIS_PSP, PADDLE_VGS_PSP];
214
225
  /** How AmountRequiredError names a processor. */
215
226
  const PROCESSOR_NAMES = { [GMO_PSP]: 'GMO', [PELECARD_PSP]: 'Pelecard', [WSPAY_PSP]: 'WSPay',
216
- [SECURE_ACCEPTANCE_PSP]: 'CyberSource Secure Acceptance', [MONERIS_PSP]: 'Moneris' };
227
+ [SECURE_ACCEPTANCE_PSP]: 'CyberSource Secure Acceptance', [MONERIS_PSP]: 'Moneris', [PADDLE_VGS_PSP]: 'Paddle' };
217
228
  /**
218
229
  * Processors whose request and answer name no amount at all, so the total the
219
230
  * payment page shows (`pageAmount`) is the one figure that ties the agent's pair
@@ -949,6 +960,71 @@ function maskCreditguardTxId(path) {
949
960
  const PATH_SECRET_HOSTS = ['proxy.wspay.biz', 'proxy-test.wspay.biz', 'ipg.monri.com', 'ipgtest.monri.com'];
950
961
  /** The segment after a `payment` segment, in any case (the WSPay proxy is IIS) or after an escaped slash. */
951
962
  const PATH_SECRET = /(\/payment(?:\/|%2f))[^/]*/gi;
963
+ /**
964
+ * Gravy (Gr4vy) gives every merchant hosts of its own under gr4vy.app, and Secure Fields sends the
965
+ * card to /checkout/sessions/<session id>/fields with no auth header, so the session id is that
966
+ * request's only credential (payment-core definitions.js): with it anyone can write card fields
967
+ * into the session until it expires. On those hosts the segment after /checkout/sessions/, in any
968
+ * case or after an escaped slash, reads {session}, as does any other id shaped like Gravy's UUID
969
+ * session ids.
970
+ */
971
+ const GRAVY_HOST = /(?:^|\.)gr4vy\.app\.?$/i;
972
+ const GRAVY_SESSION = /(\/checkout\/sessions(?:\/|%2f))[^/]*/gi;
973
+ const GRAVY_SESSION_ID = /[0-9a-f]{8}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{12}/gi;
974
+ function maskGravySession(path) {
975
+ return path.replace(GRAVY_SESSION, '$1{session}').replace(GRAVY_SESSION_ID, '{session}');
976
+ }
977
+ /**
978
+ * Rapyd's hosted pages send the card to api.rapyd.net (sandboxapi.rapyd.net in test) with no auth
979
+ * header: the page's own id in the path, checkout_<id> for a checkout page and hp_card_<id> for a
980
+ * card page, is the request's only credential (payment-core definitions.js), and with it anyone can
981
+ * read that page and post a card to it until it expires. On Rapyd's hosts every id of those shapes
982
+ * (hp_reuse_<id>, a payment link's, too) and the segment after /hosted/collect/card/, in any case or
983
+ * after an escaped slash, read {token}.
984
+ */
985
+ const RAPYD_HOST = /(?:^|\.)rapyd\.net\.?$/i;
986
+ const RAPYD_CARD_PAGE = /(\/hosted\/collect\/card(?:\/|%2f))[^/]*/gi;
987
+ const RAPYD_PAGE_ID = /(?:checkout|hp_[a-z]+)_[a-z0-9]+/gi;
988
+ function maskRapydToken(path) {
989
+ return path.replace(RAPYD_CARD_PAGE, '$1{token}').replace(RAPYD_PAGE_ID, '{token}');
990
+ }
991
+ /**
992
+ * Stripe Checkout's pages confirm a Checkout Session at /v1/payment_pages/<session id>/confirm on
993
+ * api.stripe.com with the merchant's publishable key, which is public, so the session id is what
994
+ * lets anyone confirm a payment on that checkout until it expires. There the segment after
995
+ * /payment_pages/, in any case or after an escaped slash, reads {session}. A PaymentIntent or
996
+ * SetupIntent id stays: confirming one also needs its client_secret, which rides in the body.
997
+ */
998
+ const STRIPE_API_HOST = /^api\.stripe\.com\.?$/i;
999
+ const STRIPE_PAYMENT_PAGE = /(\/payment_pages(?:\/|%2f))[^/]*/gi;
1000
+ function maskStripeSession(path) {
1001
+ return path.replace(STRIPE_PAYMENT_PAGE, '$1{session}');
1002
+ }
1003
+ /**
1004
+ * redactAciCheckoutId (aci.ts) takes the checkout id out of the paths ACI's widget sends. This rule
1005
+ * takes any other copy of it out on ACI's hosts: a trailing dot on the host, an escaped or doubled
1006
+ * slash, another case, a string that is not a URL. The segment after /v1/checkouts/ and any id of
1007
+ * ACI's shape (<32 hex>.<cluster>-vm-tx<nn>) read {checkoutId}.
1008
+ */
1009
+ const ACI_HOST = /^(?:eu-prod\.|eu-test\.|test\.)?oppwa\.com\.?$/i;
1010
+ const ACI_CHECKOUT = /(\/v1\/checkouts(?:\/|%2f))[^/]*/gi;
1011
+ const ACI_CHECKOUT_ID = /[0-9a-f]{32}\.[a-z0-9]{1,16}-vm-tx[0-9]{1,3}/gi;
1012
+ function maskAciCheckout(path) {
1013
+ return path.replace(ACI_CHECKOUT, '$1{checkoutId}').replace(ACI_CHECKOUT_ID, '{checkoutId}');
1014
+ }
1015
+ /**
1016
+ * The processors whose card request carries its own credential in the URL path, with the hosts
1017
+ * that send it, the text that names those hosts in a string that is not a URL, and the mask. The
1018
+ * guard in redact-url.test.mjs fails for a payment-core recognizer that no one has sorted into
1019
+ * this list or out of it.
1020
+ */
1021
+ const PATH_CREDENTIALS = [
1022
+ { host: CREDITGUARD_HOST, named: /creditguard\.co\.il/i, mask: maskCreditguardTxId },
1023
+ { host: GRAVY_HOST, named: /gr4vy\.app/i, mask: maskGravySession },
1024
+ { host: RAPYD_HOST, named: /rapyd\.net/i, mask: maskRapydToken },
1025
+ { host: STRIPE_API_HOST, named: /api\.stripe\.com/i, mask: maskStripeSession },
1026
+ { host: ACI_HOST, named: /oppwa\.com/i, mask: maskAciCheckout },
1027
+ ];
952
1028
  /**
953
1029
  * A URL as it may appear in an error message: origin + path only. A paused
954
1030
  * request's URL can carry a client secret in its query string, and error
@@ -958,8 +1034,13 @@ const PATH_SECRET = /(\/payment(?:\/|%2f))[^/]*/gi;
958
1034
  * so it reads {txId}, as does any other id-shaped segment there.
959
1035
  * On the Monri and WSPay hosts the secret is a path segment, so it is replaced
960
1036
  * with `{client_secret}`: https://proxy.wspay.biz/v2/payment/{client_secret}/confirm.
1037
+ * On Gravy's hosts the checkout session id is the credential, so it reads
1038
+ * `{session}`: https://api.<merchant>.gr4vy.app/checkout/sessions/{session}/fields.
1039
+ * On Rapyd's hosts the hosted page's id reads `{token}`: https://api.rapyd.net/v1/checkout/{token}.
1040
+ * On api.stripe.com a Checkout Session id reads `{session}`:
1041
+ * https://api.stripe.com/v1/payment_pages/{session}/confirm.
961
1042
  * A string that is not a URL keeps the part before any `?` or `#`, with the
962
- * same segment masked.
1043
+ * same segments masked.
963
1044
  */
964
1045
  export function redactUrl(raw) {
965
1046
  // An ACI checkout id is that checkout's only capability; it never rides on an event.
@@ -969,12 +1050,18 @@ export function redactUrl(raw) {
969
1050
  try {
970
1051
  const u = new URL(raw);
971
1052
  // A trailing dot names the same host, so it cannot let the secret through.
972
- const path = PATH_SECRET_HOSTS.includes(u.hostname.replace(/\.$/, '')) ? u.pathname.replace(PATH_SECRET, '$1{client_secret}') : u.pathname;
973
- return `${u.origin}${CREDITGUARD_HOST.test(u.hostname) ? maskCreditguardTxId(path) : path}`;
1053
+ let path = PATH_SECRET_HOSTS.includes(u.hostname.replace(/\.$/, '')) ? u.pathname.replace(PATH_SECRET, '$1{client_secret}') : u.pathname;
1054
+ for (const rule of PATH_CREDENTIALS)
1055
+ if (rule.host.test(u.hostname))
1056
+ path = rule.mask(path);
1057
+ return `${u.origin}${path}`;
974
1058
  }
975
1059
  catch {
976
- const bare = raw.split(/[?#]/)[0].replace(PATH_SECRET, '$1{client_secret}');
977
- return /creditguard\.co\.il/i.test(bare) ? maskCreditguardTxId(bare) : bare;
1060
+ let bare = raw.split(/[?#]/)[0].replace(PATH_SECRET, '$1{client_secret}');
1061
+ for (const rule of PATH_CREDENTIALS)
1062
+ if (rule.named.test(bare))
1063
+ bare = rule.mask(bare);
1064
+ return bare;
978
1065
  }
979
1066
  }
980
1067
  /**
@@ -1029,6 +1116,17 @@ const EPROTECT_PREPARATION_REFUSALS = ['merchant_profile_mismatch'];
1029
1116
  const SECURE_ACCEPTANCE_PREPARATION_REFUSALS = [
1030
1117
  'processor_unavailable', 'sandbox_host_on_live_row', 'live_host_on_sandbox_row', 'unsupported_checkout', 'merchant_profile_mismatch',
1031
1118
  ];
1119
+ /**
1120
+ * The API's refusals of a Paddle VGS preparation, before any preparation exists, that prepareCheckout
1121
+ * surfaces by name: the lane is held to Agentcard's own account (`processor_unavailable`), the environment
1122
+ * is not the client's test or live mode, a live checkout's merchant is not the name the reviewed seller pin
1123
+ * carries (`merchant_profile_mismatch`), and the locked card is on a network Paddle's card request is not
1124
+ * paid on (`paddle_vgs_card_unsupported`). An unreviewed seller's page answers `unsupported_checkout`, which
1125
+ * PREPARATION_REFUSALS names for every checkout. Only for a Paddle checkout.
1126
+ */
1127
+ const PADDLE_VGS_PREPARATION_REFUSALS = [
1128
+ 'processor_unavailable', 'sandbox_host_on_live_row', 'live_host_on_sandbox_row', 'merchant_profile_mismatch', 'paddle_vgs_card_unsupported',
1129
+ ];
1032
1130
  /**
1033
1131
  * The reason the cardholder's device declines a bound prepared row with, per processor, when its own
1034
1132
  * send boundary passed before the card left it (the Vault's sendWindow, 12 seconds after it last saw
@@ -1541,6 +1639,12 @@ export class VaultClient {
1541
1639
  const amadeus = input.psp === AMADEUS_CHECKOUT_PSP;
1542
1640
  if (amadeus && !input.cardId)
1543
1641
  throw fail('card_required');
1642
+ // Paddle VGS: Paddle's /pay sends the expiry and the card network the agent typed into Paddle's form
1643
+ // (placeholderCard) beside the card the device stores, so the preparation names that card, and the API
1644
+ // approves no other; refusing here asks nobody to approve a preparation that could never bind.
1645
+ const paddle = input.psp === PADDLE_VGS_PSP;
1646
+ if (paddle && !input.cardId)
1647
+ throw fail('card_required');
1544
1648
  // CyberSource Flex, tokenized before Pay: the loaded card field's capture context and fields,
1545
1649
  // verified here against the keys payment-core pins before anything is created (the API and the
1546
1650
  // cardholder's device verify them again).
@@ -1665,6 +1769,8 @@ export class VaultClient {
1665
1769
  || (secureAcceptance && (state.secure_acceptance_profile_id !== secureAcceptanceProfileId || state.card_id !== input.cardId))
1666
1770
  // Amadeus: the card the cardholder approved is the one whose expiry the agent typed.
1667
1771
  || (amadeus && state.card_id !== input.cardId)
1772
+ // Paddle VGS: the card the cardholder approved is the one whose facts the agent typed.
1773
+ || (paddle && state.card_id !== input.cardId)
1668
1774
  // The API says a preparation tokenizes before Pay exactly when this one carried a capture context.
1669
1775
  || (state.tokenize_before_pay === true) !== !!flexContext)
1670
1776
  throw fail('ready_unconfirmed', id);
@@ -1709,6 +1815,7 @@ export class VaultClient {
1709
1815
  && ((error.status >= 400 && error.status < 500 && (PREPARATION_REFUSALS.includes(error.code)
1710
1816
  || (input.psp === 'fiserv' && FISERV_PREPARATION_REFUSALS.includes(error.code))
1711
1817
  || (input.psp === EPROTECT_PSP && EPROTECT_PREPARATION_REFUSALS.includes(error.code))
1818
+ || (input.psp === PADDLE_VGS_PSP && PADDLE_VGS_PREPARATION_REFUSALS.includes(error.code))
1712
1819
  || (!!flexContext && CYBERSOURCE_FLEX_PREPARATION_REFUSALS.includes(error.code))))
1713
1820
  || (input.psp === 'fiserv' && error.status === 502 && error.code === 'cse_key_unavailable')))
1714
1821
  throw fail(error.code);
@@ -2138,9 +2245,12 @@ export class VaultClient {
2138
2245
  // A prepared CyberSource Secure Acceptance token create too, for 30 seconds:
2139
2246
  // the merchant's page asks its frame for the reply for 30 seconds from Save,
2140
2247
  // and the API names 500 ms as such a row's poll interval.
2248
+ // A prepared Paddle VGS card request too, for 20 seconds: Paddle's frame aborts it 10 seconds after
2249
+ // Pay and sends it once more, and the API names 500 ms as such a row's poll interval.
2141
2250
  const preparedFastPollUntil = preparation?.psp === 'mercado_pago' ? Date.now() + 10_000
2142
- : preparation?.psp === EPROTECT_PSP ? Date.now() + 15_000
2143
- : preparation?.psp === SECURE_ACCEPTANCE_PSP ? Date.now() + SECURE_ACCEPTANCE_FAST_POLL_MS : 0;
2251
+ : preparation?.psp === PADDLE_VGS_PSP ? Date.now() + 20_000
2252
+ : preparation?.psp === EPROTECT_PSP ? Date.now() + 15_000
2253
+ : preparation?.psp === SECURE_ACCEPTANCE_PSP ? Date.now() + SECURE_ACCEPTANCE_FAST_POLL_MS : 0;
2144
2254
  // A preparation that tokenized before Pay: the cardholder's device already holds CyberSource's
2145
2255
  // token and approves as soon as this bind reaches it, while the page may give createToken only
2146
2256
  // seconds (Alaska Airlines' flight checkout gives it three). Poll every 100 ms for 10 seconds,
@@ -2510,6 +2620,14 @@ export class VaultClient {
2510
2620
  throw new PaymentOutcomeUnknownError(authorizationId, 'mercado_checkout_metadata_invalid');
2511
2621
  }
2512
2622
  }
2623
+ else if (rec?.psp === PADDLE_VGS_PSP) {
2624
+ // Paddle VGS: the approved card's details the phone read from the card it stored at VGS, which the
2625
+ // frame's /pay names in place of the placeholder's (paddle-vgs-pay.ts). Exactly those five, or the
2626
+ // stored card's id never reaches the frame.
2627
+ processorContext = paddleVgsCardFactsOf(s.processor_context) ?? undefined;
2628
+ if (!processorContext)
2629
+ throw new PaymentOutcomeUnknownError(authorizationId, 'paddle_vgs_card_facts_unavailable');
2630
+ }
2513
2631
  else if (s.processor_context !== undefined)
2514
2632
  throw new PaymentOutcomeUnknownError(authorizationId, 'unexpected_processor_context');
2515
2633
  return {
@@ -2686,11 +2804,12 @@ export class VaultClient {
2686
2804
  * approval produced: a SetupIntent confirm that carries no card and pays with
2687
2805
  * exactly the approved payment method (the page's own, or written by the
2688
2806
  * adapters in place of a card the confirm carries again).
2689
- * The API allows a card save only for a company Agentcard turned it on for,
2690
- * for an approval given on the Vault page, not by voice, with no amount, and
2691
- * a SetupIntent Stripe reports still confirmable on the same Stripe account;
2692
- * any other answer rejects with a CheckoutApiError whose code says why (for
2693
- * example `continuation_invalid` where it is off, `amount_mismatch`). Nothing
2807
+ * The API allows a card save for any company, for an approval given on the
2808
+ * Vault page, not by voice, with no amount, and a SetupIntent Stripe reports
2809
+ * still confirmable on the same Stripe account; any other answer rejects with
2810
+ * a CheckoutApiError whose code says why (for example `amount_mismatch`, or
2811
+ * `continuation_invalid` for a confirm that is not a card-free SetupIntent
2812
+ * confirm). Nothing
2694
2813
  * is charged: the adapters continue the page's request once this resolves.
2695
2814
  */
2696
2815
  async checkStripeSetupContinuation(authorizationId, request) {
@@ -4,6 +4,7 @@ import type { PayrailsUnsupportedReason } from './payrails.js';
4
4
  import type { Substitutions } from './substitute.js';
5
5
  import type { GmoUnsupportedReason } from './gmo.js';
6
6
  import type { ShijiStsUnsupportedReason } from './shiji-sts.js';
7
+ import type { PaddleVgsPayRefusal } from './paddle-vgs-pay.js';
7
8
  /** The application's resolver confirmed the payment for this checkout authorization. */
8
9
  export interface MerchantPaymentConfirmation {
9
10
  kind: 'merchant_payment';
@@ -91,6 +92,11 @@ export interface ResumeResult {
91
92
  headers: Record<string, string>;
92
93
  body: string;
93
94
  };
95
+ /**
96
+ * The read's `processor_context`, as the API serves it. A Paddle VGS read carries the approved card's
97
+ * details here, which the frame's /pay names; without them the stored card's id is never handed to the page.
98
+ */
99
+ processor_context?: unknown;
94
100
  substitutions?: Substitutions;
95
101
  submitted_at?: string;
96
102
  execution_mode?: string;
@@ -239,9 +245,12 @@ export declare class CheckoutLifecycle implements CheckoutController {
239
245
  * over (unsupported_paypal_hosted_fields, paypal-hosted-fields.ts), or a card
240
246
  * a merchant's page posts to CyberSource Secure Acceptance's Silent Order
241
247
  * POST, which no Vault mode takes over either
242
- * (unsupported_cybersource_secure_acceptance, cybersource-secure-acceptance.ts).
248
+ * (unsupported_cybersource_secure_acceptance, cybersource-secure-acceptance.ts), or a
249
+ * Paddle /pay naming a stored card that its binding does not continue (the
250
+ * paddle-vgs-pay.ts reasons): once an approval exists the state keeps it, and only the
251
+ * hold changes.
243
252
  */
244
- unsupported(reason?: 'unrecognized_payment_endpoint' | 'merchant_profile_observe' | 'merchant_request_unsupported' | GmoUnsupportedReason | 'microform_unsupported' | 'juspay_request_unsupported' | 'merchant_card_endpoint_not_enabled' | 'merchant_card_endpoint_resend_refused' | 'creditguard_three_d_secure' | 'unsupported_primer_sdk' | 'unsupported_primer_request' | PayrailsUnsupportedReason | ShijiStsUnsupportedReason | 'processor_card_page' | 'unsupported_paypal_hosted_fields' | 'unsupported_cybersource_secure_acceptance'): void;
253
+ unsupported(reason?: 'unrecognized_payment_endpoint' | 'merchant_profile_observe' | 'merchant_request_unsupported' | GmoUnsupportedReason | 'microform_unsupported' | 'juspay_request_unsupported' | 'merchant_card_endpoint_not_enabled' | 'merchant_card_endpoint_resend_refused' | 'creditguard_three_d_secure' | 'unsupported_primer_sdk' | 'unsupported_primer_request' | PayrailsUnsupportedReason | ShijiStsUnsupportedReason | 'processor_card_page' | 'unsupported_paypal_hosted_fields' | 'unsupported_cybersource_secure_acceptance' | PaddleVgsPayRefusal): void;
245
254
  /**
246
255
  * A card request that only a prepared checkout pays (an Adyen merchant
247
256
  * profile's, or a Fiserv card capture) arrived with no preparation on this
package/dist/lifecycle.js CHANGED
@@ -238,7 +238,10 @@ export class CheckoutLifecycle {
238
238
  * over (unsupported_paypal_hosted_fields, paypal-hosted-fields.ts), or a card
239
239
  * a merchant's page posts to CyberSource Secure Acceptance's Silent Order
240
240
  * POST, which no Vault mode takes over either
241
- * (unsupported_cybersource_secure_acceptance, cybersource-secure-acceptance.ts).
241
+ * (unsupported_cybersource_secure_acceptance, cybersource-secure-acceptance.ts), or a
242
+ * Paddle /pay naming a stored card that its binding does not continue (the
243
+ * paddle-vgs-pay.ts reasons): once an approval exists the state keeps it, and only the
244
+ * hold changes.
242
245
  */
243
246
  unsupported(reason = 'unrecognized_payment_endpoint') {
244
247
  this.held = true;
@@ -0,0 +1,55 @@
1
+ import type { ReplayResponse } from './client.js';
2
+ /**
3
+ * Why a /pay naming a stored card was aborted, reported as the `reason` of the `unsupported_checkout` event:
4
+ * paddle_vgs_pay_unbound this attachment handed no stored card's id to the frame that sent it
5
+ * paddle_vgs_pay_expired it came more than a minute after the id reached the frame
6
+ * paddle_vgs_pay_used the one /pay for that id already went
7
+ * paddle_vgs_pay_card_mismatch it names another stored card, or the other environment's checkout service
8
+ * paddle_vgs_pay_unsupported anything else payment-core's rule refuses: another shape, a card saved at
9
+ * Paddle for later, a network the frame chose from the typed card
10
+ */
11
+ export type PaddleVgsPayRefusal = 'paddle_vgs_pay_unbound' | 'paddle_vgs_pay_expired' | 'paddle_vgs_pay_used' | 'paddle_vgs_pay_card_mismatch' | 'paddle_vgs_pay_unsupported';
12
+ /** The patterns attachToCdp holds Paddle's two checkout services under, only for an attachment the API served Paddle VGS to. */
13
+ export declare const PADDLE_VGS_PAY_PATTERNS: readonly string[];
14
+ /** Whether `url` is on one of Paddle's two checkout services: what attachToPlaywright routes, once served. */
15
+ export declare function isPaddleCheckoutService(url: string): boolean;
16
+ /** Whether the synced registry serves Paddle VGS's card request: an org Agentcard turned Paddle on for. */
17
+ export declare function recognizesPaddleVgs(vault: {
18
+ isCardRequest?: (url: string, method?: string) => boolean;
19
+ } | null | undefined): boolean;
20
+ /** The URL an event names for a /pay: its origin and path, with the checkout's id as {checkout}. */
21
+ export declare function paddleVgsPayEventUrl(url: string): string;
22
+ /**
23
+ * One attachment's /pay binding: at most one, to the stored card's id it last handed a Paddle frame. `arm` runs
24
+ * right before the id is handed over, `release` when the attachment is cancelled, and `judge` for every request
25
+ * to Paddle's checkout services. `served` says whether the synced registry serves Paddle VGS; it is asked only
26
+ * about a POST to /pay, so no other request reads the registry here.
27
+ */
28
+ export declare class PaddleVgsPayGate {
29
+ private readonly served;
30
+ private readonly now;
31
+ private bound;
32
+ constructor(served: () => boolean, now?: () => number);
33
+ /** Whether the gate judges this request: a POST to /pay, on an attachment the API served Paddle VGS to. */
34
+ holds(url: string, method: string): boolean;
35
+ /**
36
+ * Bind the next /pay of `frame`, the frame about to receive `replay` for its card request to `cardsUrl`, to the
37
+ * stored card's id in the replay and the approved card's facts the API served beside it. Does nothing for a
38
+ * request to anything but Paddle's card vault. Throws when the frame is unknown, or the replay carries no id or
39
+ * no facts payment-core reads: the id must then not reach the frame, since its /pay could only carry the
40
+ * placeholder's facts.
41
+ */
42
+ arm(frame: unknown, cardsUrl: string, replay: ReplayResponse): void;
43
+ /** Drop the binding: no /pay continues with these facts after this. */
44
+ release(): void;
45
+ /**
46
+ * What becomes of a request to Paddle's checkout services: null when it is not a /pay naming a stored card (it
47
+ * continues untouched), the body to continue it with (payment-core paddleVgsPayVerdict), or why it is aborted.
48
+ * The binding answers one /pay: the first one from its frame continues or is refused, and either way ends it.
49
+ */
50
+ judge(frame: unknown, url: string, method: string, body: string | null): {
51
+ body: string;
52
+ } | {
53
+ reason: PaddleVgsPayRefusal;
54
+ } | null;
55
+ }
@@ -0,0 +1,115 @@
1
+ // Paddle's /pay, the request that charges the card the cardholder's phone stored at VGS, as the SDK sees it
2
+ // (payment-core paddle-vgs.js has the request's shape and the rule).
3
+ //
4
+ // Once the SDK hands the stored card's id to Paddle's checkout frame, the frame posts /pay to its checkout
5
+ // service with that id beside card facts it read from the number typed into its own form: on this lane the
6
+ // agent's placeholder, so the placeholder's first six, last four and brand. This build holds that /pay and
7
+ // writes over those five values the approved card's own, which the phone read from the card it sent to VGS and
8
+ // the API served with the id (payment-core paddleVgsPayVerdict). Every other byte goes as the frame wrote it.
9
+ //
10
+ // The binding: the frame that received the stored card's id, the id, the vault's environment and the approved
11
+ // card's facts, for PADDLE_VGS_CARD_ID_WINDOW_MS after the hand-over, for one /pay. A /pay naming a stored card
12
+ // is aborted, so Paddle charges nothing, when this attachment handed no id to that frame
13
+ // (paddle_vgs_pay_unbound), after the window (paddle_vgs_pay_expired), after the one /pay already went
14
+ // (paddle_vgs_pay_used), or when payment-core's rule refuses it: another stored card or environment
15
+ // (paddle_vgs_pay_card_mismatch), or any other shape (paddle_vgs_pay_unsupported). A /pay naming no stored card
16
+ // (PayPal, a wallet) carries none of the cardholder's cards and continues untouched.
17
+ //
18
+ // Nothing is decided without the registry: an attachment the API did not serve Paddle VGS to never holds
19
+ // Paddle's checkout service, so its /pay continues untouched, exactly as before.
20
+ import { PADDLE_VGS_CARD_ID_WINDOW_MS, PADDLE_VGS_CARDS_URLS, paddleVgsCardFactsOf, paddleVgsEnvironment, paddleVgsPayCarriesStoredCard, paddleVgsPayTarget, paddleVgsPayVerdict, } from './paddle-vgs.generated.js';
21
+ const CHECKOUT_SERVICES = ['https://checkout-service.paddle.com', 'https://sandbox-checkout-service.paddle.com'];
22
+ /** The patterns attachToCdp holds Paddle's two checkout services under, only for an attachment the API served Paddle VGS to. */
23
+ export const PADDLE_VGS_PAY_PATTERNS = CHECKOUT_SERVICES.map((origin) => `${origin}/*`);
24
+ /** Whether `url` is on one of Paddle's two checkout services: what attachToPlaywright routes, once served. */
25
+ export function isPaddleCheckoutService(url) {
26
+ try {
27
+ return CHECKOUT_SERVICES.includes(new URL(url).origin);
28
+ }
29
+ catch {
30
+ return false;
31
+ }
32
+ }
33
+ /** Whether the synced registry serves Paddle VGS's card request: an org Agentcard turned Paddle on for. */
34
+ export function recognizesPaddleVgs(vault) {
35
+ if (!vault || typeof vault.isCardRequest !== 'function')
36
+ return false;
37
+ try {
38
+ return vault.isCardRequest(PADDLE_VGS_CARDS_URLS.production, 'POST') || vault.isCardRequest(PADDLE_VGS_CARDS_URLS.sandbox, 'POST');
39
+ }
40
+ catch {
41
+ return false;
42
+ }
43
+ }
44
+ /** The URL an event names for a /pay: its origin and path, with the checkout's id as {checkout}. */
45
+ export function paddleVgsPayEventUrl(url) {
46
+ return url.replace(/^(https:\/\/[^/]+\/(?:checkout|transaction-checkout)\/)[^/]+(\/pay)$/, '$1{checkout}$2');
47
+ }
48
+ const known = (frame) => frame !== undefined && frame !== null && frame !== '';
49
+ /**
50
+ * One attachment's /pay binding: at most one, to the stored card's id it last handed a Paddle frame. `arm` runs
51
+ * right before the id is handed over, `release` when the attachment is cancelled, and `judge` for every request
52
+ * to Paddle's checkout services. `served` says whether the synced registry serves Paddle VGS; it is asked only
53
+ * about a POST to /pay, so no other request reads the registry here.
54
+ */
55
+ export class PaddleVgsPayGate {
56
+ served;
57
+ now;
58
+ bound = null;
59
+ constructor(served, now = Date.now) {
60
+ this.served = served;
61
+ this.now = now;
62
+ }
63
+ /** Whether the gate judges this request: a POST to /pay, on an attachment the API served Paddle VGS to. */
64
+ holds(url, method) {
65
+ return String(method).toUpperCase() === 'POST' && paddleVgsPayTarget(url) !== null && this.served();
66
+ }
67
+ /**
68
+ * Bind the next /pay of `frame`, the frame about to receive `replay` for its card request to `cardsUrl`, to the
69
+ * stored card's id in the replay and the approved card's facts the API served beside it. Does nothing for a
70
+ * request to anything but Paddle's card vault. Throws when the frame is unknown, or the replay carries no id or
71
+ * no facts payment-core reads: the id must then not reach the frame, since its /pay could only carry the
72
+ * placeholder's facts.
73
+ */
74
+ arm(frame, cardsUrl, replay) {
75
+ const environment = paddleVgsEnvironment(cardsUrl);
76
+ if (environment === null)
77
+ return;
78
+ this.release();
79
+ const token = replay.mode === undefined || replay.mode === 'token' ? replay : null;
80
+ const facts = token ? paddleVgsCardFactsOf(token.processorContext) : null;
81
+ let cardId = null;
82
+ try {
83
+ cardId = token ? JSON.parse(token.body)?.data?.id : null;
84
+ }
85
+ catch {
86
+ cardId = null;
87
+ }
88
+ if (!known(frame) || !facts || typeof cardId !== 'string')
89
+ throw new Error('paddle_vgs_card_facts_unavailable');
90
+ this.bound = { frame, environment, cardId, facts, until: this.now() + PADDLE_VGS_CARD_ID_WINDOW_MS, used: false };
91
+ }
92
+ /** Drop the binding: no /pay continues with these facts after this. */
93
+ release() {
94
+ this.bound = null;
95
+ }
96
+ /**
97
+ * What becomes of a request to Paddle's checkout services: null when it is not a /pay naming a stored card (it
98
+ * continues untouched), the body to continue it with (payment-core paddleVgsPayVerdict), or why it is aborted.
99
+ * The binding answers one /pay: the first one from its frame continues or is refused, and either way ends it.
100
+ */
101
+ judge(frame, url, method, body) {
102
+ if (!this.holds(url, method) || !paddleVgsPayCarriesStoredCard(body))
103
+ return null;
104
+ const bound = this.bound;
105
+ if (!bound || !known(frame) || frame !== bound.frame)
106
+ return { reason: 'paddle_vgs_pay_unbound' };
107
+ if (bound.used)
108
+ return { reason: 'paddle_vgs_pay_used' };
109
+ bound.used = true;
110
+ if (this.now() > bound.until)
111
+ return { reason: 'paddle_vgs_pay_expired' };
112
+ const verdict = paddleVgsPayVerdict({ url, method, body }, { environment: bound.environment, cardId: bound.cardId, facts: bound.facts });
113
+ return verdict.verdict === 'continue' ? { body: verdict.body } : { reason: verdict.code };
114
+ }
115
+ }
@@ -0,0 +1,78 @@
1
+ declare var PADDLE_VGS_PSP: string;
2
+ declare var PADDLE_VGS_CAPABILITY: string;
3
+ declare var PADDLE_VGS_OPERATION: string;
4
+ declare var PADDLE_VGS_CONTENT_TYPE: string;
5
+ declare var PADDLE_VGS_CARDS_URLS: Readonly<{
6
+ production: "https://vault.paddle.com/cards";
7
+ sandbox: "https://sandbox-vault.paddle.com/cards";
8
+ }>;
9
+ declare var PADDLE_VGS_REFUSAL_CODES: readonly string[];
10
+ declare function paddleVgsEnvironment(url: any): "production" | "sandbox" | null;
11
+ declare function isPaddleVgsCardsUrl(url: any): boolean;
12
+ declare function paddleVgsBodyVerdict(body: any): Readonly<{
13
+ verdict: "refuse";
14
+ code: any;
15
+ }> | Readonly<{
16
+ verdict: "card";
17
+ }>;
18
+ declare function paddleVgsRequestRefusal(request: any): any;
19
+ declare function isPreparedPaddleVgsRequest(url: any, method: any, body: any, environment: any, headers: any): boolean;
20
+ declare var PADDLE_VGS_MERCHANT_PINS: readonly any[];
21
+ declare function paddleVgsMerchantPin(merchantOrigin: any, pins?: readonly any[]): any;
22
+ declare function paddleVgsMerchantRefusal(environment: any, merchantOrigin: any, authorization: any, pins?: readonly any[]): "unsupported_environment" | "paddle_vgs_merchant_unreviewed" | "paddle_vgs_seller_unreviewed" | null;
23
+ declare function paddleVgsSellerClaimsPinned(pins?: readonly any[]): boolean;
24
+ declare function paddleVgsRefusalMessage(code: any): any;
25
+ declare var PADDLE_VGS_CARD_NETWORKS: readonly string[];
26
+ declare function paddleVgsCardRefusal(body: any, card: any): any;
27
+ declare function paddleVgsStoredCardRefusal(brand: any): "paddle_vgs_card_unsupported" | null;
28
+ declare function paddleVgsTypedCardRefusal(body: any, stored: any): any;
29
+ declare function injectPaddleVgsCard(body: any, card: any): {
30
+ body: string;
31
+ where: string[];
32
+ };
33
+ declare function paddleVgsRefusalCode(text: any): string | null;
34
+ declare function paddleVgsAnswerOutcome(status: any, text: any, card: any): {
35
+ outcome: string;
36
+ reason: string;
37
+ code?: undefined;
38
+ body?: undefined;
39
+ } | {
40
+ outcome: string;
41
+ code: string;
42
+ reason?: undefined;
43
+ body?: undefined;
44
+ } | {
45
+ outcome: string;
46
+ body: string;
47
+ reason?: undefined;
48
+ code?: undefined;
49
+ };
50
+ declare var PADDLE_VGS_CARD_ID_WINDOW_MS: number;
51
+ declare function paddleVgsPayTarget(url: any): Readonly<{
52
+ environment: "production" | "sandbox";
53
+ line: "classic" | "billing";
54
+ }> | null;
55
+ declare function paddleVgsPayCarriesStoredCard(body: any): boolean;
56
+ declare function paddleVgsCardFacts(card: any): Readonly<{
57
+ card_first_six: string;
58
+ card_last_four: string;
59
+ expiry_month: number;
60
+ expiry_year: number;
61
+ card_brand: "visa" | "master" | "american_express";
62
+ }>;
63
+ declare function paddleVgsCardFactsOf(value: any): Readonly<{
64
+ card_first_six: any;
65
+ card_last_four: string;
66
+ expiry_month: any;
67
+ expiry_year: any;
68
+ card_brand: any;
69
+ }> | null;
70
+ declare function paddleVgsCardFactsAgree(facts: any, stored: any): boolean;
71
+ declare function paddleVgsPayVerdict(request: any, approved: any): Readonly<{
72
+ verdict: "refuse";
73
+ code: any;
74
+ }> | Readonly<{
75
+ verdict: "continue";
76
+ body: string;
77
+ }>;
78
+ export { PADDLE_VGS_CAPABILITY, PADDLE_VGS_CARDS_URLS, PADDLE_VGS_CARD_ID_WINDOW_MS, PADDLE_VGS_CARD_NETWORKS, PADDLE_VGS_CONTENT_TYPE, PADDLE_VGS_MERCHANT_PINS, PADDLE_VGS_OPERATION, PADDLE_VGS_PSP, PADDLE_VGS_REFUSAL_CODES, injectPaddleVgsCard, isPaddleVgsCardsUrl, isPreparedPaddleVgsRequest, paddleVgsAnswerOutcome, paddleVgsBodyVerdict, paddleVgsCardFacts, paddleVgsCardFactsAgree, paddleVgsCardFactsOf, paddleVgsCardRefusal, paddleVgsEnvironment, paddleVgsMerchantPin, paddleVgsMerchantRefusal, paddleVgsPayCarriesStoredCard, paddleVgsPayTarget, paddleVgsPayVerdict, paddleVgsRefusalCode, paddleVgsRefusalMessage, paddleVgsRequestRefusal, paddleVgsSellerClaimsPinned, paddleVgsStoredCardRefusal, paddleVgsTypedCardRefusal };