@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.
- package/CHANGELOG.md +20 -2
- package/PREFLIGHT.md +2 -0
- package/README.md +41 -19
- package/dist/card-fields.generated.js +74 -2
- package/dist/cdp.js +35 -7
- package/dist/client.d.ts +32 -8
- package/dist/client.js +164 -19
- package/dist/elavon-converge.d.ts +4 -1
- package/dist/elavon-converge.generated.js +3 -2
- package/dist/elavon-converge.js +4 -1
- package/dist/lifecycle.d.ts +9 -1
- package/dist/lifecycle.js +34 -0
- package/dist/paddle-vgs.generated.d.ts +50 -0
- package/dist/paddle-vgs.generated.js +289 -0
- package/dist/payrails.generated.js +8 -12
- package/dist/pre-claim.d.ts +7 -0
- package/dist/pre-claim.js +47 -7
- package/dist/prepared-processor.d.ts +12 -4
- package/dist/prepared-processor.js +17 -5
- package/dist/primer.generated.js +90 -18
- package/dist/stripe-checkout.generated.js +59 -2
- package/dist/worldline-mycheckout.d.ts +27 -0
- package/dist/worldline-mycheckout.generated.d.ts +29 -0
- package/dist/worldline-mycheckout.generated.js +162 -0
- package/dist/worldline-mycheckout.js +93 -0
- package/package.json +2 -2
package/dist/primer.generated.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// @ts-nocheck
|
|
2
2
|
// Generated from @agent-cards/payment-core. Do not edit.
|
|
3
|
-
// artifact-sha256:
|
|
3
|
+
// artifact-sha256: b3391e7d6137e269555a23062ce848aa7a7f6f02e626fd568b1e9597c02e5ce6
|
|
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
|
|
@@ -83,6 +83,7 @@ var ONE_TIME_RECURRING_FLAG = "false";
|
|
|
83
83
|
var CARD_SAVING_FLAGS = /* @__PURE__ */ Object.freeze(["ssl_get_token", "ssl_add_token"]);
|
|
84
84
|
var CREDENTIAL_FIELDS = /* @__PURE__ */ Object.freeze(["ssl_account_id", "ssl_merchant_id", "ssl_user_id", "ssl_pin", "ssl_vendor_id"]);
|
|
85
85
|
var AMOUNT_ADD_ON_FIELDS = /* @__PURE__ */ Object.freeze(["ssl_tip_amount", "ssl_cashback_amount"]);
|
|
86
|
+
var SOLVED_CAPTCHA = /^[A-Za-z0-9_-]{1,4096}$/;
|
|
86
87
|
var CARD_NUMBER = /^\d{12,19}$/;
|
|
87
88
|
var EXPIRY_MMYY = /^(0[1-9]|1[0-2])\d{2}$/;
|
|
88
89
|
var CVC = /^\d{3,4}$/;
|
|
@@ -138,7 +139,7 @@ function convergeBodyVerdict(body) {
|
|
|
138
139
|
return refuse("converge_credential_refused");
|
|
139
140
|
if (WALLET_FIELDS.some((key) => owns(data, key)))
|
|
140
141
|
return refuse("converge_wallet_refused");
|
|
141
|
-
if (owns(parsed, "captchaToken"))
|
|
142
|
+
if (owns(parsed, "captchaToken") && !(typeof parsed.captchaToken === "string" && SOLVED_CAPTCHA.test(parsed.captchaToken)))
|
|
142
143
|
return refuse("converge_captcha_refused");
|
|
143
144
|
if (SERVICE_FEE_FIELDS.some((key) => owns(data, key)))
|
|
144
145
|
return refuse("converge_service_fee_refused");
|
|
@@ -347,8 +348,79 @@ function amadeusBodyKind(body) {
|
|
|
347
348
|
return record2(value.data) && holdsPan(value.data.mopdata) ? "card" : "other";
|
|
348
349
|
return "other";
|
|
349
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
|
+
]);
|
|
350
417
|
// src/card-fields.js
|
|
351
418
|
function processorGate(recognizer, url, body) {
|
|
419
|
+
if (recognizer && recognizer.psp === PADDLE_VGS_PSP) {
|
|
420
|
+
if (url !== void 0 && !isPaddleVgsCardsUrl(url))
|
|
421
|
+
return { verdict: "refuse", code: "paddle_vgs_url_refused" };
|
|
422
|
+
return paddleVgsBodyVerdict(body);
|
|
423
|
+
}
|
|
352
424
|
if (recognizer && recognizer.psp === GRAVY_PSP)
|
|
353
425
|
return gravyBodyVerdict(body);
|
|
354
426
|
if (recognizer && recognizer.psp === MONERIS_PSP) {
|
|
@@ -469,12 +541,12 @@ var KEEP_ON_FILE_REFUSAL = "Refusing to pay: this Primer payment would keep the
|
|
|
469
541
|
var UNSUPPORTED_SDK_REFUSAL = "Refusing to pay: this checkout uses a Primer SDK Agentcard does not support yet. Nothing was sent.";
|
|
470
542
|
var NETWORK_MISMATCH_REFUSAL = "Refusing to pay: this Primer payment names a card network that is not your card's. Nothing was sent.";
|
|
471
543
|
var FILL_REFUSAL = "Could not fill the card fields for Primer. Nothing was sent.";
|
|
472
|
-
var
|
|
544
|
+
var BODY_LIMIT3 = 16384;
|
|
473
545
|
var RESPONSE_LIMIT = 32768;
|
|
474
|
-
var
|
|
546
|
+
var ROOT_KEYS3 = ["paymentInstrument", "userDescription", "tokenType", "paymentFlow"];
|
|
475
547
|
var INSTRUMENT_KEYS = ["number", "cvv", "expirationMonth", "expirationYear", "cardholderName", "preferredNetwork"];
|
|
476
548
|
var REQUIRED_INSTRUMENT_KEYS = ["number", "cvv", "expirationMonth", "expirationYear"];
|
|
477
|
-
var
|
|
549
|
+
var record4 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
478
550
|
var owns4 = (value, key) => Object.prototype.hasOwnProperty.call(value, key);
|
|
479
551
|
var exact = (pattern, value) => typeof value === "string" && pattern.exec(value)?.[0] === value;
|
|
480
552
|
var text = (value, max) => typeof value === "string" && value.length <= max && !/[\u0000-\u001f\u007f]/.test(value);
|
|
@@ -497,7 +569,7 @@ function isPrimerTokenizeUrl(url, method = "POST") {
|
|
|
497
569
|
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;
|
|
498
570
|
}
|
|
499
571
|
function judgeBody(body) {
|
|
500
|
-
if (typeof body !== "string" || body === "" || body.length >
|
|
572
|
+
if (typeof body !== "string" || body === "" || body.length > BODY_LIMIT3)
|
|
501
573
|
return null;
|
|
502
574
|
let value;
|
|
503
575
|
try {
|
|
@@ -506,12 +578,12 @@ function judgeBody(body) {
|
|
|
506
578
|
catch {
|
|
507
579
|
return null;
|
|
508
580
|
}
|
|
509
|
-
if (!
|
|
581
|
+
if (!record4(value) || !roundTrips(value, body))
|
|
510
582
|
return null;
|
|
511
|
-
if (!Object.keys(value).every((key) =>
|
|
583
|
+
if (!Object.keys(value).every((key) => ROOT_KEYS3.includes(key)) || !owns4(value, "paymentInstrument"))
|
|
512
584
|
return null;
|
|
513
585
|
const instrument = value.paymentInstrument;
|
|
514
|
-
if (!
|
|
586
|
+
if (!record4(instrument) || !Object.keys(instrument).every((key) => INSTRUMENT_KEYS.includes(key)) || !REQUIRED_INSTRUMENT_KEYS.every((key) => owns4(instrument, key)))
|
|
515
587
|
return null;
|
|
516
588
|
if (!digits(instrument.number, 12, 19) || !digits(instrument.cvv, 3, 4) || !exact(MONTH, instrument.expirationMonth) || !exact(YEAR, instrument.expirationYear))
|
|
517
589
|
return null;
|
|
@@ -539,7 +611,7 @@ var CLIENT_TOKEN = /^[A-Za-z0-9._~+/=-]{16,4096}$/;
|
|
|
539
611
|
var REQUEST_ID = /^[A-Za-z0-9-]{1,64}$/;
|
|
540
612
|
var API_VERSION = /^2\.[0-9]{1,2}$/;
|
|
541
613
|
function readHeaders(headers) {
|
|
542
|
-
if (!
|
|
614
|
+
if (!record4(headers))
|
|
543
615
|
return null;
|
|
544
616
|
const folded = /* @__PURE__ */ new Map();
|
|
545
617
|
for (const [name, value] of Object.entries(headers)) {
|
|
@@ -586,7 +658,7 @@ function onTokenizePath(url) {
|
|
|
586
658
|
}
|
|
587
659
|
}
|
|
588
660
|
function primerTokenizeRefusal(request) {
|
|
589
|
-
if (!
|
|
661
|
+
if (!record4(request) || request.method !== "POST" || !isPrimerTokenizeUrl(request.url))
|
|
590
662
|
return "unsupported";
|
|
591
663
|
return headersRefusal(request.headers) ?? primerBodyRefusal(request.body);
|
|
592
664
|
}
|
|
@@ -601,7 +673,7 @@ function primerCardOnFileRefusal(url, options, policy = PRIMER_SESSION_CARD_ON_F
|
|
|
601
673
|
return "unsupported";
|
|
602
674
|
if (new URL(url).hostname !== LIVE_PCI_HOST)
|
|
603
675
|
return null;
|
|
604
|
-
return primerCardOnFileDisclosure(url, policy) &&
|
|
676
|
+
return primerCardOnFileDisclosure(url, policy) && record4(options) && options.keepOnFile === true ? null : "keep_on_file";
|
|
605
677
|
}
|
|
606
678
|
function primerRefusalMessage(reason) {
|
|
607
679
|
if (reason === "keep_on_file")
|
|
@@ -685,7 +757,7 @@ function readAnswer(body) {
|
|
|
685
757
|
return null;
|
|
686
758
|
try {
|
|
687
759
|
const value = JSON.parse(body);
|
|
688
|
-
return
|
|
760
|
+
return record4(value) ? value : null;
|
|
689
761
|
}
|
|
690
762
|
catch {
|
|
691
763
|
return null;
|
|
@@ -694,16 +766,16 @@ function readAnswer(body) {
|
|
|
694
766
|
var tokenValue = (value) => exact(TOKEN, value) && !panLike(value);
|
|
695
767
|
var optional = (value, key, check) => !owns4(value, key) || value[key] === null || check(value[key]);
|
|
696
768
|
function cardDetails(data) {
|
|
697
|
-
if (!
|
|
769
|
+
if (!record4(data) || !exact(/^[0-9]{4}$/, data.last4Digits))
|
|
698
770
|
return null;
|
|
699
|
-
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",
|
|
771
|
+
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))
|
|
700
772
|
return null;
|
|
701
773
|
const safe = { last4Digits: data.last4Digits };
|
|
702
774
|
for (const key of ["first6Digits", "expirationMonth", "expirationYear", "cardholderName", "network", "isNetworkTokenized"]) {
|
|
703
775
|
if (owns4(data, key) && data[key] !== null)
|
|
704
776
|
safe[key] = data[key];
|
|
705
777
|
}
|
|
706
|
-
if (
|
|
778
|
+
if (record4(data.binData)) {
|
|
707
779
|
const bin = {};
|
|
708
780
|
for (const key of BIN_KEYS) {
|
|
709
781
|
if (!owns4(data.binData, key) || data.binData[key] === null)
|
|
@@ -719,7 +791,7 @@ function cardDetails(data) {
|
|
|
719
791
|
function threeDsSummary(value) {
|
|
720
792
|
if (value === void 0 || value === null)
|
|
721
793
|
return null;
|
|
722
|
-
if (!
|
|
794
|
+
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"))
|
|
723
795
|
return void 0;
|
|
724
796
|
const safe = { responseCode: value.responseCode };
|
|
725
797
|
for (const key of ["reasonCode", "reasonText", "protocolVersion", "challengeIssued"]) {
|
|
@@ -747,7 +819,7 @@ function rebuildToken(value) {
|
|
|
747
819
|
}
|
|
748
820
|
function primerErrorId(body) {
|
|
749
821
|
const value = readAnswer(body);
|
|
750
|
-
const id = value &&
|
|
822
|
+
const id = value && record4(value.error) ? value.error.errorId : null;
|
|
751
823
|
return exact(ERROR_ID, id) && !panLike(id) ? id : null;
|
|
752
824
|
}
|
|
753
825
|
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:
|
|
3
|
+
// artifact-sha256: dd47d4d2d97c39acb2385272d1a2bf3e647741839234283b848b67fd5ce5c114
|
|
4
4
|
// src/deep-freeze.js
|
|
5
5
|
function deepFreeze(value) {
|
|
6
6
|
if (value && typeof value === "object" && !Object.isFrozen(value)) {
|
|
@@ -211,7 +211,18 @@ var PROCESSOR_DEFINITIONS = deepFreeze([
|
|
|
211
211
|
// The result page Tranzila's card form names when the terminal set no
|
|
212
212
|
// return URL of its own (read on both hosts on 2026-10-10): the result
|
|
213
213
|
// goes back to Tranzila, which already holds it. Matched exactly.
|
|
214
|
-
processorResultUrls: ["https://direct.tranzila.com/done/", "https://directng.tranzila.com/process/fail"]
|
|
214
|
+
processorResultUrls: ["https://direct.tranzila.com/done/", "https://directng.tranzila.com/process/fail"],
|
|
215
|
+
// Tranzila's own ALTCHA proof of work, on every card form read on both
|
|
216
|
+
// hosts on 2026-10-10 (altcha 1.0.7, SHA-256, searched up to 50,000):
|
|
217
|
+
// Tranzila's script hashes the page's challenge until it finds the
|
|
218
|
+
// number, writes base64 JSON {algorithm, challenge, number, salt,
|
|
219
|
+
// signature, took} into `altcha`, and only then submits the form.
|
|
220
|
+
// Nobody answers anything and the proof names no browser, cookie or
|
|
221
|
+
// address, so it rides the device's one send as the page wrote it, once
|
|
222
|
+
// the create finds it is one solved proof (backend.js isAltchaProof).
|
|
223
|
+
// Every other challenge field is still refused (hosted_form_gated).
|
|
224
|
+
// Removing this line refuses `altcha` again.
|
|
225
|
+
proofOfWork: "altcha"
|
|
215
226
|
}
|
|
216
227
|
},
|
|
217
228
|
{
|
|
@@ -1407,6 +1418,33 @@ var PROCESSOR_DEFINITIONS = deepFreeze([
|
|
|
1407
1418
|
// A checked "remember" asks Worldline to keep the card for later purchases.
|
|
1408
1419
|
token: "remember"
|
|
1409
1420
|
}
|
|
1421
|
+
},
|
|
1422
|
+
{
|
|
1423
|
+
psp: "paddle_vgs",
|
|
1424
|
+
// Paddle's checkout frame (buy.paddle.com) on a checkout whose card method names VGS as its
|
|
1425
|
+
// tokenizer: one JSON:API POST to /cards on vault.paddle.com, a VGS inbound proxy Paddle runs
|
|
1426
|
+
// (sandbox-vault.paddle.com in test), with the checkout's own bearer JWT. PAN@
|
|
1427
|
+
// data.attributes.pan, with cvc, exp_month (1 to 12) and exp_year (two digits) beside it, all
|
|
1428
|
+
// numbers but the pan and the cvc. paddle-vgs.js holds the request rule the pause decision, the
|
|
1429
|
+
// API's create, the prepared bind and the device all read: exactly one of the two URLs, the
|
|
1430
|
+
// frame's Content-Type and bearer, and the frame's body byte for byte. VGS answers with the
|
|
1431
|
+
// stored card's id, which the frame sends on to Paddle's /pay. The frame aborts this request
|
|
1432
|
+
// after 10 seconds and sends it once more, so the lane is prepared only (preparationRequired):
|
|
1433
|
+
// the cardholder approves before the agent clicks Pay. The card leaves the cardholder's device
|
|
1434
|
+
// by a direct fetch, since the proxy answers any origin. The checkouts that name TokenEx or
|
|
1435
|
+
// Spreedly send the card elsewhere and are not this entry.
|
|
1436
|
+
// Ships dark: served only to a client that declares `paddle_vgs_cards`, gated off for every org
|
|
1437
|
+
// until an operator lists one (`paddle_vgs:<org>`), and autopilot never sends this request
|
|
1438
|
+
// (autopilot.js DEVICE_REPLAY_REFUSED_OPERATIONS). The environment is pinned (S7):
|
|
1439
|
+
// sandbox-vault only on test rows and only with documented test cards, vault only on live rows.
|
|
1440
|
+
capability: "paddle_vgs_cards",
|
|
1441
|
+
match: "(?:sandbox-)?vault\\.paddle\\.com/cards",
|
|
1442
|
+
hosts: ["^vault\\.paddle\\.com$", "^sandbox-vault\\.paddle\\.com$"],
|
|
1443
|
+
sandboxHosts: ["^sandbox-vault\\.paddle\\.com$"],
|
|
1444
|
+
hostEnvironmentPinned: true,
|
|
1445
|
+
preparationRequired: true,
|
|
1446
|
+
encoding: "json",
|
|
1447
|
+
passthroughHeaders: ["^authorization$"]
|
|
1410
1448
|
}
|
|
1411
1449
|
]);
|
|
1412
1450
|
var PSP_RECOGNIZERS = deepFreeze(PROCESSOR_DEFINITIONS.map(({ compose, encryptedCardFields, deviceSteps, deviceEncrypt, ...definition }) => definition));
|
|
@@ -1509,6 +1547,15 @@ var pin = (fields) => Object.freeze({ ...fields, origins: Object.freeze([...fiel
|
|
|
1509
1547
|
var MONERIS_MERCHANT_PINS = /* @__PURE__ */ Object.freeze([
|
|
1510
1548
|
pin({ id: "jack_org_raisin", merchant: "Jack.org", origins: ["https://jack.akaraisin.com"], profileIds: ["htL7TR9H7ICYN2B"] })
|
|
1511
1549
|
]);
|
|
1550
|
+
// src/paddle-vgs.js
|
|
1551
|
+
var pin2 = (fields) => Object.freeze({
|
|
1552
|
+
...fields,
|
|
1553
|
+
origins: Object.freeze([...fields.origins]),
|
|
1554
|
+
sellerClaim: fields.sellerClaim ? Object.freeze({ ...fields.sellerClaim }) : null
|
|
1555
|
+
});
|
|
1556
|
+
var PADDLE_VGS_MERCHANT_PINS = /* @__PURE__ */ Object.freeze([
|
|
1557
|
+
pin2({ id: "rectangle_pro", merchant: "Rectangle Pro", origins: ["https://rectangleapp.com"], sellerClaim: null })
|
|
1558
|
+
]);
|
|
1512
1559
|
// src/cybersource-secure-acceptance.js
|
|
1513
1560
|
var SA_BODY_LIMIT = 32 * 1024;
|
|
1514
1561
|
var SA_REPLY_LIMIT = 64 * 1024;
|
|
@@ -1596,6 +1643,7 @@ var ALLOWED_MEDIA_TYPES = {
|
|
|
1596
1643
|
jwe: ["application/jwt"]
|
|
1597
1644
|
};
|
|
1598
1645
|
var JWE_CONTENT_TYPE = "application/jwt; charset=utf-8";
|
|
1646
|
+
var PADDLE_VGS_MEDIA_TYPE = "application/vnd.api+json";
|
|
1599
1647
|
function contentTypeFor(pspOrEntry, headers) {
|
|
1600
1648
|
const rec = trustedClientRecognizer(pspOrEntry);
|
|
1601
1649
|
if (rec.encoding === "jwe") {
|
|
@@ -1605,6 +1653,13 @@ function contentTypeFor(pspOrEntry, headers) {
|
|
|
1605
1653
|
throw new Error("Refusing to pay: this processor takes its sealed card only as application/jwt.");
|
|
1606
1654
|
return JWE_CONTENT_TYPE;
|
|
1607
1655
|
}
|
|
1656
|
+
if (rec.psp === "paddle_vgs") {
|
|
1657
|
+
const captured = Object.entries(headers || {}).find(([k]) => /^content-type$/i.test(k));
|
|
1658
|
+
if (!captured || typeof captured[1] !== "string" || captured[1].trim().toLowerCase() !== PADDLE_VGS_MEDIA_TYPE) {
|
|
1659
|
+
throw new Error("Refusing to pay: this processor takes its card only as application/vnd.api+json.");
|
|
1660
|
+
}
|
|
1661
|
+
return PADDLE_VGS_MEDIA_TYPE;
|
|
1662
|
+
}
|
|
1608
1663
|
const encoding = rec && rec.encoding === "form" ? "form" : "json";
|
|
1609
1664
|
const allowed = ALLOWED_MEDIA_TYPES[encoding];
|
|
1610
1665
|
const fallback = allowed[0];
|
|
@@ -1624,6 +1679,8 @@ function replayHeaders(pspOrEntry, captured = {}) {
|
|
|
1624
1679
|
const headers = { "content-type": contentTypeFor(rec, captured) };
|
|
1625
1680
|
if (rec.psp === "creditguard")
|
|
1626
1681
|
headers.accept = "application/json";
|
|
1682
|
+
if (rec.psp === "paddle_vgs")
|
|
1683
|
+
headers.accept = PADDLE_VGS_MEDIA_TYPE;
|
|
1627
1684
|
for (const [name, value] of Object.entries(captured)) {
|
|
1628
1685
|
if (rec.headers.some((pattern) => pattern.test(name)) && !HEADER_NEVER_FORWARD.test(name))
|
|
1629
1686
|
headers[name] = value;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/** Whether `url` is one of MyCheckout's four card-form endpoints, written exactly (payment-core's rule). */
|
|
2
|
+
export declare function isWorldlineMyCheckoutEndpoint(url: string): boolean;
|
|
3
|
+
/** The `blocked` detail for a card submit whose product is not the one its page chose. */
|
|
4
|
+
export declare const WORLDLINE_MYCHECKOUT_PRODUCT_CHANGED = "worldline_mycheckout_product_changed";
|
|
5
|
+
/** The `blocked` detail for a card submit that names no product, on a hosted checkout whose page chose one. */
|
|
6
|
+
export declare const WORLDLINE_MYCHECKOUT_PRODUCT_MISSING = "worldline_mycheckout_product_missing";
|
|
7
|
+
export type WorldlineMyCheckoutRefusal = typeof WORLDLINE_MYCHECKOUT_PRODUCT_CHANGED | typeof WORLDLINE_MYCHECKOUT_PRODUCT_MISSING;
|
|
8
|
+
/**
|
|
9
|
+
* The card products one attachment saw chosen, per hosted checkout: its `publicMerchantId` and
|
|
10
|
+
* `token`, the pair payment-core's duplicate guard reads as one hosted checkout
|
|
11
|
+
* (WORLDLINE_MYCHECKOUT_DEDUPE).
|
|
12
|
+
*/
|
|
13
|
+
export declare class WorldlineMyCheckoutProducts {
|
|
14
|
+
private readonly chosen;
|
|
15
|
+
/**
|
|
16
|
+
* Read one POST to a card-form endpoint. A product selection (no card, and neither the Pay nor
|
|
17
|
+
* the cancel pair) records the product it chose for its hosted checkout, or forgets the choice
|
|
18
|
+
* when it picks the card group, which leaves the product to the card. A card submit is judged:
|
|
19
|
+
* WORLDLINE_MYCHECKOUT_PRODUCT_CHANGED when its paymentProductId is another product than the one
|
|
20
|
+
* chosen for its hosted checkout, WORLDLINE_MYCHECKOUT_PRODUCT_MISSING when it names none. Null for
|
|
21
|
+
* every other post, and for a card submit on the card group or on a hosted checkout whose choice
|
|
22
|
+
* this attachment never saw: the API and the device judge those as before. A body payment-core
|
|
23
|
+
* cannot read is never judged here either.
|
|
24
|
+
*/
|
|
25
|
+
judge(url: string, body: string | null): WorldlineMyCheckoutRefusal | null;
|
|
26
|
+
private remember;
|
|
27
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
declare var WORLDLINE_MYCHECKOUT_PSP: string;
|
|
2
|
+
declare var WORLDLINE_MYCHECKOUT_CAPABILITY: string;
|
|
3
|
+
declare var WORLDLINE_MYCHECKOUT_ENDPOINTS: Readonly<{
|
|
4
|
+
production: readonly string[];
|
|
5
|
+
sandbox: readonly string[];
|
|
6
|
+
}>;
|
|
7
|
+
declare function worldlineMyCheckoutEnvironment(url: any): "production" | "sandbox" | undefined;
|
|
8
|
+
declare function isWorldlineMyCheckoutUrl(url: any): boolean;
|
|
9
|
+
declare var WORLDLINE_MYCHECKOUT_CARD_PRODUCTS: Readonly<{
|
|
10
|
+
visa: readonly string[];
|
|
11
|
+
mastercard: readonly string[];
|
|
12
|
+
amex: readonly string[];
|
|
13
|
+
discover: readonly string[];
|
|
14
|
+
jcb: readonly string[];
|
|
15
|
+
diners: readonly string[];
|
|
16
|
+
}>;
|
|
17
|
+
declare function worldlineMyCheckoutProductNetwork(productId: any): string | null;
|
|
18
|
+
declare function parseWorldlineMyCheckoutForm(body: any): {
|
|
19
|
+
refusal: string;
|
|
20
|
+
pairs?: undefined;
|
|
21
|
+
} | {
|
|
22
|
+
pairs: string[][];
|
|
23
|
+
refusal?: undefined;
|
|
24
|
+
};
|
|
25
|
+
declare function worldlineMyCheckoutCardholderName(name: any): string | null;
|
|
26
|
+
declare function worldlineMyCheckoutTemplateRefusal(body: any): string | null;
|
|
27
|
+
declare function worldlineMyCheckoutRequestRefusal(request: any): string | null;
|
|
28
|
+
declare var WORLDLINE_MYCHECKOUT_DEDUPE: readonly string[];
|
|
29
|
+
export { WORLDLINE_MYCHECKOUT_CAPABILITY, WORLDLINE_MYCHECKOUT_CARD_PRODUCTS, WORLDLINE_MYCHECKOUT_DEDUPE, WORLDLINE_MYCHECKOUT_ENDPOINTS, WORLDLINE_MYCHECKOUT_PSP, isWorldlineMyCheckoutUrl, parseWorldlineMyCheckoutForm, worldlineMyCheckoutCardholderName, worldlineMyCheckoutEnvironment, worldlineMyCheckoutProductNetwork, worldlineMyCheckoutRequestRefusal, worldlineMyCheckoutTemplateRefusal };
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
// Generated from @agent-cards/payment-core. Do not edit.
|
|
3
|
+
// artifact-sha256: 486a32aa0f96f59666d4a43e42fee9f493cd3059b27ad3f92b97ad6b249133f5
|
|
4
|
+
// src/worldline-mycheckout.js
|
|
5
|
+
var WORLDLINE_MYCHECKOUT_PSP = "worldline_mycheckout";
|
|
6
|
+
var WORLDLINE_MYCHECKOUT_CAPABILITY = "worldline_mycheckout_form";
|
|
7
|
+
var WORLDLINE_MYCHECKOUT_ENDPOINTS = /* @__PURE__ */ Object.freeze({
|
|
8
|
+
production: /* @__PURE__ */ Object.freeze([
|
|
9
|
+
"https://payment.pay1.checkout.worldline-solutions.com/checkout",
|
|
10
|
+
"https://payment.pay2.checkout.worldline-solutions.com/checkout"
|
|
11
|
+
]),
|
|
12
|
+
sandbox: /* @__PURE__ */ Object.freeze([
|
|
13
|
+
"https://payment.pay1.preprod.checkout.worldline-solutions.com/checkout",
|
|
14
|
+
"https://payment.pay2.preprod.checkout.worldline-solutions.com/checkout"
|
|
15
|
+
])
|
|
16
|
+
});
|
|
17
|
+
function worldlineMyCheckoutEnvironment(url) {
|
|
18
|
+
if (WORLDLINE_MYCHECKOUT_ENDPOINTS.production.includes(url))
|
|
19
|
+
return "production";
|
|
20
|
+
if (WORLDLINE_MYCHECKOUT_ENDPOINTS.sandbox.includes(url))
|
|
21
|
+
return "sandbox";
|
|
22
|
+
return void 0;
|
|
23
|
+
}
|
|
24
|
+
function isWorldlineMyCheckoutUrl(url) {
|
|
25
|
+
return worldlineMyCheckoutEnvironment(url) !== void 0;
|
|
26
|
+
}
|
|
27
|
+
var WORLDLINE_MYCHECKOUT_CARD_PRODUCTS = /* @__PURE__ */ Object.freeze({
|
|
28
|
+
visa: /* @__PURE__ */ Object.freeze(["1", "114", "122"]),
|
|
29
|
+
mastercard: /* @__PURE__ */ Object.freeze(["3", "119"]),
|
|
30
|
+
amex: /* @__PURE__ */ Object.freeze(["2"]),
|
|
31
|
+
discover: /* @__PURE__ */ Object.freeze(["128"]),
|
|
32
|
+
jcb: /* @__PURE__ */ Object.freeze(["125"]),
|
|
33
|
+
diners: /* @__PURE__ */ Object.freeze(["132"])
|
|
34
|
+
});
|
|
35
|
+
function worldlineMyCheckoutProductNetwork(productId) {
|
|
36
|
+
for (const [network, ids] of Object.entries(WORLDLINE_MYCHECKOUT_CARD_PRODUCTS)) {
|
|
37
|
+
if (ids.includes(productId))
|
|
38
|
+
return network;
|
|
39
|
+
}
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
|
43
|
+
var FIELDS = {
|
|
44
|
+
publicMerchantId: /^[1-9]\d{0,9}$/,
|
|
45
|
+
variantCode: /^\d{1,10}$/,
|
|
46
|
+
locale: /^[a-z]{2}_[A-Z]{2}$/,
|
|
47
|
+
idempotenceKey: UUID,
|
|
48
|
+
// <merchant id>-<32 hex>:<hosted checkout id>:<32 hex>, the hosted checkout's own id.
|
|
49
|
+
token: /^[1-9]\d{0,9}-[0-9a-f]{32}:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}:[0-9a-f]{32}$/,
|
|
50
|
+
requestToken: UUID,
|
|
51
|
+
isPaymentProductDetailsShown: /^(?:true|false)$/,
|
|
52
|
+
isPaymentProductFiscalNumberShown: /^(?:true|false)$/,
|
|
53
|
+
jsEnabled: /^true$/,
|
|
54
|
+
isFramed: /^(?:true|false)$/,
|
|
55
|
+
paymentProductId: /^\d{1,4}$/,
|
|
56
|
+
paymentProductGroupId: /^cards$/,
|
|
57
|
+
colorDepth: /^\d{0,3}$/,
|
|
58
|
+
screenHeight: /^\d{0,5}$/,
|
|
59
|
+
screenWidth: /^\d{0,5}$/,
|
|
60
|
+
innerHeight: /^\d{0,5}$/,
|
|
61
|
+
innerWidth: /^\d{0,5}$/,
|
|
62
|
+
timezoneOffsetUtcMinutes: /^-?\d{0,4}$/,
|
|
63
|
+
browserLocale: /^[A-Za-z0-9-]{0,35}$/,
|
|
64
|
+
javaEnabled: /^(?:true|false)?$/,
|
|
65
|
+
cardNumber: /^[0-9 ]{12,23}$/,
|
|
66
|
+
// MM/YY as the page's mask writes it; the delimiter may be absent or padded.
|
|
67
|
+
expiryDate: /^(?:0[1-9]|1[0-2])(?: ?\/ ?)?\d{2}$/,
|
|
68
|
+
cvv: /^\d{3,4}$/,
|
|
69
|
+
cardholderName: /^[^\u0000-\u001f\u007f<>]{0,100}$/,
|
|
70
|
+
// The autofill helpers the page folds into expiryDate and then empties.
|
|
71
|
+
expiryDateMonth: /^$/,
|
|
72
|
+
expiryDateYear: /^$/,
|
|
73
|
+
// A checked "remember" asks Worldline to keep the card for later purchases.
|
|
74
|
+
remember: /^$/,
|
|
75
|
+
// Present, empty, on a page that offers Click to Pay; a value is a Click to Pay payment.
|
|
76
|
+
clickToPayCheckoutResponseSignature: /^$/,
|
|
77
|
+
// The Pay button's own pair. The click handler writes it into #buttonValue and appends one
|
|
78
|
+
// more copy per click, so it may repeat, always as doPayment=doPayment.
|
|
79
|
+
doPayment: /^doPayment$/
|
|
80
|
+
};
|
|
81
|
+
var REPEATABLE = /* @__PURE__ */ new Set(["doPayment"]);
|
|
82
|
+
var REQUIRED = ["publicMerchantId", "token", "requestToken", "idempotenceKey", "doPayment", "cardNumber", "expiryDate", "cvv"];
|
|
83
|
+
var NOT_A_CARD_SALE = /* @__PURE__ */ new Set(["cancelPayment", "encryptedPaymentData", "mobileCardholderName"]);
|
|
84
|
+
var BODY_LIMIT = 16384;
|
|
85
|
+
var VALUE_LIMIT = 2048;
|
|
86
|
+
function parseWorldlineMyCheckoutForm(body) {
|
|
87
|
+
if (typeof body !== "string" || body === "" || body.length > BODY_LIMIT || /[^\x21-\x7e]/.test(body))
|
|
88
|
+
return { refusal: "body" };
|
|
89
|
+
const pairs = [];
|
|
90
|
+
const seen = /* @__PURE__ */ new Set();
|
|
91
|
+
for (const chunk of body.split("&")) {
|
|
92
|
+
const eq = chunk.indexOf("=");
|
|
93
|
+
if (eq < 1)
|
|
94
|
+
return { refusal: "body" };
|
|
95
|
+
let key;
|
|
96
|
+
let value;
|
|
97
|
+
try {
|
|
98
|
+
key = decodeURIComponent(chunk.slice(0, eq).replace(/\+/g, " "));
|
|
99
|
+
value = decodeURIComponent(chunk.slice(eq + 1).replace(/\+/g, " "));
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
return { refusal: "body" };
|
|
103
|
+
}
|
|
104
|
+
if (value.length > VALUE_LIMIT || /[\u0000-\u001f\u007f]/.test(value))
|
|
105
|
+
return { refusal: "body" };
|
|
106
|
+
if (seen.has(key) && !REPEATABLE.has(key))
|
|
107
|
+
return { refusal: "body" };
|
|
108
|
+
seen.add(key);
|
|
109
|
+
pairs.push([key, value]);
|
|
110
|
+
}
|
|
111
|
+
return { pairs };
|
|
112
|
+
}
|
|
113
|
+
var SESSION_FIELDS = /* @__PURE__ */ new Set(["publicMerchantId", "token", "requestToken", "idempotenceKey"]);
|
|
114
|
+
var CARD_FIELDS = /* @__PURE__ */ new Set(["cardNumber", "expiryDate", "cvv", "cardholderName", "expiryDateMonth", "expiryDateYear"]);
|
|
115
|
+
function worldlineMyCheckoutCardholderName(name) {
|
|
116
|
+
if (typeof name !== "string")
|
|
117
|
+
return null;
|
|
118
|
+
const trimmed = name.trim();
|
|
119
|
+
return trimmed !== "" && FIELDS.cardholderName.test(trimmed) ? trimmed : null;
|
|
120
|
+
}
|
|
121
|
+
function worldlineMyCheckoutTemplateRefusal(body) {
|
|
122
|
+
const parsed = parseWorldlineMyCheckoutForm(body);
|
|
123
|
+
if (parsed.refusal)
|
|
124
|
+
return parsed.refusal;
|
|
125
|
+
const values = /* @__PURE__ */ new Map();
|
|
126
|
+
for (const [key, value] of parsed.pairs) {
|
|
127
|
+
if (NOT_A_CARD_SALE.has(key) || key === "clickToPayCheckoutResponseSignature" && value !== "")
|
|
128
|
+
return "not_a_card_sale";
|
|
129
|
+
if (key === "remember" && value !== "")
|
|
130
|
+
return "stored_card";
|
|
131
|
+
if (!Object.hasOwn(FIELDS, key))
|
|
132
|
+
return "unknown_field";
|
|
133
|
+
values.set(key, value);
|
|
134
|
+
}
|
|
135
|
+
for (const key of REQUIRED)
|
|
136
|
+
if (!values.has(key))
|
|
137
|
+
return "missing_field";
|
|
138
|
+
for (const [key, value] of parsed.pairs) {
|
|
139
|
+
if (FIELDS[key].test(value))
|
|
140
|
+
continue;
|
|
141
|
+
return SESSION_FIELDS.has(key) ? "session" : CARD_FIELDS.has(key) ? "card_fields" : "field_value";
|
|
142
|
+
}
|
|
143
|
+
if (values.get("token").split("-")[0] !== values.get("publicMerchantId"))
|
|
144
|
+
return "session";
|
|
145
|
+
const product = values.get("paymentProductId");
|
|
146
|
+
if (product === void 0 ? !values.has("paymentProductGroupId") : worldlineMyCheckoutProductNetwork(product) === null)
|
|
147
|
+
return "product";
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
function worldlineMyCheckoutRequestRefusal(request) {
|
|
151
|
+
if (!request || typeof request !== "object" || !isWorldlineMyCheckoutUrl(request.url))
|
|
152
|
+
return "endpoint";
|
|
153
|
+
if (request.method !== "POST")
|
|
154
|
+
return "method";
|
|
155
|
+
const headers = request.headers && typeof request.headers === "object" ? request.headers : {};
|
|
156
|
+
const types = Object.entries(headers).filter(([name]) => name.toLowerCase() === "content-type");
|
|
157
|
+
if (types.length !== 1 || typeof types[0][1] !== "string" || !/^application\/x-www-form-urlencoded(?:;\s*charset=utf-8)?$/i.test(types[0][1].trim()))
|
|
158
|
+
return "content_type";
|
|
159
|
+
return worldlineMyCheckoutTemplateRefusal(request.body);
|
|
160
|
+
}
|
|
161
|
+
var WORLDLINE_MYCHECKOUT_DEDUPE = /* @__PURE__ */ Object.freeze(["publicMerchantId", "token"]);
|
|
162
|
+
export { WORLDLINE_MYCHECKOUT_CAPABILITY, WORLDLINE_MYCHECKOUT_CARD_PRODUCTS, WORLDLINE_MYCHECKOUT_DEDUPE, WORLDLINE_MYCHECKOUT_ENDPOINTS, WORLDLINE_MYCHECKOUT_PSP, isWorldlineMyCheckoutUrl, parseWorldlineMyCheckoutForm, worldlineMyCheckoutCardholderName, worldlineMyCheckoutEnvironment, worldlineMyCheckoutProductNetwork, worldlineMyCheckoutRequestRefusal, worldlineMyCheckoutTemplateRefusal };
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
// Worldline Connect MyCheckout (psp `worldline_mycheckout`): the hosted payment page whose card
|
|
2
|
+
// form posts the card as a top-level navigation to /checkout on Worldline's own host. The form
|
|
3
|
+
// rules are payment-core's (worldline-mycheckout.generated.ts): the API applies them at create,
|
|
4
|
+
// and the cardholder's device applies them again before it rebuilds the form with the real card.
|
|
5
|
+
// This module holds the one thing only the SDK can decide: which card product the page chose.
|
|
6
|
+
//
|
|
7
|
+
// A MyCheckout page that offers several card products asks the shopper to choose one first (its
|
|
8
|
+
// select-payment-product page: Visa 1, Mastercard 3, Visa Electron 122, and so on). That choice
|
|
9
|
+
// posts to the same /checkout URL with no card, and continues untouched. The card form it opens
|
|
10
|
+
// then looks the typed number's IIN up with Worldline and, when the answer names another product
|
|
11
|
+
// the page offers, switches its own paymentProductId to that product. With the agent's placeholder
|
|
12
|
+
// typed, the answer describes the placeholder, never the card that pays: on 2026-10-12 CLZ's page
|
|
13
|
+
// read the SDK's Visa placeholder (411111) as Visa Electron (122) and switched the form from the
|
|
14
|
+
// Visa (1) the agent chose. The cardholder's device checks only that the product belongs to the
|
|
15
|
+
// real card's network (Visa takes 1, 114 and 122), so it would have submitted a Visa credit card
|
|
16
|
+
// as Visa Electron. The device never sees the choice and the API stores only the card form; this
|
|
17
|
+
// attachment sees both posts, so the SDK holds the card submit to the product chosen for its
|
|
18
|
+
// hosted checkout.
|
|
19
|
+
//
|
|
20
|
+
// The processor ships dark: the API serves the recognizer only to a build that declares
|
|
21
|
+
// `worldline_mycheckout_form`, for an org Agentcard turned Worldline on for. Without it no
|
|
22
|
+
// MyCheckout post pauses, and none reaches this module.
|
|
23
|
+
import { isWorldlineMyCheckoutUrl, parseWorldlineMyCheckoutForm } from './worldline-mycheckout.generated.js';
|
|
24
|
+
/** Whether `url` is one of MyCheckout's four card-form endpoints, written exactly (payment-core's rule). */
|
|
25
|
+
export function isWorldlineMyCheckoutEndpoint(url) {
|
|
26
|
+
return isWorldlineMyCheckoutUrl(url) === true;
|
|
27
|
+
}
|
|
28
|
+
/** The `blocked` detail for a card submit whose product is not the one its page chose. */
|
|
29
|
+
export const WORLDLINE_MYCHECKOUT_PRODUCT_CHANGED = 'worldline_mycheckout_product_changed';
|
|
30
|
+
/** The `blocked` detail for a card submit that names no product, on a hosted checkout whose page chose one. */
|
|
31
|
+
export const WORLDLINE_MYCHECKOUT_PRODUCT_MISSING = 'worldline_mycheckout_product_missing';
|
|
32
|
+
/** How many hosted checkouts one attachment remembers a choice for; the oldest is forgotten first. */
|
|
33
|
+
const REMEMBERED_CHECKOUTS = 32;
|
|
34
|
+
const PRODUCT_ID = /^\d{1,4}$/;
|
|
35
|
+
/**
|
|
36
|
+
* The card products one attachment saw chosen, per hosted checkout: its `publicMerchantId` and
|
|
37
|
+
* `token`, the pair payment-core's duplicate guard reads as one hosted checkout
|
|
38
|
+
* (WORLDLINE_MYCHECKOUT_DEDUPE).
|
|
39
|
+
*/
|
|
40
|
+
export class WorldlineMyCheckoutProducts {
|
|
41
|
+
chosen = new Map();
|
|
42
|
+
/**
|
|
43
|
+
* Read one POST to a card-form endpoint. A product selection (no card, and neither the Pay nor
|
|
44
|
+
* the cancel pair) records the product it chose for its hosted checkout, or forgets the choice
|
|
45
|
+
* when it picks the card group, which leaves the product to the card. A card submit is judged:
|
|
46
|
+
* WORLDLINE_MYCHECKOUT_PRODUCT_CHANGED when its paymentProductId is another product than the one
|
|
47
|
+
* chosen for its hosted checkout, WORLDLINE_MYCHECKOUT_PRODUCT_MISSING when it names none. Null for
|
|
48
|
+
* every other post, and for a card submit on the card group or on a hosted checkout whose choice
|
|
49
|
+
* this attachment never saw: the API and the device judge those as before. A body payment-core
|
|
50
|
+
* cannot read is never judged here either.
|
|
51
|
+
*/
|
|
52
|
+
judge(url, body) {
|
|
53
|
+
if (typeof body !== 'string' || !isWorldlineMyCheckoutUrl(url))
|
|
54
|
+
return null;
|
|
55
|
+
const parsed = parseWorldlineMyCheckoutForm(body);
|
|
56
|
+
if (!parsed.pairs)
|
|
57
|
+
return null;
|
|
58
|
+
const values = new Map();
|
|
59
|
+
for (const [key, value] of parsed.pairs)
|
|
60
|
+
if (!values.has(key))
|
|
61
|
+
values.set(key, value);
|
|
62
|
+
const merchant = values.get('publicMerchantId');
|
|
63
|
+
const token = values.get('token');
|
|
64
|
+
if (!merchant || !token)
|
|
65
|
+
return null;
|
|
66
|
+
const checkout = JSON.stringify([merchant, token]);
|
|
67
|
+
const product = values.get('paymentProductId');
|
|
68
|
+
// A card is a non-empty cardNumber, as the recognizer's card fields read it (card-fields.js).
|
|
69
|
+
if (!values.get('cardNumber')) {
|
|
70
|
+
if (values.has('doPayment') || values.has('cancelPayment'))
|
|
71
|
+
return null;
|
|
72
|
+
if (product !== undefined && PRODUCT_ID.test(product))
|
|
73
|
+
this.remember(checkout, product);
|
|
74
|
+
else if (values.get('paymentProductGroupId') === 'cards')
|
|
75
|
+
this.chosen.delete(checkout);
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
if (values.has('paymentProductGroupId'))
|
|
79
|
+
return null;
|
|
80
|
+
const chosen = this.chosen.get(checkout);
|
|
81
|
+
if (chosen === undefined)
|
|
82
|
+
return null;
|
|
83
|
+
if (!product)
|
|
84
|
+
return WORLDLINE_MYCHECKOUT_PRODUCT_MISSING;
|
|
85
|
+
return product === chosen ? null : WORLDLINE_MYCHECKOUT_PRODUCT_CHANGED;
|
|
86
|
+
}
|
|
87
|
+
remember(checkout, product) {
|
|
88
|
+
this.chosen.delete(checkout);
|
|
89
|
+
this.chosen.set(checkout, product);
|
|
90
|
+
while (this.chosen.size > REMEMBERED_CHECKOUTS)
|
|
91
|
+
this.chosen.delete(this.chosen.keys().next().value);
|
|
92
|
+
}
|
|
93
|
+
}
|