@agent-cards/sdk 0.23.10 → 0.23.12

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,289 @@
1
+ // @ts-nocheck
2
+ // Generated from @agent-cards/payment-core. Do not edit.
3
+ // artifact-sha256: b33914c3bb882b868a28a7d7abb91d6cca450c0145dca0aa338474400b6f923e
4
+ // src/paddle-vgs.js
5
+ var PADDLE_VGS_PSP = "paddle_vgs";
6
+ var PADDLE_VGS_CAPABILITY = "paddle_vgs_cards";
7
+ var PADDLE_VGS_OPERATION = "paddle_vgs.card_request";
8
+ var PADDLE_VGS_CONTENT_TYPE = "application/vnd.api+json";
9
+ var PADDLE_VGS_CARDS_URLS = /* @__PURE__ */ Object.freeze({
10
+ production: "https://vault.paddle.com/cards",
11
+ sandbox: "https://sandbox-vault.paddle.com/cards"
12
+ });
13
+ var PADDLE_VGS_REFUSAL_CODES = /* @__PURE__ */ Object.freeze(["paddle_vgs_url_refused", "paddle_vgs_headers_refused", "paddle_vgs_body_unsupported"]);
14
+ var BODY_LIMIT = 4096;
15
+ var ROOT_KEYS = /* @__PURE__ */ Object.freeze(["data"]);
16
+ var DATA_KEYS = /* @__PURE__ */ Object.freeze(["type", "attributes"]);
17
+ var ATTRIBUTE_KEYS = /* @__PURE__ */ Object.freeze(["pan", "cvc", "exp_month", "exp_year", "cardholder"]);
18
+ var WHERE = /* @__PURE__ */ Object.freeze(["data.attributes.pan", "data.attributes.cvc", "data.attributes.exp_month", "data.attributes.exp_year"]);
19
+ var CARD_NUMBER = /^[0-9]{12,19}$/;
20
+ var SECURITY_CODE = /^[0-9]{3,4}$/;
21
+ var NAME_LIMIT = 256;
22
+ var CONTROL = /[\u0000-\u001f\u007f]/;
23
+ var PAN_LIKE = /(?:[0-9][ _.:-]*){12,}/;
24
+ var cardLikeName = (name) => (name.match(/[0-9]/g) ?? []).length >= 12;
25
+ var BEARER = /^Bearer [A-Za-z0-9_-]{8,512}\.([A-Za-z0-9_-]{8,4096})\.[A-Za-z0-9_-]{8,1024}$/;
26
+ var BEARER_LIFETIME_SECONDS = 1200;
27
+ var BASE64URL = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
28
+ var record = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
29
+ var sameKeys = (value, keys) => {
30
+ const own = Object.keys(value);
31
+ return own.length === keys.length && own.every((key, at) => key === keys[at]);
32
+ };
33
+ var refuse = (code) => Object.freeze({ verdict: "refuse", code });
34
+ var CARD = /* @__PURE__ */ Object.freeze({ verdict: "card" });
35
+ function base64UrlText(segment) {
36
+ let bits = 0;
37
+ let value = 0;
38
+ let escaped = "";
39
+ for (const character of segment) {
40
+ const sextet = BASE64URL.indexOf(character);
41
+ if (sextet < 0)
42
+ return null;
43
+ value = value << 6 | sextet;
44
+ bits += 6;
45
+ if (bits >= 8) {
46
+ bits -= 8;
47
+ escaped += "%" + (value >> bits & 255).toString(16).padStart(2, "0");
48
+ value &= (1 << bits) - 1;
49
+ }
50
+ }
51
+ try {
52
+ return decodeURIComponent(escaped);
53
+ }
54
+ catch {
55
+ return null;
56
+ }
57
+ }
58
+ function bearerClaims(authorization) {
59
+ const match = typeof authorization === "string" ? BEARER.exec(authorization) : null;
60
+ const text = match ? base64UrlText(match[1]) : null;
61
+ if (text === null)
62
+ return null;
63
+ try {
64
+ const claims = JSON.parse(text);
65
+ return record(claims) ? claims : null;
66
+ }
67
+ catch {
68
+ return null;
69
+ }
70
+ }
71
+ function bearerRunning(authorization, nowSeconds = Math.floor(Date.now() / 1e3)) {
72
+ const claims = bearerClaims(authorization);
73
+ if (!claims || !Number.isSafeInteger(claims.iat) || !Number.isSafeInteger(claims.exp))
74
+ return false;
75
+ return claims.exp > claims.iat && claims.exp - claims.iat <= BEARER_LIFETIME_SECONDS && claims.exp > nowSeconds;
76
+ }
77
+ function paddleVgsEnvironment(url) {
78
+ if (url === PADDLE_VGS_CARDS_URLS.production)
79
+ return "production";
80
+ if (url === PADDLE_VGS_CARDS_URLS.sandbox)
81
+ return "sandbox";
82
+ return null;
83
+ }
84
+ function isPaddleVgsCardsUrl(url) {
85
+ return paddleVgsEnvironment(url) !== null;
86
+ }
87
+ function readBody(body) {
88
+ if (typeof body !== "string" || body === "" || body.length > BODY_LIMIT)
89
+ return null;
90
+ let value;
91
+ try {
92
+ value = JSON.parse(body);
93
+ if (!record(value) || JSON.stringify(value) !== body)
94
+ return null;
95
+ }
96
+ catch {
97
+ return null;
98
+ }
99
+ return value;
100
+ }
101
+ function paddleVgsBodyVerdict(body) {
102
+ const value = readBody(body);
103
+ if (value === null || !sameKeys(value, ROOT_KEYS) || !record(value.data) || !sameKeys(value.data, DATA_KEYS) || value.data.type !== "cards" || !record(value.data.attributes) || !sameKeys(value.data.attributes, ATTRIBUTE_KEYS)) {
104
+ return refuse("paddle_vgs_body_unsupported");
105
+ }
106
+ const card = value.data.attributes;
107
+ const holder = card.cardholder;
108
+ if (typeof card.pan !== "string" || !CARD_NUMBER.test(card.pan) || typeof card.cvc !== "string" || !SECURITY_CODE.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 || !record(holder) || !sameKeys(holder, ["name"]) || typeof holder.name !== "string" || holder.name.length > NAME_LIMIT || CONTROL.test(holder.name) || cardLikeName(holder.name)) {
109
+ return refuse("paddle_vgs_body_unsupported");
110
+ }
111
+ return CARD;
112
+ }
113
+ function headerValue(headers, name) {
114
+ const found = Object.keys(headers).filter((key) => key.toLowerCase() === name);
115
+ return found.length === 1 && typeof headers[found[0]] === "string" ? headers[found[0]] : null;
116
+ }
117
+ function paddleVgsRequestRefusal(request) {
118
+ if (!record(request) || typeof request.method !== "string" || request.method.toUpperCase() !== "POST")
119
+ return "paddle_vgs_url_refused";
120
+ if (!isPaddleVgsCardsUrl(request.url))
121
+ return "paddle_vgs_url_refused";
122
+ const headers = record(request.headers) ? request.headers : {};
123
+ const contentType = headerValue(headers, "content-type");
124
+ const authorization = headerValue(headers, "authorization");
125
+ if (contentType === null || contentType.trim().toLowerCase() !== PADDLE_VGS_CONTENT_TYPE || authorization === null || !bearerRunning(authorization))
126
+ return "paddle_vgs_headers_refused";
127
+ const verdict = paddleVgsBodyVerdict(request.body);
128
+ return verdict.verdict === "card" ? null : verdict.code;
129
+ }
130
+ function isPreparedPaddleVgsRequest(url, method, body, environment, headers) {
131
+ return (environment === "production" || environment === "sandbox") && paddleVgsEnvironment(url) === environment && paddleVgsRequestRefusal({ url, method, headers, body }) === null;
132
+ }
133
+ var pin = (fields) => Object.freeze({
134
+ ...fields,
135
+ origins: Object.freeze([...fields.origins]),
136
+ sellerClaim: fields.sellerClaim ? Object.freeze({ ...fields.sellerClaim }) : null
137
+ });
138
+ var PADDLE_VGS_MERCHANT_PINS = /* @__PURE__ */ Object.freeze([
139
+ pin({ id: "rectangle_pro", merchant: "Rectangle Pro", origins: ["https://rectangleapp.com"], sellerClaim: null })
140
+ ]);
141
+ function paddleVgsMerchantPin(merchantOrigin, pins = PADDLE_VGS_MERCHANT_PINS) {
142
+ if (typeof merchantOrigin !== "string" || !Array.isArray(pins))
143
+ return null;
144
+ return pins.find((entry) => !!entry && Array.isArray(entry.origins) && entry.origins.includes(merchantOrigin)) ?? null;
145
+ }
146
+ function paddleVgsMerchantRefusal(environment, merchantOrigin, authorization, pins = PADDLE_VGS_MERCHANT_PINS) {
147
+ if (environment === "sandbox")
148
+ return null;
149
+ if (environment !== "production")
150
+ return "unsupported_environment";
151
+ const found = paddleVgsMerchantPin(merchantOrigin, pins);
152
+ if (!found)
153
+ return "paddle_vgs_merchant_unreviewed";
154
+ if (found.sellerClaim && authorization !== void 0) {
155
+ const claims = bearerClaims(authorization);
156
+ if (!claims || claims[found.sellerClaim.name] !== found.sellerClaim.value)
157
+ return "paddle_vgs_seller_unreviewed";
158
+ }
159
+ return null;
160
+ }
161
+ function paddleVgsSellerClaimsPinned(pins = PADDLE_VGS_MERCHANT_PINS) {
162
+ return Array.isArray(pins) && pins.length > 0 && pins.every((entry) => !!entry && !!entry.sellerClaim && typeof entry.sellerClaim.name === "string" && entry.sellerClaim.name !== "" && typeof entry.sellerClaim.value === "string" && entry.sellerClaim.value !== "");
163
+ }
164
+ var REFUSAL_MESSAGES = /* @__PURE__ */ Object.freeze({
165
+ paddle_vgs_url_refused: "Refusing to pay: this Paddle card request does not go to Paddle's card vault exactly as Paddle's checkout sends it. Nothing was sent.",
166
+ paddle_vgs_headers_refused: "Refusing to pay: this Paddle card request does not carry the checkout's own headers. Nothing was sent.",
167
+ paddle_vgs_body_unsupported: "Refusing to pay: this is not the card request of Paddle's checkout. Nothing was sent."
168
+ });
169
+ function paddleVgsRefusalMessage(code) {
170
+ return Object.prototype.hasOwnProperty.call(REFUSAL_MESSAGES, code) ? REFUSAL_MESSAGES[code] : REFUSAL_MESSAGES.paddle_vgs_body_unsupported;
171
+ }
172
+ var CARD_MISMATCH_REFUSAL = "Refusing to pay: this Paddle payment names a card expiry or network that is not your card's. Nothing was sent.";
173
+ var PADDLE_VGS_CARD_NETWORKS = /* @__PURE__ */ Object.freeze(["visa", "mastercard", "amex"]);
174
+ function cardNetwork(number) {
175
+ if (typeof number !== "string" || !CARD_NUMBER.test(number))
176
+ return null;
177
+ const two = Number(number.slice(0, 2));
178
+ const four = Number(number.slice(0, 4));
179
+ if (number[0] === "4")
180
+ return "visa";
181
+ if (two >= 51 && two <= 55 || four >= 2221 && four <= 2720)
182
+ return "mastercard";
183
+ if (two === 34 || two === 37)
184
+ return "amex";
185
+ return null;
186
+ }
187
+ function paddleVgsCardRefusal(body, card) {
188
+ const verdict = paddleVgsBodyVerdict(body);
189
+ if (verdict.verdict !== "card")
190
+ return verdict.code;
191
+ const attributes = JSON.parse(body).data.attributes;
192
+ const number = card ? String(card.number) : "";
193
+ const month = card ? Number(card.month) : NaN;
194
+ const year = card ? Number(card.year) % 100 : NaN;
195
+ const network = cardNetwork(number);
196
+ if (attributes.exp_month !== month || attributes.exp_year !== year || network === null || cardNetwork(attributes.pan) !== network) {
197
+ return "paddle_vgs_card_mismatch";
198
+ }
199
+ return null;
200
+ }
201
+ var storedBrand = (brand) => typeof brand === "string" && brand !== "" ? brand.toLowerCase() : null;
202
+ function paddleVgsStoredCardRefusal(brand) {
203
+ const network = storedBrand(brand);
204
+ return network === null || PADDLE_VGS_CARD_NETWORKS.includes(network) ? null : "paddle_vgs_card_unsupported";
205
+ }
206
+ function paddleVgsTypedCardRefusal(body, stored) {
207
+ const verdict = paddleVgsBodyVerdict(body);
208
+ if (verdict.verdict !== "card")
209
+ return verdict.code;
210
+ const attributes = JSON.parse(body).data.attributes;
211
+ const card = stored && typeof stored === "object" ? stored : {};
212
+ if (Number.isInteger(card.expiryMonth) && attributes.exp_month !== card.expiryMonth)
213
+ return "paddle_vgs_card_mismatch";
214
+ if (Number.isInteger(card.expiryYear) && attributes.exp_year !== card.expiryYear % 100)
215
+ return "paddle_vgs_card_mismatch";
216
+ const brand = storedBrand(card.brand);
217
+ if (brand !== null && cardNetwork(attributes.pan) !== brand)
218
+ return "paddle_vgs_card_mismatch";
219
+ return null;
220
+ }
221
+ function injectPaddleVgsCard(body, card) {
222
+ const verdict = paddleVgsBodyVerdict(body);
223
+ if (verdict.verdict !== "card")
224
+ throw new Error(paddleVgsRefusalMessage(verdict.code));
225
+ const number = card ? String(card.number) : "";
226
+ const cvc = card ? String(card.cvc) : "";
227
+ const month = card ? Number(card.month) : NaN;
228
+ const year = card ? Number(card.year) % 100 : NaN;
229
+ if (!CARD_NUMBER.test(number) || !SECURITY_CODE.test(cvc) || !Number.isInteger(month) || month < 1 || month > 12 || !Number.isInteger(year) || year < 0) {
230
+ throw new Error("Could not fill the card fields for Paddle. Nothing was sent.");
231
+ }
232
+ if (paddleVgsCardRefusal(body, { number, month, year }) !== null)
233
+ throw new Error(CARD_MISMATCH_REFUSAL);
234
+ const value = JSON.parse(body);
235
+ const attributes = value.data.attributes;
236
+ attributes.pan = number;
237
+ attributes.cvc = cvc;
238
+ attributes.exp_month = month;
239
+ attributes.exp_year = year;
240
+ return { body: JSON.stringify(value), where: [...WHERE] };
241
+ }
242
+ var ANSWER_LIMIT = 65536;
243
+ var STORED_CARD_ID = /^[A-Za-z0-9_-]{1,128}$/;
244
+ var REFUSAL_CODE = /^[A-Za-z0-9_.:-]{1,64}$/;
245
+ var MAX_CODE_DIGITS = 11;
246
+ function readAnswer(text) {
247
+ if (typeof text !== "string" || text === "" || text.length > ANSWER_LIMIT)
248
+ return null;
249
+ try {
250
+ const value = JSON.parse(text);
251
+ return record(value) ? value : null;
252
+ }
253
+ catch {
254
+ return null;
255
+ }
256
+ }
257
+ function storedCardId(answer) {
258
+ const data = answer && answer.data;
259
+ if (!record(data) || data.type !== "cards" || typeof data.id !== "string")
260
+ return null;
261
+ const id = data.id;
262
+ return STORED_CARD_ID.test(id) && /[A-Za-z]/.test(id) && !PAN_LIKE.test(id) ? id : null;
263
+ }
264
+ var boundedCode = (value) => typeof value === "string" && REFUSAL_CODE.test(value) && (value.match(/[0-9]/g) ?? []).length <= MAX_CODE_DIGITS ? value : null;
265
+ function paddleVgsRefusalCode(text) {
266
+ const answer = readAnswer(text);
267
+ if (!answer)
268
+ return null;
269
+ const first = Array.isArray(answer.errors) && record(answer.errors[0]) ? answer.errors[0] : null;
270
+ return boundedCode(first && first.error_code) ?? boundedCode(first && first.code) ?? boundedCode(answer.error_code) ?? boundedCode(answer.code);
271
+ }
272
+ function paddleVgsAnswerOutcome(status, text, card) {
273
+ if (!Number.isInteger(status))
274
+ return { outcome: "unknown", reason: "paddle_vgs_unconfirmed" };
275
+ if (status >= 500)
276
+ return { outcome: "unknown", reason: "server_error" };
277
+ if (status >= 400 && status < 500)
278
+ return { outcome: "refused", code: paddleVgsRefusalCode(text) ?? `http_${status}` };
279
+ if (status !== 200 && status !== 201)
280
+ return { outcome: "unknown", reason: "paddle_vgs_unconfirmed" };
281
+ const id = storedCardId(readAnswer(text));
282
+ if (id === null)
283
+ return { outcome: "unknown", reason: "paddle_vgs_unsafe_response" };
284
+ const number = card ? String(card.number).replace(/[^0-9]/g, "") : "";
285
+ if (number && id.replace(/[^0-9]/g, "").includes(number))
286
+ return { outcome: "unknown", reason: "paddle_vgs_unsafe_response" };
287
+ return { outcome: "approved", body: JSON.stringify({ data: { type: "cards", id } }) };
288
+ }
289
+ export { PADDLE_VGS_CAPABILITY, PADDLE_VGS_CARDS_URLS, 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, paddleVgsCardRefusal, paddleVgsEnvironment, paddleVgsMerchantPin, paddleVgsMerchantRefusal, paddleVgsRefusalCode, paddleVgsRefusalMessage, paddleVgsRequestRefusal, paddleVgsSellerClaimsPinned, paddleVgsStoredCardRefusal, paddleVgsTypedCardRefusal };
@@ -1,6 +1,6 @@
1
1
  // @ts-nocheck
