@agent-cards/sdk 0.23.9 → 0.23.11

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.
Files changed (65) hide show
  1. package/CHANGELOG.md +48 -0
  2. package/PREFLIGHT.md +7 -5
  3. package/README.md +211 -102
  4. package/dist/aci.d.ts +7 -5
  5. package/dist/aci.generated.d.ts +2 -1
  6. package/dist/aci.generated.js +14 -4
  7. package/dist/aci.js +28 -7
  8. package/dist/amadeus-checkout.generated.js +2 -2
  9. package/dist/builtin-registry.generated.js +1 -1
  10. package/dist/card-fields.generated.js +165 -10
  11. package/dist/cdp.js +86 -34
  12. package/dist/client.d.ts +73 -33
  13. package/dist/client.js +136 -45
  14. package/dist/creditguard.generated.d.ts +1 -1
  15. package/dist/creditguard.generated.js +1 -3
  16. package/dist/cse-body.d.ts +7 -6
  17. package/dist/cse-body.js +8 -7
  18. package/dist/datatrans.generated.js +8 -5
  19. package/dist/elavon-converge.d.ts +5 -2
  20. package/dist/elavon-converge.generated.js +9 -5
  21. package/dist/elavon-converge.js +8 -4
  22. package/dist/fiserv.d.ts +9 -5
  23. package/dist/fiserv.generated.d.ts +3 -3
  24. package/dist/fiserv.generated.js +50 -20
  25. package/dist/fiserv.js +10 -6
  26. package/dist/lifecycle.d.ts +9 -1
  27. package/dist/lifecycle.js +34 -0
  28. package/dist/payrails.d.ts +78 -18
  29. package/dist/payrails.generated.d.ts +12 -2
  30. package/dist/payrails.generated.js +213 -18
  31. package/dist/payrails.js +165 -44
  32. package/dist/pre-claim.d.ts +7 -0
  33. package/dist/pre-claim.js +98 -36
  34. package/dist/preflight-capabilities.generated.d.ts +0 -106
  35. package/dist/preflight-capabilities.generated.js +0 -153
  36. package/dist/preflight-catalog.json +61 -181
  37. package/dist/preflight-schemas.json +6 -6
  38. package/dist/preflight.generated.d.ts +0 -106
  39. package/dist/preflight.generated.js +27 -15
  40. package/dist/preparation.d.ts +2 -0
  41. package/dist/preparation.js +4 -2
  42. package/dist/prepared-processor.d.ts +19 -5
  43. package/dist/prepared-processor.js +21 -5
  44. package/dist/primer.generated.d.ts +5 -1
  45. package/dist/primer.generated.js +202 -34
  46. package/dist/registry.d.ts +18 -12
  47. package/dist/registry.js +11 -8
  48. package/dist/shiji-sts.d.ts +24 -22
  49. package/dist/shiji-sts.generated.d.ts +1 -1
  50. package/dist/shiji-sts.generated.js +12 -8
  51. package/dist/shiji-sts.js +49 -46
  52. package/dist/stripe-checkout.generated.js +157 -49
  53. package/dist/verifone.generated.js +192 -17
  54. package/dist/verifone.js +1 -1
  55. package/dist/worldline-mycheckout.d.ts +27 -0
  56. package/dist/worldline-mycheckout.generated.d.ts +29 -0
  57. package/dist/worldline-mycheckout.generated.js +162 -0
  58. package/dist/worldline-mycheckout.js +93 -0
  59. package/dist/wspay.d.ts +17 -16
  60. package/dist/wspay.generated.d.ts +6 -1
  61. package/dist/wspay.generated.js +13 -3
  62. package/dist/wspay.js +9 -11
  63. package/examples/preflight/kernel-native/README.md +1 -1
  64. package/examples/preflight/kernel-native/inventory.json +1 -19
  65. package/package.json +3 -3
@@ -1,6 +1,6 @@
1
1
  // @ts-nocheck
2
2
  // Generated from @agent-cards/payment-core. Do not edit.
3
- // artifact-sha256: cf655438763f54e5d5bfea2555f76941ef80dbfd5aa7bc6342a2a75d1ac9cd39
3
+ // artifact-sha256: db732bdbb517b37ea27d05debafb7659bb456f686edd0c90d78b2fbdb993d68d
4
4
  // src/deep-freeze.js
