@boelabs/billete-sdk 0.3.2 → 0.3.3
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/README.md +214 -35
- package/dist/client.js +1 -1
- package/dist/{http-kkjWt1D4.js → http-CTnngMSn.js} +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,28 +1,130 @@
|
|
|
1
|
-
|
|
1
|
+
<div align="center">
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
# Billete SDK
|
|
4
|
+
|
|
5
|
+
**SDK oficial de TypeScript para la API de [Billete](https://billete.run).**
|
|
6
|
+
|
|
7
|
+
Cobros con Pago Móvil, precios en bolívares o en divisas a tasa BCV y suscripciones, desde tu
|
|
8
|
+
servidor y tu frontend.
|
|
9
|
+
|
|
10
|
+
[](https://www.npmjs.com/package/@boelabs/billete-sdk)
|
|
11
|
+
[](./LICENSE)
|
|
12
|
+

|
|
13
|
+

|
|
14
|
+
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
- **Tipado de punta a punta:** cada recurso, entrada y evento tiene su tipo.
|
|
20
|
+
- **Sin dependencias:** solo `fetch` y Web Crypto; funciona en Node.js 20+, Bun, Deno y
|
|
21
|
+
runtimes edge (Cloudflare Workers, Vercel).
|
|
22
|
+
- **Seguro ante reintentos:** cada mutación lleva una clave de idempotencia, así un reintento
|
|
23
|
+
nunca cobra dos veces.
|
|
24
|
+
|
|
25
|
+
## Contenido
|
|
26
|
+
|
|
27
|
+
- [Instalación](#instalación)
|
|
28
|
+
- [Inicio rápido](#inicio-rápido)
|
|
29
|
+
- [Configuración](#configuración)
|
|
30
|
+
- [Recursos](#recursos)
|
|
31
|
+
- [Montos](#montos)
|
|
32
|
+
- [Paginación](#paginación)
|
|
33
|
+
- [Errores](#errores)
|
|
34
|
+
- [Idempotencia y reintentos](#idempotencia-y-reintentos)
|
|
35
|
+
- [Navegador](#navegador)
|
|
36
|
+
- [Webhooks](#webhooks)
|
|
37
|
+
- [Licencia](#licencia)
|
|
38
|
+
|
|
39
|
+
## Instalación
|
|
6
40
|
|
|
7
41
|
```bash
|
|
8
42
|
npm install @boelabs/billete-sdk
|
|
9
43
|
```
|
|
10
44
|
|
|
11
|
-
|
|
45
|
+
El paquete tiene tres puntos de entrada:
|
|
46
|
+
|
|
47
|
+
| Import | Dónde se usa | Para qué |
|
|
48
|
+
| --- | --- | --- |
|
|
49
|
+
| `@boelabs/billete-sdk` | Servidor | Toda la API, con tu clave secreta |
|
|
50
|
+
| `@boelabs/billete-sdk/client` | Navegador | Checkout y estado de un cliente, sin clave secreta |
|
|
51
|
+
| `@boelabs/billete-sdk/webhooks` | Servidor | Verificar la firma de los webhooks |
|
|
52
|
+
|
|
53
|
+
## Inicio rápido
|
|
54
|
+
|
|
55
|
+
Crea una clave secreta en el dashboard (**Desarrolladores → Claves API**) y guárdala en
|
|
56
|
+
`BILLETE_API_KEY`.
|
|
12
57
|
|
|
13
58
|
```ts
|
|
14
59
|
import { createBillete } from "@boelabs/billete-sdk";
|
|
15
60
|
|
|
61
|
+
const billete = createBillete({ environment: "test" });
|
|
62
|
+
|
|
63
|
+
const checkout = await billete.checkouts.create({
|
|
64
|
+
plan: "plan_…",
|
|
65
|
+
months: 1,
|
|
66
|
+
customer: { email: "ana@example.com", externalId: "user_123" },
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// Redirige a tu cliente a checkout.url para que pague.
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Cuando el pago se confirma, Billete te avisa con el webhook `checkout.completed` (ver
|
|
73
|
+
[Webhooks](#webhooks)).
|
|
74
|
+
|
|
75
|
+
## Configuración
|
|
76
|
+
|
|
77
|
+
```ts
|
|
16
78
|
const billete = createBillete({
|
|
17
|
-
environment: "
|
|
18
|
-
|
|
19
|
-
|
|
79
|
+
environment: "production",
|
|
80
|
+
apiKey: process.env.BILLETE_API_KEY,
|
|
81
|
+
timeoutMs: 30_000,
|
|
82
|
+
maxRetries: 2,
|
|
20
83
|
});
|
|
21
84
|
```
|
|
22
85
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
86
|
+
| Opción | Por defecto | Descripción |
|
|
87
|
+
| --- | --- | --- |
|
|
88
|
+
| `environment` | — (obligatoria) | `"test"` o `"production"`. Debe coincidir con la clave. |
|
|
89
|
+
| `apiKey` | `BILLETE_API_KEY` | Clave secreta: `sk_test_…` o `sk_prod_…`. |
|
|
90
|
+
| `baseUrl` | `BILLETE_BASE_URL`, o `https://api.billete.run` | URL de la API. |
|
|
91
|
+
| `timeoutMs` | `30000` | Tiempo máximo por intento. |
|
|
92
|
+
| `maxRetries` | `2` | Reintentos ante errores de red, `429` y `5xx`. |
|
|
93
|
+
| `fetch` | `globalThis.fetch` | Implementación de `fetch` propia (proxies, tests). |
|
|
94
|
+
|
|
95
|
+
Si `environment` no coincide con el prefijo de la clave, `createBillete` lanza un error: una
|
|
96
|
+
clave de prueba nunca opera en producción ni al revés. Por la misma razón, el cliente de servidor
|
|
97
|
+
se niega a crearse en un navegador.
|
|
98
|
+
|
|
99
|
+
> [!WARNING]
|
|
100
|
+
> La clave secreta da acceso completo a tu negocio. Úsala solo en el servidor y nunca la
|
|
101
|
+
> incluyas en código que llegue al navegador.
|
|
102
|
+
|
|
103
|
+
## Recursos
|
|
104
|
+
|
|
105
|
+
Todos los métodos devuelven promesas con el recurso tipado. Los recursos con listado tienen
|
|
106
|
+
`create`, `retrieve`, `update`, `list` e `iterate`, además de sus acciones propias:
|
|
107
|
+
|
|
108
|
+
| Recurso | Acciones propias |
|
|
109
|
+
| --- | --- |
|
|
110
|
+
| `customers` | — |
|
|
111
|
+
| `customerSessions` | `create` |
|
|
112
|
+
| `products` | `retire`, `retireVariant` |
|
|
113
|
+
| `plans` | — |
|
|
114
|
+
| `features` | `check`, `isEnabled`, `listForCustomer`, `consume`, `release`, `set` |
|
|
115
|
+
| `coupons` | — |
|
|
116
|
+
| `checkouts` | `expire` |
|
|
117
|
+
| `paymentLinks` | `retire` |
|
|
118
|
+
| `subscriptions` | `cancel`, `resume` |
|
|
119
|
+
| `payments` | `refund`, `listRefunds` |
|
|
120
|
+
| `payouts` | — |
|
|
121
|
+
| `balance` | `retrieve` |
|
|
122
|
+
| `webhookEndpoints` | `delete` |
|
|
123
|
+
| `events` | `retrieve`, `list`, `iterate` |
|
|
124
|
+
| `exchangeRates` | `list` |
|
|
125
|
+
| `currencies` | `list` |
|
|
126
|
+
|
|
127
|
+
### Ejemplo: plan con cuotas
|
|
26
128
|
|
|
27
129
|
```ts
|
|
28
130
|
await billete.features.create({
|
|
@@ -43,48 +145,125 @@ const plan = await billete.plans.create({
|
|
|
43
145
|
features: { tokens: 100_000 },
|
|
44
146
|
});
|
|
45
147
|
|
|
46
|
-
|
|
47
|
-
plan: plan.id,
|
|
48
|
-
months: 1,
|
|
49
|
-
customer: { email: "ana@example.com", externalId: "user_123" },
|
|
50
|
-
});
|
|
51
|
-
// Redirige al cliente a checkout.url
|
|
52
|
-
|
|
148
|
+
// `customer` acepta el ID de Billete (cus_…) o tu propio externalId.
|
|
53
149
|
const { allowed } = await billete.features.consume("user_123", "tokens", 850);
|
|
54
150
|
```
|
|
55
151
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
152
|
+
`consume` es atómico: si el consumo supera el límite, no se descuenta nada y `allowed` es
|
|
153
|
+
`false`.
|
|
154
|
+
|
|
155
|
+
## Montos
|
|
156
|
+
|
|
157
|
+
Los montos son siempre **enteros en unidad menor** (céntimos), nunca decimales: `1_000` en `USD`
|
|
158
|
+
son 10,00 USD y `1_000` en `VES` son 10,00 Bs. Las tasas de cambio llegan como texto decimal para
|
|
159
|
+
no perder precisión.
|
|
160
|
+
|
|
161
|
+
## Paginación
|
|
162
|
+
|
|
163
|
+
`list` devuelve una página (`{ data, nextCursor }`); `iterate` recorre todas por ti:
|
|
164
|
+
|
|
165
|
+
```ts
|
|
166
|
+
const page = await billete.payments.list({ limit: 50 });
|
|
167
|
+
const next = await billete.payments.list({ limit: 50, cursor: page.nextCursor ?? undefined });
|
|
168
|
+
|
|
169
|
+
for await (const payment of billete.payments.iterate()) {
|
|
170
|
+
console.log(payment.id, payment.amount);
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## Errores
|
|
175
|
+
|
|
176
|
+
Toda respuesta fallida lanza un `BilleteError`:
|
|
177
|
+
|
|
178
|
+
```ts
|
|
179
|
+
import { BilleteError } from "@boelabs/billete-sdk";
|
|
180
|
+
|
|
181
|
+
try {
|
|
182
|
+
await billete.payments.refund("pay_…");
|
|
183
|
+
} catch (error) {
|
|
184
|
+
if (error instanceof BilleteError && error.code === "payment_not_refundable") {
|
|
185
|
+
// …
|
|
186
|
+
}
|
|
187
|
+
throw error;
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
| Propiedad | Descripción |
|
|
192
|
+
| --- | --- |
|
|
193
|
+
| `type` | Categoría: `invalid_request`, `authentication`, `permission`, `not_found`, `conflict`, `validation`, `rate_limit`, `provider`, `internal` o `network`. |
|
|
194
|
+
| `code` | Código estable, apto para decidir en tu código. |
|
|
195
|
+
| `message` | Descripción legible del error. |
|
|
196
|
+
| `status` | Código HTTP (`0` si no hubo respuesta). |
|
|
197
|
+
| `param` | Campo que causó el error, cuando aplica. |
|
|
198
|
+
| `requestId` | Identificador de la petición; inclúyelo al pedir soporte. |
|
|
199
|
+
|
|
200
|
+
## Idempotencia y reintentos
|
|
201
|
+
|
|
202
|
+
Cada mutación envía una `Idempotency-Key` generada automáticamente, que se mantiene entre
|
|
203
|
+
reintentos. Si quieres que un reintento tuyo (por ejemplo, de un job) también sea seguro, pasa
|
|
204
|
+
una clave estable:
|
|
205
|
+
|
|
206
|
+
```ts
|
|
207
|
+
await billete.payments.refund("pay_…", {}, { idempotencyKey: `refund-${orderId}` });
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
Todos los métodos aceptan como último argumento `{ idempotencyKey?, signal? }`; `signal` permite
|
|
211
|
+
cancelar la petición con un `AbortController`.
|
|
59
212
|
|
|
60
213
|
## Navegador
|
|
61
214
|
|
|
62
|
-
|
|
63
|
-
|
|
215
|
+
El cliente de navegador nunca usa tu clave secreta. Tu servidor crea una **sesión de cliente**
|
|
216
|
+
de corta duración y se la entrega al frontend:
|
|
64
217
|
|
|
65
218
|
```ts
|
|
219
|
+
// Servidor
|
|
220
|
+
const session = await billete.customerSessions.create({ customer: "user_123" });
|
|
221
|
+
return { token: session.token };
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
```ts
|
|
225
|
+
// Navegador
|
|
66
226
|
import { createClient } from "@boelabs/billete-sdk/client";
|
|
67
227
|
|
|
68
|
-
const billete = createClient({ customerSession:
|
|
69
|
-
|
|
70
|
-
|
|
228
|
+
const billete = createClient({ customerSession: token });
|
|
229
|
+
|
|
230
|
+
const { data: subscriptions } = await billete.customer.subscriptions();
|
|
231
|
+
const tokens = await billete.customer.feature("tokens");
|
|
71
232
|
```
|
|
72
233
|
|
|
234
|
+
`customerSession` también acepta una función, útil cuando el token se renueva. Sin sesión, el
|
|
235
|
+
cliente puede leer y pagar checkouts y links de pago, y consultar tasas y monedas.
|
|
236
|
+
|
|
73
237
|
## Webhooks
|
|
74
238
|
|
|
75
|
-
|
|
239
|
+
Billete firma los webhooks con [Standard Webhooks](https://www.standardwebhooks.com). Verifica la
|
|
240
|
+
firma con el cuerpo **exactamente como llegó**, antes de parsearlo:
|
|
76
241
|
|
|
77
242
|
```ts
|
|
78
|
-
import { verifyWebhook } from "@boelabs/billete-sdk/webhooks";
|
|
243
|
+
import { verifyWebhook, WebhookVerificationError } from "@boelabs/billete-sdk/webhooks";
|
|
79
244
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
245
|
+
export async function POST(request: Request) {
|
|
246
|
+
try {
|
|
247
|
+
const event = await verifyWebhook({
|
|
248
|
+
payload: await request.text(),
|
|
249
|
+
headers: request.headers,
|
|
250
|
+
secret: process.env.BILLETE_WEBHOOK_SECRET!,
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
if (event.type === "checkout.completed") {
|
|
254
|
+
// event.data es un Checkout
|
|
255
|
+
}
|
|
256
|
+
return new Response(null, { status: 204 });
|
|
257
|
+
} catch (error) {
|
|
258
|
+
if (error instanceof WebhookVerificationError) return new Response(null, { status: 400 });
|
|
259
|
+
throw error;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
86
262
|
```
|
|
87
263
|
|
|
264
|
+
`event` es una unión discriminada por `type`, así que `event.data` tiene el tipo correcto en cada
|
|
265
|
+
rama. Se rechazan las firmas con más de 5 minutos (`toleranceSeconds`).
|
|
266
|
+
|
|
88
267
|
## Licencia
|
|
89
268
|
|
|
90
|
-
MIT
|
|
269
|
+
[MIT](./LICENSE)
|
package/dist/client.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as segment, r as BilleteError, t as createTransport } from "./http-
|
|
1
|
+
import { n as segment, r as BilleteError, t as createTransport } from "./http-CTnngMSn.js";
|
|
2
2
|
//#region src/create-client.ts
|
|
3
3
|
/**
|
|
4
4
|
* Browser client. It never holds a secret key: checkouts are addressed by their unguessable ID
|
|
@@ -18,7 +18,7 @@ var BilleteError = class extends Error {
|
|
|
18
18
|
};
|
|
19
19
|
//#endregion
|
|
20
20
|
//#region src/http.ts
|
|
21
|
-
const SDK_VERSION = "0.3.
|
|
21
|
+
const SDK_VERSION = "0.3.3";
|
|
22
22
|
const DEFAULT_BASE_URL = "https://api.billete.run";
|
|
23
23
|
const RETRYABLE_STATUS = /* @__PURE__ */ new Set([
|
|
24
24
|
429,
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as segment, r as BilleteError, t as createTransport } from "./http-
|
|
1
|
+
import { n as segment, r as BilleteError, t as createTransport } from "./http-CTnngMSn.js";
|
|
2
2
|
import { WebhookVerificationError, verifyWebhook } from "./webhooks.js";
|
|
3
3
|
//#region ../contracts/src/environment.ts
|
|
4
4
|
/**
|