@agent-cards/sdk 0.21.0 → 0.22.0

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 (103) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/PREFLIGHT.md +30 -0
  3. package/README.md +1070 -21
  4. package/dist/aci.d.ts +173 -0
  5. package/dist/aci.generated.d.ts +87 -0
  6. package/dist/aci.generated.js +306 -0
  7. package/dist/aci.js +371 -0
  8. package/dist/adyen-merchant-hosted.generated.d.ts +3 -3
  9. package/dist/adyen-merchant-hosted.generated.js +283 -49
  10. package/dist/agentcard.d.ts +9 -2
  11. package/dist/agentcard.js +4 -2
  12. package/dist/amadeus-checkout.generated.d.ts +57 -0
  13. package/dist/amadeus-checkout.generated.js +549 -0
  14. package/dist/amadeus.d.ts +41 -0
  15. package/dist/amadeus.js +81 -0
  16. package/dist/builtin-registry.generated.js +1 -1
  17. package/dist/card-endpoint.d.ts +177 -0
  18. package/dist/card-endpoint.js +286 -0
  19. package/dist/card-fields.generated.d.ts +5 -3
  20. package/dist/card-fields.generated.js +284 -7
  21. package/dist/cdp.d.ts +5 -1
  22. package/dist/cdp.js +841 -135
  23. package/dist/client.d.ts +403 -19
  24. package/dist/client.js +949 -98
  25. package/dist/creditguard.generated.d.ts +79 -0
  26. package/dist/creditguard.generated.js +662 -0
  27. package/dist/cse-body.js +11 -1
  28. package/dist/datatrans.d.ts +96 -0
  29. package/dist/datatrans.generated.d.ts +64 -0
  30. package/dist/datatrans.generated.js +426 -0
  31. package/dist/datatrans.js +178 -0
  32. package/dist/elavon-converge.d.ts +37 -0
  33. package/dist/elavon-converge.generated.d.ts +49 -0
  34. package/dist/elavon-converge.generated.js +439 -0
  35. package/dist/elavon-converge.js +84 -0
  36. package/dist/eprotect.d.ts +152 -0
  37. package/dist/eprotect.generated.d.ts +77 -0
  38. package/dist/eprotect.generated.js +498 -0
  39. package/dist/eprotect.js +222 -0
  40. package/dist/hosted-form.d.ts +3 -2
  41. package/dist/hosted-form.js +3 -2
  42. package/dist/index.d.ts +17 -3
  43. package/dist/index.js +6 -1
  44. package/dist/juspay-gate.d.ts +82 -0
  45. package/dist/juspay-gate.js +126 -0
  46. package/dist/juspay.generated.d.ts +89 -0
  47. package/dist/juspay.generated.js +705 -0
  48. package/dist/lifecycle.d.ts +57 -6
  49. package/dist/lifecycle.js +119 -8
  50. package/dist/merchant-card-endpoints.generated.d.ts +63 -0
  51. package/dist/merchant-card-endpoints.generated.js +1103 -0
  52. package/dist/merchant-hosted.d.ts +4 -1
  53. package/dist/merchant-hosted.js +5 -2
  54. package/dist/merchant-session-probe.d.ts +48 -0
  55. package/dist/merchant-session-probe.js +53 -0
  56. package/dist/merchant-session.generated.d.ts +69 -0
  57. package/dist/merchant-session.generated.js +919 -0
  58. package/dist/payrails.d.ts +133 -0
  59. package/dist/payrails.generated.d.ts +73 -0
  60. package/dist/payrails.generated.js +596 -0
  61. package/dist/payrails.js +305 -0
  62. package/dist/pelecard.d.ts +32 -0
  63. package/dist/pelecard.generated.d.ts +32 -0
  64. package/dist/pelecard.generated.js +283 -0
  65. package/dist/pelecard.js +42 -0
  66. package/dist/pre-claim.d.ts +72 -2
  67. package/dist/pre-claim.js +857 -5
  68. package/dist/preflight-catalog.json +2218 -287
  69. package/dist/preflight-schemas.json +179 -6
  70. package/dist/preflight.generated.d.ts +1 -1
  71. package/dist/preflight.generated.js +233 -4
  72. package/dist/preparation.d.ts +5 -2
  73. package/dist/preparation.js +36 -10
  74. package/dist/prepared-processor.d.ts +84 -5
  75. package/dist/prepared-processor.js +147 -6
  76. package/dist/primer.d.ts +53 -0
  77. package/dist/primer.generated.d.ts +38 -0
  78. package/dist/primer.generated.js +624 -0
  79. package/dist/primer.js +82 -0
  80. package/dist/registry.d.ts +100 -8
  81. package/dist/registry.js +85 -11
  82. package/dist/shiji-sts.d.ts +144 -0
  83. package/dist/shiji-sts.generated.d.ts +72 -0
  84. package/dist/shiji-sts.generated.js +913 -0
  85. package/dist/shiji-sts.js +222 -0
  86. package/dist/stripe-checkout.generated.d.ts +1 -1
  87. package/dist/stripe-checkout.generated.js +699 -9
  88. package/dist/stripe-setup.d.ts +16 -0
  89. package/dist/stripe-setup.js +61 -0
  90. package/dist/substitutions.generated.js +187 -29
  91. package/dist/verifone.d.ts +68 -0
  92. package/dist/verifone.generated.d.ts +51 -0
  93. package/dist/verifone.generated.js +1401 -0
  94. package/dist/verifone.js +119 -0
  95. package/dist/voice-types.d.ts +27 -0
  96. package/dist/voice.d.ts +6 -1
  97. package/dist/voice.js +6 -1
  98. package/dist/wspay.d.ts +41 -0
  99. package/dist/wspay.generated.d.ts +41 -0
  100. package/dist/wspay.generated.js +370 -0
  101. package/dist/wspay.js +65 -0
  102. package/examples/preflight/kernel-native/inventory.json +1 -1
  103. package/package.json +3 -3
