@agent-cards/sdk 0.23.22 → 0.23.24
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 +34 -0
- package/PREFLIGHT.md +2 -0
- package/README.md +34 -4
- package/dist/aci.generated.js +3 -3
- package/dist/agent-amount.generated.d.ts +7 -0
- package/dist/agent-amount.generated.js +3 -0
- package/dist/amadeus.d.ts +5 -2
- package/dist/amadeus.js +18 -3
- package/dist/amazon-payment-services.d.ts +25 -0
- package/dist/amazon-payment-services.generated.d.ts +76 -0
- package/dist/amazon-payment-services.generated.js +883 -0
- package/dist/amazon-payment-services.js +53 -0
- package/dist/capabilities.generated.d.ts +2 -0
- package/dist/capabilities.generated.js +3 -0
- package/dist/card-fields.generated.js +1127 -25
- package/dist/cdp.js +116 -15
- package/dist/client.d.ts +107 -8
- package/dist/client.js +277 -128
- package/dist/hosted-entry.js +2 -1
- package/dist/hosted-form.d.ts +5 -0
- package/dist/hosted-form.js +5 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/openpay.generated.d.ts +67 -0
- package/dist/openpay.generated.js +446 -0
- package/dist/paddle-checkout-watch.d.ts +70 -0
- package/dist/paddle-checkout-watch.js +239 -0
- package/dist/paddle-tokenex.generated.d.ts +29 -1
- package/dist/paddle-tokenex.generated.js +175 -26
- package/dist/paytrail.d.ts +58 -0
- package/dist/paytrail.js +98 -0
- package/dist/pre-claim.d.ts +4 -0
- package/dist/pre-claim.js +46 -2
- package/dist/prepared-processor.d.ts +12 -4
- package/dist/prepared-processor.js +31 -5
- package/dist/primer.generated.js +1155 -53
- package/dist/registry.d.ts +4 -3
- package/dist/registry.js +12 -4
- package/dist/sbps.d.ts +57 -0
- package/dist/sbps.generated.d.ts +96 -0
- package/dist/sbps.generated.js +427 -0
- package/dist/sbps.js +90 -0
- package/dist/stripe-checkout.generated.js +458 -8
- package/dist/tap-payments.d.ts +43 -0
- package/dist/tap-payments.generated.d.ts +59 -0
- package/dist/tap-payments.generated.js +990 -0
- package/dist/tap-payments.js +72 -0
- package/dist/transbank.d.ts +33 -0
- package/dist/transbank.js +62 -0
- package/dist/url-mask-parts.d.ts +24 -0
- package/dist/url-mask-parts.js +22 -0
- package/dist/url-masks.generated.d.ts +3 -0
- package/dist/url-masks.generated.js +2 -0
- package/package.json +4 -4
|
@@ -0,0 +1,883 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
// Generated from @agent-cards/payment-core. Do not edit.
|
|
3
|
+
// artifact-sha256: f04bf9ac19a1a9d8aed3819383514a6cc3cc1a781354142ec41879320417eb9e
|
|
4
|
+
// src/deep-freeze.js
|
|
5
|
+
function deepFreeze(value) {
|
|
6
|
+
if (value && typeof value === "object" && !Object.isFrozen(value)) {
|
|
7
|
+
for (const child of Object.values(value))
|
|
8
|
+
deepFreeze(child);
|
|
9
|
+
Object.freeze(value);
|
|
10
|
+
}
|
|
11
|
+
return value;
|
|
12
|
+
}
|
|
13
|
+
// src/sha256.js
|
|
14
|
+
var K = /* @__PURE__ */ new Uint32Array([
|
|
15
|
+
1116352408,
|
|
16
|
+
1899447441,
|
|
17
|
+
3049323471,
|
|
18
|
+
3921009573,
|
|
19
|
+
961987163,
|
|
20
|
+
1508970993,
|
|
21
|
+
2453635748,
|
|
22
|
+
2870763221,
|
|
23
|
+
3624381080,
|
|
24
|
+
310598401,
|
|
25
|
+
607225278,
|
|
26
|
+
1426881987,
|
|
27
|
+
1925078388,
|
|
28
|
+
2162078206,
|
|
29
|
+
2614888103,
|
|
30
|
+
3248222580,
|
|
31
|
+
3835390401,
|
|
32
|
+
4022224774,
|
|
33
|
+
264347078,
|
|
34
|
+
604807628,
|
|
35
|
+
770255983,
|
|
36
|
+
1249150122,
|
|
37
|
+
1555081692,
|
|
38
|
+
1996064986,
|
|
39
|
+
2554220882,
|
|
40
|
+
2821834349,
|
|
41
|
+
2952996808,
|
|
42
|
+
3210313671,
|
|
43
|
+
3336571891,
|
|
44
|
+
3584528711,
|
|
45
|
+
113926993,
|
|
46
|
+
338241895,
|
|
47
|
+
666307205,
|
|
48
|
+
773529912,
|
|
49
|
+
1294757372,
|
|
50
|
+
1396182291,
|
|
51
|
+
1695183700,
|
|
52
|
+
1986661051,
|
|
53
|
+
2177026350,
|
|
54
|
+
2456956037,
|
|
55
|
+
2730485921,
|
|
56
|
+
2820302411,
|
|
57
|
+
3259730800,
|
|
58
|
+
3345764771,
|
|
59
|
+
3516065817,
|
|
60
|
+
3600352804,
|
|
61
|
+
4094571909,
|
|
62
|
+
275423344,
|
|
63
|
+
430227734,
|
|
64
|
+
506948616,
|
|
65
|
+
659060556,
|
|
66
|
+
883997877,
|
|
67
|
+
958139571,
|
|
68
|
+
1322822218,
|
|
69
|
+
1537002063,
|
|
70
|
+
1747873779,
|
|
71
|
+
1955562222,
|
|
72
|
+
2024104815,
|
|
73
|
+
2227730452,
|
|
74
|
+
2361852424,
|
|
75
|
+
2428436474,
|
|
76
|
+
2756734187,
|
|
77
|
+
3204031479,
|
|
78
|
+
3329325298
|
|
79
|
+
]);
|
|
80
|
+
var rotr = (x, n) => x >>> n | x << 32 - n;
|
|
81
|
+
function utf8(text) {
|
|
82
|
+
const out = [];
|
|
83
|
+
for (const char of text) {
|
|
84
|
+
let cp = char.codePointAt(0);
|
|
85
|
+
if (cp >= 55296 && cp <= 57343)
|
|
86
|
+
cp = 65533;
|
|
87
|
+
if (cp < 128)
|
|
88
|
+
out.push(cp);
|
|
89
|
+
else if (cp < 2048)
|
|
90
|
+
out.push(192 | cp >> 6, 128 | cp & 63);
|
|
91
|
+
else if (cp < 65536)
|
|
92
|
+
out.push(224 | cp >> 12, 128 | cp >> 6 & 63, 128 | cp & 63);
|
|
93
|
+
else
|
|
94
|
+
out.push(240 | cp >> 18, 128 | cp >> 12 & 63, 128 | cp >> 6 & 63, 128 | cp & 63);
|
|
95
|
+
}
|
|
96
|
+
return Uint8Array.from(out);
|
|
97
|
+
}
|
|
98
|
+
function sha256Hex(input) {
|
|
99
|
+
const isBytes = Object.prototype.toString.call(input) === "[object Uint8Array]";
|
|
100
|
+
if (typeof input !== "string" && !isBytes)
|
|
101
|
+
throw new TypeError("sha256Hex takes a string or a Uint8Array.");
|
|
102
|
+
const bytes = typeof input === "string" ? utf8(input) : input;
|
|
103
|
+
const length = bytes.length;
|
|
104
|
+
const padded = new Uint8Array((length + 8 >> 6) + 1 << 6);
|
|
105
|
+
padded.set(bytes);
|
|
106
|
+
padded[length] = 128;
|
|
107
|
+
const view = new DataView(padded.buffer);
|
|
108
|
+
view.setUint32(padded.length - 8, Math.floor(length / 536870912));
|
|
109
|
+
view.setUint32(padded.length - 4, length << 3 >>> 0);
|
|
110
|
+
const H = new Uint32Array([1779033703, 3144134277, 1013904242, 2773480762, 1359893119, 2600822924, 528734635, 1541459225]);
|
|
111
|
+
const W = new Uint32Array(64);
|
|
112
|
+
for (let offset = 0; offset < padded.length; offset += 64) {
|
|
113
|
+
for (let t = 0; t < 16; t++)
|
|
114
|
+
W[t] = view.getUint32(offset + t * 4);
|
|
115
|
+
for (let t = 16; t < 64; t++) {
|
|
116
|
+
const s0 = rotr(W[t - 15], 7) ^ rotr(W[t - 15], 18) ^ W[t - 15] >>> 3;
|
|
117
|
+
const s1 = rotr(W[t - 2], 17) ^ rotr(W[t - 2], 19) ^ W[t - 2] >>> 10;
|
|
118
|
+
W[t] = W[t - 16] + s0 + W[t - 7] + s1 >>> 0;
|
|
119
|
+
}
|
|
120
|
+
let a = H[0], b = H[1], c = H[2], d = H[3], e = H[4], f = H[5], g = H[6], h = H[7];
|
|
121
|
+
for (let t = 0; t < 64; t++) {
|
|
122
|
+
const t1 = h + (rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25)) + (e & f ^ ~e & g) + K[t] + W[t] >>> 0;
|
|
123
|
+
const t2 = (rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22)) + (a & b ^ a & c ^ b & c) >>> 0;
|
|
124
|
+
h = g;
|
|
125
|
+
g = f;
|
|
126
|
+
f = e;
|
|
127
|
+
e = d + t1 >>> 0;
|
|
128
|
+
d = c;
|
|
129
|
+
c = b;
|
|
130
|
+
b = a;
|
|
131
|
+
a = t1 + t2 >>> 0;
|
|
132
|
+
}
|
|
133
|
+
H[0] += a;
|
|
134
|
+
H[1] += b;
|
|
135
|
+
H[2] += c;
|
|
136
|
+
H[3] += d;
|
|
137
|
+
H[4] += e;
|
|
138
|
+
H[5] += f;
|
|
139
|
+
H[6] += g;
|
|
140
|
+
H[7] += h;
|
|
141
|
+
}
|
|
142
|
+
return Array.from(H, (word) => word.toString(16).padStart(8, "0")).join("");
|
|
143
|
+
}
|
|
144
|
+
// src/test-pans.js
|
|
145
|
+
function immutableSet(values, name) {
|
|
146
|
+
const set = new Set(values);
|
|
147
|
+
for (const method of ["add", "delete", "clear"]) {
|
|
148
|
+
set[method] = () => {
|
|
149
|
+
throw new TypeError(`${name} is immutable`);
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
return Object.freeze(set);
|
|
153
|
+
}
|
|
154
|
+
var DOCUMENTED_TEST_PANS = /* @__PURE__ */ immutableSet([
|
|
155
|
+
"4242424242424242",
|
|
156
|
+
"4000000000000002",
|
|
157
|
+
"4000002500003155",
|
|
158
|
+
"4111111111111111"
|
|
159
|
+
], "DOCUMENTED_TEST_PANS");
|
|
160
|
+
// src/amazon-payment-services.js
|
|
161
|
+
var APS_PSP = "amazon_payment_services";
|
|
162
|
+
var APS_CAPABILITY = "aps_custom_merchant_page";
|
|
163
|
+
var APS_OPERATION = "amazon_payment_services.card_request";
|
|
164
|
+
var APS_HOSTS = /* @__PURE__ */ Object.freeze({ "checkout.payfort.com": "production", "sbcheckout.payfort.com": "sandbox" });
|
|
165
|
+
var APS_TOKENIZATION_PATH = "/FortAPI/paymentPage";
|
|
166
|
+
var APS_SIGNED_FIELDS = /* @__PURE__ */ Object.freeze([
|
|
167
|
+
"service_command",
|
|
168
|
+
"access_code",
|
|
169
|
+
"merchant_identifier",
|
|
170
|
+
"merchant_reference",
|
|
171
|
+
"language",
|
|
172
|
+
"return_url",
|
|
173
|
+
"signature"
|
|
174
|
+
]);
|
|
175
|
+
var APS_CARD_FIELDS = /* @__PURE__ */ Object.freeze(["card_number", "expiry_date", "card_security_code"]);
|
|
176
|
+
var APS_OPTIONAL_FIELDS = /* @__PURE__ */ Object.freeze(["card_holder_name", "remember_me"]);
|
|
177
|
+
var APS_REFUSAL_CODES = /* @__PURE__ */ Object.freeze([
|
|
178
|
+
"aps_url_refused",
|
|
179
|
+
"aps_headers_refused",
|
|
180
|
+
"aps_body_unsupported",
|
|
181
|
+
"aps_service_command_refused",
|
|
182
|
+
"aps_saved_card_refused",
|
|
183
|
+
"aps_recurring_refused",
|
|
184
|
+
"aps_return_url_refused"
|
|
185
|
+
]);
|
|
186
|
+
var MESSAGES = /* @__PURE__ */ Object.freeze({
|
|
187
|
+
aps_url_refused: "Refusing to pay: this is not the Amazon Payment Services tokenization URL. Nothing was sent.",
|
|
188
|
+
aps_headers_refused: "Refusing to pay: this Amazon Payment Services form was not posted by the checkout page's own form. Nothing was sent.",
|
|
189
|
+
aps_body_unsupported: "Refusing to pay: this Amazon Payment Services form is not the card form this lane pays. Nothing was sent.",
|
|
190
|
+
aps_service_command_refused: "Refusing to pay: this Amazon Payment Services form asks for something other than a card token. Nothing was sent.",
|
|
191
|
+
aps_saved_card_refused: "Refusing to pay: this Amazon Payment Services form names a saved card. Nothing was sent.",
|
|
192
|
+
aps_recurring_refused: "Refusing to pay: this Amazon Payment Services form sets up recurring payments. Nothing was sent.",
|
|
193
|
+
aps_return_url_refused: "Refusing to pay: this Amazon Payment Services form names no https page to return to. Nothing was sent."
|
|
194
|
+
});
|
|
195
|
+
var URL_LIMIT = 2048;
|
|
196
|
+
var BODY_LIMIT = 32 * 1024;
|
|
197
|
+
var exact = (pattern, value) => typeof value === "string" && pattern.exec(value)?.[0] === value;
|
|
198
|
+
var controlFree = (value, max) => typeof value === "string" && value.length <= max && !/[\u0000-\u001f\u007f]/.test(value);
|
|
199
|
+
var SHAPES = /* @__PURE__ */ new Map([
|
|
200
|
+
["service_command", (v) => v === "TOKENIZATION"],
|
|
201
|
+
["access_code", (v) => exact(/^[A-Za-z0-9]{1,20}$/, v)],
|
|
202
|
+
["merchant_identifier", (v) => exact(/^[A-Za-z0-9]{1,20}$/, v)],
|
|
203
|
+
["merchant_reference", (v) => exact(/^[A-Za-z0-9._-]{1,40}$/, v)],
|
|
204
|
+
["language", (v) => v === "en" || v === "ar"],
|
|
205
|
+
// Hex SHA-256 (APS's default), SHA-512, or the 40 characters of the SHA-1 APS calls SHA-128.
|
|
206
|
+
["signature", (v) => exact(/^(?:[0-9A-Fa-f]{40}|[0-9A-Fa-f]{64}|[0-9A-Fa-f]{128})$/, v)],
|
|
207
|
+
["card_number", (v) => exact(/^[0-9]{12,19}$/, v)],
|
|
208
|
+
["expiry_date", (v) => exact(/^[0-9]{2}(?:0[1-9]|1[0-2])$/, v)],
|
|
209
|
+
["card_security_code", (v) => exact(/^[0-9]{3,4}$/, v)],
|
|
210
|
+
["card_holder_name", (v) => controlFree(v, 255)],
|
|
211
|
+
["remember_me", (v) => v === "YES" || v === "NO"]
|
|
212
|
+
]);
|
|
213
|
+
function apsTokenizationUrl(url) {
|
|
214
|
+
if (typeof url !== "string" || url.length > URL_LIMIT || url.includes("?") || url.includes("#"))
|
|
215
|
+
return null;
|
|
216
|
+
let parsed;
|
|
217
|
+
try {
|
|
218
|
+
parsed = new URL(url);
|
|
219
|
+
}
|
|
220
|
+
catch {
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
if (parsed.href !== url || parsed.protocol !== "https:" || parsed.port || parsed.username || parsed.password || parsed.pathname !== APS_TOKENIZATION_PATH || !Object.prototype.hasOwnProperty.call(APS_HOSTS, parsed.hostname))
|
|
224
|
+
return null;
|
|
225
|
+
return Object.freeze({ host: parsed.hostname, environment: APS_HOSTS[parsed.hostname] });
|
|
226
|
+
}
|
|
227
|
+
var isApsTokenizationUrl = (url) => apsTokenizationUrl(url) !== null;
|
|
228
|
+
var apsEnvironment = (url) => apsTokenizationUrl(url)?.environment ?? null;
|
|
229
|
+
function formEncode(value) {
|
|
230
|
+
let encoded;
|
|
231
|
+
try {
|
|
232
|
+
encoded = encodeURIComponent(value);
|
|
233
|
+
}
|
|
234
|
+
catch {
|
|
235
|
+
return null;
|
|
236
|
+
}
|
|
237
|
+
return encoded.replace(/[!'()~]/g, (ch) => `%${ch.charCodeAt(0).toString(16).toUpperCase()}`).replace(/%20/g, "+");
|
|
238
|
+
}
|
|
239
|
+
function formDecode(raw) {
|
|
240
|
+
if (!/^(?:[A-Za-z0-9*._+-]|%[0-9A-F]{2})*$/.test(raw))
|
|
241
|
+
return null;
|
|
242
|
+
let value;
|
|
243
|
+
try {
|
|
244
|
+
value = decodeURIComponent(raw.replace(/\+/g, "%20"));
|
|
245
|
+
}
|
|
246
|
+
catch {
|
|
247
|
+
return null;
|
|
248
|
+
}
|
|
249
|
+
return formEncode(value) === raw ? value : null;
|
|
250
|
+
}
|
|
251
|
+
function parseForm(body) {
|
|
252
|
+
if (typeof body !== "string" || body === "" || body.length > BODY_LIMIT)
|
|
253
|
+
return null;
|
|
254
|
+
const pairs = [];
|
|
255
|
+
const values = /* @__PURE__ */ new Map();
|
|
256
|
+
for (const chunk of body.split("&")) {
|
|
257
|
+
const separator = chunk.indexOf("=");
|
|
258
|
+
if (separator < 1)
|
|
259
|
+
return null;
|
|
260
|
+
const rawName = chunk.slice(0, separator), raw = chunk.slice(separator + 1);
|
|
261
|
+
const name = formDecode(rawName), value = formDecode(raw);
|
|
262
|
+
if (name === null || value === null || values.has(name))
|
|
263
|
+
return null;
|
|
264
|
+
pairs.push({ name, rawName, raw, value });
|
|
265
|
+
values.set(name, value);
|
|
266
|
+
}
|
|
267
|
+
return { pairs, values };
|
|
268
|
+
}
|
|
269
|
+
function returnUrlAdmitted(value) {
|
|
270
|
+
if (typeof value !== "string" || value.length > 400 || value.includes("#") || /[\u0000- \u007f]/.test(value))
|
|
271
|
+
return false;
|
|
272
|
+
let parsed;
|
|
273
|
+
try {
|
|
274
|
+
parsed = new URL(value);
|
|
275
|
+
}
|
|
276
|
+
catch {
|
|
277
|
+
return false;
|
|
278
|
+
}
|
|
279
|
+
return parsed.protocol === "https:" && !parsed.username && !parsed.password && parsed.href === value;
|
|
280
|
+
}
|
|
281
|
+
function apsBodyRefusal(body) {
|
|
282
|
+
const form = parseForm(body);
|
|
283
|
+
if (!form)
|
|
284
|
+
return "aps_body_unsupported";
|
|
285
|
+
const has = (name) => form.values.has(name);
|
|
286
|
+
if (has("token_name"))
|
|
287
|
+
return "aps_saved_card_refused";
|
|
288
|
+
if ([...form.values.keys()].some((name) => /^merchant_extra[1-5]?$|^agreement_id$|^recurring_/.test(name)))
|
|
289
|
+
return "aps_recurring_refused";
|
|
290
|
+
if (has("service_command") && form.values.get("service_command") !== "TOKENIZATION")
|
|
291
|
+
return "aps_service_command_refused";
|
|
292
|
+
const allowed = /* @__PURE__ */ new Set([...APS_SIGNED_FIELDS, ...APS_CARD_FIELDS, ...APS_OPTIONAL_FIELDS]);
|
|
293
|
+
if (form.pairs.some(({ name }) => !allowed.has(name)))
|
|
294
|
+
return "aps_body_unsupported";
|
|
295
|
+
if (!returnUrlAdmitted(form.values.get("return_url")))
|
|
296
|
+
return "aps_return_url_refused";
|
|
297
|
+
for (const name of [...APS_SIGNED_FIELDS, ...APS_CARD_FIELDS]) {
|
|
298
|
+
if (name !== "return_url" && !SHAPES.get(name)(form.values.get(name)))
|
|
299
|
+
return "aps_body_unsupported";
|
|
300
|
+
}
|
|
301
|
+
for (const name of APS_OPTIONAL_FIELDS) {
|
|
302
|
+
if (has(name) && !SHAPES.get(name)(form.values.get(name)))
|
|
303
|
+
return "aps_body_unsupported";
|
|
304
|
+
}
|
|
305
|
+
return null;
|
|
306
|
+
}
|
|
307
|
+
function apsBodyVerdict(body, url) {
|
|
308
|
+
if (url !== void 0 && !apsTokenizationUrl(url))
|
|
309
|
+
return { verdict: "refuse", code: "aps_url_refused" };
|
|
310
|
+
const form = parseForm(body);
|
|
311
|
+
const names = form ? [...form.values.keys()] : typeof body === "string" ? body.split("&").map((chunk) => {
|
|
312
|
+
try {
|
|
313
|
+
return decodeURIComponent(chunk.split("=")[0].replace(/\+/g, " "));
|
|
314
|
+
}
|
|
315
|
+
catch {
|
|
316
|
+
return APS_CARD_FIELDS[0];
|
|
317
|
+
}
|
|
318
|
+
}) : [APS_CARD_FIELDS[0]];
|
|
319
|
+
const mentionsCard = !form && typeof body === "string" && APS_CARD_FIELDS.some((name) => body.toLowerCase().includes(name) || body.replace(/%([0-9A-Fa-f]{2})/g, (_, hex) => String.fromCharCode(parseInt(hex, 16))).toLowerCase().includes(name));
|
|
320
|
+
if (!mentionsCard && !names.some((name) => APS_CARD_FIELDS.includes(name.trim().toLowerCase())))
|
|
321
|
+
return { verdict: "continue" };
|
|
322
|
+
const code = apsBodyRefusal(body);
|
|
323
|
+
return code ? { verdict: "refuse", code } : { verdict: "card" };
|
|
324
|
+
}
|
|
325
|
+
function apsRefusalMessage(code) {
|
|
326
|
+
return Object.prototype.hasOwnProperty.call(MESSAGES, code) ? MESSAGES[code] : MESSAGES.aps_body_unsupported;
|
|
327
|
+
}
|
|
328
|
+
var FORM_TYPE = "application/x-www-form-urlencoded";
|
|
329
|
+
var record = (value) => !!value && typeof value === "object" && !Array.isArray(value);
|
|
330
|
+
function requestHeaders(headers) {
|
|
331
|
+
if (headers === void 0)
|
|
332
|
+
return /* @__PURE__ */ new Map();
|
|
333
|
+
if (!record(headers))
|
|
334
|
+
return null;
|
|
335
|
+
const out = /* @__PURE__ */ new Map();
|
|
336
|
+
for (const [name, value] of Object.entries(headers)) {
|
|
337
|
+
const lower = name.toLowerCase();
|
|
338
|
+
if (typeof value !== "string" || out.has(lower))
|
|
339
|
+
return null;
|
|
340
|
+
out.set(lower, value);
|
|
341
|
+
}
|
|
342
|
+
return out;
|
|
343
|
+
}
|
|
344
|
+
function checkoutOriginOf(value) {
|
|
345
|
+
if (typeof value !== "string" || value.length > URL_LIMIT)
|
|
346
|
+
return null;
|
|
347
|
+
let parsed;
|
|
348
|
+
try {
|
|
349
|
+
parsed = new URL(value);
|
|
350
|
+
}
|
|
351
|
+
catch {
|
|
352
|
+
return null;
|
|
353
|
+
}
|
|
354
|
+
return parsed.origin === value && parsed.protocol === "https:" ? value : null;
|
|
355
|
+
}
|
|
356
|
+
function apsRequestRefusal(request, options) {
|
|
357
|
+
if (!record(request) || request.method !== "POST" || !apsTokenizationUrl(request.url))
|
|
358
|
+
return "aps_url_refused";
|
|
359
|
+
const headers = requestHeaders(request.headers);
|
|
360
|
+
const type = headers?.get("content-type");
|
|
361
|
+
if (!headers || typeof type !== "string" || type.trim().toLowerCase() !== FORM_TYPE)
|
|
362
|
+
return "aps_headers_refused";
|
|
363
|
+
if (record(options) && options.merchantOrigin !== void 0) {
|
|
364
|
+
const origin = checkoutOriginOf(options.merchantOrigin);
|
|
365
|
+
if (!origin || headers.get("origin") !== origin)
|
|
366
|
+
return "aps_headers_refused";
|
|
367
|
+
if (headers.has("referer")) {
|
|
368
|
+
let referer = null;
|
|
369
|
+
try {
|
|
370
|
+
referer = new URL(headers.get("referer"));
|
|
371
|
+
}
|
|
372
|
+
catch {
|
|
373
|
+
referer = null;
|
|
374
|
+
}
|
|
375
|
+
if (!referer || referer.href !== headers.get("referer") || referer.origin !== origin || referer.username || referer.password)
|
|
376
|
+
return "aps_headers_refused";
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
return apsBodyRefusal(request.body);
|
|
380
|
+
}
|
|
381
|
+
function apsKeepsCardOnFile(body) {
|
|
382
|
+
const form = parseForm(body);
|
|
383
|
+
return form ? form.values.get("remember_me") !== "NO" : null;
|
|
384
|
+
}
|
|
385
|
+
function apsRequestFacts(request) {
|
|
386
|
+
if (!record(request) || request.method !== "POST" || !apsTokenizationUrl(request.url) || apsBodyRefusal(request.body) !== null)
|
|
387
|
+
return null;
|
|
388
|
+
const values = parseForm(request.body).values;
|
|
389
|
+
const v = (name) => values.get(name);
|
|
390
|
+
return Object.freeze({
|
|
391
|
+
environment: apsEnvironment(request.url),
|
|
392
|
+
merchantIdentifier: v("merchant_identifier"),
|
|
393
|
+
accessCodeSha256: sha256Hex(v("access_code")),
|
|
394
|
+
merchantReference: v("merchant_reference"),
|
|
395
|
+
returnUrl: v("return_url"),
|
|
396
|
+
keepsCardOnFile: v("remember_me") !== "NO"
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
var APS_MERCHANTS = /* @__PURE__ */ deepFreeze([]);
|
|
400
|
+
var APS_MERCHANT_REFUSALS = /* @__PURE__ */ Object.freeze([
|
|
401
|
+
"aps_request_unreadable",
|
|
402
|
+
"aps_unsupported_environment",
|
|
403
|
+
"aps_merchant_unreviewed",
|
|
404
|
+
"aps_merchant_pending",
|
|
405
|
+
"aps_access_code_unreviewed",
|
|
406
|
+
"aps_return_url_unreviewed",
|
|
407
|
+
"aps_merchant_client_signed",
|
|
408
|
+
"aps_merchant_observe"
|
|
409
|
+
]);
|
|
410
|
+
var DECISION_DATE = /^[0-9]{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12][0-9]|3[01])$/;
|
|
411
|
+
var pinList = (value) => Array.isArray(value) ? value : [];
|
|
412
|
+
var decided = (entry) => record(entry.decision) && typeof entry.decision.reference === "string" && entry.decision.reference.trim() !== "" && exact(DECISION_DATE, entry.decision.reviewed_on);
|
|
413
|
+
function originOf(value) {
|
|
414
|
+
try {
|
|
415
|
+
return new URL(value).origin;
|
|
416
|
+
}
|
|
417
|
+
catch {
|
|
418
|
+
return null;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
function apsMerchantRefusal(facts, merchantOrigin, entries = APS_MERCHANTS) {
|
|
422
|
+
if (!record(facts) || typeof facts.merchantIdentifier !== "string" || typeof facts.accessCodeSha256 !== "string" || typeof facts.returnUrl !== "string")
|
|
423
|
+
return "aps_request_unreadable";
|
|
424
|
+
if (facts.environment === "sandbox")
|
|
425
|
+
return null;
|
|
426
|
+
if (facts.environment !== "production")
|
|
427
|
+
return "aps_unsupported_environment";
|
|
428
|
+
const forOrigin = pinList(entries).filter((entry2) => record(entry2) && entry2.environment === "production" && pinList(entry2.origins).includes(merchantOrigin));
|
|
429
|
+
if (forOrigin.length === 0)
|
|
430
|
+
return "aps_merchant_unreviewed";
|
|
431
|
+
const pending = (entry2) => !Array.isArray(entry2.pending) || entry2.pending.length > 0;
|
|
432
|
+
const entry = forOrigin.find((candidate) => pinList(candidate.merchantIdentifiers).includes(facts.merchantIdentifier));
|
|
433
|
+
if (!entry)
|
|
434
|
+
return forOrigin.some(pending) ? "aps_merchant_pending" : "aps_merchant_unreviewed";
|
|
435
|
+
if (pending(entry))
|
|
436
|
+
return "aps_merchant_pending";
|
|
437
|
+
if (!pinList(entry.accessCodeSha256).includes(facts.accessCodeSha256))
|
|
438
|
+
return "aps_access_code_unreviewed";
|
|
439
|
+
if (!pinList(entry.returnOrigins).includes(originOf(facts.returnUrl)))
|
|
440
|
+
return "aps_return_url_unreviewed";
|
|
441
|
+
if (entry.signing !== "server" && !decided(entry))
|
|
442
|
+
return "aps_merchant_client_signed";
|
|
443
|
+
return entry.status === "enabled" ? null : "aps_merchant_observe";
|
|
444
|
+
}
|
|
445
|
+
function apsMerchantPin(facts, merchantOrigin, entries = APS_MERCHANTS) {
|
|
446
|
+
if (!record(facts) || facts.environment !== "production" || apsMerchantRefusal(facts, merchantOrigin, entries) !== null)
|
|
447
|
+
return null;
|
|
448
|
+
return pinList(entries).find((entry) => record(entry) && entry.environment === "production" && pinList(entry.origins).includes(merchantOrigin) && pinList(entry.merchantIdentifiers).includes(facts.merchantIdentifier)) ?? null;
|
|
449
|
+
}
|
|
450
|
+
var APS_TEST_PANS = /* @__PURE__ */ immutableSet([
|
|
451
|
+
"4005550000000001",
|
|
452
|
+
"5123456789012346",
|
|
453
|
+
"345678901234564",
|
|
454
|
+
"4557012345678902",
|
|
455
|
+
"5313581000123430",
|
|
456
|
+
"374200000000004",
|
|
457
|
+
"4916783391760242",
|
|
458
|
+
"4556437649762022",
|
|
459
|
+
"5308097663032831",
|
|
460
|
+
"4188870000000002",
|
|
461
|
+
"4546830000000003",
|
|
462
|
+
"4278370000000004",
|
|
463
|
+
"5297410000000002",
|
|
464
|
+
"5078036246600381"
|
|
465
|
+
], "APS_TEST_PANS");
|
|
466
|
+
var FILL_FAILED = "Could not fill the card fields for Amazon Payment Services. Nothing was sent.";
|
|
467
|
+
var SANDBOX_CARD = "Refusing to pay: a sandbox or test host accepts only the processor's documented test cards, not a real card.";
|
|
468
|
+
var MALFORMED = "Refusing to pay: malformed payment request.";
|
|
469
|
+
function luhn(number) {
|
|
470
|
+
let sum = 0;
|
|
471
|
+
for (let i = 0; i < number.length; i++) {
|
|
472
|
+
let digit = number.charCodeAt(number.length - 1 - i) - 48;
|
|
473
|
+
if (i % 2 === 1) {
|
|
474
|
+
digit *= 2;
|
|
475
|
+
if (digit > 9)
|
|
476
|
+
digit -= 9;
|
|
477
|
+
}
|
|
478
|
+
sum += digit;
|
|
479
|
+
}
|
|
480
|
+
return sum % 10 === 0;
|
|
481
|
+
}
|
|
482
|
+
function yymmOf(card) {
|
|
483
|
+
const month = typeof card?.month === "number" || typeof card?.month === "string" ? String(card.month) : "";
|
|
484
|
+
const year = typeof card?.year === "number" || typeof card?.year === "string" ? String(card.year) : "";
|
|
485
|
+
if (!/^[0-9]{1,2}$/.test(month) || Number(month) < 1 || Number(month) > 12 || !/^(?:[0-9]{2}|20[0-9]{2})$/.test(year))
|
|
486
|
+
return null;
|
|
487
|
+
return `${year.slice(-2)}${month.padStart(2, "0")}`;
|
|
488
|
+
}
|
|
489
|
+
function apsCardRefusal(environment, card) {
|
|
490
|
+
if (environment !== "production" && environment !== "sandbox")
|
|
491
|
+
return "unsupported_environment";
|
|
492
|
+
const pan = record(card) && typeof card.number === "string" ? card.number : "";
|
|
493
|
+
if (!/^[0-9]{12,19}$/.test(pan) || /^([0-9])\1+$/.test(pan) || !luhn(pan))
|
|
494
|
+
return "invalid_card_number";
|
|
495
|
+
const test = APS_TEST_PANS.has(pan) || DOCUMENTED_TEST_PANS.has(pan);
|
|
496
|
+
if (environment === "sandbox" && !test)
|
|
497
|
+
return "sandbox_host_requires_documented_test_card";
|
|
498
|
+
if (environment === "production" && test)
|
|
499
|
+
return "live_host_refuses_documented_test_card";
|
|
500
|
+
if (!yymmOf(card))
|
|
501
|
+
return "invalid_expiry";
|
|
502
|
+
if (!exact(/^[0-9]{3,4}$/, card.cvc))
|
|
503
|
+
return "invalid_security_code";
|
|
504
|
+
return null;
|
|
505
|
+
}
|
|
506
|
+
function isApsDeviceRequest(templateBody, deviceBody, environment) {
|
|
507
|
+
const template = parseForm(templateBody);
|
|
508
|
+
const device = parseForm(deviceBody);
|
|
509
|
+
if (!template || !device || apsBodyRefusal(templateBody) !== null || device.pairs.length !== template.pairs.length)
|
|
510
|
+
return false;
|
|
511
|
+
return device.pairs.every((pair, i) => {
|
|
512
|
+
const page = template.pairs[i];
|
|
513
|
+
if (pair.rawName !== page.rawName)
|
|
514
|
+
return false;
|
|
515
|
+
if (!APS_CARD_FIELDS.includes(pair.name))
|
|
516
|
+
return pair.raw === page.raw;
|
|
517
|
+
if (!SHAPES.get(pair.name)(pair.value))
|
|
518
|
+
return false;
|
|
519
|
+
if (pair.name !== "card_number" || pair.raw !== page.raw)
|
|
520
|
+
return true;
|
|
521
|
+
return environment === "sandbox" && (APS_TEST_PANS.has(pair.value) || DOCUMENTED_TEST_PANS.has(pair.value));
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
function apsDeviceRequest(request, card, options) {
|
|
525
|
+
const merchantOrigin = record(options) ? options.merchantOrigin : void 0;
|
|
526
|
+
if (typeof merchantOrigin !== "string" || apsRequestRefusal(request, { merchantOrigin }) !== null)
|
|
527
|
+
throw new Error(MALFORMED);
|
|
528
|
+
const environment = apsEnvironment(request.url);
|
|
529
|
+
const refusal = apsCardRefusal(environment, card);
|
|
530
|
+
if (refusal === "sandbox_host_requires_documented_test_card")
|
|
531
|
+
throw new Error(SANDBOX_CARD);
|
|
532
|
+
if (refusal)
|
|
533
|
+
throw new Error(FILL_FAILED);
|
|
534
|
+
const fresh = /* @__PURE__ */ new Map([["card_number", card.number], ["expiry_date", yymmOf(card)], ["card_security_code", card.cvc]]);
|
|
535
|
+
const form = parseForm(request.body);
|
|
536
|
+
const body = form.pairs.map(({ name, rawName, raw }) => `${rawName}=${fresh.has(name) ? formEncode(fresh.get(name)) : raw}`).join("&");
|
|
537
|
+
if (!isApsDeviceRequest(request.body, body, environment))
|
|
538
|
+
throw new Error(MALFORMED);
|
|
539
|
+
const captured = requestHeaders(request.headers);
|
|
540
|
+
const headers = { "content-type": FORM_TYPE, origin: merchantOrigin };
|
|
541
|
+
if (captured.has("referer"))
|
|
542
|
+
headers.referer = captured.get("referer");
|
|
543
|
+
return { url: request.url, method: "POST", headers, body, where: form.pairs.filter(({ name }) => fresh.has(name)).map(({ name }) => name) };
|
|
544
|
+
}
|
|
545
|
+
var APS_TOKEN_RESPONSE_CODES = /* @__PURE__ */ Object.freeze(["18000", "18062", "18063"]);
|
|
546
|
+
var APS_KEPT_CARD_CODES = /* @__PURE__ */ Object.freeze(["18062", "18063"]);
|
|
547
|
+
var APS_REFUSAL_MESSAGES = /* @__PURE__ */ Object.freeze([
|
|
548
|
+
"001",
|
|
549
|
+
"002",
|
|
550
|
+
"003",
|
|
551
|
+
"004",
|
|
552
|
+
"007",
|
|
553
|
+
"008",
|
|
554
|
+
"009",
|
|
555
|
+
"010",
|
|
556
|
+
"012",
|
|
557
|
+
"014",
|
|
558
|
+
"015",
|
|
559
|
+
"016",
|
|
560
|
+
"017",
|
|
561
|
+
"018",
|
|
562
|
+
"027",
|
|
563
|
+
"032",
|
|
564
|
+
"033",
|
|
565
|
+
"034",
|
|
566
|
+
"037",
|
|
567
|
+
"038",
|
|
568
|
+
"039",
|
|
569
|
+
"040",
|
|
570
|
+
"046",
|
|
571
|
+
"066",
|
|
572
|
+
"073",
|
|
573
|
+
"080",
|
|
574
|
+
"087",
|
|
575
|
+
"100",
|
|
576
|
+
"110",
|
|
577
|
+
"123"
|
|
578
|
+
]);
|
|
579
|
+
var APS_ANSWER_REASONS = /* @__PURE__ */ Object.freeze([
|
|
580
|
+
"answer_unreadable",
|
|
581
|
+
"answer_status",
|
|
582
|
+
"answer_type",
|
|
583
|
+
"answer_size",
|
|
584
|
+
"answer_location",
|
|
585
|
+
"answer_page",
|
|
586
|
+
"answer_fields",
|
|
587
|
+
"answer_mismatch",
|
|
588
|
+
"card_in_answer",
|
|
589
|
+
"card_mismatch",
|
|
590
|
+
"card_kept",
|
|
591
|
+
"token_shape",
|
|
592
|
+
"answer_unconfirmed"
|
|
593
|
+
]);
|
|
594
|
+
var APS_PAGE_CONTENT_TYPE = "text/html;charset=UTF-8";
|
|
595
|
+
var ANSWER_LIMIT = 64 * 1024;
|
|
596
|
+
var TOKEN_NAME = /^[A-Za-z0-9.@_-]{1,100}$/;
|
|
597
|
+
var MASKED_NUMBER = /^([0-9]{6,8})[*Xx]{2,9}[0-9]{4}$/;
|
|
598
|
+
var SIGNATURE = /^(?:[0-9a-fA-F]{40}|[0-9a-fA-F]{64}|[0-9a-fA-F]{128})$/;
|
|
599
|
+
var NAMED_ENTITIES = { amp: "&", lt: "<", gt: ">", quot: '"', apos: "'" };
|
|
600
|
+
function decodeEntities(text) {
|
|
601
|
+
return text.replace(/&(#[xX][0-9a-fA-F]{1,6}|#[0-9]{1,7}|[A-Za-z]{2,4});/g, (whole, name) => {
|
|
602
|
+
if (name[0] !== "#")
|
|
603
|
+
return Object.prototype.hasOwnProperty.call(NAMED_ENTITIES, name.toLowerCase()) ? NAMED_ENTITIES[name.toLowerCase()] : whole;
|
|
604
|
+
const code = name[1] === "x" || name[1] === "X" ? parseInt(name.slice(2), 16) : parseInt(name.slice(1), 10);
|
|
605
|
+
return code > 0 && code <= 1114111 ? String.fromCodePoint(code) : whole;
|
|
606
|
+
});
|
|
607
|
+
}
|
|
608
|
+
function answerHeaders(headers) {
|
|
609
|
+
if (headers === void 0)
|
|
610
|
+
return void 0;
|
|
611
|
+
const out = /* @__PURE__ */ new Map();
|
|
612
|
+
const add = (name, value) => {
|
|
613
|
+
if (typeof name !== "string" || typeof value !== "string")
|
|
614
|
+
return false;
|
|
615
|
+
out.set(name.toLowerCase(), [...out.get(name.toLowerCase()) ?? [], value]);
|
|
616
|
+
return true;
|
|
617
|
+
};
|
|
618
|
+
if (Array.isArray(headers)) {
|
|
619
|
+
for (const pair of headers)
|
|
620
|
+
if (!Array.isArray(pair) || pair.length !== 2 || !add(pair[0], pair[1]))
|
|
621
|
+
return null;
|
|
622
|
+
return out;
|
|
623
|
+
}
|
|
624
|
+
if (!record(headers))
|
|
625
|
+
return null;
|
|
626
|
+
for (const [name, value] of Object.entries(headers))
|
|
627
|
+
for (const item of Array.isArray(value) ? value : [value])
|
|
628
|
+
if (!add(name, item))
|
|
629
|
+
return null;
|
|
630
|
+
return out;
|
|
631
|
+
}
|
|
632
|
+
function htmlUtf8(values, text) {
|
|
633
|
+
if (!values || values.length !== 1)
|
|
634
|
+
return false;
|
|
635
|
+
const [type, ...params] = values[0].split(";").map((part) => part.trim());
|
|
636
|
+
const charsets = params.filter((param) => /^charset\s*=/i.test(param)).map((param) => param.replace(/^charset\s*=\s*"?([^"]*)"?$/i, "$1").toLowerCase());
|
|
637
|
+
if (type.toLowerCase() !== "text/html" || charsets.length > 1)
|
|
638
|
+
return false;
|
|
639
|
+
return charsets.length === 1 ? charsets[0] === "utf-8" || charsets[0] === "utf8" : /^[\u0000-\u007f]*$/.test(text);
|
|
640
|
+
}
|
|
641
|
+
var JSON_STRING = /"(?:[^"\\\u0000-\u001f]|\\(?:["\\/bfnrt]|u[0-9a-fA-F]{4}))*"/y;
|
|
642
|
+
var JSON_WHOLE = /0|[1-9][0-9]{0,14}/y;
|
|
643
|
+
var JSON_NULL = /null/y;
|
|
644
|
+
var JSON_SPACE = /[ \t\n\r]*/y;
|
|
645
|
+
function returnUrlParamsOf(html) {
|
|
646
|
+
const assignments = [...html.matchAll(/\breturnUrlParams\s*=\s*/g)];
|
|
647
|
+
if (assignments.length !== 1)
|
|
648
|
+
return null;
|
|
649
|
+
let at = assignments[0].index + assignments[0][0].length;
|
|
650
|
+
const take = (pattern) => {
|
|
651
|
+
pattern.lastIndex = at;
|
|
652
|
+
const match = pattern.exec(html);
|
|
653
|
+
if (match)
|
|
654
|
+
at = pattern.lastIndex;
|
|
655
|
+
return match ? match[0] : null;
|
|
656
|
+
};
|
|
657
|
+
if (html[at] !== "{")
|
|
658
|
+
return null;
|
|
659
|
+
at += 1;
|
|
660
|
+
take(JSON_SPACE);
|
|
661
|
+
const out = /* @__PURE__ */ new Map();
|
|
662
|
+
if (html[at] === "}")
|
|
663
|
+
return out;
|
|
664
|
+
for (;;) {
|
|
665
|
+
const key = take(JSON_STRING);
|
|
666
|
+
take(JSON_SPACE);
|
|
667
|
+
if (key === null || html[at] !== ":")
|
|
668
|
+
return null;
|
|
669
|
+
at += 1;
|
|
670
|
+
take(JSON_SPACE);
|
|
671
|
+
const start = at;
|
|
672
|
+
const raw = html[at] === '"' ? take(JSON_STRING) : html[at] === "n" ? take(JSON_NULL) : take(JSON_WHOLE);
|
|
673
|
+
const name = JSON.parse(key);
|
|
674
|
+
if (raw === null || out.has(name))
|
|
675
|
+
return null;
|
|
676
|
+
const quoted = raw[0] === '"';
|
|
677
|
+
out.set(name, {
|
|
678
|
+
value: quoted ? JSON.parse(raw) : raw === "null" ? null : raw,
|
|
679
|
+
numeric: !quoted && raw !== "null",
|
|
680
|
+
start: quoted ? start + 1 : start,
|
|
681
|
+
end: quoted ? at - 1 : at
|
|
682
|
+
});
|
|
683
|
+
take(JSON_SPACE);
|
|
684
|
+
if (html[at] === "}")
|
|
685
|
+
return out;
|
|
686
|
+
if (html[at] !== ",")
|
|
687
|
+
return null;
|
|
688
|
+
at += 1;
|
|
689
|
+
take(JSON_SPACE);
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
function returnUrlFormOf(html) {
|
|
693
|
+
const forms = [...html.matchAll(/<form\b([^>]*)>/gi)];
|
|
694
|
+
if (forms.length !== 1)
|
|
695
|
+
return null;
|
|
696
|
+
const base = forms[0].index + "<form".length;
|
|
697
|
+
const attrs = /* @__PURE__ */ new Map();
|
|
698
|
+
for (const match of forms[0][1].matchAll(/([A-Za-z_:][-A-Za-z0-9_:.]*)\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'>]+))/g)) {
|
|
699
|
+
const name = match[1].toLowerCase();
|
|
700
|
+
if (attrs.has(name))
|
|
701
|
+
return null;
|
|
702
|
+
const raw = match[2] ?? match[3] ?? match[4];
|
|
703
|
+
const end = base + match.index + match[0].length - (match[4] === void 0 ? 1 : 0);
|
|
704
|
+
attrs.set(name, { value: decodeEntities(raw), start: end - raw.length, end });
|
|
705
|
+
}
|
|
706
|
+
const action = attrs.get("action");
|
|
707
|
+
return attrs.get("id")?.value === "returnUrlForm" ? { action: action?.value, actionSpan: action ? { start: action.start, end: action.end } : null, method: (attrs.get("method")?.value ?? "").toLowerCase() } : null;
|
|
708
|
+
}
|
|
709
|
+
function answerParams(reply, returnUrl) {
|
|
710
|
+
const headers = answerHeaders(reply.headers);
|
|
711
|
+
if (headers === null)
|
|
712
|
+
return { error: "answer_type" };
|
|
713
|
+
if (reply.status === 302 || reply.status === 303) {
|
|
714
|
+
const location = headers?.get("location");
|
|
715
|
+
const prefix = `${returnUrl}${returnUrl.includes("?") ? "&" : "?"}`;
|
|
716
|
+
if (!location || location.length !== 1 || location[0].length > 16 * 1024 || !location[0].startsWith(prefix) || location[0].includes("#"))
|
|
717
|
+
return { error: "answer_location" };
|
|
718
|
+
const fields2 = /* @__PURE__ */ new Map();
|
|
719
|
+
let at = prefix.length;
|
|
720
|
+
for (const chunk of location[0].slice(prefix.length).split("&")) {
|
|
721
|
+
const eq = chunk.indexOf("=");
|
|
722
|
+
let name = null, value = null;
|
|
723
|
+
try {
|
|
724
|
+
name = decodeURIComponent(chunk.slice(0, eq).replace(/\+/g, " "));
|
|
725
|
+
value = decodeURIComponent(chunk.slice(eq + 1).replace(/\+/g, " "));
|
|
726
|
+
}
|
|
727
|
+
catch {
|
|
728
|
+
name = null;
|
|
729
|
+
}
|
|
730
|
+
if (eq < 1 || name === null || fields2.has(name))
|
|
731
|
+
return { error: "answer_location" };
|
|
732
|
+
fields2.set(name, { value, start: at + eq + 1, end: at + chunk.length });
|
|
733
|
+
at += chunk.length + 1;
|
|
734
|
+
}
|
|
735
|
+
return { shape: "redirect", fields: fields2, text: location[0], own: [{ start: 0, end: returnUrl.length }] };
|
|
736
|
+
}
|
|
737
|
+
if (reply.status !== 200)
|
|
738
|
+
return { error: "answer_status" };
|
|
739
|
+
if (headers?.has("location"))
|
|
740
|
+
return { error: "answer_location" };
|
|
741
|
+
if (headers !== void 0 && !htmlUtf8(headers.get("content-type"), reply.bodyText))
|
|
742
|
+
return { error: "answer_type" };
|
|
743
|
+
if (new TextEncoder().encode(reply.bodyText).length > ANSWER_LIMIT)
|
|
744
|
+
return { error: "answer_size" };
|
|
745
|
+
const form = returnUrlFormOf(reply.bodyText);
|
|
746
|
+
const fields = returnUrlParamsOf(reply.bodyText);
|
|
747
|
+
if (!form || !fields || form.method !== "post" || form.action !== returnUrl)
|
|
748
|
+
return { error: "answer_page" };
|
|
749
|
+
return { shape: "page", fields, text: reply.bodyText, own: [form.actionSpan] };
|
|
750
|
+
}
|
|
751
|
+
function writesCardNumber(text) {
|
|
752
|
+
const digit = String.raw `(?:[0-9]|\\u003[0-9]|%3[0-9]|�*(?:4[89]|5[0-7]);|�*3[0-9];)`;
|
|
753
|
+
const escape = String.raw `\\(?:[/\\"bfnrt]|u(?:00(?:[01][0-9a-f]|2[0-9a-f]|3[a-f]|40|5[b-f]|60|7[b-f]|[89a-f][0-9a-f])|0[1-9a-f][0-9a-f]{2}|[1-9a-f][0-9a-f]{3}))` + String.raw `|%(?:[01][0-9a-f]|2[0-9a-f]|3[a-f]|40|5[b-f]|60|7[b-f]|[89a-f][0-9a-f])` + String.raw `|&(?:#0*(?:[0-9]|[1-3][0-9]|4[0-7]|5[89]|6[0-4]|9[1-6]|12[3-9]|1[3-9][0-9]|[2-9][0-9]{2}|[1-9][0-9]{3,6})|#x0*(?:[0-9a-f]|[01][0-9a-f]|2[0-9a-f]|3[a-f]|40|5[b-f]|60|7[b-f]|[89a-f][0-9a-f]|[1-9a-f][0-9a-f]{2,5})|[a-z][a-z0-9]{1,31});`;
|
|
754
|
+
const separator = `(?:${escape}|[^0-9A-Za-z\\\\%&]|%(?![0-9a-f]{2})|&(?!#?x?[0-9a-z]+;))`;
|
|
755
|
+
for (const [run] of text.matchAll(new RegExp(`${digit}(?:${separator}*${digit})*`, "gi"))) {
|
|
756
|
+
const groups = run.replace(new RegExp(escape, "gi"), " ").replace(/\\u003([0-9])/gi, "$1").replace(/%3([0-9])/g, "$1").replace(/�*3([0-9]);/gi, "$1").replace(/�*(4[89]|5[0-7]);/g, (_form, code) => String.fromCharCode(Number(code))).split(/[^0-9]+/).filter(Boolean);
|
|
757
|
+
for (let first = 0; first < groups.length; first += 1) {
|
|
758
|
+
let digits = "";
|
|
759
|
+
for (let last = first; last < groups.length; last += 1) {
|
|
760
|
+
digits += groups[last];
|
|
761
|
+
if (digits.length > 19)
|
|
762
|
+
break;
|
|
763
|
+
if (digits.length >= 13 && digits[0] >= "2" && digits[0] <= "6" && luhn(digits))
|
|
764
|
+
return true;
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
return false;
|
|
769
|
+
}
|
|
770
|
+
function percentUndone(text) {
|
|
771
|
+
let out = text;
|
|
772
|
+
for (let pass = 0; pass < 16; pass += 1) {
|
|
773
|
+
const next = out.replace(/%([0-9A-Fa-f]{2})/g, (_, hex) => String.fromCharCode(parseInt(hex, 16)));
|
|
774
|
+
if (next === out)
|
|
775
|
+
return out;
|
|
776
|
+
out = next;
|
|
777
|
+
}
|
|
778
|
+
return null;
|
|
779
|
+
}
|
|
780
|
+
function passedOver(name, value, sent) {
|
|
781
|
+
if (name === "signature")
|
|
782
|
+
return exact(SIGNATURE, value);
|
|
783
|
+
if (name === "token_name")
|
|
784
|
+
return exact(TOKEN_NAME, value);
|
|
785
|
+
return !APS_CARD_FIELDS.includes(name) && sent.has(name) && sent.get(name) === value;
|
|
786
|
+
}
|
|
787
|
+
var unicodeDigit = (code) => code >= 1632 && code <= 1641 ? code - 1632 : code >= 1776 && code <= 1785 ? code - 1776 : code >= 65296 && code <= 65305 ? code - 65296 : -1;
|
|
788
|
+
function asciiDigits(text) {
|
|
789
|
+
const asDigit = (whole, code) => unicodeDigit(code) < 0 ? whole : String(unicodeDigit(code));
|
|
790
|
+
return text.replace(/&#(?:[xX]0*([0-9a-fA-F]{1,6})|0*([0-9]{1,7}));/g, (whole, hex, dec) => asDigit(whole, hex ? parseInt(hex, 16) : parseInt(dec, 10))).replace(/\\u([0-9a-fA-F]{4})/g, (whole, hex) => asDigit(whole, parseInt(hex, 16))).replace(/%D9%A([0-9])|%DB%B([0-9])|%EF%BC%9([0-9])/gi, (_whole, a, b, c) => a ?? b ?? c).replace(/[\u0660-\u0669\u06F0-\u06F9\uFF10-\uFF19]/g, (digit) => String(unicodeDigit(digit.charCodeAt(0))));
|
|
791
|
+
}
|
|
792
|
+
function scannedTexts(read, sent) {
|
|
793
|
+
const spans = [
|
|
794
|
+
...[...read.fields].filter(([name, field]) => field.end > field.start && passedOver(name, field.value, sent)).map(([, field]) => field),
|
|
795
|
+
...read.own
|
|
796
|
+
].sort((a, b) => b.start - a.start);
|
|
797
|
+
let out = read.text;
|
|
798
|
+
for (const { start, end } of spans)
|
|
799
|
+
out = out.slice(0, start) + out.slice(end);
|
|
800
|
+
out = asciiDigits(out);
|
|
801
|
+
return read.shape === "page" ? [out, out.replace(/<[^>]*>/g, " ")] : [out];
|
|
802
|
+
}
|
|
803
|
+
function statedValues(fields) {
|
|
804
|
+
const out = /* @__PURE__ */ new Map();
|
|
805
|
+
for (const [name, field] of fields) {
|
|
806
|
+
if (field.value === null || field.value === "")
|
|
807
|
+
continue;
|
|
808
|
+
const width = !field.numeric ? 0 : name === "status" ? 2 : name === "response_code" ? 5 : 0;
|
|
809
|
+
out.set(name, field.value.padStart(width, "0"));
|
|
810
|
+
}
|
|
811
|
+
return out;
|
|
812
|
+
}
|
|
813
|
+
var BINDING_ECHOES = /* @__PURE__ */ Object.freeze(["merchant_identifier", "access_code", "merchant_reference"]);
|
|
814
|
+
var sentFields = (template) => record(template) && apsTokenizationUrl(template.url) && apsBodyRefusal(template.body) === null ? parseForm(template.body).values : null;
|
|
815
|
+
function apsScannedTexts(reply, template) {
|
|
816
|
+
const sent = sentFields(template);
|
|
817
|
+
if (!record(reply) || !Number.isInteger(reply.status) || typeof reply.bodyText !== "string" || !sent)
|
|
818
|
+
return null;
|
|
819
|
+
const read = answerParams(reply, sent.get("return_url"));
|
|
820
|
+
return read.error ? null : scannedTexts(read, sent);
|
|
821
|
+
}
|
|
822
|
+
function apsAnswerOutcome(reply, template, card, options) {
|
|
823
|
+
const unknown = (reason) => ({ outcome: "unknown", reason });
|
|
824
|
+
const sent = sentFields(template);
|
|
825
|
+
if (!record(reply) || !Number.isInteger(reply.status) || typeof reply.bodyText !== "string" || !sent)
|
|
826
|
+
return unknown("answer_unreadable");
|
|
827
|
+
const read = answerParams(reply, sent.get("return_url"));
|
|
828
|
+
if (read.error)
|
|
829
|
+
return unknown(read.error);
|
|
830
|
+
const p = statedValues(read.fields);
|
|
831
|
+
if (p.has("card_security_code") || !p.has("status") || !p.has("response_code"))
|
|
832
|
+
return unknown("answer_fields");
|
|
833
|
+
if (BINDING_ECHOES.some((name) => p.has(name) && p.get(name) !== sent.get(name)) || p.has("service_command") && p.get("service_command") !== "TOKENIZATION")
|
|
834
|
+
return unknown("answer_mismatch");
|
|
835
|
+
const masked = p.get("card_number");
|
|
836
|
+
const shown = masked === void 0 ? null : MASKED_NUMBER.exec(masked);
|
|
837
|
+
if (masked !== void 0 && !shown)
|
|
838
|
+
return unknown("card_in_answer");
|
|
839
|
+
if (scannedTexts(read, sent).some(writesCardNumber))
|
|
840
|
+
return unknown("card_in_answer");
|
|
841
|
+
for (const [name, value] of p) {
|
|
842
|
+
if (name === "card_number" || passedOver(name, value, sent))
|
|
843
|
+
continue;
|
|
844
|
+
const open = percentUndone(asciiDigits(value));
|
|
845
|
+
if (open === null || writesCardNumber(asciiDigits(open)))
|
|
846
|
+
return unknown("card_in_answer");
|
|
847
|
+
}
|
|
848
|
+
if (card !== void 0 && card !== null) {
|
|
849
|
+
const number = record(card) && typeof card.number === "string" ? card.number : "";
|
|
850
|
+
const leak = record(options) && Object.prototype.hasOwnProperty.call(options, "cardLeak") ? options.cardLeak : void 0;
|
|
851
|
+
if (!/^[0-9]{12,19}$/.test(number) || leak !== null || [...p.values()].some((value) => asciiDigits(value).replace(/[^0-9]/g, "").includes(number))) {
|
|
852
|
+
return unknown("card_in_answer");
|
|
853
|
+
}
|
|
854
|
+
if (shown && (!number.startsWith(shown[1]) || masked.slice(-4) !== number.slice(-4)) || p.has("card_bin") && !(exact(/^[0-9]{6,8}$/, p.get("card_bin")) && number.startsWith(p.get("card_bin"))) || p.has("expiry_date") && p.get("expiry_date") !== yymmOf(card)) {
|
|
855
|
+
return unknown("card_mismatch");
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
const last4 = record(options) ? options.cardLast4 : void 0;
|
|
859
|
+
if (last4 !== void 0 && (!exact(/^[0-9]{4}$/, last4) || masked !== void 0 && masked.slice(-4) !== last4))
|
|
860
|
+
return unknown("card_mismatch");
|
|
861
|
+
const status = p.get("status"), code = p.get("response_code");
|
|
862
|
+
if (status === "18" && APS_TOKEN_RESPONSE_CODES.includes(code)) {
|
|
863
|
+
if (p.get("service_command") !== "TOKENIZATION" || !exact(TOKEN_NAME, p.get("token_name")) || masked === void 0 || BINDING_ECHOES.some((name) => !p.has(name)))
|
|
864
|
+
return unknown("token_shape");
|
|
865
|
+
if (sent.get("remember_me") === "NO" && (APS_KEPT_CARD_CODES.includes(code) || p.has("remember_me") && p.get("remember_me") !== "NO")) {
|
|
866
|
+
return unknown("card_kept");
|
|
867
|
+
}
|
|
868
|
+
return { outcome: "token", shape: read.shape, body: read.shape === "page" ? reply.bodyText : "", ...read.shape === "redirect" ? { location: read.text } : {} };
|
|
869
|
+
}
|
|
870
|
+
if ((status === "00" || status === "17") && exact(/^[0-9]{5}$/, code) && code.startsWith(status) && APS_REFUSAL_MESSAGES.includes(code.slice(2)) && !p.has("token_name"))
|
|
871
|
+
return { outcome: "refused", code };
|
|
872
|
+
return unknown("answer_unconfirmed");
|
|
873
|
+
}
|
|
874
|
+
function apsRefusalCode(bodyText) {
|
|
875
|
+
if (typeof bodyText !== "string" || bodyText.length > ANSWER_LIMIT)
|
|
876
|
+
return null;
|
|
877
|
+
const fields = returnUrlParamsOf(bodyText);
|
|
878
|
+
const p = fields ? statedValues(fields) : null;
|
|
879
|
+
const status = p?.get("status"), code = p?.get("response_code");
|
|
880
|
+
return p && (status === "00" || status === "17") && exact(/^[0-9]{5}$/, code) && code.startsWith(status) && APS_REFUSAL_MESSAGES.includes(code.slice(2)) && !p.has("token_name") ? code : null;
|
|
881
|
+
}
|
|
882
|
+
var isApsRefusalCode = (code) => typeof code === "string" && /^(?:00|17)[0-9]{3}$/.test(code) && APS_REFUSAL_MESSAGES.includes(code.slice(2));
|
|
883
|
+
export { APS_ANSWER_REASONS, APS_CAPABILITY, APS_CARD_FIELDS, APS_HOSTS, APS_KEPT_CARD_CODES, APS_MERCHANTS, APS_MERCHANT_REFUSALS, APS_OPERATION, APS_OPTIONAL_FIELDS, APS_PAGE_CONTENT_TYPE, APS_PSP, APS_REFUSAL_CODES, APS_REFUSAL_MESSAGES, APS_SIGNED_FIELDS, APS_TEST_PANS, APS_TOKENIZATION_PATH, APS_TOKEN_RESPONSE_CODES, apsAnswerOutcome, apsBodyRefusal, apsBodyVerdict, apsCardRefusal, apsDeviceRequest, apsEnvironment, apsKeepsCardOnFile, apsMerchantPin, apsMerchantRefusal, apsRefusalCode, apsRefusalMessage, apsRequestFacts, apsRequestRefusal, apsScannedTexts, apsTokenizationUrl, isApsDeviceRequest, isApsRefusalCode, isApsTokenizationUrl };
|