5
5
  function deepFreeze(value) {
6
6
  if (value && typeof value === "object" && !Object.isFrozen(value)) {
@@ -415,18 +415,44 @@ var VERIFONE_MERCHANT_PROFILES = /* @__PURE__ */ deepFreeze([
415
415
  // employee variants (featureConfig verifoneCCEnabled, verifonePLCCEnabled,
416
416
  // verifoneEmployeeCCEnabled, verifoneEmployeePLCCEnabled; live from late 2023, still on
417
417
  // 2026-09-19). Checkout is www.containerstore.com/checkout?fulfillmentMethod=STANDARD|PICKUP,
418
- // on the legacy app behind PerimeterX. Its card request has never been observed: the
419
- // CheckoutV2 bundle was never archived and the page is session-gated. So no leg and no key
420
- // are pinned, and every blob-bearing request to www.containerstore.com is aborted, until one
421
- // person-run checkout captured as a HAR pins the endpoint, the member holding the blob, the
422
- // key and its alias, and whether the blob is sent once or twice.
418
+ // on the legacy app behind PerimeterX.
419
+ //
420
+ // The card request, read from TCS's own CheckoutV2 build 3.340.1.16022 (its public source
421
+ // map: CheckoutForm and the checkout service) and its live guest checkout on 2026-10-10:
422
+ // Place Order encrypts the card once with verifone.js, and axios posts the order once, as
423
+ // JSON, to /checkout/api/submit, with the new card last in paymentMethods after any gift
424
+ // card, as {defaultWallet, cardNumber (masked but its last four), encryptedCard, type
425
+ // 'CREDIT_CARD', cardType}. A saved card is type 'COF' with a walletId, TCS's own card
426
+ // 'TCS_CREDIT_CARD'; defaultWallet saves the card to the shopper's wallet, and
427
+ // popTermsAndConditionsAccepted (the form's popEnroll box) enrolls them in TCS's loyalty
428
+ // program, Organized Insider (once POP!). A gift card's balance check posts its own blob to
429
+ // /checkout/api/gift-card, which aborts like any request off the leg.
430
+ //
431
+ // The body names no amount (TCS's total is only in its cart response), and the key the page
432
+ // encrypts under, its verifoneEncryptionKey, is not pinned (verifone-keys.js). So the profile
433
+ // stays in observe: the leg's requests abort as unreviewed until a person pins the key and the
434
+ // merchant's total has a reviewed source.
423
435
  id: "containerstore_checkout",
424
436
  merchant: "The Container Store",
425
437
  processor: "verifone",
426
438
  status: "observe",
427
439
  environment: "production",
428
440
  origins: [{ checkout: "https://www.containerstore.com", endpoint: "https://www.containerstore.com" }],
429
- legs: [],
441
+ legs: [{
442
+ name: "card",
443
+ method: "POST",
444
+ path: "/checkout/api/submit",
445
+ query: {},
446
+ requires: [{ path: ["paymentMethods", 0, "type"], equals: "CREDIT_CARD" }],
447
+ refuse: [
448
+ { path: ["paymentMethods", 1], when: "present" },
449
+ { path: ["paymentMethods", 0, "defaultWallet"], when: "true" },
450
+ { path: ["paymentMethods", 0, "walletId"], when: "present" },
451
+ { path: ["popTermsAndConditionsAccepted"], when: "true" }
452
+ ],
453
+ holder: ["paymentMethods", 0],
454
+ field: "encryptedCard"
455
+ }],
430
456
  keyPin: null,
431
457
  amount: null
432
458
  }
@@ -652,8 +678,143 @@ function decodeBase64(text) {
652
678
  }
653
679
  return encodeBase64(out) === text ? out : null;
654
680
  }
681
+ // src/secp256k1.js
682
+ var P = (1n << 256n) - (1n << 32n) - 977n;
683
+ var N = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141n;
684
+ var G = Object.freeze({
685
+ x: 0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798n,
686
+ y: 0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8n
687
+ });
688
+ var BYTES = 32;
689
+ var MAX_DRAWS = 16;
690
+ var mod = (a) => {
691
+ const r = a % P;
692
+ return r < 0n ? r + P : r;
693
+ };
694
+ function toBigInt(bytes) {
695
+ let hex2 = "0";
696
+ for (let i = 0; i < bytes.length; i++)
697
+ hex2 += (bytes[i] < 16 ? "0" : "") + bytes[i].toString(16);
698
+ return BigInt(`0x${hex2}`);
699
+ }
700
+ function toBytes(value) {
701
+ const hex2 = value.toString(16).padStart(BYTES * 2, "0");
702
+ const out = new Uint8Array(BYTES);
703
+ for (let i = 0; i < BYTES; i++)
704
+ out[i] = parseInt(hex2.slice(2 * i, 2 * i + 2), 16);
705
+ return out;
706
+ }
707
+ function decodePoint(bytes) {
708
+ if (!(bytes instanceof Uint8Array) || bytes.length !== 1 + 2 * BYTES || bytes[0] !== 4)
709
+ return null;
710
+ const x = toBigInt(bytes.subarray(1, 1 + BYTES));
711
+ const y = toBigInt(bytes.subarray(1 + BYTES));
712
+ if (x >= P || y >= P)
713
+ return null;
714
+ return mod(y * y) === mod(x * x * x + 7n) ? { x, y } : null;
715
+ }
716
+ function isSecp256k1Point(bytes) {
717
+ return decodePoint(bytes) !== null;
718
+ }
719
+ var INFINITY = Object.freeze({ X: 0n, Y: 1n, Z: 0n });
720
+ function double(p) {
721
+ if (p.Z === 0n || p.Y === 0n)
722
+ return INFINITY;
723
+ const a = mod(p.X * p.X);
724
+ const b = mod(p.Y * p.Y);
725
+ const c = mod(b * b);
726
+ const d = mod(2n * (mod((p.X + b) * (p.X + b)) - a - c));
727
+ const e = mod(3n * a);
728
+ const X = mod(e * e - 2n * d);
729
+ return { X, Y: mod(e * (d - X) - 8n * c), Z: mod(2n * p.Y * p.Z) };
730
+ }
731
+ function add(p, q) {
732
+ if (p.Z === 0n)
733
+ return q;
734
+ if (q.Z === 0n)
735
+ return p;
736
+ const z1z1 = mod(p.Z * p.Z);
737
+ const z2z2 = mod(q.Z * q.Z);
738
+ const u1 = mod(p.X * z2z2);
739
+ const u2 = mod(q.X * z1z1);
740
+ const s1 = mod(p.Y * q.Z * z2z2);
741
+ const s2 = mod(q.Y * p.Z * z1z1);
742
+ const h = mod(u2 - u1);
743
+ const r = mod(2n * (s2 - s1));
744
+ if (h === 0n)
745
+ return r === 0n ? double(p) : INFINITY;
746
+ const i = mod(4n * h * h);
747
+ const j = mod(h * i);
748
+ const v = mod(u1 * i);
749
+ const X = mod(r * r - j - 2n * v);
750
+ return { X, Y: mod(r * (v - X) - 2n * s1 * j), Z: mod((mod((p.Z + q.Z) * (p.Z + q.Z)) - z1z1 - z2z2) * h) };
751
+ }
752
+ function power(base, exponent) {
753
+ let result = 1n;
754
+ let square = mod(base);
755
+ for (let rest = exponent; rest > 0n; rest >>= 1n) {
756
+ if (rest & 1n)
757
+ result = mod(result * square);
758
+ square = mod(square * square);
759
+ }
760
+ return result;
761
+ }
762
+ function multiply(k, point) {
763
+ let r0 = INFINITY;
764
+ let r1 = { X: point.x, Y: point.y, Z: 1n };
765
+ for (let i = 255; i >= 0; i--) {
766
+ if (k >> BigInt(i) & 1n) {
767
+ r0 = add(r0, r1);
768
+ r1 = double(r1);
769
+ }
770
+ else {
771
+ r1 = add(r0, r1);
772
+ r0 = double(r0);
773
+ }
774
+ }
775
+ if (r0.Z === 0n)
776
+ return null;
777
+ const inverse = power(r0.Z, P - 2n);
778
+ const inverse2 = mod(inverse * inverse);
779
+ return { x: mod(r0.X * inverse2), y: mod(r0.Y * inverse2 * inverse) };
780
+ }
781
+ function ephemeralScalar(random) {
782
+ if (!random || typeof random.getRandomValues !== "function")
783
+ throw new Error("Refusing to encrypt: no random source.");
784
+ const draw = new Uint8Array(BYTES);
785
+ try {
786
+ for (let round = 0; round < MAX_DRAWS; round++) {
787
+ random.getRandomValues(draw);
788
+ const k = toBigInt(draw);
789
+ if (k > 0n && k < N)
790
+ return k;
791
+ }
792
+ }
793
+ finally {
794
+ draw.fill(0);
795
+ }
796
+ throw new Error("Refusing to encrypt: the random source drew no usable key.");
797
+ }
798
+ function secp256k1Agreement(recipient, random) {
799
+ const q = decodePoint(recipient);
800
+ if (!q)
801
+ throw new Error("Refusing to encrypt: the key is not a point on secp256k1.");
802
+ const k = ephemeralScalar(random);
803
+ const v = multiply(k, G);
804
+ const s = multiply(k, q);
805
+ if (!v || !s)
806
+ throw new Error("Refusing to encrypt: the key agreement reached the point at infinity.");
807
+ const ephemeralPoint = new Uint8Array(1 + 2 * BYTES);
808
+ ephemeralPoint[0] = 4;
809
+ ephemeralPoint.set(toBytes(v.x), 1);
810
+ ephemeralPoint.set(toBytes(v.y), 1 + BYTES);
811
+ return { ephemeralPoint, sharedSecret: toBytes(s.x) };
812
+ }
655
813
  // src/verifone-pgp.js
656
- var P256_OID = Object.freeze([42, 134, 72, 206, 61, 3, 1, 7]);
814
+ var CURVE_OIDS = Object.freeze({
815
+ "P-256": Object.freeze([42, 134, 72, 206, 61, 3, 1, 7]),
816
+ secp256k1: Object.freeze([43, 129, 4, 0, 10])
817
+ });
657
818
  var ALGORITHM_ECDH = 18;
658
819
  var ENCRYPTING_ALGORITHMS = Object.freeze([1, 2, 16, 18]);
659
820
  var KNOWN_SYMMETRIC = Object.freeze([2, 3, 4, 7, 8, 9, 10]);
@@ -863,7 +1024,8 @@ function keyPacket(body) {
863
1024
  function ecdhFields(fields) {
864
1025
  const oidLength = fields[0];
865
1026
  const oid = fields.subarray(1, 1 + oidLength);
866
- if (oidLength !== P256_OID.length || !sameBytes(oid, P256_OID))
1027
+ const curve = sameBytes(oid, CURVE_OIDS["P-256"]) ? "P-256" : sameBytes(oid, CURVE_OIDS.secp256k1) ? "secp256k1" : null;
1028
+ if (!curve)
867
1029
  return { refusal: "verifone_key_curve_unsupported" };
868
1030
  let at = 1 + oidLength;
869
1031
  const bits = fields[at] << 8 | fields[at + 1];
@@ -871,12 +1033,14 @@ function ecdhFields(fields) {
871
1033
  at += 2 + point.length;
872
1034
  if (bits !== 515 || point.length !== 65 || point[0] !== 4)
873
1035
  return { refusal: "verifone_key_malformed" };
1036
+ if (curve === "secp256k1" && !isSecp256k1Point(point))
1037
+ return { refusal: "verifone_key_malformed" };
874
1038
  const kdf = fields.subarray(at, at + 1 + fields[at]);
875
1039
  if (kdf.length !== 4 || kdf[0] !== 3 || kdf[1] !== 1 || at + 4 !== fields.length)
876
1040
  return { refusal: "verifone_key_malformed" };
877
1041
  if (!KDF_HASHES[kdf[2]] || !AES[kdf[3]])
878
1042
  return { refusal: "verifone_key_kdf_unsupported" };
879
- return { point: Uint8Array.from(point), kdfHash: kdf[2], kekAlgorithm: kdf[3] };
1043
+ return { curve, point: Uint8Array.from(point), kdfHash: kdf[2], kekAlgorithm: kdf[3] };
880
1044
  }
881
1045
  async function fingerprintOf(body, subtle) {
882
1046
  return new Uint8Array(await subtle.digest("SHA-1", concat(Uint8Array.of(153), u16(body.length), body)));
@@ -962,6 +1126,7 @@ async function readVerifonePublicKey(publicKeyBase64, facilities) {
962
1126
  primaryFingerprint: hex(primaryFingerprint),
963
1127
  encryptionSubkeyId: hex(fingerprint.subarray(12, 20)),
964
1128
  encryptionFingerprint: fingerprint,
1129
+ curve: ecdh.curve,
965
1130
  point: ecdh.point,
966
1131
  kdfHash: ecdh.kdfHash,
967
1132
  kekAlgorithm: ecdh.kekAlgorithm,
@@ -999,21 +1164,31 @@ ${MESSAGE_END}\r
999
1164
  `;
1000
1165
  return encodeBase64(asciiBytes(armored));
1001
1166
  }
1167
+ async function agree(curve, point, crypto) {
1168
+ if (curve === "secp256k1") {
1169
+ const { ephemeralPoint: ephemeralPoint2, sharedSecret } = secp256k1Agreement(point, crypto);
1170
+ return { ephemeralPoint: ephemeralPoint2, shared: sharedSecret };
1171
+ }
1172
+ const subtle = crypto.subtle;
1173
+ const ephemeral = await subtle.generateKey({ name: "ECDH", namedCurve: "P-256" }, true, ["deriveBits"]);
1174
+ const ephemeralPoint = new Uint8Array(await subtle.exportKey("raw", ephemeral.publicKey));
1175
+ const recipient = await subtle.importKey("raw", point, { name: "ECDH", namedCurve: "P-256" }, false, []);
1176
+ const shared = new Uint8Array(await subtle.deriveBits({ name: "ECDH", public: recipient }, ephemeral.privateKey, 256));
1177
+ return { ephemeralPoint, shared };
1178
+ }
1002
1179
  async function verifoneEncryptText(key, text, facilities) {
1003
1180
  const crypto = facilities?.crypto;
1004
1181
  const subtle = crypto?.subtle;
1005
1182
  if (!subtle || typeof crypto.getRandomValues !== "function")
1006
1183
  throw new Error("Refusing to encrypt: no WebCrypto.");
1007
- if (!key || key.refusal || !(key.point instanceof Uint8Array) || !AES[key.sessionCipher] || !AES[key.kekAlgorithm] || !KDF_HASHES[key.kdfHash]) {
1184
+ const oid = key && Object.prototype.hasOwnProperty.call(CURVE_OIDS, key.curve) ? CURVE_OIDS[key.curve] : null;
1185
+ if (!key || key.refusal || !oid || !(key.point instanceof Uint8Array) || !AES[key.sessionCipher] || !AES[key.kekAlgorithm] || !KDF_HASHES[key.kdfHash]) {
1008
1186
  throw new Error("Refusing to encrypt: the Verifone key is not one this codec reads.");
1009
1187
  }
1010
1188
  if (!Number.isSafeInteger(facilities.nowMs) || facilities.nowMs < 0)
1011
1189
  throw new Error("Refusing to encrypt: no clock.");
1012
- const ephemeral = await subtle.generateKey({ name: "ECDH", namedCurve: "P-256" }, true, ["deriveBits"]);
1013
- const ephemeralPoint = new Uint8Array(await subtle.exportKey("raw", ephemeral.publicKey));
1014
- const recipient = await subtle.importKey("raw", key.point, { name: "ECDH", namedCurve: "P-256" }, false, []);
1015
- const shared = new Uint8Array(await subtle.deriveBits({ name: "ECDH", public: recipient }, ephemeral.privateKey, 256));
1016
- const param = concat(Uint8Array.of(P256_OID.length), Uint8Array.from(P256_OID), Uint8Array.of(ALGORITHM_ECDH, 3, 1, key.kdfHash, key.kekAlgorithm), asciiBytes("Anonymous Sender "), key.encryptionFingerprint);
1190
+ const { ephemeralPoint, shared } = await agree(key.curve, key.point, crypto);
1191
+ const param = concat(Uint8Array.of(oid.length), Uint8Array.from(oid), Uint8Array.of(ALGORITHM_ECDH, 3, 1, key.kdfHash, key.kekAlgorithm), asciiBytes("Anonymous Sender "), key.encryptionFingerprint);
1017
1192
  const derived = new Uint8Array(await subtle.digest(KDF_HASHES[key.kdfHash], concat(u32(1), shared, param)));
1018
1193
  const kek = await subtle.importKey("raw", derived.subarray(0, AES[key.kekAlgorithm]), "AES-KW", false, ["wrapKey"]);
1019
1194
  const sessionKey = crypto.getRandomValues(new Uint8Array(AES[key.sessionCipher]));
@@ -1142,7 +1317,7 @@ function verifoneClockSkewRefusal(deviceNowMs, serverNowMs) {
1142
1317
  }
1143
1318
  var KEY_REFUSALS = Object.freeze({
1144
1319
  verifone_key_unpinned: "Refusing to encrypt: Agentcard has not pinned this Verifone key.",
1145
- verifone_key_curve_unsupported: "Refusing to encrypt: the Verifone key is not on NIST P-256.",
1320
+ verifone_key_curve_unsupported: "Refusing to encrypt: the Verifone key is on neither NIST P-256 nor secp256k1.",
1146
1321
  verifone_key_expired: "Refusing to encrypt: this Verifone key has expired.",
1147
1322
  verifone_key_revoked: "Refusing to encrypt: this Verifone key was revoked."
1148
1323
  });
package/dist/verifone.js CHANGED
@@ -18,7 +18,7 @@
18
18
  * key paths, never a value) and aborts it, so the agent's placeholder card never
19
19
  * reaches the merchant and nothing is charged; 'enabled' lets a prepared checkout
20
20
  * pay the reviewed leg. A profile is 'enabled' only when the API and this build both
21
- * say so. The Container Store ships in 'observe', with no leg and no key reviewed.
21
+ * say so. The Container Store ships in 'observe', its card request reviewed and no key pinned.
22
22
  */
23
23
  import * as generated from './verifone.generated.js';
24
24
  import { sha256Hex as sharedSha256Hex } from './adyen-merchant-hosted.generated.js';
@@ -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
+ }
package/dist/wspay.d.ts CHANGED
@@ -4,27 +4,28 @@ export declare const WSPAY_PSP = "wspay";
4
4
  export declare const WSPAY_CAPABILITY = "wspay_confirm";
5
5
  /**
6
6
  * Why the SDK refuses a WSPay request before anything is claimed, created or prompted:
7
- * - `unsupported_wspay_payment_method`: not the card frame's new-card confirm. That covers a
8
- * saved_card or temp_card_id body (no new card to fill), a card-shaped object the page
9
- * built into the transaction, another URL shape on the host, or headers the frame does not
10
- * send. The cardholder's device could never fill it.
11
- * - `wspay_keep_on_file_unsupported`: a sound confirm that asks WSPay to keep the card on file
12
- * (tokenize_pan true), so the merchant could charge it later without another approval. Only
13
- * the cardholder can agree to that, and no approval asks them yet.
14
- * The API refuses the same requests at create (invalid_wspay_request and
15
- * wspay_keep_on_file_unsupported), so a refusal here only saves the round trip.
7
+ * `unsupported_wspay_payment_method`, not the card frame's new-card confirm. That covers a
8
+ * saved_card or temp_card_id body (no new card to fill), a card-shaped object the page built
9
+ * into the transaction, another URL shape on the host, or headers the frame does not send. The
10
+ * cardholder's device could never fill it, and the API refuses the same requests at create
11
+ * (invalid_wspay_request), so a refusal here only saves the round trip.
12
+ *
13
+ * A sound confirm that asks WSPay to keep the card on file (tokenize_pan true) pauses like a
14
+ * one-time one: the approval that follows asks the cardholder whether the merchant may keep the
15
+ * card. The API offers it for that approval only as a hotel's EUR 1.00 card guarantee paid with a
16
+ * card the cardholder stored, and refuses any other at create (wspay_keep_on_file_unsupported,
17
+ * card_not_stored).
16
18
  */
17
- export type WspayRefusalReason = 'unsupported_wspay_payment_method' | 'wspay_keep_on_file_unsupported';
19
+ export type WspayRefusalReason = 'unsupported_wspay_payment_method';
18
20
  /** True when the URL's host is one of WSPay's two Components hosts. */
19
21
  export declare function isWspayHost(url: string): boolean;
20
22
  /**
21
23
  * The pause decision for a POST the registry recognized on a WSPay host. Null when it is the
22
- * card frame's confirm of a new card for a one-time payment, which pauses for approval;
23
- * otherwise the reason it is refused. It judges the whole request (the exact URL, the frame's
24
- * content type, Accept and WP3-v2-Client authorization, and a body of the frame's own keys
25
- * bound to the URL's client_secret) with payment-core's wspayConfirmRefusal, the check the API
26
- * runs at create. It never passes keep-on-file consent: that waits for an approval that asks
27
- * the cardholder.
24
+ * card frame's confirm of a new card, which pauses for approval; otherwise the reason it is
25
+ * refused. It judges the whole request (the exact URL, the frame's content type, Accept and
26
+ * WP3-v2-Client authorization, and a body of the frame's own keys bound to the URL's
27
+ * client_secret) with payment-core's wspayConfirmRefusal, the check the API runs at create.
28
+ * It passes keep-on-file consent because the approval that follows asks the cardholder for it.
28
29
  */
29
30
  export declare function wspayRefusal(request: {
30
31
  url: string;