@agent-cards/sdk 0.23.5 → 0.23.7
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 +9 -1
- package/PREFLIGHT.md +1 -1
- package/README.md +168 -12
- package/dist/amadeus.d.ts +16 -4
- package/dist/amadeus.js +51 -16
- package/dist/cdp.js +183 -26
- package/dist/client.d.ts +22 -0
- package/dist/client.js +165 -13
- package/dist/creditguard.generated.d.ts +1 -1
- package/dist/cybersource-secure-acceptance.d.ts +142 -34
- package/dist/cybersource-secure-acceptance.generated.d.ts +66 -0
- package/dist/cybersource-secure-acceptance.generated.js +955 -0
- package/dist/cybersource-secure-acceptance.js +244 -3
- package/dist/eprotect.d.ts +2 -2
- package/dist/eprotect.generated.d.ts +54 -15
- package/dist/eprotect.generated.js +256 -23
- package/dist/eprotect.js +5 -4
- package/dist/index.d.ts +1 -0
- package/dist/juspay.generated.d.ts +1 -1
- package/dist/pre-claim.d.ts +3 -1
- package/dist/pre-claim.js +85 -6
- package/dist/preparation.js +15 -2
- package/dist/prepared-processor.d.ts +19 -5
- package/dist/prepared-processor.js +70 -3
- package/dist/registry.js +14 -1
- package/dist/stripe-checkout.generated.js +35 -1
- package/package.json +1 -1
|
@@ -0,0 +1,955 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
// Generated from @agent-cards/payment-core. Do not edit.
|
|
3
|
+
// artifact-sha256: bd26046b49ee14791e119bd7d6c8ebf370940ee2371099e13582566579c485d5
|
|
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/test-pans.js
|
|
14
|
+
function immutableSet(values, name) {
|
|
15
|
+
const set = new Set(values);
|
|
16
|
+
for (const method of ["add", "delete", "clear"]) {
|
|
17
|
+
set[method] = () => {
|
|
18
|
+
throw new TypeError(`${name} is immutable`);
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
return Object.freeze(set);
|
|
22
|
+
}
|
|
23
|
+
var DOCUMENTED_TEST_PANS = /* @__PURE__ */ immutableSet([
|
|
24
|
+
"4242424242424242",
|
|
25
|
+
"4000000000000002",
|
|
26
|
+
"4000002500003155",
|
|
27
|
+
"4111111111111111"
|
|
28
|
+
], "DOCUMENTED_TEST_PANS");
|
|
29
|
+
// src/cybersource-secure-acceptance.js
|
|
30
|
+
var SECURE_ACCEPTANCE_PSP = "cybersource_secure_acceptance";
|
|
31
|
+
var SECURE_ACCEPTANCE_CAPABILITY = "cybersource_sa_token_create";
|
|
32
|
+
var SECURE_ACCEPTANCE_HOSTS = /* @__PURE__ */ Object.freeze({
|
|
33
|
+
production: "secureacceptance.cybersource.com",
|
|
34
|
+
sandbox: "testsecureacceptance.cybersource.com"
|
|
35
|
+
});
|
|
36
|
+
var SECURE_ACCEPTANCE_TOKEN_CREATE_PATH = "/silent/embedded/token/create";
|
|
37
|
+
var SECURE_ACCEPTANCE_SIGNED_FIELDS = /* @__PURE__ */ Object.freeze([
|
|
38
|
+
"access_key",
|
|
39
|
+
"amount",
|
|
40
|
+
"currency",
|
|
41
|
+
"locale",
|
|
42
|
+
"payment_method",
|
|
43
|
+
"profile_id",
|
|
44
|
+
"reference_number",
|
|
45
|
+
"signed_date_time",
|
|
46
|
+
"signed_field_names",
|
|
47
|
+
"transaction_type",
|
|
48
|
+
"transaction_uuid",
|
|
49
|
+
"unsigned_field_names",
|
|
50
|
+
"bill_to_forename",
|
|
51
|
+
"bill_to_surname",
|
|
52
|
+
"bill_to_email",
|
|
53
|
+
"bill_to_address_country",
|
|
54
|
+
"bill_to_address_city",
|
|
55
|
+
"bill_to_address_line1",
|
|
56
|
+
"override_custom_receipt_page",
|
|
57
|
+
"device_fingerprint_id"
|
|
58
|
+
]);
|
|
59
|
+
var SECURE_ACCEPTANCE_OPTIONAL_SIGNED_FIELDS = /* @__PURE__ */ Object.freeze(["bill_to_address_state", "bill_to_address_postal_code"]);
|
|
60
|
+
var SECURE_ACCEPTANCE_UNSIGNED_FIELDS = /* @__PURE__ */ Object.freeze([
|
|
61
|
+
"card_type",
|
|
62
|
+
"card_number",
|
|
63
|
+
"card_expiry_date",
|
|
64
|
+
"merchant_defined_data5",
|
|
65
|
+
"merchant_defined_data6",
|
|
66
|
+
"merchant_defined_data7",
|
|
67
|
+
"bill_to_address_line2",
|
|
68
|
+
"card_cvn"
|
|
69
|
+
]);
|
|
70
|
+
var SECURE_ACCEPTANCE_CARD_TYPES = /* @__PURE__ */ Object.freeze({ visa: "001", mastercard: "002", amex: "003", discover: "004" });
|
|
71
|
+
var SECURE_ACCEPTANCE_TEST_PANS = /* @__PURE__ */ immutableSet([
|
|
72
|
+
"4111111111111111",
|
|
73
|
+
"4622943127013705",
|
|
74
|
+
"4622943127013713",
|
|
75
|
+
"4622943127013721",
|
|
76
|
+
"4622943127013739",
|
|
77
|
+
"4622943127013747",
|
|
78
|
+
"2222420000001113",
|
|
79
|
+
"2222630000001125",
|
|
80
|
+
"5555555555554444",
|
|
81
|
+
"378282246310005",
|
|
82
|
+
"6011111111111117",
|
|
83
|
+
"6011000990139424",
|
|
84
|
+
"3566111111111113"
|
|
85
|
+
], "SECURE_ACCEPTANCE_TEST_PANS");
|
|
86
|
+
var SECURE_ACCEPTANCE_REQUEST_REFUSALS = /* @__PURE__ */ Object.freeze([
|
|
87
|
+
"not_token_create",
|
|
88
|
+
"content_type",
|
|
89
|
+
"origin_header",
|
|
90
|
+
"referer_header",
|
|
91
|
+
"body_unreadable",
|
|
92
|
+
"fields_unreviewed",
|
|
93
|
+
"signed_fields_unreviewed",
|
|
94
|
+
"unsigned_fields_unreviewed",
|
|
95
|
+
"transaction_type",
|
|
96
|
+
"amount_not_zero",
|
|
97
|
+
"payment_method",
|
|
98
|
+
"currency",
|
|
99
|
+
"locale",
|
|
100
|
+
"profile_id",
|
|
101
|
+
"access_key",
|
|
102
|
+
"receipt_page",
|
|
103
|
+
"keeps_card",
|
|
104
|
+
"edits_saved_card",
|
|
105
|
+
"card_type",
|
|
106
|
+
"card_fields",
|
|
107
|
+
"bill_to",
|
|
108
|
+
"signature",
|
|
109
|
+
"signed_date_time",
|
|
110
|
+
"request_ids",
|
|
111
|
+
"device_fingerprint"
|
|
112
|
+
]);
|
|
113
|
+
var SECURE_ACCEPTANCE_REFUSAL_REASON_CODES = /* @__PURE__ */ Object.freeze(["102", "202", "211", "231", "232", "240"]);
|
|
114
|
+
var SECURE_ACCEPTANCE_REPLY_REASONS = /* @__PURE__ */ Object.freeze([
|
|
115
|
+
"reply_status",
|
|
116
|
+
"reply_redirect",
|
|
117
|
+
"reply_type",
|
|
118
|
+
"reply_size",
|
|
119
|
+
"reply_markup",
|
|
120
|
+
"reply_form",
|
|
121
|
+
"reply_action",
|
|
122
|
+
"reply_fields",
|
|
123
|
+
"reply_mismatch",
|
|
124
|
+
"card_in_reply",
|
|
125
|
+
"card_mismatch",
|
|
126
|
+
"token_shape",
|
|
127
|
+
"reply_unconfirmed"
|
|
128
|
+
]);
|
|
129
|
+
var SECURE_ACCEPTANCE_PAGE_CONTENT_TYPE = "text/html;charset=UTF-8";
|
|
130
|
+
var SECURE_ACCEPTANCE_MERCHANTS = /* @__PURE__ */ deepFreeze([
|
|
131
|
+
{
|
|
132
|
+
id: "arrow_token_create",
|
|
133
|
+
merchant: "Arrow Electronics",
|
|
134
|
+
environment: "production",
|
|
135
|
+
status: "enabled",
|
|
136
|
+
origins: ["https://www.arrow.com"],
|
|
137
|
+
profileIds: ["C29B5F8F-3688-4634-BAFB-8EC6763F47CD"],
|
|
138
|
+
accessKeys: ["d8768bb0b9873004a76cd20f6fa0a922"],
|
|
139
|
+
pending: [],
|
|
140
|
+
navigation: "frame",
|
|
141
|
+
frameName: "secureAcceptanceiFrame",
|
|
142
|
+
followUp: { method: "POST", url: "https://www.arrow.com/experienceservices/cart/current/user/current/checkout/paymentmethod/set" },
|
|
143
|
+
sources: [
|
|
144
|
+
{ kind: "saved_observation", reference: "arrow-2026-10-08:secure-acceptance-silent-embedded-token-create-hidden-form", reviewed_on: "2026-10-08" },
|
|
145
|
+
{ kind: "public_page_model", reference: "arrow-2026-10-08:en-cart-and-checkout-model-json-config-cybersource", reviewed_on: "2026-10-08" }
|
|
146
|
+
]
|
|
147
|
+
}
|
|
148
|
+
]);
|
|
149
|
+
var SA_FORM_TYPE = "application/x-www-form-urlencoded";
|
|
150
|
+
var SA_URL_LIMIT = 2048;
|
|
151
|
+
var SA_BODY_LIMIT = 32 * 1024;
|
|
152
|
+
var SA_REPLY_LIMIT = 64 * 1024;
|
|
153
|
+
var SA_PROFILE_ID = /^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}$/;
|
|
154
|
+
var SA_ACCESS_KEY = /^[0-9A-Fa-f]{32}$/;
|
|
155
|
+
var SA_REQUEST_ID = /^[A-Za-z0-9-]{1,50}$/;
|
|
156
|
+
var SA_SIGNED_DATE_TIME = /^[0-9]{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12][0-9]|3[01])T(?:[01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]Z$/;
|
|
157
|
+
var SA_SIGNATURE = /^[A-Za-z0-9+/]{43}=$/;
|
|
158
|
+
var SA_DEVICE_FINGERPRINT = /^[A-Za-z0-9_.:-]{1,88}$/;
|
|
159
|
+
var SA_EXPIRY = /^(?:0[1-9]|1[0-2])-[0-9]{4}$/;
|
|
160
|
+
var saExact = (pattern, value) => typeof value === "string" && pattern.exec(value)?.[0] === value;
|
|
161
|
+
var saControlFree = (value, min, max) => typeof value === "string" && value.length >= min && value.length <= max && !/[\u0000-\u001f\u007f]/.test(value);
|
|
162
|
+
var saRecord = (value) => !!value && typeof value === "object" && !Array.isArray(value);
|
|
163
|
+
var SA_MALFORMED = "Refusing to pay: malformed payment request.";
|
|
164
|
+
var SA_SANDBOX_CARD = "Refusing to pay: a sandbox or test host accepts only the processor's documented test cards, not a real card.";
|
|
165
|
+
var SA_FILL_FAILED = "Could not fill the card fields for CyberSource Secure Acceptance. Nothing was sent.";
|
|
166
|
+
function secureAcceptanceEnvironment(hostname) {
|
|
167
|
+
if (hostname === SECURE_ACCEPTANCE_HOSTS.production)
|
|
168
|
+
return "production";
|
|
169
|
+
if (hostname === SECURE_ACCEPTANCE_HOSTS.sandbox)
|
|
170
|
+
return "sandbox";
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
function secureAcceptancePreparationUrl(environment) {
|
|
174
|
+
if (environment !== "production" && environment !== "sandbox")
|
|
175
|
+
throw new Error("unsupported_secure_acceptance_environment");
|
|
176
|
+
return `https://${SECURE_ACCEPTANCE_HOSTS[environment]}${SECURE_ACCEPTANCE_TOKEN_CREATE_PATH}`;
|
|
177
|
+
}
|
|
178
|
+
function isSecureAcceptanceTokenRequest(url, method) {
|
|
179
|
+
if (method !== "POST" || typeof url !== "string" || url.length > SA_URL_LIMIT || url.includes("?") || url.includes("#"))
|
|
180
|
+
return false;
|
|
181
|
+
let parsed;
|
|
182
|
+
try {
|
|
183
|
+
parsed = new URL(url);
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
return false;
|
|
187
|
+
}
|
|
188
|
+
return parsed.href === url && parsed.protocol === "https:" && !parsed.port && !parsed.username && !parsed.password && secureAcceptanceEnvironment(parsed.hostname) !== null && parsed.pathname === SECURE_ACCEPTANCE_TOKEN_CREATE_PATH;
|
|
189
|
+
}
|
|
190
|
+
function saFormEncode(value) {
|
|
191
|
+
let encoded;
|
|
192
|
+
try {
|
|
193
|
+
encoded = encodeURIComponent(value);
|
|
194
|
+
}
|
|
195
|
+
catch {
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
return encoded.replace(/[!'()~]/g, (ch) => `%${ch.charCodeAt(0).toString(16).toUpperCase()}`).replace(/%20/g, "+");
|
|
199
|
+
}
|
|
200
|
+
function saFormDecode(raw) {
|
|
201
|
+
if (!/^(?:[A-Za-z0-9*._+-]|%[0-9A-F]{2})*$/.test(raw))
|
|
202
|
+
return null;
|
|
203
|
+
let value;
|
|
204
|
+
try {
|
|
205
|
+
value = decodeURIComponent(raw.replace(/\+/g, "%20"));
|
|
206
|
+
}
|
|
207
|
+
catch {
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
210
|
+
return saFormEncode(value) === raw ? value : null;
|
|
211
|
+
}
|
|
212
|
+
function saParseForm(body) {
|
|
213
|
+
if (typeof body !== "string" || body === "" || body.length > SA_BODY_LIMIT)
|
|
214
|
+
return null;
|
|
215
|
+
const pairs = [];
|
|
216
|
+
const values = /* @__PURE__ */ new Map();
|
|
217
|
+
for (const chunk of body.split("&")) {
|
|
218
|
+
const separator = chunk.indexOf("=");
|
|
219
|
+
if (separator < 1)
|
|
220
|
+
return null;
|
|
221
|
+
const rawName = chunk.slice(0, separator), raw = chunk.slice(separator + 1);
|
|
222
|
+
const name = saFormDecode(rawName), value = saFormDecode(raw);
|
|
223
|
+
if (name === null || value === null || values.has(name))
|
|
224
|
+
return null;
|
|
225
|
+
pairs.push({ name, rawName, raw, value });
|
|
226
|
+
values.set(name, value);
|
|
227
|
+
}
|
|
228
|
+
return { pairs, values };
|
|
229
|
+
}
|
|
230
|
+
function saSignedListAllowed(value) {
|
|
231
|
+
const base = SECURE_ACCEPTANCE_SIGNED_FIELDS.join(",");
|
|
232
|
+
const [state, postal] = SECURE_ACCEPTANCE_OPTIONAL_SIGNED_FIELDS;
|
|
233
|
+
return [base, `${base},${state}`, `${base},${postal}`, `${base},${state},${postal}`].includes(value);
|
|
234
|
+
}
|
|
235
|
+
function saFieldListRefusal(form) {
|
|
236
|
+
const signed = form.values.get("signed_field_names");
|
|
237
|
+
if (!saSignedListAllowed(signed))
|
|
238
|
+
return "signed_fields_unreviewed";
|
|
239
|
+
if (form.values.get("unsigned_field_names") !== SECURE_ACCEPTANCE_UNSIGNED_FIELDS.join(","))
|
|
240
|
+
return "unsigned_fields_unreviewed";
|
|
241
|
+
const expected = /* @__PURE__ */ new Set([...signed.split(","), ...SECURE_ACCEPTANCE_UNSIGNED_FIELDS, "signature"]);
|
|
242
|
+
if (form.pairs.length !== expected.size || form.pairs.some(({ name }) => !expected.has(name)))
|
|
243
|
+
return "fields_unreviewed";
|
|
244
|
+
return null;
|
|
245
|
+
}
|
|
246
|
+
function saExpiryOf(card) {
|
|
247
|
+
const month = typeof card?.month === "number" || typeof card?.month === "string" ? String(card.month) : "";
|
|
248
|
+
const year = typeof card?.year === "number" || typeof card?.year === "string" ? String(card.year) : "";
|
|
249
|
+
if (!/^[0-9]{1,2}$/.test(month) || Number(month) < 1 || Number(month) > 12 || !/^(?:[0-9]{2}|[0-9]{4})$/.test(year))
|
|
250
|
+
return null;
|
|
251
|
+
return `${month.padStart(2, "0")}-${year.length === 2 ? `20${year}` : year}`;
|
|
252
|
+
}
|
|
253
|
+
var SA_CARD_VALUES = /* @__PURE__ */ new Map([
|
|
254
|
+
["card_type", (value) => Object.values(SECURE_ACCEPTANCE_CARD_TYPES).includes(value)],
|
|
255
|
+
["card_number", (value) => saExact(/^[0-9]{13,19}$/, value)],
|
|
256
|
+
["card_expiry_date", (value) => saExact(SA_EXPIRY, value)],
|
|
257
|
+
["card_cvn", (value) => saExact(/^[0-9]{3,4}$/, value)]
|
|
258
|
+
]);
|
|
259
|
+
var SA_BILL_TO_RULES = /* @__PURE__ */ new Map([
|
|
260
|
+
["bill_to_forename", (value) => saControlFree(value, 1, 60)],
|
|
261
|
+
["bill_to_surname", (value) => saControlFree(value, 1, 60)],
|
|
262
|
+
["bill_to_address_line1", (value) => saControlFree(value, 1, 60)],
|
|
263
|
+
["bill_to_email", (value) => saControlFree(value, 1, 255) && value.split("@").length === 2],
|
|
264
|
+
["bill_to_address_city", (value) => saControlFree(value, 1, 50)],
|
|
265
|
+
["bill_to_address_country", (value) => saExact(/^[A-Z]{2}$/, value)],
|
|
266
|
+
["bill_to_address_state", (value) => saControlFree(value, 1, 60)],
|
|
267
|
+
["bill_to_address_postal_code", (value) => saExact(/^[A-Za-z0-9 -]{1,10}$/, value)],
|
|
268
|
+
["bill_to_address_line2", (value) => saControlFree(value, 0, 60)]
|
|
269
|
+
]);
|
|
270
|
+
function saAdmittedOrigin(merchantOrigin, environment) {
|
|
271
|
+
if (typeof merchantOrigin !== "string" || merchantOrigin.length > SA_URL_LIMIT)
|
|
272
|
+
return null;
|
|
273
|
+
let parsed;
|
|
274
|
+
try {
|
|
275
|
+
parsed = new URL(merchantOrigin);
|
|
276
|
+
}
|
|
277
|
+
catch {
|
|
278
|
+
return null;
|
|
279
|
+
}
|
|
280
|
+
if (parsed.origin !== merchantOrigin)
|
|
281
|
+
return null;
|
|
282
|
+
if (parsed.protocol === "https:")
|
|
283
|
+
return merchantOrigin;
|
|
284
|
+
return environment === "sandbox" && parsed.protocol === "http:" && parsed.hostname === "localhost" ? merchantOrigin : null;
|
|
285
|
+
}
|
|
286
|
+
function saUrlOnOrigin(value, origin) {
|
|
287
|
+
if (typeof value !== "string" || value.length > SA_URL_LIMIT || value.includes("#") || /[\u0000-\u0020\u007f]/.test(value))
|
|
288
|
+
return false;
|
|
289
|
+
let parsed;
|
|
290
|
+
try {
|
|
291
|
+
parsed = new URL(value);
|
|
292
|
+
}
|
|
293
|
+
catch {
|
|
294
|
+
return false;
|
|
295
|
+
}
|
|
296
|
+
return parsed.origin === origin && !parsed.username && !parsed.password;
|
|
297
|
+
}
|
|
298
|
+
function saRequestHeaders(headers) {
|
|
299
|
+
if (headers === void 0)
|
|
300
|
+
return /* @__PURE__ */ new Map();
|
|
301
|
+
if (!saRecord(headers))
|
|
302
|
+
return null;
|
|
303
|
+
const out = /* @__PURE__ */ new Map();
|
|
304
|
+
for (const [name, value] of Object.entries(headers)) {
|
|
305
|
+
const lower = name.toLowerCase();
|
|
306
|
+
if (typeof value !== "string" || out.has(lower))
|
|
307
|
+
return null;
|
|
308
|
+
out.set(lower, value);
|
|
309
|
+
}
|
|
310
|
+
return out;
|
|
311
|
+
}
|
|
312
|
+
function secureAcceptanceRequestRefusal(request, options) {
|
|
313
|
+
if (!saRecord(request) || !isSecureAcceptanceTokenRequest(request.url, request.method))
|
|
314
|
+
return "not_token_create";
|
|
315
|
+
const environment = secureAcceptanceEnvironment(new URL(request.url).hostname);
|
|
316
|
+
const origin = saAdmittedOrigin(saRecord(options) ? options.merchantOrigin : void 0, environment);
|
|
317
|
+
const headers = saRequestHeaders(request.headers);
|
|
318
|
+
const type = headers?.get("content-type");
|
|
319
|
+
if (!headers || typeof type !== "string" || type.trim().toLowerCase() !== SA_FORM_TYPE)
|
|
320
|
+
return "content_type";
|
|
321
|
+
if (!origin || headers.get("origin") !== origin)
|
|
322
|
+
return "origin_header";
|
|
323
|
+
if (headers.has("referer")) {
|
|
324
|
+
const referer = headers.get("referer");
|
|
325
|
+
let parsed = null;
|
|
326
|
+
try {
|
|
327
|
+
parsed = new URL(referer);
|
|
328
|
+
}
|
|
329
|
+
catch {
|
|
330
|
+
parsed = null;
|
|
331
|
+
}
|
|
332
|
+
if (!parsed || parsed.href !== referer || !saUrlOnOrigin(referer, origin))
|
|
333
|
+
return "referer_header";
|
|
334
|
+
}
|
|
335
|
+
const form = saParseForm(request.body);
|
|
336
|
+
if (!form)
|
|
337
|
+
return "body_unreadable";
|
|
338
|
+
const lists = saFieldListRefusal(form);
|
|
339
|
+
if (lists)
|
|
340
|
+
return lists;
|
|
341
|
+
const v = (name) => form.values.get(name);
|
|
342
|
+
if (v("transaction_type") !== "create_payment_token")
|
|
343
|
+
return "transaction_type";
|
|
344
|
+
if (v("amount") !== "0")
|
|
345
|
+
return "amount_not_zero";
|
|
346
|
+
if (v("payment_method") !== "card")
|
|
347
|
+
return "payment_method";
|
|
348
|
+
const currency = saRecord(options) ? options.currency : void 0;
|
|
349
|
+
if (!saExact(/^[A-Z]{3}$/, v("currency")) || currency !== void 0 && (typeof currency !== "string" || v("currency").toLowerCase() !== currency.toLowerCase()))
|
|
350
|
+
return "currency";
|
|
351
|
+
if (!saExact(/^[a-z]{2}(?:-[a-z]{2})?$/, v("locale")))
|
|
352
|
+
return "locale";
|
|
353
|
+
if (!saExact(SA_PROFILE_ID, v("profile_id")))
|
|
354
|
+
return "profile_id";
|
|
355
|
+
if (!saExact(SA_ACCESS_KEY, v("access_key")))
|
|
356
|
+
return "access_key";
|
|
357
|
+
if (!saUrlOnOrigin(v("override_custom_receipt_page"), origin))
|
|
358
|
+
return "receipt_page";
|
|
359
|
+
if (v("merchant_defined_data5") !== "false" || v("merchant_defined_data6") !== "false")
|
|
360
|
+
return "keeps_card";
|
|
361
|
+
if (v("merchant_defined_data7") !== "")
|
|
362
|
+
return "edits_saved_card";
|
|
363
|
+
if (!SA_CARD_VALUES.get("card_type")(v("card_type")))
|
|
364
|
+
return "card_type";
|
|
365
|
+
if (!["card_number", "card_expiry_date", "card_cvn"].every((name) => SA_CARD_VALUES.get(name)(v(name))))
|
|
366
|
+
return "card_fields";
|
|
367
|
+
for (const [name, rule] of SA_BILL_TO_RULES) {
|
|
368
|
+
if (form.values.has(name) && !rule(v(name)))
|
|
369
|
+
return "bill_to";
|
|
370
|
+
}
|
|
371
|
+
if (!saExact(SA_SIGNATURE, v("signature")))
|
|
372
|
+
return "signature";
|
|
373
|
+
if (!saExact(SA_SIGNED_DATE_TIME, v("signed_date_time")))
|
|
374
|
+
return "signed_date_time";
|
|
375
|
+
if (!saExact(SA_REQUEST_ID, v("reference_number")) || !saExact(SA_REQUEST_ID, v("transaction_uuid")))
|
|
376
|
+
return "request_ids";
|
|
377
|
+
if (!saExact(SA_DEVICE_FINGERPRINT, v("device_fingerprint_id")))
|
|
378
|
+
return "device_fingerprint";
|
|
379
|
+
return null;
|
|
380
|
+
}
|
|
381
|
+
function secureAcceptanceRequestFacts(request) {
|
|
382
|
+
if (!saRecord(request) || !isSecureAcceptanceTokenRequest(request.url, request.method))
|
|
383
|
+
return null;
|
|
384
|
+
const form = saParseForm(request.body);
|
|
385
|
+
if (!form)
|
|
386
|
+
return null;
|
|
387
|
+
const v = (name) => form.values.get(name);
|
|
388
|
+
if (!saExact(SA_PROFILE_ID, v("profile_id")) || !saExact(SA_ACCESS_KEY, v("access_key")) || !saExact(SA_REQUEST_ID, v("transaction_uuid")) || !saExact(SA_REQUEST_ID, v("reference_number")) || !saExact(/^[A-Z]{3}$/, v("currency")) || !saExact(SA_SIGNED_DATE_TIME, v("signed_date_time")))
|
|
389
|
+
return null;
|
|
390
|
+
return {
|
|
391
|
+
environment: secureAcceptanceEnvironment(new URL(request.url).hostname),
|
|
392
|
+
profileId: v("profile_id"),
|
|
393
|
+
accessKey: v("access_key"),
|
|
394
|
+
transactionUuid: v("transaction_uuid"),
|
|
395
|
+
referenceNumber: v("reference_number"),
|
|
396
|
+
currency: v("currency"),
|
|
397
|
+
signedDateTime: v("signed_date_time")
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
function isPreparedSecureAcceptanceRequest(url, method, body, headers, environment, binding) {
|
|
401
|
+
if (environment !== "production" && environment !== "sandbox")
|
|
402
|
+
return false;
|
|
403
|
+
const profileId = saRecord(binding) ? binding.profileId : void 0;
|
|
404
|
+
const merchantOrigin = saRecord(binding) ? binding.merchantOrigin : void 0;
|
|
405
|
+
if (!saExact(SA_PROFILE_ID, profileId))
|
|
406
|
+
return false;
|
|
407
|
+
const request = { url, method, headers, body };
|
|
408
|
+
if (secureAcceptanceRequestRefusal(request, { merchantOrigin }) !== null)
|
|
409
|
+
return false;
|
|
410
|
+
const facts = secureAcceptanceRequestFacts(request);
|
|
411
|
+
return !!facts && facts.environment === environment && facts.profileId === profileId;
|
|
412
|
+
}
|
|
413
|
+
function secureAcceptanceMerchantEntry(profileId, merchantOrigin, entries = SECURE_ACCEPTANCE_MERCHANTS) {
|
|
414
|
+
if (!saExact(SA_PROFILE_ID, profileId) || typeof merchantOrigin !== "string" || !Array.isArray(entries))
|
|
415
|
+
return null;
|
|
416
|
+
return entries.find((entry) => saRecord(entry) && entry.environment === "production" && Array.isArray(entry.origins) && Array.isArray(entry.profileIds) && entry.origins.includes(merchantOrigin) && entry.profileIds.includes(profileId)) ?? null;
|
|
417
|
+
}
|
|
418
|
+
function secureAcceptanceMerchantRefusal(environment, profileId, merchantOrigin, accessKey, entries = SECURE_ACCEPTANCE_MERCHANTS) {
|
|
419
|
+
if (!saExact(SA_PROFILE_ID, profileId))
|
|
420
|
+
return "invalid_profile_id";
|
|
421
|
+
if (environment === "sandbox")
|
|
422
|
+
return null;
|
|
423
|
+
if (environment !== "production")
|
|
424
|
+
return "unsupported_environment";
|
|
425
|
+
const forOrigin = (Array.isArray(entries) ? entries : []).filter((entry2) => saRecord(entry2) && entry2.environment === "production" && Array.isArray(entry2.origins) && entry2.origins.includes(merchantOrigin));
|
|
426
|
+
if (forOrigin.length === 0)
|
|
427
|
+
return "secure_acceptance_merchant_unreviewed";
|
|
428
|
+
const pending = (entry2) => !Array.isArray(entry2.pending) || entry2.pending.length > 0;
|
|
429
|
+
const entry = secureAcceptanceMerchantEntry(profileId, merchantOrigin, forOrigin);
|
|
430
|
+
if (!entry)
|
|
431
|
+
return forOrigin.some(pending) ? "secure_acceptance_merchant_pending" : "secure_acceptance_merchant_unreviewed";
|
|
432
|
+
if (pending(entry))
|
|
433
|
+
return "secure_acceptance_merchant_pending";
|
|
434
|
+
if (accessKey !== void 0 && !(Array.isArray(entry.accessKeys) && entry.accessKeys.includes(accessKey)))
|
|
435
|
+
return "secure_acceptance_access_key_unreviewed";
|
|
436
|
+
return entry.status === "enabled" ? null : "secure_acceptance_merchant_observe";
|
|
437
|
+
}
|
|
438
|
+
function secureAcceptanceCardType(number) {
|
|
439
|
+
if (typeof number !== "string" || !/^[0-9]{12,19}$/.test(number))
|
|
440
|
+
return null;
|
|
441
|
+
const two = Number(number.slice(0, 2)), three = Number(number.slice(0, 3)), four = Number(number.slice(0, 4));
|
|
442
|
+
if (number[0] === "4")
|
|
443
|
+
return SECURE_ACCEPTANCE_CARD_TYPES.visa;
|
|
444
|
+
if (two >= 51 && two <= 55 || four >= 2221 && four <= 2720)
|
|
445
|
+
return SECURE_ACCEPTANCE_CARD_TYPES.mastercard;
|
|
446
|
+
if (two === 34 || two === 37)
|
|
447
|
+
return SECURE_ACCEPTANCE_CARD_TYPES.amex;
|
|
448
|
+
if (four === 6011 || three >= 644 && three <= 649 || two === 65)
|
|
449
|
+
return SECURE_ACCEPTANCE_CARD_TYPES.discover;
|
|
450
|
+
return null;
|
|
451
|
+
}
|
|
452
|
+
function saLuhn(number) {
|
|
453
|
+
let sum = 0;
|
|
454
|
+
for (let i = 0; i < number.length; i++) {
|
|
455
|
+
let digit = number.charCodeAt(number.length - 1 - i) - 48;
|
|
456
|
+
if (i % 2 === 1) {
|
|
457
|
+
digit *= 2;
|
|
458
|
+
if (digit > 9)
|
|
459
|
+
digit -= 9;
|
|
460
|
+
}
|
|
461
|
+
sum += digit;
|
|
462
|
+
}
|
|
463
|
+
return sum % 10 === 0;
|
|
464
|
+
}
|
|
465
|
+
function secureAcceptanceCardRefusal(environment, card) {
|
|
466
|
+
if (environment !== "production" && environment !== "sandbox")
|
|
467
|
+
return "unsupported_environment";
|
|
468
|
+
const pan = saRecord(card) && typeof card.number === "string" ? card.number : "";
|
|
469
|
+
if (!/^[0-9]{13,19}$/.test(pan) || /^([0-9])\1+$/.test(pan) || !saLuhn(pan))
|
|
470
|
+
return "invalid_card_number";
|
|
471
|
+
if (!secureAcceptanceCardType(pan))
|
|
472
|
+
return "card_network_unsupported";
|
|
473
|
+
if (environment === "sandbox" && !SECURE_ACCEPTANCE_TEST_PANS.has(pan))
|
|
474
|
+
return "sandbox_host_requires_documented_test_card";
|
|
475
|
+
if (environment === "production" && (SECURE_ACCEPTANCE_TEST_PANS.has(pan) || DOCUMENTED_TEST_PANS.has(pan)))
|
|
476
|
+
return "live_host_refuses_documented_test_card";
|
|
477
|
+
if (!saExpiryOf(card))
|
|
478
|
+
return "invalid_expiry";
|
|
479
|
+
if (!saExact(/^[0-9]{3,4}$/, card.cvc))
|
|
480
|
+
return "invalid_cvn";
|
|
481
|
+
return null;
|
|
482
|
+
}
|
|
483
|
+
function isSecureAcceptanceDeviceRequest(templateBody, deviceBody, environment) {
|
|
484
|
+
const template = saParseForm(templateBody);
|
|
485
|
+
const device = saParseForm(deviceBody);
|
|
486
|
+
if (!template || !device || saFieldListRefusal(template) !== null || device.pairs.length !== template.pairs.length)
|
|
487
|
+
return false;
|
|
488
|
+
return device.pairs.every((pair, i) => {
|
|
489
|
+
const page = template.pairs[i];
|
|
490
|
+
if (pair.rawName !== page.rawName)
|
|
491
|
+
return false;
|
|
492
|
+
const rule = SA_CARD_VALUES.get(pair.name);
|
|
493
|
+
if (!rule)
|
|
494
|
+
return pair.raw === page.raw;
|
|
495
|
+
if (!rule(pair.value))
|
|
496
|
+
return false;
|
|
497
|
+
if (pair.name !== "card_number" || pair.raw !== page.raw)
|
|
498
|
+
return true;
|
|
499
|
+
return environment === "sandbox" && SECURE_ACCEPTANCE_TEST_PANS.has(pair.value);
|
|
500
|
+
});
|
|
501
|
+
}
|
|
502
|
+
function secureAcceptanceDeviceRequest(request, card, options) {
|
|
503
|
+
const merchantOrigin = saRecord(options) ? options.merchantOrigin : void 0;
|
|
504
|
+
if (typeof merchantOrigin !== "string" || secureAcceptanceRequestRefusal(request, { merchantOrigin }) !== null)
|
|
505
|
+
throw new Error(SA_MALFORMED);
|
|
506
|
+
const environment = secureAcceptanceEnvironment(new URL(request.url).hostname);
|
|
507
|
+
const refusal = secureAcceptanceCardRefusal(environment, card);
|
|
508
|
+
if (refusal === "sandbox_host_requires_documented_test_card")
|
|
509
|
+
throw new Error(SA_SANDBOX_CARD);
|
|
510
|
+
if (refusal)
|
|
511
|
+
throw new Error(SA_FILL_FAILED);
|
|
512
|
+
const fresh = /* @__PURE__ */ new Map([
|
|
513
|
+
["card_type", secureAcceptanceCardType(card.number)],
|
|
514
|
+
["card_number", card.number],
|
|
515
|
+
["card_expiry_date", saExpiryOf(card)],
|
|
516
|
+
["card_cvn", card.cvc]
|
|
517
|
+
]);
|
|
518
|
+
const form = saParseForm(request.body);
|
|
519
|
+
const body = form.pairs.map(({ name, rawName, raw }) => `${rawName}=${fresh.has(name) ? saFormEncode(fresh.get(name)) : raw}`).join("&");
|
|
520
|
+
if (!isSecureAcceptanceDeviceRequest(request.body, body, environment))
|
|
521
|
+
throw new Error(SA_MALFORMED);
|
|
522
|
+
const captured = saRequestHeaders(request.headers);
|
|
523
|
+
const headers = { "content-type": SA_FORM_TYPE, origin: merchantOrigin };
|
|
524
|
+
if (captured.has("referer"))
|
|
525
|
+
headers.referer = captured.get("referer");
|
|
526
|
+
return { url: request.url, method: "POST", headers, body, where: form.pairs.filter(({ name }) => fresh.has(name)).map(({ name }) => name) };
|
|
527
|
+
}
|
|
528
|
+
var SA_ENTITY = { amp: "&", lt: "<", gt: ">", quot: '"', apos: "'", nbsp: " " };
|
|
529
|
+
function saDecodeEntities(text) {
|
|
530
|
+
return text.replace(/&(#x[0-9a-f]{1,6}|#[0-9]{1,7}|[a-z]{2,6});/gi, (whole, name) => {
|
|
531
|
+
if (name[0] === "#") {
|
|
532
|
+
const code = name[1] === "x" || name[1] === "X" ? parseInt(name.slice(2), 16) : parseInt(name.slice(1), 10);
|
|
533
|
+
return Number.isFinite(code) && code > 0 && code <= 1114111 ? String.fromCodePoint(code) : whole;
|
|
534
|
+
}
|
|
535
|
+
return Object.prototype.hasOwnProperty.call(SA_ENTITY, name.toLowerCase()) ? SA_ENTITY[name.toLowerCase()] : whole;
|
|
536
|
+
});
|
|
537
|
+
}
|
|
538
|
+
function saReadAttributes(html, from, to) {
|
|
539
|
+
const out = /* @__PURE__ */ new Map();
|
|
540
|
+
let at = from;
|
|
541
|
+
while (at < to) {
|
|
542
|
+
if (/\s/.test(html[at]) || html[at] === "/") {
|
|
543
|
+
at += 1;
|
|
544
|
+
continue;
|
|
545
|
+
}
|
|
546
|
+
let end = at;
|
|
547
|
+
while (end < to && !/[\s"'>/=]/.test(html[end]))
|
|
548
|
+
end += 1;
|
|
549
|
+
if (end === at)
|
|
550
|
+
return null;
|
|
551
|
+
const name = html.slice(at, end).toLowerCase();
|
|
552
|
+
at = end;
|
|
553
|
+
while (at < to && /\s/.test(html[at]))
|
|
554
|
+
at += 1;
|
|
555
|
+
let start = -1, stop = -1;
|
|
556
|
+
if (html[at] === "=") {
|
|
557
|
+
at += 1;
|
|
558
|
+
while (at < to && /\s/.test(html[at]))
|
|
559
|
+
at += 1;
|
|
560
|
+
const quote = html[at];
|
|
561
|
+
if (quote === '"' || quote === "'") {
|
|
562
|
+
const close = html.indexOf(quote, at + 1);
|
|
563
|
+
if (close < 0 || close >= to)
|
|
564
|
+
return null;
|
|
565
|
+
start = at + 1;
|
|
566
|
+
stop = close;
|
|
567
|
+
at = close + 1;
|
|
568
|
+
}
|
|
569
|
+
else {
|
|
570
|
+
let last = at;
|
|
571
|
+
while (last < to && !/[\s"'=<>`]/.test(html[last]))
|
|
572
|
+
last += 1;
|
|
573
|
+
if (last === at)
|
|
574
|
+
return null;
|
|
575
|
+
start = at;
|
|
576
|
+
stop = last;
|
|
577
|
+
at = last;
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
if (out.has(name))
|
|
581
|
+
return null;
|
|
582
|
+
out.set(name, { value: start < 0 ? "" : saDecodeEntities(html.slice(start, stop)), start, end: stop });
|
|
583
|
+
}
|
|
584
|
+
return out;
|
|
585
|
+
}
|
|
586
|
+
var SA_REFUSED_TAGS = /* @__PURE__ */ new Set([
|
|
587
|
+
"iframe",
|
|
588
|
+
"frame",
|
|
589
|
+
"frameset",
|
|
590
|
+
"object",
|
|
591
|
+
"embed",
|
|
592
|
+
"applet",
|
|
593
|
+
"img",
|
|
594
|
+
"image",
|
|
595
|
+
"picture",
|
|
596
|
+
"source",
|
|
597
|
+
"audio",
|
|
598
|
+
"video",
|
|
599
|
+
"track",
|
|
600
|
+
"link",
|
|
601
|
+
"base",
|
|
602
|
+
"portal",
|
|
603
|
+
"svg",
|
|
604
|
+
"style",
|
|
605
|
+
"textarea",
|
|
606
|
+
"select",
|
|
607
|
+
"button",
|
|
608
|
+
"isindex",
|
|
609
|
+
"keygen",
|
|
610
|
+
"xmp",
|
|
611
|
+
"plaintext",
|
|
612
|
+
"noembed",
|
|
613
|
+
"noframes",
|
|
614
|
+
"template"
|
|
615
|
+
]);
|
|
616
|
+
var SA_URL_ATTRIBUTES = /* @__PURE__ */ new Set([
|
|
617
|
+
"src",
|
|
618
|
+
"srcset",
|
|
619
|
+
"href",
|
|
620
|
+
"background",
|
|
621
|
+
"poster",
|
|
622
|
+
"data",
|
|
623
|
+
"action",
|
|
624
|
+
"formaction",
|
|
625
|
+
"ping",
|
|
626
|
+
"manifest",
|
|
627
|
+
"lowsrc",
|
|
628
|
+
"dynsrc",
|
|
629
|
+
"codebase",
|
|
630
|
+
"archive",
|
|
631
|
+
"longdesc",
|
|
632
|
+
"cite",
|
|
633
|
+
"xlink:href",
|
|
634
|
+
"icon",
|
|
635
|
+
"imagesrcset"
|
|
636
|
+
]);
|
|
637
|
+
var SA_FIELD_NAME = /^[a-z][a-z0-9_]{0,63}$/;
|
|
638
|
+
function saReadReplyPage(html, scriptOrigin) {
|
|
639
|
+
const tags = [];
|
|
640
|
+
const ends = [];
|
|
641
|
+
let at = 0;
|
|
642
|
+
while (at < html.length) {
|
|
643
|
+
const open = html.indexOf("<", at);
|
|
644
|
+
if (open < 0)
|
|
645
|
+
break;
|
|
646
|
+
if (html.startsWith("<!--", open)) {
|
|
647
|
+
const close = html.indexOf("-->", open + 4);
|
|
648
|
+
if (close < 0)
|
|
649
|
+
return { error: "reply_markup" };
|
|
650
|
+
at = close + 3;
|
|
651
|
+
continue;
|
|
652
|
+
}
|
|
653
|
+
const next = html.charAt(open + 1);
|
|
654
|
+
if (next === "!") {
|
|
655
|
+
const doctype = /<!doctype\s+html\s*>/iy;
|
|
656
|
+
doctype.lastIndex = open;
|
|
657
|
+
if (!doctype.test(html))
|
|
658
|
+
return { error: "reply_markup" };
|
|
659
|
+
at = doctype.lastIndex;
|
|
660
|
+
continue;
|
|
661
|
+
}
|
|
662
|
+
if (next === "/") {
|
|
663
|
+
const end = /<\/([A-Za-z][A-Za-z0-9-]*)\s*>/y;
|
|
664
|
+
end.lastIndex = open;
|
|
665
|
+
const match2 = end.exec(html);
|
|
666
|
+
if (!match2)
|
|
667
|
+
return { error: "reply_markup" };
|
|
668
|
+
ends.push({ name: match2[1].toLowerCase(), start: open });
|
|
669
|
+
at = end.lastIndex;
|
|
670
|
+
continue;
|
|
671
|
+
}
|
|
672
|
+
if (next === "?")
|
|
673
|
+
return { error: "reply_markup" };
|
|
674
|
+
if (!/[A-Za-z]/.test(next)) {
|
|
675
|
+
at = open + 1;
|
|
676
|
+
continue;
|
|
677
|
+
}
|
|
678
|
+
const start = /<([A-Za-z][A-Za-z0-9-]*)((?:[^>"']|"[^"]*"|'[^']*')*)>/y;
|
|
679
|
+
start.lastIndex = open;
|
|
680
|
+
const match = start.exec(html);
|
|
681
|
+
if (!match)
|
|
682
|
+
return { error: "reply_markup" };
|
|
683
|
+
const name = match[1].toLowerCase();
|
|
684
|
+
const tag = { name, start: open, end: start.lastIndex, attrs: saReadAttributes(html, open + 1 + match[1].length, start.lastIndex - 1) };
|
|
685
|
+
tags.push(tag);
|
|
686
|
+
at = start.lastIndex;
|
|
687
|
+
if (name === "script" || name === "title") {
|
|
688
|
+
const close = new RegExp(`</${name}[\\s/>]`, "ig");
|
|
689
|
+
close.lastIndex = at;
|
|
690
|
+
const found = close.exec(html);
|
|
691
|
+
if (!found)
|
|
692
|
+
return { error: "reply_markup" };
|
|
693
|
+
const after = html.indexOf(">", found.index);
|
|
694
|
+
if (after < 0)
|
|
695
|
+
return { error: "reply_markup" };
|
|
696
|
+
at = after + 1;
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
for (const tag of tags) {
|
|
700
|
+
if (SA_REFUSED_TAGS.has(tag.name))
|
|
701
|
+
return { error: "reply_markup" };
|
|
702
|
+
if (!tag.attrs)
|
|
703
|
+
return { error: tag.name === "form" || tag.name === "input" ? "reply_form" : "reply_markup" };
|
|
704
|
+
for (const [attribute, { value }] of tag.attrs) {
|
|
705
|
+
if (SA_URL_ATTRIBUTES.has(attribute) && !(attribute === "action" && tag.name === "form") && !(attribute === "src" && tag.name === "script")) {
|
|
706
|
+
return { error: "reply_markup" };
|
|
707
|
+
}
|
|
708
|
+
if (attribute === "style" && /url\s*\(|@import|expression\s*\(/i.test(value))
|
|
709
|
+
return { error: "reply_markup" };
|
|
710
|
+
}
|
|
711
|
+
if (tag.name === "meta" && (tag.attrs.get("http-equiv")?.value ?? "").trim().toLowerCase() === "refresh")
|
|
712
|
+
return { error: "reply_markup" };
|
|
713
|
+
if (tag.name === "script" && tag.attrs.has("src")) {
|
|
714
|
+
let src = null;
|
|
715
|
+
try {
|
|
716
|
+
src = new URL(tag.attrs.get("src").value, `${scriptOrigin}/`);
|
|
717
|
+
}
|
|
718
|
+
catch {
|
|
719
|
+
src = null;
|
|
720
|
+
}
|
|
721
|
+
if (!src || src.origin !== scriptOrigin)
|
|
722
|
+
return { error: "reply_markup" };
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
const forms = tags.filter((tag) => tag.name === "form");
|
|
726
|
+
if (forms.length !== 1)
|
|
727
|
+
return { error: "reply_form" };
|
|
728
|
+
const form = forms[0];
|
|
729
|
+
const target = form.attrs.get("target")?.value.trim().toLowerCase();
|
|
730
|
+
const enctype = form.attrs.get("enctype")?.value.trim().toLowerCase();
|
|
731
|
+
if (target !== void 0 && target !== "_self" || enctype !== void 0 && enctype !== SA_FORM_TYPE || (form.attrs.get("method")?.value.trim().toLowerCase() ?? "") !== "post")
|
|
732
|
+
return { error: "reply_form" };
|
|
733
|
+
const closing = ends.find((end) => end.name === "form" && end.start > form.start);
|
|
734
|
+
const inside = (tag) => tag.start > form.start && tag.start < (closing ? closing.start : html.length);
|
|
735
|
+
const fields = /* @__PURE__ */ new Map();
|
|
736
|
+
let submits = 0;
|
|
737
|
+
for (const input of tags.filter((tag) => tag.name === "input")) {
|
|
738
|
+
if (!inside(input) || [...input.attrs.keys()].some((attribute) => attribute.startsWith("form")))
|
|
739
|
+
return { error: "reply_form" };
|
|
740
|
+
const type = (input.attrs.get("type")?.value ?? "text").trim().toLowerCase();
|
|
741
|
+
if (type === "submit" && !input.attrs.has("name") && submits === 0) {
|
|
742
|
+
submits += 1;
|
|
743
|
+
continue;
|
|
744
|
+
}
|
|
745
|
+
const fieldName = input.attrs.get("name")?.value;
|
|
746
|
+
if (type !== "hidden" || !saExact(SA_FIELD_NAME, fieldName) || fields.has(fieldName))
|
|
747
|
+
return { error: "reply_form" };
|
|
748
|
+
const value = input.attrs.get("value") ?? { value: "", start: -1, end: -1 };
|
|
749
|
+
fields.set(fieldName, value);
|
|
750
|
+
}
|
|
751
|
+
return { action: form.attrs.get("action")?.value, fields };
|
|
752
|
+
}
|
|
753
|
+
function saTemplateFields(template) {
|
|
754
|
+
if (!saRecord(template) || !isSecureAcceptanceTokenRequest(template.url, "POST"))
|
|
755
|
+
return null;
|
|
756
|
+
const form = saParseForm(template.body);
|
|
757
|
+
if (!form || typeof form.values.get("override_custom_receipt_page") !== "string")
|
|
758
|
+
return null;
|
|
759
|
+
return { origin: new URL(template.url).origin, values: form.values };
|
|
760
|
+
}
|
|
761
|
+
var SA_SCRUBBED = /* @__PURE__ */ new Map([
|
|
762
|
+
["signature", SA_SIGNATURE],
|
|
763
|
+
["payment_token", /^[0-9A-Fa-f]{32}$/],
|
|
764
|
+
["transaction_id", /^[0-9]{22}$/],
|
|
765
|
+
["request_token", /^[A-Za-z0-9+/]{16,512}={0,2}$/],
|
|
766
|
+
["payment_token_instrument_identifier_id", /^[0-9]{12,32}$/],
|
|
767
|
+
["payment_account_reference", /^(?=[A-Za-z0-9]*[A-Za-z])[A-Za-z0-9]{29}$/],
|
|
768
|
+
["auth_trans_ref_no", /^(?=[A-Za-z0-9]*[A-Za-z])[A-Za-z0-9]{1,60}$/]
|
|
769
|
+
]);
|
|
770
|
+
var SA_CARD_ECHOES = /* @__PURE__ */ new Set(["req_card_type", "req_card_number", "req_card_expiry_date", "req_card_cvn"]);
|
|
771
|
+
function saScannedPage(html, page, sent) {
|
|
772
|
+
const spans = [];
|
|
773
|
+
for (const [name, field] of page.fields) {
|
|
774
|
+
if (field.start >= 0 && field.start !== field.end && saScrubbedOrEchoed(name, field, sent))
|
|
775
|
+
spans.push(field);
|
|
776
|
+
}
|
|
777
|
+
spans.sort((a, b) => b.start - a.start);
|
|
778
|
+
let out = html;
|
|
779
|
+
for (const { start, end } of spans)
|
|
780
|
+
out = out.slice(0, start) + out.slice(end);
|
|
781
|
+
return out;
|
|
782
|
+
}
|
|
783
|
+
function secureAcceptanceScannedPage(bodyText, template) {
|
|
784
|
+
if (typeof bodyText !== "string")
|
|
785
|
+
return null;
|
|
786
|
+
const sent = template === void 0 ? null : saTemplateFields(template);
|
|
787
|
+
if (template !== void 0 && !sent)
|
|
788
|
+
return null;
|
|
789
|
+
const page = saReadReplyPage(bodyText, sent ? sent.origin : `https://${SECURE_ACCEPTANCE_HOSTS.production}`);
|
|
790
|
+
return page.error ? null : saScannedPage(bodyText, page, sent);
|
|
791
|
+
}
|
|
792
|
+
function saWritesCardNumber(text) {
|
|
793
|
+
const digit = String.raw `(?:[0-9]|\\u003[0-9]|%3[0-9]|�*(?:4[89]|5[0-7]);|�*3[0-9];)`;
|
|
794
|
+
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});`;
|
|
795
|
+
const separator = `(?:${escape}|[^0-9A-Za-z\\\\%&]|%(?![0-9a-f]{2})|&(?!#?x?[0-9a-z]+;))`;
|
|
796
|
+
for (const [run] of text.matchAll(new RegExp(`${digit}(?:${separator}*${digit})*`, "gi"))) {
|
|
797
|
+
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);
|
|
798
|
+
for (let first = 0; first < groups.length; first += 1) {
|
|
799
|
+
let digits = "";
|
|
800
|
+
for (let last = first; last < groups.length; last += 1) {
|
|
801
|
+
digits += groups[last];
|
|
802
|
+
if (digits.length > 19)
|
|
803
|
+
break;
|
|
804
|
+
if (digits.length >= 13 && digits[0] >= "2" && digits[0] <= "6" && saLuhn(digits))
|
|
805
|
+
return true;
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
return false;
|
|
810
|
+
}
|
|
811
|
+
function saUnescaped(text) {
|
|
812
|
+
let out = text;
|
|
813
|
+
for (let pass = 0; pass < 16; pass += 1) {
|
|
814
|
+
const next = out.replace(/%([0-9A-Fa-f]{2})/g, (_, hex) => String.fromCharCode(parseInt(hex, 16)));
|
|
815
|
+
if (next === out)
|
|
816
|
+
return out;
|
|
817
|
+
out = next;
|
|
818
|
+
}
|
|
819
|
+
return null;
|
|
820
|
+
}
|
|
821
|
+
function saReplyHeaders(headers) {
|
|
822
|
+
if (headers === void 0)
|
|
823
|
+
return void 0;
|
|
824
|
+
const out = /* @__PURE__ */ new Map();
|
|
825
|
+
const add = (name, value) => {
|
|
826
|
+
if (typeof name !== "string" || typeof value !== "string")
|
|
827
|
+
return false;
|
|
828
|
+
const lower = name.toLowerCase();
|
|
829
|
+
out.set(lower, [...out.get(lower) ?? [], value]);
|
|
830
|
+
return true;
|
|
831
|
+
};
|
|
832
|
+
if (Array.isArray(headers)) {
|
|
833
|
+
for (const pair of headers)
|
|
834
|
+
if (!Array.isArray(pair) || pair.length !== 2 || !add(pair[0], pair[1]))
|
|
835
|
+
return null;
|
|
836
|
+
return out;
|
|
837
|
+
}
|
|
838
|
+
if (!saRecord(headers))
|
|
839
|
+
return null;
|
|
840
|
+
for (const [name, value] of Object.entries(headers)) {
|
|
841
|
+
for (const item of Array.isArray(value) ? value : [value])
|
|
842
|
+
if (!add(name, item))
|
|
843
|
+
return null;
|
|
844
|
+
}
|
|
845
|
+
return out;
|
|
846
|
+
}
|
|
847
|
+
function saHtmlUtf8(values, bodyText) {
|
|
848
|
+
if (!values || values.length !== 1)
|
|
849
|
+
return false;
|
|
850
|
+
const [type, ...params] = values[0].split(";").map((part) => part.trim());
|
|
851
|
+
if (type.toLowerCase() !== "text/html")
|
|
852
|
+
return false;
|
|
853
|
+
const charsets = params.filter((param) => /^charset\s*=/i.test(param)).map((param) => param.replace(/^charset\s*=\s*/i, "").replace(/^"(.*)"$/, "$1").toLowerCase());
|
|
854
|
+
if (charsets.length > 1)
|
|
855
|
+
return false;
|
|
856
|
+
return charsets.length === 1 ? charsets[0] === "utf-8" || charsets[0] === "utf8" : /^[\u0000-\u007f]*$/.test(bodyText);
|
|
857
|
+
}
|
|
858
|
+
function saUtf8Length(text) {
|
|
859
|
+
let bytes = 0;
|
|
860
|
+
for (const ch of text) {
|
|
861
|
+
const code = ch.codePointAt(0);
|
|
862
|
+
bytes += code < 128 ? 1 : code < 2048 ? 2 : code < 65536 ? 3 : 4;
|
|
863
|
+
}
|
|
864
|
+
return bytes;
|
|
865
|
+
}
|
|
866
|
+
var SA_MATCHED_ECHOES = /* @__PURE__ */ Object.freeze([
|
|
867
|
+
"transaction_uuid",
|
|
868
|
+
"profile_id",
|
|
869
|
+
"access_key",
|
|
870
|
+
"reference_number",
|
|
871
|
+
"transaction_type",
|
|
872
|
+
"amount",
|
|
873
|
+
"currency"
|
|
874
|
+
]);
|
|
875
|
+
function secureAcceptanceReplyOutcome(reply, template, card, options) {
|
|
876
|
+
const unknown = (reason) => ({ outcome: "unknown", reason });
|
|
877
|
+
const sent = saTemplateFields(template);
|
|
878
|
+
if (!saRecord(reply) || !Number.isInteger(reply.status) || typeof reply.bodyText !== "string" || !sent)
|
|
879
|
+
return unknown("reply_unconfirmed");
|
|
880
|
+
const headers = saReplyHeaders(reply.headers);
|
|
881
|
+
if (headers === null)
|
|
882
|
+
return unknown("reply_type");
|
|
883
|
+
if (reply.status >= 300 && reply.status <= 399 || headers?.has("location"))
|
|
884
|
+
return unknown("reply_redirect");
|
|
885
|
+
if (reply.status !== 200)
|
|
886
|
+
return unknown("reply_status");
|
|
887
|
+
if (headers !== void 0 && !saHtmlUtf8(headers.get("content-type"), reply.bodyText))
|
|
888
|
+
return unknown("reply_type");
|
|
889
|
+
if (saUtf8Length(reply.bodyText) > SA_REPLY_LIMIT)
|
|
890
|
+
return unknown("reply_size");
|
|
891
|
+
const page = saReadReplyPage(reply.bodyText, sent.origin);
|
|
892
|
+
if (page.error)
|
|
893
|
+
return unknown(page.error);
|
|
894
|
+
if (page.action !== sent.values.get("override_custom_receipt_page"))
|
|
895
|
+
return unknown("reply_action");
|
|
896
|
+
const fields = page.fields;
|
|
897
|
+
const value = (name) => fields.get(name)?.value;
|
|
898
|
+
if ([...fields.keys()].some((name) => /cvn|cvv|cvc|security_code/i.test(name)) || ["decision", "reason_code", "transaction_id", "req_card_number"].some((name) => !fields.has(name)))
|
|
899
|
+
return unknown("reply_fields");
|
|
900
|
+
if (SA_MATCHED_ECHOES.some((name) => fields.has(`req_${name}`) && value(`req_${name}`) !== sent.values.get(name)))
|
|
901
|
+
return unknown("reply_mismatch");
|
|
902
|
+
if (!saExact(/^[0-9]{22}$/, value("transaction_id")) || fields.has("payment_token") && !saExact(/^[0-9A-Fa-f]{32}$/, value("payment_token"))) {
|
|
903
|
+
return unknown("token_shape");
|
|
904
|
+
}
|
|
905
|
+
const masked = value("req_card_number");
|
|
906
|
+
if (!saExact(/^[xX]{9,15}[0-9]{4}$/, masked))
|
|
907
|
+
return unknown("card_in_reply");
|
|
908
|
+
const scanned = saScannedPage(reply.bodyText, page, sent);
|
|
909
|
+
if (saWritesCardNumber(scanned))
|
|
910
|
+
return unknown("card_in_reply");
|
|
911
|
+
for (const [name, field] of fields) {
|
|
912
|
+
if (saScrubbedOrEchoed(name, field, sent))
|
|
913
|
+
continue;
|
|
914
|
+
const open = saUnescaped(field.value);
|
|
915
|
+
if (open === null || saWritesCardNumber(open))
|
|
916
|
+
return unknown("card_in_reply");
|
|
917
|
+
}
|
|
918
|
+
if (card !== void 0 && card !== null) {
|
|
919
|
+
if (secureAcceptanceCardRefusal(secureAcceptanceEnvironment(new URL(template.url).hostname), card) !== null)
|
|
920
|
+
return unknown("card_mismatch");
|
|
921
|
+
const leak = saRecord(options) && Object.prototype.hasOwnProperty.call(options, "cardLeak") ? options.cardLeak : void 0;
|
|
922
|
+
if (leak !== null || [...fields.values()].some((field) => field.value.includes(card.number)))
|
|
923
|
+
return unknown("card_in_reply");
|
|
924
|
+
if (masked.slice(-4) !== card.number.slice(-4) || fields.has("req_card_expiry_date") && value("req_card_expiry_date") !== saExpiryOf(card) || fields.has("req_card_type") && value("req_card_type") !== secureAcceptanceCardType(card.number))
|
|
925
|
+
return unknown("card_mismatch");
|
|
926
|
+
}
|
|
927
|
+
const last4 = saRecord(options) ? options.cardLast4 : void 0;
|
|
928
|
+
if (last4 !== void 0 && (!saExact(/^[0-9]{4}$/, last4) || masked.slice(-4) !== last4))
|
|
929
|
+
return unknown("card_mismatch");
|
|
930
|
+
const decision = value("decision");
|
|
931
|
+
const code = value("reason_code");
|
|
932
|
+
if (decision === "ACCEPT" && code === "100" && fields.has("payment_token"))
|
|
933
|
+
return { outcome: "token", page: reply.bodyText };
|
|
934
|
+
if ((decision === "DECLINE" || decision === "ERROR") && SECURE_ACCEPTANCE_REFUSAL_REASON_CODES.includes(code) && !fields.has("payment_token")) {
|
|
935
|
+
return { outcome: "refused", code };
|
|
936
|
+
}
|
|
937
|
+
return unknown("reply_unconfirmed");
|
|
938
|
+
}
|
|
939
|
+
function saScrubbedOrEchoed(name, field, sent) {
|
|
940
|
+
const pattern = SA_SCRUBBED.get(name);
|
|
941
|
+
if (pattern && saExact(pattern, field.value))
|
|
942
|
+
return true;
|
|
943
|
+
return !!sent && name.startsWith("req_") && !SA_CARD_ECHOES.has(name) && sent.values.has(name.slice(4)) && sent.values.get(name.slice(4)) === field.value;
|
|
944
|
+
}
|
|
945
|
+
function secureAcceptanceRefusalCode(bodyText) {
|
|
946
|
+
if (typeof bodyText !== "string" || saUtf8Length(bodyText) > SA_REPLY_LIMIT)
|
|
947
|
+
return null;
|
|
948
|
+
const page = saReadReplyPage(bodyText, `https://${SECURE_ACCEPTANCE_HOSTS.production}`);
|
|
949
|
+
if (page.error)
|
|
950
|
+
return null;
|
|
951
|
+
const decision = page.fields.get("decision")?.value;
|
|
952
|
+
const code = page.fields.get("reason_code")?.value;
|
|
953
|
+
return (decision === "DECLINE" || decision === "ERROR") && saExact(/^[0-9]{3}$/, code) ? code : null;
|
|
954
|
+
}
|
|
955
|
+
export { SECURE_ACCEPTANCE_CAPABILITY, SECURE_ACCEPTANCE_CARD_TYPES, SECURE_ACCEPTANCE_HOSTS, SECURE_ACCEPTANCE_MERCHANTS, SECURE_ACCEPTANCE_OPTIONAL_SIGNED_FIELDS, SECURE_ACCEPTANCE_PAGE_CONTENT_TYPE, SECURE_ACCEPTANCE_PSP, SECURE_ACCEPTANCE_REFUSAL_REASON_CODES, SECURE_ACCEPTANCE_REPLY_REASONS, SECURE_ACCEPTANCE_REQUEST_REFUSALS, SECURE_ACCEPTANCE_SIGNED_FIELDS, SECURE_ACCEPTANCE_TEST_PANS, SECURE_ACCEPTANCE_TOKEN_CREATE_PATH, SECURE_ACCEPTANCE_UNSIGNED_FIELDS, isPreparedSecureAcceptanceRequest, isSecureAcceptanceDeviceRequest, isSecureAcceptanceTokenRequest, secureAcceptanceCardRefusal, secureAcceptanceCardType, secureAcceptanceDeviceRequest, secureAcceptanceEnvironment, secureAcceptanceMerchantEntry, secureAcceptanceMerchantRefusal, secureAcceptancePreparationUrl, secureAcceptanceRefusalCode, secureAcceptanceReplyOutcome, secureAcceptanceRequestFacts, secureAcceptanceRequestRefusal, secureAcceptanceScannedPage };
|