@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/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Cristian Encarnación
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,157 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @pagosrd/azul
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
SDK **no oficial** en TypeScript para la pasarela de pagos **AZUL** (República Dominicana).
|
|
4
|
+
|
|
5
|
+
> AZUL es una marca de Servicios Digitales Popular. Este paquete no está afiliado ni respaldado por AZUL.
|
|
6
|
+
|
|
7
|
+
## Qué cubre
|
|
8
|
+
|
|
9
|
+
| Operación | Método |
|
|
10
|
+
|---|---|
|
|
11
|
+
| Venta con tarjeta | `sale()` |
|
|
12
|
+
| Pre-autorización y captura | `hold()` y `capture()` |
|
|
13
|
+
| Anulación del mismo día | `void()` |
|
|
14
|
+
| Reembolso | `refund()` |
|
|
15
|
+
| Consultar el estado | `verifyPayment()` |
|
|
16
|
+
| Guardar y borrar tarjetas | `dataVault.create()` y `dataVault.delete()` |
|
|
17
|
+
| Cobrar con tarjeta guardada | `tokenSale()` |
|
|
18
|
+
| Página de Pagos alojada | `paymentPage()` |
|
|
19
|
+
|
|
20
|
+
Además:
|
|
21
|
+
- **Failover seguro:** en producción usa la URL alterna de AZUL solo cuando la solicitud no llegó a salir. Si pudo haberse cobrado, lanza `AzulAmbiguousError` en vez de reintentar.
|
|
22
|
+
- Montos en centavos enteros, con `toCents("1,500.50")` para convertir.
|
|
23
|
+
- Validación previa al envío: Luhn, vencimiento y largo de los campos.
|
|
24
|
+
- Logs censurados con `onRequest` y `onResponse`.
|
|
25
|
+
|
|
26
|
+
## Instalación
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pnpm add @pagosrd/azul
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Requiere Node 20 o superior.
|
|
33
|
+
|
|
34
|
+
## Uso
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
import { AzulClient, AzulAmbiguousError, toCents } from "@pagosrd/azul";
|
|
38
|
+
|
|
39
|
+
const azul = new AzulClient({
|
|
40
|
+
environment: "pruebas", // o "produccion"
|
|
41
|
+
merchantId: process.env.AZUL_MERCHANT_ID!,
|
|
42
|
+
auth1: process.env.AZUL_AUTH1!,
|
|
43
|
+
auth2: process.env.AZUL_AUTH2!,
|
|
44
|
+
certificate: process.env.AZUL_CERT_PEM!, // contenido PEM, no la ruta
|
|
45
|
+
privateKey: process.env.AZUL_KEY_PEM!,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
const result = await azul.tokenSale({
|
|
50
|
+
amount: toCents("4,500.00"),
|
|
51
|
+
orderNumber: "CUOTA-4B-10",
|
|
52
|
+
customOrderId: "cuota-apto-4b-2026-10", // guárdalo antes de cobrar
|
|
53
|
+
dataVaultToken: tarjetaGuardada,
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
if (result.status === "approved") {
|
|
57
|
+
// guarda result.azulOrderId para reembolsos o anulaciones
|
|
58
|
+
} else if (result.status === "declined") {
|
|
59
|
+
console.log(result.errorDescription);
|
|
60
|
+
}
|
|
61
|
+
} catch (err) {
|
|
62
|
+
if (err instanceof AzulAmbiguousError) {
|
|
63
|
+
// No reintentes: consulta primero
|
|
64
|
+
const estado = await azul.verifyPayment({ customOrderId: err.customOrderId! });
|
|
65
|
+
}
|
|
66
|
+
throw err;
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Guía: cobrar con la Página de Pago de AZUL en Node
|
|
71
|
+
|
|
72
|
+
La Página de Pago es la forma recomendada: el cliente escribe la tarjeta en la página de AZUL y tu servidor nunca la ve. Son tres pasos.
|
|
73
|
+
|
|
74
|
+
**1. Configura el cliente con la AuthKey de la Página de Pago.**
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
import { AzulClient, toCents, verifyPaymentPageResponse } from "@pagosrd/azul";
|
|
78
|
+
|
|
79
|
+
const azul = new AzulClient({
|
|
80
|
+
environment: "pruebas", // o "produccion"
|
|
81
|
+
merchantId: process.env.AZUL_MERCHANT_ID!,
|
|
82
|
+
auth1: process.env.AZUL_AUTH1!,
|
|
83
|
+
auth2: process.env.AZUL_AUTH2!,
|
|
84
|
+
certificate: process.env.AZUL_CERT_PEM!,
|
|
85
|
+
privateKey: process.env.AZUL_KEY_PEM!,
|
|
86
|
+
paymentPage: {
|
|
87
|
+
authKey: process.env.AZUL_AUTH_KEY!,
|
|
88
|
+
merchantName: "Residencial Las Palmas",
|
|
89
|
+
merchantType: "ECommerce",
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
**2. Arma el formulario y envía al cliente a AZUL.** `paymentPage()` calcula el `AuthHash` (HMAC-SHA512 con la AuthKey, como indica el documento técnico 2026) y devuelve un HTML que redirige solo.
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
// Ejemplo con Hono; con Express es igual: res.type("html").send(page.html)
|
|
98
|
+
app.post("/cuotas/:id/pagar", (c) => {
|
|
99
|
+
const page = azul.paymentPage({
|
|
100
|
+
amount: toCents("4,500.00"), // centavos enteros
|
|
101
|
+
orderNumber: "CUOTA4B10", // guárdalo junto a la cuota
|
|
102
|
+
approvedUrl: "https://tusitio.do/azul/aprobado",
|
|
103
|
+
declineUrl: "https://tusitio.do/azul/declinado",
|
|
104
|
+
cancelUrl: "https://tusitio.do/azul/cancelado",
|
|
105
|
+
customFields: [{ label: "Apartamento", value: "4B" }],
|
|
106
|
+
});
|
|
107
|
+
return c.html(page.html);
|
|
108
|
+
});
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
**3. Verifica la respuesta en tu servidor antes de dar el pago por cobrado.** AZUL devuelve al cliente a `approvedUrl` o `declineUrl` con un `AuthHash` propio (HMAC-SHA512 en UTF-16LE). Llegar a `approvedUrl` no basta: valida la firma y que el número de orden y el monto sean los que enviaste.
|
|
112
|
+
|
|
113
|
+
```ts
|
|
114
|
+
app.get("/azul/aprobado", (c) => {
|
|
115
|
+
const params = c.req.query(); // AZUL envía los campos en la URL
|
|
116
|
+
const cuota = buscarCuota(params.OrderNumber);
|
|
117
|
+
|
|
118
|
+
const firmaValida = verifyPaymentPageResponse(params, process.env.AZUL_AUTH_KEY!);
|
|
119
|
+
const coincide = cuota && params.Amount === String(cuota.montoEnCentavos);
|
|
120
|
+
const aprobado = params.ResponseCode === "ISO8583" && params.IsoCode === "00";
|
|
121
|
+
|
|
122
|
+
if (firmaValida && coincide && aprobado) {
|
|
123
|
+
marcarPagada(cuota, { autorizacion: params.AuthorizationCode, rrn: params.RRN });
|
|
124
|
+
} else {
|
|
125
|
+
marcarParaRevision(params.OrderNumber); // nunca la des por pagada
|
|
126
|
+
}
|
|
127
|
+
return c.redirect(`/cuotas/${cuota?.id ?? ""}`);
|
|
128
|
+
});
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
`AzulOrderId` y `CardNumber` llegan en la respuesta pero no forman parte de la firma: no los uses para decidir.
|
|
132
|
+
|
|
133
|
+
## Errores
|
|
134
|
+
|
|
135
|
+
| Error | Significa | Qué hacer |
|
|
136
|
+
|---|---|---|
|
|
137
|
+
| `AzulValidationError` | Datos inválidos, no se envió nada | Corregir la entrada |
|
|
138
|
+
| `AzulConfigError` | Configuración incompleta | Revisar credenciales y certificado |
|
|
139
|
+
| `AzulResponseError` | AZUL respondió con error de sistema | No se aprobó; revisar `response` |
|
|
140
|
+
| `AzulHttpError` | HTTP 4xx | No se procesó |
|
|
141
|
+
| `AzulAmbiguousError` | No sabemos si se cobró | `verifyPayment({ customOrderId })` antes de reintentar |
|
|
142
|
+
|
|
143
|
+
Un pago **declinado no es una excepción**: llega como `status: "declined"`.
|
|
144
|
+
|
|
145
|
+
## Estado
|
|
146
|
+
|
|
147
|
+
Confirmado con los documentos técnicos de AZUL 2026 y con transacciones reales en su ambiente de pruebas:
|
|
148
|
+
|
|
149
|
+
- El `AuthHash` del requerimiento de la Página de Pago.
|
|
150
|
+
- La firma (`AuthHash`) de la respuesta a `approvedUrl` y `declineUrl`.
|
|
151
|
+
- La respuesta de `VerifyPayment` cuando la transacción no existe (`Found: false`).
|
|
152
|
+
|
|
153
|
+
Pendiente de verificar: la URL alterna de producción para el failover (viene del SDK de referencia `pyazul`, MIT; el manual menciona un sitio secundario sin dar la URL).
|
|
154
|
+
|
|
155
|
+
## Licencia
|
|
156
|
+
|
|
157
|
+
MIT
|