2
2
  // Generated from @agent-cards/payment-core. Do not edit.
3
- // artifact-sha256: 66c54c571ca1294220b070eaca1ca3888feebcd7f2562b761d74ded25a453c57
3
+ // artifact-sha256: 38065a5bd834d5233979a0528fde4345378914e8e8b1a63e329bff0994572da5
4
4
  // src/json-path.js
5
5
  function roundTrips(parsed, body) {
6
6
  try {
@@ -404,14 +404,8 @@ function payrailsCardOnFileRefusal(body) {
404
404
  }
405
405
  var AUTHORIZE_LIMIT = 32768;
406
406
  var AUTHORIZE_KEYS = Object.freeze(["amount", "paymentComposition", "returnInfo", "meta"]);
407
- var COMPOSITION_KEYS = Object.freeze([
408
- "paymentMethodCode",
409
- "integrationType",
410
- "amount",
411
- "storeInstrument",
412
- "paymentInstrumentData",
413
- "enrollInstrumentToNetworkOffers"
414
- ]);
407
+ var COMPOSITION_KEYS = Object.freeze(["paymentMethodCode", "integrationType", "amount", "storeInstrument", "paymentInstrumentData"]);
408
+ var ENROLLMENT_COMPOSITION_KEYS = Object.freeze([...COMPOSITION_KEYS, "enrollInstrumentToNetworkOffers"]);
415
409
  var RETURN_KEYS = Object.freeze(["success", "cancel", "error", "pending"]);
416
410
  var AMOUNT_VALUE = /^[0-9]{1,15}(?:\.[0-9]{1,12})?$/;
417
411
  var CURRENCY = /^[A-Z]{3}$/;
@@ -476,12 +470,14 @@ function readAuthorizeBody(body) {
476
470
  const amount = readAmount(parsed.amount);
477
471
  const parts = parsed.paymentComposition;
478
472
  const part = Array.isArray(parts) && parts.length === 1 && record(parts[0]) ? parts[0] : null;
479
- if (!amount || !part || !sameKeys(part, COMPOSITION_KEYS) || part.paymentMethodCode !== "card" || part.integrationType !== "api")
473
+ const flagged = !!part && owns(part, "enrollInstrumentToNetworkOffers");
474
+ if (!amount || !part || !sameKeys(part, flagged ? ENROLLMENT_COMPOSITION_KEYS : COMPOSITION_KEYS) || part.paymentMethodCode !== "card" || part.integrationType !== "api")
480
475
  return malformed;
481
476
  const partAmount = readAmount(part.amount);
482
477
  if (!partAmount || partAmount.value !== amount.value || partAmount.currency !== amount.currency)
483
478
  return malformed;
484
- if (typeof part.storeInstrument !== "boolean" || typeof part.enrollInstrumentToNetworkOffers !== "boolean")
479
+ const enrollInstrumentToNetworkOffers = flagged ? part.enrollInstrumentToNetworkOffers : false;
480
+ if (typeof part.storeInstrument !== "boolean" || typeof enrollInstrumentToNetworkOffers !== "boolean")
485
481
  return malformed;
486
482
  const data = part.paymentInstrumentData;
487
483
  const keys = record(data) ? Object.keys(data) : [];
@@ -502,7 +498,7 @@ function readAuthorizeBody(body) {
502
498
  value: Object.freeze({
503
499
  amount: Object.freeze({ value: amount.value, currency: amount.currency }),
504
500
  storeInstrument: part.storeInstrument,
505
- enrollInstrumentToNetworkOffers: part.enrollInstrumentToNetworkOffers,
501
+ enrollInstrumentToNetworkOffers,
506
502
  encryptedData: data.encryptedData,
507
503
  vaultProviderConfigId: data.vaultProviderConfigId,
508
504
  wrappedKeyBytes: jwe.wrappedKey.length
@@ -12,6 +12,7 @@ import { type CybersourceFlexDecision, type CybersourceFlexPause, type FlexReque
12
12
  import { type CardEndpointProfile } from './card-endpoint.js';
13
13
  import { type PayrailsSessionCapture, type PayrailsSidecar } from './payrails.js';
14
14
  import { type VerifoneProfile } from './verifone.js';
15
+ import { type WorldlineMyCheckoutProducts } from './worldline-mycheckout.js';
15
16
  /** The native Stripe step the gate claimed, or null when none was: exactly what StripeCheckoutGate.claim returns. */
16
17
  type StripeStep = ReturnType<StripeCheckoutGate['claim']>;
17
18
  /** A synthetic response a gate fulfills locally. The effect applies CORS and encoding. */
@@ -183,6 +184,11 @@ export interface PreClaimContext {
183
184
  companions: ShijiStsCompanions;
184
185
  page: () => string | Promise<string>;
185
186
  };
187
+ /**
188
+ * The card products this attachment saw a Worldline MyCheckout page choose (worldline-mycheckout.ts).
189
+ * Absent, no MyCheckout card submit is held to a product here.
190
+ */
191
+ worldlineMyCheckout?: WorldlineMyCheckoutProducts;
186
192
  }
187
193
  /**
188
194
  * The URL an event may name for a request. For an armed merchant profile's
@@ -204,6 +210,7 @@ export declare const fiservPreClaim: PreClaimGate;
204
210
  export declare const ekashuPreClaim: PreClaimGate;
205
211
  export declare const paypalHostedFieldsPreClaim: PreClaimGate;
206
212
  export declare const cybersourceSecureAcceptancePreClaim: PreClaimGate;
213
+ export declare const worldlineMyCheckoutPreClaim: PreClaimGate;
207
214
  /** The processor gates, in order. A new family adds one entry here; the terminal
208
215
  * arbiter runs after all of them and is not part of this list. A gate appended
209
216
  * after Stripe is never offered a step Stripe claimed: Stripe answers, aborts or
package/dist/pre-claim.js CHANGED
@@ -17,7 +17,8 @@
17
17
  // no new card, Shiji STS's getTokenId and getCvvId, which also carry
18
18
  // requests the vault must never pay, and PayPal's legacy Hosted Fields
19
19
  // confirm and CyberSource Secure Acceptance's Silent Order POST, whose card
20
- // no Vault mode takes over.
20
+ // no Vault mode takes over, and Worldline MyCheckout's card form, held to the
21
+ // card product its page chose.
21
22
  // The first three used to be
22
23
  // written inline in Fetch.requestPaused and again in the page.route handler, so
23
24
  // a decision could drift between the two adapters and a new family had to be
@@ -77,6 +78,7 @@ import { classifyVerifoneProfileRequest, verifoneTemplatedUrl } from './verifone
77
78
  import { ekashuEntryUrl, isEkashuEntry, isEkashuHost } from './ekashu.js';
78
79
  import { PAYPAL_HOSTED_FIELDS_REASON, isPaypalHostedFieldsConfirm, paypalHostedFieldsBody } from './paypal-hosted-fields.js';
79
80
  import { CYBERSOURCE_SECURE_ACCEPTANCE_REASON, CYBERSOURCE_SECURE_ACCEPTANCE_RECIPIENT, cybersourceSecureAcceptanceRequest, isCybersourceSecureAcceptanceUrl, isSecureAcceptanceFramePost, } from './cybersource-secure-acceptance.js';
81
+ import { isWorldlineMyCheckoutEndpoint } from './worldline-mycheckout.js';
80
82
  /**
81
83
  * The URL an event may name for a request. For an armed merchant profile's
82
84
  * endpoint or a sibling of it: the endpoint origin, the path TEMPLATE and the
@@ -1288,11 +1290,11 @@ async function payrailsUnsupported(ctx, reason) {
1288
1290
  // www.convergepay.com or api.demo.convergepay.com, also carries electronic check and financing payments
1289
1291
  // with no card, and card-shaped bodies the Vault never pays: a wallet with a card
1290
1292
  // number the server supplied, the debit, gift card and push-to-card tabs, the
1291
- // follow-ups after a 3-D Secure, Pay Defense or surcharge step, a CAPTCHA, a
1292
- // service fee, recurring or installment fields, card saving, a tip, another
1293
- // currency. So a POST the registry recognizes on a Converge host that carries a
1294
- // card is judged here, before the terminal arbiter would pause it, by the
1295
- // whole-request check the API runs at create:
1293
+ // follow-ups after a 3-D Secure, Pay Defense or surcharge step, a CAPTCHA answer
1294
+ // that is not a solved reCAPTCHA token, a service fee, recurring or installment
1295
+ // fields, card saving, a tip, another currency. So a POST the registry recognizes
1296
+ // on a Converge host that carries a card is judged here, before the terminal
1297
+ // arbiter would pause it, by the whole-request check the API runs at create:
1296
1298
  // - the page's own first credit card sale defers, and the arbiter pauses it
1297
1299
  // for approval;
1298
1300
  // - anything else aborts with a `blocked` event naming payment-core's code
@@ -1697,6 +1699,44 @@ async function cybersourceSecureAcceptanceStop(ctx) {
1697
1699
  await ctx.effects.abort().catch(() => { });
1698
1700
  return { kind: 'abort' };
1699
1701
  }
1702
+ // Worldline MyCheckout (worldline-mycheckout.ts). Once the registry serves its recognizer (a build
1703
+ // that declares worldline_mycheckout_form, for an org Agentcard turned Worldline on for), every POST
1704
+ // to its four card-form endpoints reaches this gate first:
1705
+ // - a product selection (no card, and neither the Pay nor the cancel pair: the post of the page's
1706
+ // select-payment-product step) is remembered for its hosted checkout and defers, and the adapter
1707
+ // continues it untouched as before (passWithoutCard);
1708
+ // - a card submit whose paymentProductId is another product than the one chosen for its hosted
1709
+ // checkout aborts with a `blocked` event, `worldline_mycheckout_product_changed`, and one that
1710
+ // names no product with `worldline_mycheckout_product_missing`, with no claim, create or prompt.
1711
+ // The page's IIN lookup on the agent's placeholder switched the product, and the cardholder's
1712
+ // device, which checks only the real card's network, would submit the card under that product.
1713
+ // Like Converge's refusals it neither holds the attachment nor changes its state, so the page's
1714
+ // next submit is judged afresh;
1715
+ // - anything else defers: a card submit with the chosen product, one on the card group, one on a
1716
+ // hosted checkout whose choice this attachment never saw, the page's cancel, a redirect product.
1717
+ // Synchronous until the abort, so a card submit pauses with no await. Exported for the gate
1718
+ // registry's tests.
1719
+ export const worldlineMyCheckoutPreClaim = (ctx) => {
1720
+ const { url, method } = ctx.request;
1721
+ if (!ctx.worldlineMyCheckout || String(method).toUpperCase() !== 'POST' || !isWorldlineMyCheckoutEndpoint(url)
1722
+ || !ctx.vault.isCardRequest(url, method))
1723
+ return undefined;
1724
+ let body;
1725
+ // A reader that throws (Playwright's postData() can) proves nothing either way.
1726
+ try {
1727
+ body = ctx.request.body;
1728
+ }
1729
+ catch {
1730
+ body = null;
1731
+ }
1732
+ const refusal = ctx.worldlineMyCheckout.judge(url, body);
1733
+ return refusal === null ? undefined : worldlineMyCheckoutBlock(ctx, refusal);
1734
+ };
1735
+ async function worldlineMyCheckoutBlock(ctx, reason) {
1736
+ ctx.onEvent?.({ type: 'blocked', detail: reason });
1737
+ await ctx.effects.abort();
1738
+ return { kind: 'abort' };
1739
+ }
1700
1740
  // The terminal arbiter, always last. A request that no gate claimed and that is
1701
1741
  // neither a native Stripe step nor a recognized card request continues, unless
1702
1742
  // it matches a declared payment endpoint we do not support (then it aborts as an
@@ -1731,7 +1771,7 @@ async function unsupportedContinue(ctx) {
1731
1771
  * after Stripe is never offered a step Stripe claimed: Stripe answers, aborts or
1732
1772
  * pauses each one itself. */
1733
1773
  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,
1734
- cybersourceSecureAcceptancePreClaim];
1774
+ cybersourceSecureAcceptancePreClaim, worldlineMyCheckoutPreClaim];
1735
1775
  /** Run each gate in order; the first to return a decision wins. If none does,
1736
1776
  * the terminal arbiter decides between continue, abort (unsupported) and pause.
1737
1777
  * 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).