package/dist/cse-body.js CHANGED
@@ -3,6 +3,7 @@
3
3
  import { substituteEncryptedFields } from './substitute.js';
4
4
  import { substituteMerchantHostedBody } from './merchant-hosted.js';
5
5
  import { fiservCapturePinId, isFiservCaptureHost, substituteFiservEnvelope } from './fiserv.js';
6
+ import { substituteVerifoneBody } from './verifone.js';
6
7
  /**
7
8
  * The cse continuation: the paused body with the vault's ciphertext in place
8
9
  * of the dummy blobs. The adapters send it as postData ALONE. Chromium
@@ -25,8 +26,17 @@ import { fiservCapturePinId, isFiservCaptureHost, substituteFiservEnvelope } fro
25
26
  * a Fiserv capture host. Every refusal throws before anything is sent.
26
27
  */
27
28
  export function cseBody(body, replay, url, preparation) {
29
+ // A Verifone approval is always a prepared one for the profile it pays, and continues only the
30
+ // leg that profile reviewed, with the device's blob written into the LIVE paused body, which
31
+ // must still be the body the API checked at create (verifone.ts).
32
+ if (preparation?.psp === 'verifone' || replay.kind === 'verifone') {
33
+ if (replay.kind !== 'verifone' || preparation?.psp !== 'verifone' || preparation.merchantProfile !== replay.profile) {
34
+ throw new Error('Refusing to substitute: the paused request is not the Verifone card request the cardholder approved.');
35
+ }
36
+ return substituteVerifoneBody({ url, method: 'POST', body }, replay);
37
+ }
28
38
  if (preparation?.psp === 'fiserv') {
29
- if (replay.kind === 'merchant_hosted' || !isFiservCaptureHost(url) || fiservCapturePinId(url, body) !== preparation.merchantProfile) {
39
+ if (replay.kind !== undefined || !isFiservCaptureHost(url) || fiservCapturePinId(url, body) !== preparation.merchantProfile) {
30
40
  throw new Error('Refusing to substitute: the paused request is not a Fiserv capture under the key pin the cardholder approved.');
31
41
  }
32
42
  return substituteFiservEnvelope(body, replay.substitutions);
@@ -0,0 +1,96 @@
1
+ import { DATATRANS_CARD_ON_FILE_REFUSED, DATATRANS_INVALID_REQUEST } from './datatrans.generated.js';
2
+ import type { ReplayResponse } from './client.js';
3
+ export declare const DATATRANS_PSP = "datatrans";
4
+ /** The dark-launch capability the API serves the datatrans recognizer behind. */
5
+ export declare const DATATRANS_CAPABILITY = "datatrans_secure_fields_tokenize";
6
+ /**
7
+ * Why the SDK refuses a Datatrans card post before anything is claimed, created or prompted. The
8
+ * API answers the same two codes at create, so a refusal here only saves the round trip:
9
+ * - `invalid_datatrans_request`: not a sound Secure Fields TOKENIZE card post. That covers
10
+ * another mode, a payment or 3-D Secure post (`transactionId`, `3D`, `dccSelected`), a key
11
+ * field.js never sends, a URL variant (a query, a case or dot spelling) or a content type that
12
+ * is not jQuery's form type. The cardholder's device could never fill it.
13
+ * - `datatrans_card_on_file_refused`: a sound post that asks PCI Proxy to keep the card on file
14
+ * (usage=REGISTRATION: a reusable alias plus a network token, so the merchant could charge the
15
+ * card again later), while the card-on-file policy the API served refuses that
16
+ * (datatransCardOnFilePolicy). British Airways's Nexus checkout sends it for every new card.
17
+ */
18
+ export type DatatransRefusalReason = typeof DATATRANS_INVALID_REQUEST | typeof DATATRANS_CARD_ON_FILE_REFUSED;
19
+ /** True when the URL's host is one of Datatrans's two pay hosts. */
20
+ export declare function isDatatransHost(url: string): boolean;
21
+ /**
22
+ * The card-on-file policy this build applies to a Datatrans card post: the one the API served with
23
+ * the datatrans recognizer (Recognizer.cardOnFile, VaultClient.cardOnFilePolicy) when payment-core
24
+ * defines it (`refuse`, `disclose`, `agentcard_single_use`), and `refuse` for anything else, an entry
25
+ * that names none included. The decision is payment-core's DATATRANS_REGISTRATION_POLICY, which the
26
+ * API reads each time it serves the catalog and again at create, so a change to that one line
27
+ * reaches every build already published at its next syncRegistry(), with no SDK release.
28
+ */
29
+ export declare function datatransCardOnFilePolicy(served: unknown): string;
30
+ /**
31
+ * The pause decision for a POST the registry recognized on a Datatrans pay host, by payment-core's
32
+ * classifyDatatransRequest under the card-on-file policy the API served (datatransCardOnFilePolicy):
33
+ * 'card' the exact card URL with jQuery's form type and a sound TOKENIZE card body: it pauses. A
34
+ * usage=REGISTRATION post is one too once the served policy allows card on file, and the
35
+ * API and the cardholder's device then apply that policy;
36
+ * a DatatransRefusalReason: it is refused in the browser;
37
+ * null no card in the body (a wallet, bank-account or CVV-only post) or not the card path:
38
+ * the SDK's ordinary handling decides, and such a post continues untouched.
39
+ */
40
+ export declare function datatransPauseDecision(request: {
41
+ url: string;
42
+ headers: Record<string, string>;
43
+ body: string | null;
44
+ }, servedPolicy?: unknown): 'card' | DatatransRefusalReason | null;
45
+ /**
46
+ * How long an approved Datatrans answer is replayed to its own frame: the token's own lifetime
47
+ * (PCI Proxy keeps a tokenization readable for 30 minutes). Past it the stored transactionId names a
48
+ * token the merchant can no longer read, so a later post is a new question.
49
+ */
50
+ export declare const DATATRANS_REPEAT_WINDOW_MS: number;
51
+ /**
52
+ * How long a stored answer waits for its form's next post. British Airways re-submits while the agent
53
+ * finishes the card form, right after the approved answer. A form that posts nothing for this long has
54
+ * moved on: the order was placed or abandoned, which the SDK cannot tell apart without reconcile(). Its
55
+ * next post is a new approval. Each answered re-submit restarts the wait, and the token's lifetime
56
+ * still ends every answer.
57
+ */
58
+ export declare const DATATRANS_REPEAT_IDLE_MS: number;
59
+ /** A stored answer handed to a repeat, with the approval it came from. */
60
+ export interface DatatransRepeatAnswer {
61
+ authorizationId: string;
62
+ response: {
63
+ status: number;
64
+ headers: Record<string, string>;
65
+ body: string;
66
+ };
67
+ }
68
+ /**
69
+ * The approved Datatrans answers one attachment handed to a card post, per frame. `frame` is the
70
+ * adapter's identity for the request's frame (the CDP frameId, the Playwright Frame); a request
71
+ * whose frame is unknown is never remembered and never answered. Only an answer payment-core reads
72
+ * as approved ({transactionId, cardInfo}) is kept: an error answer leaves the next post a new
73
+ * question, so the page can try again.
74
+ */
75
+ export declare class DatatransRepeats {
76
+ private readonly windowMs;
77
+ private readonly now;
78
+ private readonly idleMs;
79
+ private remembered;
80
+ constructor(windowMs?: number, now?: () => number, idleMs?: number);
81
+ /**
82
+ * Keep the answer a token replay handed to this card post, when Datatrans approved it. Called
83
+ * after the answer reached the browser, so it never throws: a failure here only leaves the
84
+ * page's next post a new question.
85
+ */
86
+ remember(frame: unknown, url: string, body: string | null, replay: ReplayResponse): void;
87
+ /**
88
+ * The stored answer for a repeat of an answered card post from the same frame, or null. Only an
89
+ * answer from `authorizationId`, the approval the attachment is handing off now, is handed out,
90
+ * and every answer from another approval is dropped: a later authorization ends the window of
91
+ * every earlier one. An answer whose form stayed quiet past the idle bound is dropped too, and
92
+ * handing one out restarts that bound.
93
+ */
94
+ answerFor(frame: unknown, url: string, body: string | null, authorizationId: string | null): DatatransRepeatAnswer | null;
95
+ private prune;
96
+ }
@@ -0,0 +1,64 @@
1
+ declare var DATATRANS_HOSTS: readonly string[];
2
+ declare var DATATRANS_TOKENIZE_PATH: string;
3
+ declare var DATATRANS_REFUSAL_REASONS: readonly string[];
4
+ declare var DATATRANS_INVALID_REQUEST: string;
5
+ declare var DATATRANS_CARD_ON_FILE_REFUSED: string;
6
+ declare var DATATRANS_REGISTRATION_POLICIES: readonly string[];
7
+ declare var DATATRANS_REGISTRATION_POLICY: string;
8
+ declare var DATATRANS_REVIEWED_MERCHANT_IDS: Readonly<{
9
+ "pay.datatrans.com": readonly never[];
10
+ "pay.sandbox.datatrans.com": readonly string[];
11
+ }>;
12
+ declare var DATATRANS_PAYMENT_METHODS: readonly string[];
13
+ declare var DATATRANS_TEST_PANS: readonly string[];
14
+ declare function encodeDatatransFormValue(value: any): string;
15
+ declare function parseDatatransForm(body: any): {
16
+ key: string;
17
+ value: string;
18
+ rawKey: string;
19
+ raw: string;
20
+ }[] | null;
21
+ declare function serializeDatatransForm(pairs: any): any;
22
+ declare function isDatatransTokenizeUrl(url: any, method?: string): boolean;
23
+ declare function datatransRegistrationRefusal(usage: any, card: any, policy?: string): "card_on_file" | null;
24
+ declare function datatransCardOnFileDisclosure(body: any, policy?: string): boolean;
25
+ declare function datatransBodyRefusal(body: any, card: any, policy?: string): "unsupported" | "card_on_file" | null;
26
+ declare function datatransMerchantId(body: any): string | null | undefined;
27
+ declare function classifyDatatransRequest(url: any, method: any, body: any, headers: any, policy?: string): "card" | "unsupported" | "card_on_file" | null;
28
+ declare function datatransTokenizeRefusal(request: any, card: any, policy?: string): "unsupported" | "card_on_file" | null;
29
+ declare function isDatatransTokenizeRequest(request: any, card: any): boolean;
30
+ declare function datatransMerchantRefusal(url: any, body: any, merchants?: Readonly<{
31
+ "pay.datatrans.com": readonly never[];
32
+ "pay.sandbox.datatrans.com": readonly string[];
33
+ }>): "unregistered_merchant" | null;
34
+ declare function datatransDeviceRefusal(request: any, card: any, policy?: string, merchants?: Readonly<{
35
+ "pay.datatrans.com": readonly never[];
36
+ "pay.sandbox.datatrans.com": readonly string[];
37
+ }>): "unsupported" | "card_on_file" | "brand_mismatch" | "unregistered_merchant" | null;
38
+ declare function datatransRefusalMessage(reason: any): string;
39
+ declare function datatransCardBrand(number: any): "VIS" | "ECA" | "AMX" | null;
40
+ declare function datatransBrandRefusal(body: any, card: any): "unsupported" | "brand_mismatch" | null;
41
+ declare function injectDatatransCard(body: any, card: any, policy?: string): {
42
+ body: string;
43
+ where: string[];
44
+ };
45
+ declare function datatransErrorCode(message: any): string | RegExp;
46
+ declare function datatransResponseOutcome(status: any, body: any, card: any): {
47
+ outcome: string;
48
+ reason: string;
49
+ code?: undefined;
50
+ } | {
51
+ outcome: string;
52
+ code: string | RegExp;
53
+ reason?: undefined;
54
+ } | {
55
+ outcome: string;
56
+ reason?: undefined;
57
+ code?: undefined;
58
+ };
59
+ declare function sanitizeDatatransTokenResponse(body: any): string;
60
+ declare function datatransFacts(status: any, body: any): {
61
+ outcome: string;
62
+ refusalCode: string | RegExp | null | undefined;
63
+ };
64
+ export { DATATRANS_CARD_ON_FILE_REFUSED, DATATRANS_HOSTS, DATATRANS_INVALID_REQUEST, DATATRANS_PAYMENT_METHODS, DATATRANS_REFUSAL_REASONS, DATATRANS_REGISTRATION_POLICIES, DATATRANS_REGISTRATION_POLICY, DATATRANS_REVIEWED_MERCHANT_IDS, DATATRANS_TEST_PANS, DATATRANS_TOKENIZE_PATH, classifyDatatransRequest, datatransBodyRefusal, datatransBrandRefusal, datatransCardBrand, datatransCardOnFileDisclosure, datatransDeviceRefusal, datatransErrorCode, datatransFacts, datatransMerchantId, datatransMerchantRefusal, datatransRefusalMessage, datatransRegistrationRefusal, datatransResponseOutcome, datatransTokenizeRefusal, encodeDatatransFormValue, injectDatatransCard, isDatatransTokenizeRequest, isDatatransTokenizeUrl, parseDatatransForm, sanitizeDatatransTokenResponse, serializeDatatransForm };
@@ -0,0 +1,426 @@
1
+ // @ts-nocheck
2
+ // Generated from @agent-cards/payment-core. Do not edit.
3
+ // artifact-sha256: 0179250ba6f44c33967a08dc4ce3a891d8b73f0f7eb60e9cab34f801dc2a4c57
4
+ // src/datatrans.js
5
+ var DATATRANS_HOSTS = Object.freeze(["pay.datatrans.com", "pay.sandbox.datatrans.com"]);
6
+ var DATATRANS_TOKENIZE_PATH = "/upp/payment/SecureFields/paymentField";
7
+ var DATATRANS_REFUSAL_REASONS = Object.freeze(["unsupported", "card_on_file", "brand_mismatch", "unregistered_merchant"]);
8
+ var DATATRANS_INVALID_REQUEST = "invalid_datatrans_request";
9
+ var DATATRANS_CARD_ON_FILE_REFUSED = "datatrans_card_on_file_refused";
10
+ var DATATRANS_REGISTRATION_POLICIES = Object.freeze(["refuse", "disclose", "agentcard_single_use"]);
11
+ var DATATRANS_REGISTRATION_POLICY = "refuse";
12
+ var DATATRANS_REVIEWED_MERCHANT_IDS = Object.freeze({
13
+ "pay.datatrans.com": Object.freeze([]),
14
+ "pay.sandbox.datatrans.com": Object.freeze(["1110022968", "1100007006"])
15
+ });
16
+ var DATATRANS_PAYMENT_METHODS = Object.freeze(["VIS", "ECA", "AMX", "DIN", "DIS", "JCB", "CUP", "MAU", "UAP", "DNK"]);
17
+ var DATATRANS_TEST_PANS = Object.freeze([
18
+ "4242424242424242",
19
+ "4900000000000086",
20
+ "4900000000000003",
21
+ "5404000000000001",
22
+ "5200000000000007",
23
+ "5200000000000080",
24
+ "375811111111115",
25
+ "375000000000007",
26
+ "375811111111123",
27
+ "36168002586009",
28
+ "36167719110012",
29
+ "6011000000000004",
30
+ "3569990010030442",
31
+ "3569990010030400",
32
+ "6362970000457013",
33
+ "3841000000000007",
34
+ "6200104444444447",
35
+ "6210946888060008",
36
+ "6759000000000018",
37
+ "6759000000000026",
38
+ "4000001000000034",
39
+ "4000001000000018",
40
+ "4000001000000042",
41
+ "4000001000000026",
42
+ "5100001000000022",
43
+ "5100001000000048",
44
+ "5100001000000014",
45
+ "5100001000000030",
46
+ "340000100000024",
47
+ "340000100000040",
48
+ "340000100000016",
49
+ "340000100000032",
50
+ "36000010000022",
51
+ "3550998599961037"
52
+ ]);
53
+ var UNSUPPORTED_REFUSAL = "Refusing to pay: unsupported Datatrans card request.";
54
+ var CARD_ON_FILE_REFUSAL = "Refusing to pay: this Datatrans payment would let the merchant keep your card on file, which needs your consent first. Nothing was sent.";
55
+ var BRAND_MISMATCH_REFUSAL = "Refusing to pay: this Datatrans payment names a card brand that is not your card's. Nothing was sent.";
56
+ var UNREGISTERED_MERCHANT_REFUSAL = "Refusing to pay: this Datatrans payment goes to a merchant account Agentcard has not reviewed. Nothing was sent.";
57
+ var FILL_REFUSAL = "Could not fill the card fields for Datatrans. Nothing was sent.";
58
+ var BODY_LIMIT = 8192;
59
+ var RESPONSE_LIMIT = 4096;
60
+ var record = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
61
+ var owns = (value, key) => Object.prototype.hasOwnProperty.call(value, key);
62
+ var exact = (pattern, value) => typeof value === "string" && pattern.exec(value)?.[0] === value;
63
+ var text = (value, max) => typeof value === "string" && value.length <= max && !/[\u0000-\u001f\u007f]/.test(value);
64
+ var digits = (value, min, max) => typeof value === "string" && value.length >= min && value.length <= max && !/[^0-9]/.test(value);
65
+ var panLike = (value) => (value.match(/\p{Nd}/gu) ?? []).length >= 12;
66
+ var plain = (value, max) => text(value, max) && !panLike(value);
67
+ function encodeDatatransFormValue(value) {
68
+ return encodeURIComponent(String(value)).replace(/%20/g, "+");
69
+ }
70
+ function decodeFormComponent(raw) {
71
+ try {
72
+ return decodeURIComponent(raw.replace(/\+/g, " "));
73
+ }
74
+ catch {
75
+ return null;
76
+ }
77
+ }
78
+ function parseDatatransForm(body) {
79
+ if (typeof body !== "string" || body === "" || body.length > BODY_LIMIT)
80
+ return null;
81
+ const pairs = [];
82
+ const seen = /* @__PURE__ */ new Set();
83
+ for (const chunk of body.split("&")) {
84
+ const eq = chunk.indexOf("=");
85
+ if (eq < 1)
86
+ return null;
87
+ const rawKey = chunk.slice(0, eq), raw = chunk.slice(eq + 1);
88
+ const key = decodeFormComponent(rawKey), value = decodeFormComponent(raw);
89
+ if (key === null || value === null || seen.has(key))
90
+ return null;
91
+ try {
92
+ if (encodeDatatransFormValue(key) !== rawKey || encodeDatatransFormValue(value) !== raw)
93
+ return null;
94
+ }
95
+ catch {
96
+ return null;
97
+ }
98
+ seen.add(key);
99
+ pairs.push({ key, value, rawKey, raw });
100
+ }
101
+ return pairs;
102
+ }
103
+ function serializeDatatransForm(pairs) {
104
+ return pairs.map(({ key, value }) => `${encodeDatatransFormValue(key)}=${encodeDatatransFormValue(value)}`).join("&");
105
+ }
106
+ function isDatatransTokenizeUrl(url, method = "POST") {
107
+ if (method !== "POST" || typeof url !== "string" || url.length > 256 || url.includes("?") || url.includes("#"))
108
+ return false;
109
+ let parsed;
110
+ try {
111
+ parsed = new URL(url);
112
+ }
113
+ catch {
114
+ return false;
115
+ }
116
+ return parsed.href === url && parsed.protocol === "https:" && !parsed.port && !parsed.username && !parsed.password && DATATRANS_HOSTS.includes(parsed.hostname) && parsed.pathname === DATATRANS_TOKENIZE_PATH;
117
+ }
118
+ function onPaymentFieldPath(url) {
119
+ if (typeof url !== "string" || url.length > 2048)
120
+ return false;
121
+ try {
122
+ const parsed = new URL(url);
123
+ return DATATRANS_HOSTS.includes(parsed.hostname.replace(/\.$/, "")) && parsed.pathname.toLowerCase() === DATATRANS_TOKENIZE_PATH.toLowerCase();
124
+ }
125
+ catch {
126
+ return false;
127
+ }
128
+ }
129
+ var KEYS = /* @__PURE__ */ new Set([
130
+ "mode",
131
+ "formId",
132
+ "cardNumber",
133
+ "cvv",
134
+ "iban",
135
+ "accountNumber",
136
+ "branchCode",
137
+ "paymentMethod",
138
+ "countryCode",
139
+ "merchantId",
140
+ "expy",
141
+ "expm",
142
+ "email",
143
+ "phoneNumber",
144
+ "usage",
145
+ "3D",
146
+ "browserUserAgent",
147
+ "browserJavaEnabled",
148
+ "browserLanguage",
149
+ "browserColorDepth",
150
+ "browserScreenHeight",
151
+ "browserScreenWidth",
152
+ "browserTZ"
153
+ ]);
154
+ var REQUIRED = ["mode", "formId", "cardNumber", "paymentMethod", "merchantId", "expy", "expm"];
155
+ var EMPTY_ONLY = /* @__PURE__ */ new Set(["iban", "accountNumber", "branchCode", "countryCode", "3D"]);
156
+ var RULES = {
157
+ mode: (value) => value === "TOKENIZE",
158
+ formId: (value) => exact(/^[0-9]{18}$/, value),
159
+ cardNumber: (value) => digits(value, 12, 19),
160
+ cvv: (value) => value === "" || digits(value, 3, 4),
161
+ paymentMethod: (value) => DATATRANS_PAYMENT_METHODS.includes(value),
162
+ merchantId: (value) => exact(/^[0-9]{10}$/, value),
163
+ expm: (value) => exact(/^(?:0?[1-9]|1[0-2])$/, value),
164
+ expy: (value) => exact(/^(?:[0-9]{2}|20[0-9]{2})$/, value),
165
+ usage: (value) => value === "" || value === "SIMPLE" || value === "REGISTRATION",
166
+ email: (value) => value === "" || text(value, 254) && exact(/^[^\s@]{1,64}@[^\s@]{1,189}$/, value),
167
+ phoneNumber: (value) => value === "" || exact(/^\+?[0-9 ()./-]{1,32}$/, value),
168
+ browserUserAgent: (value) => value !== "" && text(value, 1024),
169
+ browserJavaEnabled: (value) => value === "true" || value === "false",
170
+ browserLanguage: (value) => value === "" || exact(/^[A-Za-z]{1,8}(?:-[A-Za-z0-9]{1,8}){0,4}$/, value),
171
+ browserColorDepth: (value) => exact(/^[0-9]{1,3}$/, value),
172
+ browserScreenHeight: (value) => exact(/^[0-9]{1,5}$/, value),
173
+ browserScreenWidth: (value) => exact(/^[0-9]{1,5}$/, value),
174
+ browserTZ: (value) => exact(/^-?[0-9]{1,4}$/, value)
175
+ };
176
+ function judgeBody(body) {
177
+ const pairs = parseDatatransForm(body);
178
+ if (!pairs)
179
+ return null;
180
+ const fields = new Map(pairs.map(({ key, value }) => [key, value]));
181
+ for (const [key, value] of fields) {
182
+ if (!KEYS.has(key))
183
+ return null;
184
+ if (EMPTY_ONLY.has(key) ? value !== "" : !RULES[key](value))
185
+ return null;
186
+ }
187
+ if (!REQUIRED.every((key) => fields.has(key)))
188
+ return null;
189
+ return { pairs, fields, usage: fields.get("usage") ?? "" };
190
+ }
191
+ function datatransRegistrationRefusal(usage, card, policy = DATATRANS_REGISTRATION_POLICY) {
192
+ if (usage !== "REGISTRATION")
193
+ return null;
194
+ if (policy !== "disclose" && policy !== "agentcard_single_use")
195
+ return "card_on_file";
196
+ if (card === void 0)
197
+ return null;
198
+ if (!record(card) || card.cardOnFileConsent !== true)
199
+ return "card_on_file";
200
+ return policy === "agentcard_single_use" && card.agentcardSingleUse !== true ? "card_on_file" : null;
201
+ }
202
+ function datatransCardOnFileDisclosure(body, policy = DATATRANS_REGISTRATION_POLICY) {
203
+ const judged = judgeBody(body);
204
+ return judged !== null && judged.usage === "REGISTRATION" && (policy === "disclose" || policy === "agentcard_single_use");
205
+ }
206
+ function datatransBodyRefusal(body, card, policy = DATATRANS_REGISTRATION_POLICY) {
207
+ const judged = judgeBody(body);
208
+ if (!judged)
209
+ return "unsupported";
210
+ return datatransRegistrationRefusal(judged.usage, card, policy);
211
+ }
212
+ function datatransMerchantId(body) {
213
+ const judged = judgeBody(body);
214
+ return judged ? judged.fields.get("merchantId") : null;
215
+ }
216
+ function carriesCard(body) {
217
+ if (typeof body !== "string" || body === "")
218
+ return false;
219
+ for (const chunk of body.split("&")) {
220
+ const eq = chunk.indexOf("=");
221
+ if (eq <= 0 || eq === chunk.length - 1)
222
+ continue;
223
+ if (decodeFormComponent(chunk.slice(0, eq)) === "cardNumber")
224
+ return true;
225
+ }
226
+ return false;
227
+ }
228
+ var CONTENT_TYPE = /^application\/x-www-form-urlencoded(?:;\s*charset=utf-8)?$/i;
229
+ function headersRefusal(headers) {
230
+ if (!record(headers))
231
+ return "unsupported";
232
+ const folded = /* @__PURE__ */ new Map();
233
+ for (const [name, value] of Object.entries(headers)) {
234
+ const lower = name.toLowerCase();
235
+ if (folded.has(lower) || typeof value !== "string")
236
+ return "unsupported";
237
+ folded.set(lower, value);
238
+ }
239
+ return exact(CONTENT_TYPE, folded.get("content-type")) ? null : "unsupported";
240
+ }
241
+ function classifyDatatransRequest(url, method, body, headers, policy = DATATRANS_REGISTRATION_POLICY) {
242
+ if (method !== "POST" || !onPaymentFieldPath(url) || !carriesCard(body))
243
+ return null;
244
+ if (!isDatatransTokenizeUrl(url))
245
+ return "unsupported";
246
+ return headersRefusal(headers) ?? datatransBodyRefusal(body, void 0, policy) ?? "card";
247
+ }
248
+ function datatransTokenizeRefusal(request, card, policy = DATATRANS_REGISTRATION_POLICY) {
249
+ if (!record(request) || request.method !== "POST" || !isDatatransTokenizeUrl(request.url))
250
+ return "unsupported";
251
+ return headersRefusal(request.headers) ?? datatransBodyRefusal(request.body, card, policy);
252
+ }
253
+ function isDatatransTokenizeRequest(request, card) {
254
+ return datatransTokenizeRefusal(request, card) === null;
255
+ }
256
+ function datatransMerchantRefusal(url, body, merchants = DATATRANS_REVIEWED_MERCHANT_IDS) {
257
+ const merchantId = datatransMerchantId(body);
258
+ let host = "";
259
+ try {
260
+ host = new URL(url).hostname;
261
+ }
262
+ catch {
263
+ return "unregistered_merchant";
264
+ }
265
+ const listed = record(merchants) && owns(merchants, host) && Array.isArray(merchants[host]) ? merchants[host] : [];
266
+ return merchantId !== null && listed.includes(merchantId) ? null : "unregistered_merchant";
267
+ }
268
+ function datatransDeviceRefusal(request, card, policy = DATATRANS_REGISTRATION_POLICY, merchants = DATATRANS_REVIEWED_MERCHANT_IDS) {
269
+ const refusal = datatransTokenizeRefusal(request, card, policy);
270
+ if (refusal)
271
+ return refusal;
272
+ if (record(card) && digits(card.number, 12, 19) && datatransBrandRefusal(request.body, card) !== null)
273
+ return "brand_mismatch";
274
+ return datatransMerchantRefusal(request.url, request.body, merchants);
275
+ }
276
+ function datatransRefusalMessage(reason) {
277
+ if (reason === "card_on_file")
278
+ return CARD_ON_FILE_REFUSAL;
279
+ if (reason === "brand_mismatch")
280
+ return BRAND_MISMATCH_REFUSAL;
281
+ if (reason === "unregistered_merchant")
282
+ return UNREGISTERED_MERCHANT_REFUSAL;
283
+ return UNSUPPORTED_REFUSAL;
284
+ }
285
+ function datatransCardBrand(number) {
286
+ if (!digits(number, 12, 19))
287
+ return null;
288
+ const two = Number(number.slice(0, 2));
289
+ const four = Number(number.slice(0, 4));
290
+ if (number[0] === "4")
291
+ return "VIS";
292
+ if (two >= 51 && two <= 55 || four >= 2221 && four <= 2720)
293
+ return "ECA";
294
+ if (two === 34 || two === 37)
295
+ return "AMX";
296
+ return null;
297
+ }
298
+ function datatransBrandRefusal(body, card) {
299
+ const judged = judgeBody(body);
300
+ if (!judged)
301
+ return "unsupported";
302
+ const brand = datatransCardBrand(card && card.number);
303
+ return brand !== null && brand === judged.fields.get("paymentMethod") ? null : "brand_mismatch";
304
+ }
305
+ function injectDatatransCard(body, card, policy = DATATRANS_REGISTRATION_POLICY) {
306
+ const judged = judgeBody(body);
307
+ if (!judged)
308
+ throw new Error(UNSUPPORTED_REFUSAL);
309
+ if (datatransRegistrationRefusal(judged.usage, card ?? null, policy) !== null)
310
+ throw new Error(CARD_ON_FILE_REFUSAL);
311
+ const month = card && (Number.isInteger(card.month) || digits(card.month, 1, 2)) ? Number(card.month) : NaN;
312
+ const year = card && (Number.isInteger(card.year) || digits(card.year, 2, 4)) ? Number(card.year) : NaN;
313
+ if (!card || !digits(card.number, 12, 19) || !(month >= 1 && month <= 12) || !(year >= 0 && year <= 99 || year >= 2e3 && year <= 2099)) {
314
+ throw new Error(FILL_REFUSAL);
315
+ }
316
+ if (datatransBrandRefusal(body, card) !== null)
317
+ throw new Error(BRAND_MISMATCH_REFUSAL);
318
+ const fields = judged.fields;
319
+ if (!fields.get("cvv") || !digits(card.cvc, 3, 4))
320
+ throw new Error(FILL_REFUSAL);
321
+ const yy = String(year % 100).padStart(2, "0");
322
+ const values = {
323
+ cardNumber: card.number,
324
+ cvv: card.cvc,
325
+ expm: fields.get("expm").length === 2 ? String(month).padStart(2, "0") : String(month),
326
+ expy: fields.get("expy").length === 4 ? `20${yy}` : yy
327
+ };
328
+ const written = judged.pairs.map((pair) => owns(values, pair.key) ? `${pair.rawKey}=${encodeDatatransFormValue(values[pair.key])}` : `${pair.rawKey}=${pair.raw}`);
329
+ return { body: written.join("&"), where: ["cardNumber", "cvv", "expm", "expy"] };
330
+ }
331
+ var TRANSACTION_ID = /^[0-9]{18}$/;
332
+ var CARD_WORD = /^[A-Za-z][A-Za-z _-]{0,31}$/;
333
+ var COUNTRY = /^[A-Z]{2}$/;
334
+ var CARD_INFO_KEYS = ["brand", "type", "usage", "country", "issuer", "coBrand"];
335
+ var UNFOLLOWED_KEYS = ["tds2EnrollmentResult", "redirect", "dcc", "mcp"];
336
+ var ERROR_CODES = [
337
+ [/^Invalid merchantId$/, "invalid_merchant_id"],
338
+ [/^Invalid merchant setup\b/, "invalid_merchant_setup"],
339
+ [/^Invalid field$/, "invalid_field"],
340
+ [/^Field .{1,64} not allowed for (?:alias version|mode) /, "field_not_allowed"],
341
+ [/^Invalid session$/, "invalid_session"],
342
+ [/^Too many requests$/, "too_many_requests"]
343
+ ];
344
+ function readAnswer(body) {
345
+ if (typeof body !== "string" || body === "" || body.length > RESPONSE_LIMIT)
346
+ return null;
347
+ try {
348
+ const value = JSON.parse(body);
349
+ return record(value) ? value : null;
350
+ }
351
+ catch {
352
+ return null;
353
+ }
354
+ }
355
+ var optionalText = (value, pattern) => value === null || exact(pattern, value);
356
+ function rebuildCardInfo(info) {
357
+ if (!record(info) || !Object.keys(info).every((key) => CARD_INFO_KEYS.includes(key)) || !exact(CARD_WORD, info.brand))
358
+ return null;
359
+ if (owns(info, "type") && !optionalText(info.type, CARD_WORD) || owns(info, "usage") && !optionalText(info.usage, CARD_WORD) || owns(info, "country") && !optionalText(info.country, COUNTRY) || owns(info, "issuer") && info.issuer !== null && !plain(info.issuer, 128) || owns(info, "coBrand") && info.coBrand !== null && !plain(info.coBrand, 64))
360
+ return null;
361
+ const safe = {};
362
+ for (const key of CARD_INFO_KEYS)
363
+ if (owns(info, key))
364
+ safe[key] = info[key];
365
+ return safe;
366
+ }
367
+ function rebuildSuccess(value) {
368
+ if (Object.keys(value).length !== 2 || !exact(TRANSACTION_ID, value.transactionId))
369
+ return null;
370
+ const cardInfo = rebuildCardInfo(value.cardInfo);
371
+ return cardInfo ? { transactionId: value.transactionId, cardInfo } : null;
372
+ }
373
+ function rebuildError(value) {
374
+ return Object.keys(value).length === 1 && plain(value.error, 256) && value.error.trim() !== "" ? { error: value.error } : null;
375
+ }
376
+ function datatransErrorCode(message) {
377
+ for (const [pattern, code] of ERROR_CODES)
378
+ if (typeof message === "string" && pattern.test(message))
379
+ return code;
380
+ return "datatrans_error";
381
+ }
382
+ function datatransResponseOutcome(status, body, card) {
383
+ if (status === 408 || status === 429)
384
+ return { outcome: "unknown", reason: "datatrans_status_unconfirmed" };
385
+ if (status >= 400 && status < 500)
386
+ return { outcome: "refused", code: `http_${status}` };
387
+ if (status !== 200)
388
+ return { outcome: "unknown", reason: "datatrans_status_unconfirmed" };
389
+ const value = readAnswer(body);
390
+ if (!value)
391
+ return { outcome: "unknown", reason: "datatrans_unreadable" };
392
+ if (owns(value, "error")) {
393
+ const error = rebuildError(value);
394
+ if (!error)
395
+ return { outcome: "unknown", reason: "datatrans_unexpected_response" };
396
+ const code = datatransErrorCode(error.error);
397
+ return code === "too_many_requests" ? { outcome: "unknown", reason: "datatrans_repeat_request" } : { outcome: "refused", code };
398
+ }
399
+ if (UNFOLLOWED_KEYS.some((key) => owns(value, key)))
400
+ return { outcome: "unknown", reason: "datatrans_unsupported_flow" };
401
+ const safe = rebuildSuccess(value);
402
+ if (!safe)
403
+ return { outcome: "unknown", reason: "datatrans_unexpected_response" };
404
+ if (card && digits(card.number, 12, 19) && safe.transactionId.includes(card.number)) {
405
+ return { outcome: "unknown", reason: "datatrans_token_mismatch" };
406
+ }
407
+ return { outcome: "approved" };
408
+ }
409
+ function sanitizeDatatransTokenResponse(body) {
410
+ const value = readAnswer(body);
411
+ const fail = () => {
412
+ throw new Error("Invalid Datatrans tokenization response.");
413
+ };
414
+ if (!value)
415
+ return fail();
416
+ if (owns(value, "error"))
417
+ return JSON.stringify(rebuildError(value) ?? fail());
418
+ if (UNFOLLOWED_KEYS.some((key) => owns(value, key)))
419
+ return fail();
420
+ return JSON.stringify(rebuildSuccess(value) ?? fail());
421
+ }
422
+ function datatransFacts(status, body) {
423
+ const verdict = datatransResponseOutcome(status, body);
424
+ return { outcome: verdict.outcome, refusalCode: verdict.outcome === "refused" ? verdict.code : null };
425
+ }
426
+ export { DATATRANS_CARD_ON_FILE_REFUSED, DATATRANS_HOSTS, DATATRANS_INVALID_REQUEST, DATATRANS_PAYMENT_METHODS, DATATRANS_REFUSAL_REASONS, DATATRANS_REGISTRATION_POLICIES, DATATRANS_REGISTRATION_POLICY, DATATRANS_REVIEWED_MERCHANT_IDS, DATATRANS_TEST_PANS, DATATRANS_TOKENIZE_PATH, classifyDatatransRequest, datatransBodyRefusal, datatransBrandRefusal, datatransCardBrand, datatransCardOnFileDisclosure, datatransDeviceRefusal, datatransErrorCode, datatransFacts, datatransMerchantId, datatransMerchantRefusal, datatransRefusalMessage, datatransRegistrationRefusal, datatransResponseOutcome, datatransTokenizeRefusal, encodeDatatransFormValue, injectDatatransCard, isDatatransTokenizeRequest, isDatatransTokenizeUrl, parseDatatransForm, sanitizeDatatransTokenResponse, serializeDatatransForm };