@pagosrd/azul 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +156 -2
- package/dist/index.cjs +817 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +380 -0
- package/dist/index.d.ts +380 -0
- package/dist/index.js +773 -0
- package/dist/index.js.map +1 -0
- package/package.json +61 -3
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,817 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
AZUL_URLS: () => AZUL_URLS,
|
|
24
|
+
AzulAmbiguousError: () => AzulAmbiguousError,
|
|
25
|
+
AzulClient: () => AzulClient,
|
|
26
|
+
AzulConfigError: () => AzulConfigError,
|
|
27
|
+
AzulError: () => AzulError,
|
|
28
|
+
AzulHttpError: () => AzulHttpError,
|
|
29
|
+
AzulResponseError: () => AzulResponseError,
|
|
30
|
+
AzulValidationError: () => AzulValidationError,
|
|
31
|
+
PAYMENT_PAGE_HASH_FIELDS: () => PAYMENT_PAGE_HASH_FIELDS,
|
|
32
|
+
PAYMENT_PAGE_RESPONSE_HASH_FIELDS: () => PAYMENT_PAGE_RESPONSE_HASH_FIELDS,
|
|
33
|
+
defaultPaymentPageHash: () => defaultPaymentPageHash,
|
|
34
|
+
escapeHtml: () => escapeHtml,
|
|
35
|
+
maskPan: () => maskPan,
|
|
36
|
+
passesLuhn: () => passesLuhn,
|
|
37
|
+
paymentPageResponseHash: () => paymentPageResponseHash,
|
|
38
|
+
redact: () => redact,
|
|
39
|
+
toCents: () => toCents,
|
|
40
|
+
verifyPaymentPageResponse: () => verifyPaymentPageResponse
|
|
41
|
+
});
|
|
42
|
+
module.exports = __toCommonJS(index_exports);
|
|
43
|
+
|
|
44
|
+
// src/client.ts
|
|
45
|
+
var import_node_crypto2 = require("crypto");
|
|
46
|
+
|
|
47
|
+
// src/config.ts
|
|
48
|
+
var import_zod = require("zod");
|
|
49
|
+
|
|
50
|
+
// src/errors.ts
|
|
51
|
+
var AzulError = class extends Error {
|
|
52
|
+
name = "AzulError";
|
|
53
|
+
constructor(message, options) {
|
|
54
|
+
super(message, options);
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
var AzulConfigError = class extends AzulError {
|
|
58
|
+
name = "AzulConfigError";
|
|
59
|
+
};
|
|
60
|
+
var AzulValidationError = class extends AzulError {
|
|
61
|
+
name = "AzulValidationError";
|
|
62
|
+
issues;
|
|
63
|
+
constructor(message, issues = []) {
|
|
64
|
+
super(message);
|
|
65
|
+
this.issues = issues;
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
var AzulResponseError = class extends AzulError {
|
|
69
|
+
name = "AzulResponseError";
|
|
70
|
+
response;
|
|
71
|
+
constructor(message, response) {
|
|
72
|
+
super(message);
|
|
73
|
+
this.response = response;
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
var AzulHttpError = class extends AzulError {
|
|
77
|
+
name = "AzulHttpError";
|
|
78
|
+
statusCode;
|
|
79
|
+
body;
|
|
80
|
+
constructor(message, statusCode, body) {
|
|
81
|
+
super(message);
|
|
82
|
+
this.statusCode = statusCode;
|
|
83
|
+
this.body = body;
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
var AzulAmbiguousError = class extends AzulError {
|
|
87
|
+
name = "AzulAmbiguousError";
|
|
88
|
+
customOrderId;
|
|
89
|
+
constructor(message, customOrderId, options) {
|
|
90
|
+
super(message, options);
|
|
91
|
+
this.customOrderId = customOrderId;
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
// src/config.ts
|
|
96
|
+
var AZUL_URLS = {
|
|
97
|
+
pruebas: {
|
|
98
|
+
webservices: "https://pruebas.azul.com.do/webservices/JSON/Default.aspx",
|
|
99
|
+
paymentPage: "https://pruebas.azul.com.do/PaymentPage/"
|
|
100
|
+
},
|
|
101
|
+
produccion: {
|
|
102
|
+
webservices: "https://pagos.azul.com.do/webservices/JSON/Default.aspx",
|
|
103
|
+
webservicesAlterna: "https://contpagos.azul.com.do/Webservices/JSON/default.aspx",
|
|
104
|
+
paymentPage: "https://pagos.azul.com.do/PaymentPage/Default.aspx"
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
var nonEmpty = (field) => import_zod.z.string({ error: `${field} es requerido` }).trim().min(1, `${field} no puede estar vac\xEDo`);
|
|
108
|
+
var pem = (field) => import_zod.z.union([import_zod.z.string(), import_zod.z.instanceof(Buffer)]).refine((v) => String(v).includes("-----BEGIN"), {
|
|
109
|
+
message: `${field} debe ser el contenido PEM (empieza con "-----BEGIN"), no una ruta de archivo`
|
|
110
|
+
});
|
|
111
|
+
var configSchema = import_zod.z.object({
|
|
112
|
+
merchantId: nonEmpty("merchantId").max(11, "merchantId tiene un m\xE1ximo de 11 caracteres"),
|
|
113
|
+
auth1: nonEmpty("auth1"),
|
|
114
|
+
auth2: nonEmpty("auth2"),
|
|
115
|
+
environment: import_zod.z.enum(["pruebas", "produccion"], { error: 'environment debe ser "pruebas" o "produccion"' }),
|
|
116
|
+
channel: import_zod.z.string().max(3).optional(),
|
|
117
|
+
timeoutMs: import_zod.z.number().int().positive().max(12e4).optional(),
|
|
118
|
+
paymentPage: import_zod.z.object({ authKey: nonEmpty("paymentPage.authKey"), merchantName: nonEmpty("paymentPage.merchantName"), merchantType: nonEmpty("paymentPage.merchantType") }).optional()
|
|
119
|
+
});
|
|
120
|
+
function resolveConfig(input) {
|
|
121
|
+
const parsed = configSchema.safeParse(input);
|
|
122
|
+
const issues = parsed.success ? [] : parsed.error.issues.map((i) => i.message);
|
|
123
|
+
if (!input.dispatcher) {
|
|
124
|
+
for (const [field, value] of [["certificate", input.certificate], ["privateKey", input.privateKey]]) {
|
|
125
|
+
const check = pem(field).safeParse(value);
|
|
126
|
+
if (!check.success) issues.push(...check.error.issues.map((i) => i.message));
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
if (issues.length > 0) {
|
|
130
|
+
throw new AzulConfigError(`Configuraci\xF3n de AZUL inv\xE1lida: ${issues.join("; ")}`);
|
|
131
|
+
}
|
|
132
|
+
const env = input.environment;
|
|
133
|
+
const urls = AZUL_URLS[env];
|
|
134
|
+
return {
|
|
135
|
+
...input,
|
|
136
|
+
channel: input.channel ?? "EC",
|
|
137
|
+
timeoutMs: input.timeoutMs ?? 3e4,
|
|
138
|
+
webservicesUrl: input.urls?.webservices ?? urls.webservices,
|
|
139
|
+
webservicesAlternaUrl: input.urls?.webservicesAlterna ?? (env === "produccion" ? AZUL_URLS.produccion.webservicesAlterna : void 0),
|
|
140
|
+
paymentPageUrl: urls.paymentPage
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// src/money.ts
|
|
145
|
+
var MAX_CENTS = 999999999999;
|
|
146
|
+
function centsToAzul(value, field, options = {}) {
|
|
147
|
+
if (!Number.isSafeInteger(value)) {
|
|
148
|
+
throw new AzulValidationError(
|
|
149
|
+
`${field} debe ser un entero de centavos (por ejemplo 150050 para RD$1,500.50). Recibido: ${String(value)}`,
|
|
150
|
+
[field]
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
if (value < 0) {
|
|
154
|
+
throw new AzulValidationError(`${field} no puede ser negativo.`, [field]);
|
|
155
|
+
}
|
|
156
|
+
if (value === 0 && !options.allowZero) {
|
|
157
|
+
throw new AzulValidationError(`${field} debe ser mayor que cero.`, [field]);
|
|
158
|
+
}
|
|
159
|
+
if (value > MAX_CENTS) {
|
|
160
|
+
throw new AzulValidationError(`${field} excede el m\xE1ximo de 12 d\xEDgitos que acepta AZUL.`, [field]);
|
|
161
|
+
}
|
|
162
|
+
return String(value);
|
|
163
|
+
}
|
|
164
|
+
function itbisToAzul(value) {
|
|
165
|
+
if (value === void 0 || value === 0) return "000";
|
|
166
|
+
return centsToAzul(value, "itbis");
|
|
167
|
+
}
|
|
168
|
+
function toCents(amount) {
|
|
169
|
+
const text = typeof amount === "number" ? amount.toString() : amount;
|
|
170
|
+
const noSymbol = text.trim().replace(/^RD\$\s*/i, "");
|
|
171
|
+
if (noSymbol.includes(",") && !/^\d{1,3}(,\d{3})+(\.\d{1,2})?$/.test(noSymbol)) {
|
|
172
|
+
throw new AzulValidationError(`Monto ambiguo: "${text}". Usa punto para los decimales y coma solo para los miles.`, ["amount"]);
|
|
173
|
+
}
|
|
174
|
+
const cleaned = noSymbol.replace(/,/g, "");
|
|
175
|
+
const match = /^(\d+)(?:\.(\d{1,2}))?$/.exec(cleaned);
|
|
176
|
+
if (!match) {
|
|
177
|
+
throw new AzulValidationError(
|
|
178
|
+
`Monto inv\xE1lido: "${text}". Usa como m\xE1ximo 2 decimales, por ejemplo "1,500.50".`,
|
|
179
|
+
["amount"]
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
const pesos = match[1] ?? "0";
|
|
183
|
+
const decimals = (match[2] ?? "").padEnd(2, "0");
|
|
184
|
+
const cents = Number(pesos) * 100 + Number(decimals);
|
|
185
|
+
if (!Number.isSafeInteger(cents)) {
|
|
186
|
+
throw new AzulValidationError(`Monto demasiado grande: "${text}".`, ["amount"]);
|
|
187
|
+
}
|
|
188
|
+
return cents;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// src/payment-page.ts
|
|
192
|
+
var import_node_crypto = require("crypto");
|
|
193
|
+
|
|
194
|
+
// src/validation.ts
|
|
195
|
+
function passesLuhn(digits) {
|
|
196
|
+
let sum = 0;
|
|
197
|
+
let double = false;
|
|
198
|
+
for (let i = digits.length - 1; i >= 0; i--) {
|
|
199
|
+
let d = digits.charCodeAt(i) - 48;
|
|
200
|
+
if (double) {
|
|
201
|
+
d *= 2;
|
|
202
|
+
if (d > 9) d -= 9;
|
|
203
|
+
}
|
|
204
|
+
sum += d;
|
|
205
|
+
double = !double;
|
|
206
|
+
}
|
|
207
|
+
return sum % 10 === 0;
|
|
208
|
+
}
|
|
209
|
+
function cardNumber(value) {
|
|
210
|
+
const digits = String(value).replace(/[\s-]/g, "");
|
|
211
|
+
if (!/^\d{12,19}$/.test(digits)) {
|
|
212
|
+
throw new AzulValidationError("cardNumber debe tener entre 12 y 19 d\xEDgitos.", ["cardNumber"]);
|
|
213
|
+
}
|
|
214
|
+
if (!passesLuhn(digits)) {
|
|
215
|
+
throw new AzulValidationError("cardNumber no es un n\xFAmero de tarjeta v\xE1lido (falla la verificaci\xF3n de Luhn).", ["cardNumber"]);
|
|
216
|
+
}
|
|
217
|
+
return digits;
|
|
218
|
+
}
|
|
219
|
+
function expiration(value) {
|
|
220
|
+
const v = String(value).trim();
|
|
221
|
+
let year;
|
|
222
|
+
let month;
|
|
223
|
+
let m;
|
|
224
|
+
if (m = /^(\d{4})(\d{2})$/.exec(v)) {
|
|
225
|
+
year = Number(m[1]);
|
|
226
|
+
month = Number(m[2]);
|
|
227
|
+
} else if (m = /^(\d{1,2})\s*\/\s*(\d{2}|\d{4})$/.exec(v)) {
|
|
228
|
+
month = Number(m[1]);
|
|
229
|
+
year = Number(m[2].length === 2 ? `20${m[2]}` : m[2]);
|
|
230
|
+
} else {
|
|
231
|
+
throw new AzulValidationError('expiration debe tener el formato "YYYYMM", "MM/YY" o "MM/YYYY".', ["expiration"]);
|
|
232
|
+
}
|
|
233
|
+
if (month < 1 || month > 12) {
|
|
234
|
+
throw new AzulValidationError("expiration tiene un mes inv\xE1lido.", ["expiration"]);
|
|
235
|
+
}
|
|
236
|
+
return `${year}${String(month).padStart(2, "0")}`;
|
|
237
|
+
}
|
|
238
|
+
function cvc(value) {
|
|
239
|
+
const v = String(value).trim();
|
|
240
|
+
if (!/^\d{3,4}$/.test(v)) {
|
|
241
|
+
throw new AzulValidationError("cvc debe tener 3 o 4 d\xEDgitos.", ["cvc"]);
|
|
242
|
+
}
|
|
243
|
+
return v;
|
|
244
|
+
}
|
|
245
|
+
function maxText(value, field, max, options = {}) {
|
|
246
|
+
if (value === void 0 || value === "") {
|
|
247
|
+
if (options.required) throw new AzulValidationError(`${field} es requerido.`, [field]);
|
|
248
|
+
return void 0;
|
|
249
|
+
}
|
|
250
|
+
if (value.length > max) {
|
|
251
|
+
throw new AzulValidationError(`${field} admite como m\xE1ximo ${max} caracteres (tiene ${value.length}).`, [field]);
|
|
252
|
+
}
|
|
253
|
+
return value;
|
|
254
|
+
}
|
|
255
|
+
function azulOrderId(value) {
|
|
256
|
+
const v = String(value ?? "").trim();
|
|
257
|
+
if (!/^\d+$/.test(v)) {
|
|
258
|
+
throw new AzulValidationError("azulOrderId debe ser el identificador num\xE9rico que devolvi\xF3 AZUL.", ["azulOrderId"]);
|
|
259
|
+
}
|
|
260
|
+
return v;
|
|
261
|
+
}
|
|
262
|
+
function dataVaultToken(value) {
|
|
263
|
+
const v = String(value ?? "").trim();
|
|
264
|
+
if (!/^[A-Fa-f0-9-]{30,40}$/.test(v)) {
|
|
265
|
+
throw new AzulValidationError("dataVaultToken no tiene el formato de un token de DataVault.", ["dataVaultToken"]);
|
|
266
|
+
}
|
|
267
|
+
return v;
|
|
268
|
+
}
|
|
269
|
+
function httpsUrl(value, field) {
|
|
270
|
+
let url;
|
|
271
|
+
try {
|
|
272
|
+
url = new URL(value);
|
|
273
|
+
} catch {
|
|
274
|
+
throw new AzulValidationError(`${field} debe ser una URL v\xE1lida.`, [field]);
|
|
275
|
+
}
|
|
276
|
+
if (url.protocol !== "https:") {
|
|
277
|
+
throw new AzulValidationError(`${field} debe usar https://.`, [field]);
|
|
278
|
+
}
|
|
279
|
+
return value;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// src/payment-page.ts
|
|
283
|
+
var PAYMENT_PAGE_HASH_FIELDS = [
|
|
284
|
+
"MerchantId",
|
|
285
|
+
"MerchantName",
|
|
286
|
+
"MerchantType",
|
|
287
|
+
"CurrencyCode",
|
|
288
|
+
"OrderNumber",
|
|
289
|
+
"Amount",
|
|
290
|
+
"ITBIS",
|
|
291
|
+
"ApprovedUrl",
|
|
292
|
+
"DeclinedUrl",
|
|
293
|
+
"CancelUrl",
|
|
294
|
+
"UseCustomField1",
|
|
295
|
+
"CustomField1Label",
|
|
296
|
+
"CustomField1Value",
|
|
297
|
+
"UseCustomField2",
|
|
298
|
+
"CustomField2Label",
|
|
299
|
+
"CustomField2Value"
|
|
300
|
+
];
|
|
301
|
+
var PAYMENT_PAGE_RESPONSE_HASH_FIELDS = [
|
|
302
|
+
"OrderNumber",
|
|
303
|
+
"Amount",
|
|
304
|
+
"AuthorizationCode",
|
|
305
|
+
"DateTime",
|
|
306
|
+
"ResponseCode",
|
|
307
|
+
"IsoCode",
|
|
308
|
+
"ResponseMessage",
|
|
309
|
+
"ErrorDescription",
|
|
310
|
+
"RRN"
|
|
311
|
+
];
|
|
312
|
+
function defaultPaymentPageHash(fields, authKey) {
|
|
313
|
+
const text = PAYMENT_PAGE_HASH_FIELDS.map((k) => fields[k] ?? "").join("") + authKey;
|
|
314
|
+
return (0, import_node_crypto.createHmac)("sha512", authKey).update(text, "utf8").digest("hex");
|
|
315
|
+
}
|
|
316
|
+
function paymentPageResponseHash(params, authKey) {
|
|
317
|
+
const text = PAYMENT_PAGE_RESPONSE_HASH_FIELDS.map((k) => params[k] ?? "").join("") + authKey;
|
|
318
|
+
return (0, import_node_crypto.createHmac)("sha512", authKey).update(Buffer.from(text, "utf16le")).digest("hex");
|
|
319
|
+
}
|
|
320
|
+
function verifyPaymentPageResponse(params, authKey) {
|
|
321
|
+
const received = (params.AuthHash ?? "").trim().toLowerCase();
|
|
322
|
+
if (!/^[0-9a-f]{128}$/.test(received)) return false;
|
|
323
|
+
const expected = paymentPageResponseHash(params, authKey);
|
|
324
|
+
return (0, import_node_crypto.timingSafeEqual)(Buffer.from(received, "hex"), Buffer.from(expected, "hex"));
|
|
325
|
+
}
|
|
326
|
+
function buildPaymentPage(input, merchant) {
|
|
327
|
+
const amount = centsToAzul(input.amount, "amount");
|
|
328
|
+
const itbis = input.itbis ? centsToAzul(input.itbis, "itbis", { allowZero: true }) : "000";
|
|
329
|
+
if (input.itbis !== void 0 && input.itbis > input.amount) {
|
|
330
|
+
throw new AzulValidationError("itbis no puede ser mayor que amount.", ["itbis"]);
|
|
331
|
+
}
|
|
332
|
+
const orderNumber = maxText(input.orderNumber, "orderNumber", 15, { required: true });
|
|
333
|
+
const alt = maxText(input.altMerchantName, "altMerchantName", 25);
|
|
334
|
+
if (alt && !/^[a-zA-Z0-9\s.,]*$/.test(alt)) {
|
|
335
|
+
throw new AzulValidationError("altMerchantName solo admite letras sin acento, n\xFAmeros, espacios, punto y coma.", ["altMerchantName"]);
|
|
336
|
+
}
|
|
337
|
+
const [cf1, cf2] = input.customFields ?? [];
|
|
338
|
+
const fields = {
|
|
339
|
+
TrxType: "Sale",
|
|
340
|
+
MerchantId: merchant.merchantId,
|
|
341
|
+
MerchantName: merchant.merchantName,
|
|
342
|
+
MerchantType: merchant.merchantType,
|
|
343
|
+
CurrencyCode: "$",
|
|
344
|
+
OrderNumber: orderNumber,
|
|
345
|
+
Amount: amount,
|
|
346
|
+
ITBIS: itbis,
|
|
347
|
+
ApprovedUrl: httpsUrl(input.approvedUrl, "approvedUrl"),
|
|
348
|
+
DeclinedUrl: httpsUrl(input.declineUrl, "declineUrl"),
|
|
349
|
+
CancelUrl: httpsUrl(input.cancelUrl, "cancelUrl"),
|
|
350
|
+
UseCustomField1: cf1 ? "1" : "0",
|
|
351
|
+
CustomField1Label: cf1?.label ?? "",
|
|
352
|
+
CustomField1Value: cf1?.value ?? "",
|
|
353
|
+
UseCustomField2: cf2 ? "1" : "0",
|
|
354
|
+
CustomField2Label: cf2?.label ?? "",
|
|
355
|
+
CustomField2Value: cf2?.value ?? "",
|
|
356
|
+
ShowTransactionResult: input.showTransactionResult === false ? "0" : "1",
|
|
357
|
+
Locale: input.locale ?? "ES"
|
|
358
|
+
};
|
|
359
|
+
if (alt) fields.AltMerchantName = alt;
|
|
360
|
+
if (input.saveToDataVault) fields.SaveToDataVault = "1";
|
|
361
|
+
fields.AuthHash = (merchant.hashFn ?? defaultPaymentPageHash)(fields, merchant.authKey);
|
|
362
|
+
return { url: merchant.url, fields, html: renderAutoSubmitForm(merchant.url, fields) };
|
|
363
|
+
}
|
|
364
|
+
var HTML_ESCAPES = { "&": "&", "<": "<", ">": ">", '"': """, "'": "'" };
|
|
365
|
+
function escapeHtml(value) {
|
|
366
|
+
return value.replace(/[&<>"']/g, (c) => HTML_ESCAPES[c]);
|
|
367
|
+
}
|
|
368
|
+
function renderAutoSubmitForm(url, fields) {
|
|
369
|
+
const inputs = Object.entries(fields).map(([name, value]) => ` <input type="hidden" name="${escapeHtml(name)}" value="${escapeHtml(value)}">`).join("\n");
|
|
370
|
+
return `<!doctype html>
|
|
371
|
+
<html lang="es">
|
|
372
|
+
<head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>Redirigiendo al pago seguro</title></head>
|
|
373
|
+
<body>
|
|
374
|
+
<form id="azul-payment" method="post" action="${escapeHtml(url)}">
|
|
375
|
+
${inputs}
|
|
376
|
+
<noscript><button type="submit">Continuar al pago</button></noscript>
|
|
377
|
+
</form>
|
|
378
|
+
<script>document.getElementById("azul-payment").submit();</script>
|
|
379
|
+
</body>
|
|
380
|
+
</html>`;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// src/response.ts
|
|
384
|
+
var SYSTEM_GATEWAY_ERRORS = [
|
|
385
|
+
"timeout",
|
|
386
|
+
"not been processed",
|
|
387
|
+
"Internal Server Error",
|
|
388
|
+
"Unauthorized",
|
|
389
|
+
"Server Unavailable",
|
|
390
|
+
"SSL/TLS",
|
|
391
|
+
"connection"
|
|
392
|
+
];
|
|
393
|
+
var str = (v) => typeof v === "string" && v !== "" ? v : void 0;
|
|
394
|
+
function interpretResponse(data, customOrderId) {
|
|
395
|
+
const responseCode = str(data.ResponseCode);
|
|
396
|
+
const isoCode = str(data.IsoCode);
|
|
397
|
+
const message = str(data.ResponseMessage);
|
|
398
|
+
const errorDescription = str(data.ErrorDescription);
|
|
399
|
+
const build = (status) => ({
|
|
400
|
+
status,
|
|
401
|
+
responseCode,
|
|
402
|
+
isoCode,
|
|
403
|
+
message,
|
|
404
|
+
errorDescription,
|
|
405
|
+
authorizationCode: str(data.AuthorizationCode),
|
|
406
|
+
azulOrderId: str(data.AzulOrderId),
|
|
407
|
+
rrn: str(data.RRN),
|
|
408
|
+
dataVaultToken: str(data.DataVaultToken),
|
|
409
|
+
customOrderId: str(data.CustomOrderId) ?? customOrderId,
|
|
410
|
+
raw: data
|
|
411
|
+
});
|
|
412
|
+
if (responseCode === "Error") {
|
|
413
|
+
throw new AzulResponseError(`Error de sistema de AZUL: ${errorDescription ?? "sin descripci\xF3n"}`, data);
|
|
414
|
+
}
|
|
415
|
+
if (responseCode === "ISO8583") {
|
|
416
|
+
if (isoCode === "00") return build("approved");
|
|
417
|
+
if (isoCode === "3D" || isoCode === "3D2METHOD") return build("requires_3ds");
|
|
418
|
+
return build("declined");
|
|
419
|
+
}
|
|
420
|
+
if (errorDescription?.includes("SGS-")) {
|
|
421
|
+
if (SYSTEM_GATEWAY_ERRORS.some((e) => errorDescription.includes(e))) {
|
|
422
|
+
throw new AzulResponseError(`Error de gateway de AZUL: ${errorDescription}`, data);
|
|
423
|
+
}
|
|
424
|
+
return build("declined");
|
|
425
|
+
}
|
|
426
|
+
const errorMessage = str(data.ErrorMessage);
|
|
427
|
+
if (errorMessage) {
|
|
428
|
+
throw new AzulResponseError(`Error de API de AZUL: ${errorMessage}`, data);
|
|
429
|
+
}
|
|
430
|
+
if (isoCode === "00") return build("approved");
|
|
431
|
+
if (isoCode) return build("declined");
|
|
432
|
+
throw new AzulResponseError("Respuesta de AZUL con estructura desconocida", data);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// src/transport.ts
|
|
436
|
+
var import_undici = require("undici");
|
|
437
|
+
|
|
438
|
+
// src/redact.ts
|
|
439
|
+
var DROP = /* @__PURE__ */ new Set(["CVC", "Auth1", "Auth2"]);
|
|
440
|
+
var MASK_PAN = /* @__PURE__ */ new Set(["CardNumber"]);
|
|
441
|
+
var MASK_PARTIAL = /* @__PURE__ */ new Set(["DataVaultToken"]);
|
|
442
|
+
function maskPan(pan) {
|
|
443
|
+
const digits = pan.replace(/\D/g, "");
|
|
444
|
+
if (digits.length < 10) return digits.length === 0 ? "" : "****";
|
|
445
|
+
return `${digits.slice(0, 6)}${"*".repeat(digits.length - 10)}${digits.slice(-4)}`;
|
|
446
|
+
}
|
|
447
|
+
function redact(payload) {
|
|
448
|
+
const out = {};
|
|
449
|
+
for (const [key, value] of Object.entries(payload)) {
|
|
450
|
+
if (DROP.has(key)) {
|
|
451
|
+
if (value !== void 0 && value !== "") out[key] = "[censurado]";
|
|
452
|
+
continue;
|
|
453
|
+
}
|
|
454
|
+
if (MASK_PAN.has(key) && typeof value === "string") {
|
|
455
|
+
out[key] = maskPan(value);
|
|
456
|
+
continue;
|
|
457
|
+
}
|
|
458
|
+
if (MASK_PARTIAL.has(key) && typeof value === "string" && value.length > 8) {
|
|
459
|
+
out[key] = `${value.slice(0, 4)}\u2026${value.slice(-4)}`;
|
|
460
|
+
continue;
|
|
461
|
+
}
|
|
462
|
+
out[key] = value;
|
|
463
|
+
}
|
|
464
|
+
return out;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// src/transport.ts
|
|
468
|
+
var CONNECT_PHASE_CODES = /* @__PURE__ */ new Set([
|
|
469
|
+
"ECONNREFUSED",
|
|
470
|
+
"ENOTFOUND",
|
|
471
|
+
"EAI_AGAIN",
|
|
472
|
+
"EHOSTUNREACH",
|
|
473
|
+
"ENETUNREACH",
|
|
474
|
+
"UND_ERR_CONNECT_TIMEOUT"
|
|
475
|
+
]);
|
|
476
|
+
function errorCode(err) {
|
|
477
|
+
if (err && typeof err === "object") {
|
|
478
|
+
const code = err.code;
|
|
479
|
+
if (typeof code === "string") return code;
|
|
480
|
+
const cause = err.cause;
|
|
481
|
+
if (cause && cause !== err) return errorCode(cause);
|
|
482
|
+
}
|
|
483
|
+
return void 0;
|
|
484
|
+
}
|
|
485
|
+
function isConnectPhaseError(err) {
|
|
486
|
+
const code = errorCode(err);
|
|
487
|
+
return code !== void 0 && CONNECT_PHASE_CODES.has(code);
|
|
488
|
+
}
|
|
489
|
+
var AzulTransport = class {
|
|
490
|
+
constructor(config) {
|
|
491
|
+
this.config = config;
|
|
492
|
+
if (config.dispatcher) {
|
|
493
|
+
this.dispatcher = config.dispatcher;
|
|
494
|
+
this.ownsDispatcher = false;
|
|
495
|
+
} else {
|
|
496
|
+
this.dispatcher = new import_undici.Agent({
|
|
497
|
+
connect: {
|
|
498
|
+
cert: config.certificate,
|
|
499
|
+
key: config.privateKey,
|
|
500
|
+
timeout: Math.min(1e4, config.timeoutMs)
|
|
501
|
+
}
|
|
502
|
+
});
|
|
503
|
+
this.ownsDispatcher = true;
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
config;
|
|
507
|
+
dispatcher;
|
|
508
|
+
ownsDispatcher;
|
|
509
|
+
async close() {
|
|
510
|
+
if (this.ownsDispatcher) await this.dispatcher.close();
|
|
511
|
+
}
|
|
512
|
+
async send(payload, options) {
|
|
513
|
+
const urls = [this.config.webservicesUrl];
|
|
514
|
+
if (this.config.webservicesAlternaUrl) urls.push(this.config.webservicesAlternaUrl);
|
|
515
|
+
let lastError;
|
|
516
|
+
for (let i = 0; i < urls.length; i++) {
|
|
517
|
+
const base = urls[i];
|
|
518
|
+
const url = options.operation ? `${base}?${options.operation}` : base;
|
|
519
|
+
const hasFallback = i < urls.length - 1;
|
|
520
|
+
this.config.onRequest?.({ operation: options.name, url, payload: redact(payload) });
|
|
521
|
+
let statusCode;
|
|
522
|
+
let text;
|
|
523
|
+
try {
|
|
524
|
+
const res = await (0, import_undici.request)(url, {
|
|
525
|
+
method: "POST",
|
|
526
|
+
dispatcher: this.dispatcher,
|
|
527
|
+
headers: {
|
|
528
|
+
"content-type": "application/json",
|
|
529
|
+
Auth1: this.config.auth1,
|
|
530
|
+
Auth2: this.config.auth2
|
|
531
|
+
},
|
|
532
|
+
body: JSON.stringify(payload),
|
|
533
|
+
headersTimeout: this.config.timeoutMs,
|
|
534
|
+
bodyTimeout: this.config.timeoutMs
|
|
535
|
+
});
|
|
536
|
+
statusCode = res.statusCode;
|
|
537
|
+
text = await res.body.text();
|
|
538
|
+
} catch (err) {
|
|
539
|
+
lastError = err;
|
|
540
|
+
if (isConnectPhaseError(err)) {
|
|
541
|
+
if (hasFallback) continue;
|
|
542
|
+
throw new AzulError(`No se pudo conectar con AZUL (${options.name}). La solicitud no se envi\xF3.`, { cause: err });
|
|
543
|
+
}
|
|
544
|
+
if (options.idempotent && hasFallback) continue;
|
|
545
|
+
throw this.ambiguous(options, `fallo de red despu\xE9s de enviar la solicitud (${errorCode(err) ?? "desconocido"})`, err);
|
|
546
|
+
}
|
|
547
|
+
if (statusCode >= 500) {
|
|
548
|
+
lastError = new AzulHttpError(`HTTP ${statusCode}`, statusCode, text);
|
|
549
|
+
if (options.idempotent && hasFallback) continue;
|
|
550
|
+
throw this.ambiguous(options, `AZUL respondi\xF3 HTTP ${statusCode}`, lastError);
|
|
551
|
+
}
|
|
552
|
+
if (statusCode >= 400) {
|
|
553
|
+
throw new AzulHttpError(`AZUL rechaz\xF3 la solicitud ${options.name} con HTTP ${statusCode}`, statusCode, text);
|
|
554
|
+
}
|
|
555
|
+
let data;
|
|
556
|
+
try {
|
|
557
|
+
data = JSON.parse(text);
|
|
558
|
+
} catch (err) {
|
|
559
|
+
if (options.idempotent && hasFallback) {
|
|
560
|
+
lastError = err;
|
|
561
|
+
continue;
|
|
562
|
+
}
|
|
563
|
+
throw this.ambiguous(options, "AZUL devolvi\xF3 una respuesta que no es JSON v\xE1lido", err);
|
|
564
|
+
}
|
|
565
|
+
if (!data || typeof data !== "object" || Array.isArray(data)) {
|
|
566
|
+
throw this.ambiguous(options, "AZUL devolvi\xF3 una respuesta vac\xEDa o con formato inesperado", void 0);
|
|
567
|
+
}
|
|
568
|
+
const record = data;
|
|
569
|
+
this.config.onResponse?.({ operation: options.name, url, payload: redact(record) });
|
|
570
|
+
return record;
|
|
571
|
+
}
|
|
572
|
+
if (isConnectPhaseError(lastError)) {
|
|
573
|
+
throw new AzulError(`No se pudo conectar con AZUL (${options.name}). La solicitud no se envi\xF3.`, { cause: lastError });
|
|
574
|
+
}
|
|
575
|
+
throw this.ambiguous(options, "todas las URLs de AZUL fallaron", lastError);
|
|
576
|
+
}
|
|
577
|
+
ambiguous(options, reason, cause) {
|
|
578
|
+
const hint = options.customOrderId ? ` Consulta el estado con verifyPayment({ customOrderId: "${options.customOrderId}" }) antes de reintentar.` : " Verifica el estado en el portal de AZUL antes de reintentar.";
|
|
579
|
+
return new AzulAmbiguousError(
|
|
580
|
+
`Resultado desconocido para ${options.name}: ${reason}. Es posible que AZUL s\xED la haya procesado.${hint}`,
|
|
581
|
+
options.customOrderId,
|
|
582
|
+
{ cause }
|
|
583
|
+
);
|
|
584
|
+
}
|
|
585
|
+
};
|
|
586
|
+
|
|
587
|
+
// src/client.ts
|
|
588
|
+
var AzulClient = class {
|
|
589
|
+
config;
|
|
590
|
+
transport;
|
|
591
|
+
constructor(config) {
|
|
592
|
+
this.config = resolveConfig(config);
|
|
593
|
+
this.transport = new AzulTransport(this.config);
|
|
594
|
+
}
|
|
595
|
+
/** Cierra las conexiones abiertas. Llámalo al apagar tu servidor. */
|
|
596
|
+
async close() {
|
|
597
|
+
await this.transport.close();
|
|
598
|
+
}
|
|
599
|
+
// ---------------------------------------------------------------- ventas
|
|
600
|
+
/**
|
|
601
|
+
* Venta directa con datos de tarjeta.
|
|
602
|
+
*
|
|
603
|
+
* OJO: recibir el número de tarjeta en tu servidor te pone dentro del
|
|
604
|
+
* alcance de PCI DSS. Si puedes, usa la Página de Pagos (paymentPage) o
|
|
605
|
+
* cobra con un token de DataVault (tokenSale).
|
|
606
|
+
*/
|
|
607
|
+
async sale(input) {
|
|
608
|
+
return this.cardTransaction("Sale", input);
|
|
609
|
+
}
|
|
610
|
+
/** Pre-autorización: retiene el monto sin cobrarlo. Captúralo luego con capture(). */
|
|
611
|
+
async hold(input) {
|
|
612
|
+
return this.cardTransaction("Hold", input);
|
|
613
|
+
}
|
|
614
|
+
/** Venta con una tarjeta guardada en DataVault. */
|
|
615
|
+
async tokenSale(input) {
|
|
616
|
+
const order = this.orderFields(input);
|
|
617
|
+
const payload = {
|
|
618
|
+
...this.base(),
|
|
619
|
+
...order.fields,
|
|
620
|
+
TrxType: "Sale",
|
|
621
|
+
PosInputMode: "E-Commerce",
|
|
622
|
+
CardNumber: "",
|
|
623
|
+
Expiration: "",
|
|
624
|
+
DataVaultToken: dataVaultToken(input.dataVaultToken),
|
|
625
|
+
...input.cvc ? { CVC: cvc(input.cvc) } : {}
|
|
626
|
+
};
|
|
627
|
+
const data = await this.transport.send(payload, {
|
|
628
|
+
operation: "",
|
|
629
|
+
name: "tokenSale",
|
|
630
|
+
idempotent: false,
|
|
631
|
+
customOrderId: order.customOrderId
|
|
632
|
+
});
|
|
633
|
+
return interpretResponse(data, order.customOrderId);
|
|
634
|
+
}
|
|
635
|
+
// ------------------------------------------------- después de la venta
|
|
636
|
+
/** Captura (total o parcial) una pre-autorización hecha con hold(). */
|
|
637
|
+
async capture(input) {
|
|
638
|
+
const payload = {
|
|
639
|
+
...this.base(),
|
|
640
|
+
AzulOrderId: azulOrderId(input.azulOrderId),
|
|
641
|
+
Amount: centsToAzul(input.amount, "amount"),
|
|
642
|
+
Itbis: itbisToAzul(input.itbis)
|
|
643
|
+
};
|
|
644
|
+
const data = await this.transport.send(payload, { operation: "ProcessPost", name: "capture", idempotent: false });
|
|
645
|
+
return interpretResponse(data);
|
|
646
|
+
}
|
|
647
|
+
/** Anula una transacción del mismo día (antes del cierre de lote). */
|
|
648
|
+
async void(input) {
|
|
649
|
+
const payload = { ...this.base(), AzulOrderId: azulOrderId(input.azulOrderId) };
|
|
650
|
+
const data = await this.transport.send(payload, { operation: "ProcessVoid", name: "void", idempotent: false });
|
|
651
|
+
return interpretResponse(data);
|
|
652
|
+
}
|
|
653
|
+
/** Reembolsa (total o parcialmente) una venta ya liquidada. */
|
|
654
|
+
async refund(input) {
|
|
655
|
+
const customOrderId = this.customOrderId(input.customOrderId);
|
|
656
|
+
const payload = {
|
|
657
|
+
...this.base(),
|
|
658
|
+
TrxType: "Refund",
|
|
659
|
+
PosInputMode: "E-Commerce",
|
|
660
|
+
AzulOrderId: azulOrderId(input.azulOrderId),
|
|
661
|
+
Amount: centsToAzul(input.amount, "amount"),
|
|
662
|
+
Itbis: itbisToAzul(input.itbis),
|
|
663
|
+
OrderNumber: maxText(input.orderNumber, "orderNumber", 15, { required: true }),
|
|
664
|
+
CustomOrderId: customOrderId
|
|
665
|
+
};
|
|
666
|
+
const data = await this.transport.send(payload, {
|
|
667
|
+
operation: "",
|
|
668
|
+
name: "refund",
|
|
669
|
+
idempotent: false,
|
|
670
|
+
customOrderId
|
|
671
|
+
});
|
|
672
|
+
return interpretResponse(data, customOrderId);
|
|
673
|
+
}
|
|
674
|
+
/**
|
|
675
|
+
* Consulta el estado de una transacción hecha por la API (ProcessPayment) por
|
|
676
|
+
* su customOrderId. Es la forma correcta de salir de la duda después de un
|
|
677
|
+
* AzulAmbiguousError. Si hay varias con el mismo id, AZUL devuelve la última.
|
|
678
|
+
* Si no existe, AZUL responde Found=false y aquí se devuelve `{ found: false }`.
|
|
679
|
+
*/
|
|
680
|
+
async verifyPayment(input) {
|
|
681
|
+
const customOrderId = maxText(input.customOrderId, "customOrderId", 75, { required: true });
|
|
682
|
+
const data = await this.transport.send(
|
|
683
|
+
{ ...this.base(), CustomOrderId: customOrderId },
|
|
684
|
+
{ operation: "VerifyPayment", name: "verifyPayment", idempotent: true, customOrderId }
|
|
685
|
+
);
|
|
686
|
+
const found = data.Found;
|
|
687
|
+
if ((found === false || found === "false" || found === "False") && data.ResponseCode !== "Error") {
|
|
688
|
+
return { found: false, customOrderId, raw: data };
|
|
689
|
+
}
|
|
690
|
+
return { found: true, ...interpretResponse(data, customOrderId) };
|
|
691
|
+
}
|
|
692
|
+
// ------------------------------------------------------------- DataVault
|
|
693
|
+
dataVault = {
|
|
694
|
+
/** Guarda una tarjeta y devuelve un token reutilizable. */
|
|
695
|
+
create: async (input) => {
|
|
696
|
+
const payload = {
|
|
697
|
+
...this.base(),
|
|
698
|
+
TrxType: "CREATE",
|
|
699
|
+
CardNumber: cardNumber(input.cardNumber),
|
|
700
|
+
Expiration: expiration(input.expiration),
|
|
701
|
+
CVC: cvc(input.cvc)
|
|
702
|
+
};
|
|
703
|
+
const data = await this.transport.send(payload, {
|
|
704
|
+
operation: "ProcessDatavault",
|
|
705
|
+
name: "dataVault.create",
|
|
706
|
+
idempotent: false
|
|
707
|
+
});
|
|
708
|
+
return toDataVaultResult(data);
|
|
709
|
+
},
|
|
710
|
+
/** Elimina un token de DataVault. */
|
|
711
|
+
delete: async (input) => {
|
|
712
|
+
const payload = { ...this.base(), TrxType: "DELETE", DataVaultToken: dataVaultToken(input.dataVaultToken) };
|
|
713
|
+
const data = await this.transport.send(payload, {
|
|
714
|
+
operation: "ProcessDatavault",
|
|
715
|
+
name: "dataVault.delete",
|
|
716
|
+
idempotent: true
|
|
717
|
+
});
|
|
718
|
+
return toDataVaultResult(data);
|
|
719
|
+
}
|
|
720
|
+
};
|
|
721
|
+
// ------------------------------------------------------- Página de Pagos
|
|
722
|
+
/**
|
|
723
|
+
* Prepara el formulario para redirigir al cliente a la Página de Pagos de
|
|
724
|
+
* AZUL. Con esta vía tu servidor nunca ve el número de tarjeta.
|
|
725
|
+
*/
|
|
726
|
+
paymentPage(input) {
|
|
727
|
+
if (!this.config.paymentPage) {
|
|
728
|
+
throw new AzulConfigError("Para usar la P\xE1gina de Pagos configura paymentPage: { authKey, merchantName, merchantType }.");
|
|
729
|
+
}
|
|
730
|
+
return buildPaymentPage(input, {
|
|
731
|
+
merchantId: this.config.merchantId,
|
|
732
|
+
url: this.config.paymentPageUrl,
|
|
733
|
+
...this.config.paymentPage
|
|
734
|
+
});
|
|
735
|
+
}
|
|
736
|
+
// -------------------------------------------------------------- internos
|
|
737
|
+
base() {
|
|
738
|
+
return { Channel: this.config.channel, Store: this.config.merchantId };
|
|
739
|
+
}
|
|
740
|
+
customOrderId(value) {
|
|
741
|
+
return maxText(value, "customOrderId", 75) ?? (0, import_node_crypto2.randomUUID)();
|
|
742
|
+
}
|
|
743
|
+
orderFields(input) {
|
|
744
|
+
const customOrderId = this.customOrderId(input.customOrderId);
|
|
745
|
+
const fields = {
|
|
746
|
+
Amount: centsToAzul(input.amount, "amount"),
|
|
747
|
+
Itbis: itbisToAzul(input.itbis),
|
|
748
|
+
OrderNumber: maxText(input.orderNumber, "orderNumber", 15, { required: true }),
|
|
749
|
+
CustomOrderId: customOrderId
|
|
750
|
+
};
|
|
751
|
+
const alt = maxText(input.altMerchantName, "altMerchantName", 25);
|
|
752
|
+
if (alt) fields.AltMerchantName = alt;
|
|
753
|
+
if (input.forceNo3DS) fields.ForceNo3DS = "1";
|
|
754
|
+
if (input.itbis !== void 0 && input.itbis > input.amount) {
|
|
755
|
+
throw new AzulValidationError("itbis no puede ser mayor que amount: amount es el total con ITBIS incluido.", ["itbis"]);
|
|
756
|
+
}
|
|
757
|
+
return { customOrderId, fields };
|
|
758
|
+
}
|
|
759
|
+
async cardTransaction(trxType, input) {
|
|
760
|
+
const order = this.orderFields(input);
|
|
761
|
+
const payload = {
|
|
762
|
+
...this.base(),
|
|
763
|
+
...order.fields,
|
|
764
|
+
TrxType: trxType,
|
|
765
|
+
PosInputMode: "E-Commerce",
|
|
766
|
+
CardNumber: cardNumber(input.cardNumber),
|
|
767
|
+
Expiration: expiration(input.expiration),
|
|
768
|
+
CVC: cvc(input.cvc),
|
|
769
|
+
SaveToDataVault: input.saveToDataVault ? "1" : "0"
|
|
770
|
+
};
|
|
771
|
+
const data = await this.transport.send(payload, {
|
|
772
|
+
operation: "",
|
|
773
|
+
name: trxType === "Sale" ? "sale" : "hold",
|
|
774
|
+
idempotent: false,
|
|
775
|
+
customOrderId: order.customOrderId
|
|
776
|
+
});
|
|
777
|
+
return interpretResponse(data, order.customOrderId);
|
|
778
|
+
}
|
|
779
|
+
};
|
|
780
|
+
function toDataVaultResult(data) {
|
|
781
|
+
const s = (k) => typeof data[k] === "string" && data[k] !== "" ? data[k] : void 0;
|
|
782
|
+
const result = interpretResponse(data);
|
|
783
|
+
return {
|
|
784
|
+
status: result.status === "approved" ? "approved" : "declined",
|
|
785
|
+
dataVaultToken: s("DataVaultToken"),
|
|
786
|
+
maskedCardNumber: s("CardNumber"),
|
|
787
|
+
brand: s("DataVaultBrand"),
|
|
788
|
+
expiration: s("DataVaultExpiration"),
|
|
789
|
+
hasCvv: data.HasCVV === true || data.HasCVV === "true",
|
|
790
|
+
isoCode: result.isoCode,
|
|
791
|
+
message: result.message,
|
|
792
|
+
errorDescription: result.errorDescription,
|
|
793
|
+
raw: data
|
|
794
|
+
};
|
|
795
|
+
}
|
|
796
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
797
|
+
0 && (module.exports = {
|
|
798
|
+
AZUL_URLS,
|
|
799
|
+
AzulAmbiguousError,
|
|
800
|
+
AzulClient,
|
|
801
|
+
AzulConfigError,
|
|
802
|
+
AzulError,
|
|
803
|
+
AzulHttpError,
|
|
804
|
+
AzulResponseError,
|
|
805
|
+
AzulValidationError,
|
|
806
|
+
PAYMENT_PAGE_HASH_FIELDS,
|
|
807
|
+
PAYMENT_PAGE_RESPONSE_HASH_FIELDS,
|
|
808
|
+
defaultPaymentPageHash,
|
|
809
|
+
escapeHtml,
|
|
810
|
+
maskPan,
|
|
811
|
+
passesLuhn,
|
|
812
|
+
paymentPageResponseHash,
|
|
813
|
+
redact,
|
|
814
|
+
toCents,
|
|
815
|
+
verifyPaymentPageResponse
|
|
816
|
+
});
|
|
817
|
+
//# sourceMappingURL=index.cjs.map
|