@boelabs/billete-sdk 0.3.1 → 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 CHANGED
@@ -1,28 +1,130 @@
1
- # @boelabs/billete-sdk
1
+ <div align="center">
2
2
 
3
- SDK oficial de [Billete](https://billete.run): pagos y suscripciones para Venezuela con Pago Móvil,
4
- precios en bolívares o en divisas a tasa BCV y cuotas por plan. Tipado de punta a punta, sin
5
- dependencias y compatible con Node, Bun, Deno y runtimes edge.
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
+ [![npm](https://img.shields.io/npm/v/@boelabs/billete-sdk?color=000&label=npm)](https://www.npmjs.com/package/@boelabs/billete-sdk)
11
+ [![Licencia: MIT](https://img.shields.io/badge/licencia-MIT-000)](./LICENSE)
12
+ ![Tipos incluidos](https://img.shields.io/npm/types/@boelabs/billete-sdk?color=000)
13
+ ![Sin dependencias](https://img.shields.io/badge/dependencias-0-000)
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
- ## Servidor
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: "test", // obligatorio: "test" o "production"
18
- // apiKey: por defecto lee BILLETE_API_KEY
19
- // baseUrl: por defecto lee BILLETE_BASE_URL, o usa https://api.billete.run
79
+ environment: "production",
80
+ apiKey: process.env.BILLETE_API_KEY,
81
+ timeoutMs: 30_000,
82
+ maxRetries: 2,
20
83
  });
21
84
  ```
22
85
 
23
- `environment` debe coincidir con la clave (`sk_test_…` o `sk_prod_…`): si no coinciden, el cliente
24
- falla al crearse, así una clave nunca opera en el entorno equivocado. Las claves se crean en el
25
- dashboard, en **Desarrolladores → Claves API**. Nunca uses una clave en el navegador.
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
- const checkout = await billete.checkouts.create({
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
- Los montos son siempre enteros en unidad menor (céntimos): `1_000` son 10,00 USD. Las mutaciones
57
- llevan una `Idempotency-Key` automática, así los reintentos ante fallos de red nunca duplican un
58
- cobro. Los errores de la API llegan como `BilleteError`, con `code` estable y `status`.
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
- Sin clave: tu servidor crea una sesión de cliente de corta duración
63
- (`billete.customerSessions.create`) y el navegador la usa para leer el estado de ese cliente.
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: tokenFromYourServer });
69
- const [subscription] = (await billete.customer.subscriptions()).data;
70
- // subscription.renewal → { status: "due" | "overdue", daysRemaining, checkoutUrl, … }
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
- Firma [Standard Webhooks](https://www.standardwebhooks.com). Pasa el cuerpo tal como llegó:
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
- const event = await verifyWebhook({
81
- payload: rawBody,
82
- headers: request.headers,
83
- secret: process.env.BILLETE_WEBHOOK_SECRET!,
84
- });
85
- if (event.type === "subscription.past_due") notify(event.data.customerId);
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. El SDK es el único código público de Billete; el resto del proyecto es propietario.
269
+ [MIT](./LICENSE)
package/dist/client.d.ts CHANGED
@@ -50,5 +50,4 @@ export declare function createClient(options?: ClientOptions): {
50
50
  };
51
51
  type BilleteClient = ReturnType<typeof createClient>;
52
52
  //#endregion
53
- export { type API_ERROR_TYPES, type Actor, type Address, type AddressInput, type AfterCompletion, type AfterCompletionInput, type ApiErrorBody, type ApiErrorEnvelope, type ApiErrorType, type BILLING_MONTHS, type Balance, type BalanceAmount, type BilleteClient, BilleteError, type BillingMonths, type CHECKOUT_PURPOSES, type CURRENCIES, type Checkout, type CheckoutCreate, type CheckoutItem, type CheckoutItemInput, type CheckoutPaymentMethod, type CheckoutPurpose, type ClientCheckoutPay, type ClientCheckoutUpdate, type ClientLinkCheckout, type ClientOptions, type CollectInput, type CollectSettings, type Coupon, type CouponCreate, type CouponUpdate, type Currency, type CurrencyAvailability, type CustomField, type CustomFieldInput, type Customer, type CustomerCreate, type CustomerDetails, type CustomerFeature, type CustomerInput, type CustomerSession, type CustomerSessionCreate, type CustomerUpdate, type ENVIRONMENTS, type EVENT_TYPES, type Environment, type Event, type EventDataMap, type EventSelector, type EventType, type ExchangeRate, type FEATURE_TYPES, type Feature, type FeatureCreate, type FeatureReset, type FeatureType, type FeatureUpdate, type FeatureUsageResult, type GRACE_PERIOD_DAYS, type GracePeriodDays, type KEY_PATTERN, type List, type ListQuery, type MOBILE_PREFIXES, type Metadata, type MobilePrefix, type PAYMENT_FAILURE_CODES, type PAYMENT_STATUSES, type PRODUCT_CATEGORIES, type Payment, type PaymentAttempt, type PaymentFailure, type PaymentFailureCode, type PaymentInstructions, type PaymentLink, type PaymentLinkCreate, type PaymentLinkUpdate, type PaymentMethodField, type PaymentSettlement, type PaymentStatus, type PaymentTestScenario, type Payout, type Plan, type PlanCheckoutCreate, type PlanCreate, type PlanFeatureValues, type PlanFeaturesInput, type PlanLinkCreate, type PlanPrice, type PlanPriceInput, type PlanUpdate, type Product, type ProductCategory, type ProductCheckoutCreate, type ProductCreate, type ProductLinkCreate, type ProductUpdate, type ProductVariant, type PublicCheckout, type PublicPaymentLink, type QuantityInput, type QuantitySettings, type RESET_FREQUENCIES, type RISK_LEVELS, type Refund, type RefundCreate, type RequestOptions, type ResetFrequency, type RetireInput, type Retirement, type RiskLevel, type SUBMIT_LABELS, type SUBSCRIPTION_STATUSES, type SubmitLabel, type Subscription, type SubscriptionCancel, type SubscriptionCreate, type SubscriptionRenewal, type SubscriptionStatus, type SubscriptionUpdate, type TAX_BEHAVIORS, type TaxBehavior, type Timestamp, type TokenKind, type USAGE_FEATURE_TYPES, type VariantInput, type WebhookEndpoint, type WebhookEndpointCreate, type WebhookEndpointUpdate, type WebhookEvent, type Weekday, type isLandlinePhone, type isMobilePhone, type nationalPhone, type tokenEnvironment, type tokenPrefix };
54
- //# sourceMappingURL=client.d.ts.map
53
+ export { type API_ERROR_TYPES, type Actor, type Address, type AddressInput, type AfterCompletion, type AfterCompletionInput, type ApiErrorBody, type ApiErrorEnvelope, type ApiErrorType, type BILLING_MONTHS, type Balance, type BalanceAmount, type BilleteClient, BilleteError, type BillingMonths, type CHECKOUT_PURPOSES, type CURRENCIES, type Checkout, type CheckoutCreate, type CheckoutItem, type CheckoutItemInput, type CheckoutPaymentMethod, type CheckoutPurpose, type ClientCheckoutPay, type ClientCheckoutUpdate, type ClientLinkCheckout, type ClientOptions, type CollectInput, type CollectSettings, type Coupon, type CouponCreate, type CouponUpdate, type Currency, type CurrencyAvailability, type CustomField, type CustomFieldInput, type Customer, type CustomerCreate, type CustomerDetails, type CustomerFeature, type CustomerInput, type CustomerSession, type CustomerSessionCreate, type CustomerUpdate, type ENVIRONMENTS, type EVENT_TYPES, type Environment, type Event, type EventDataMap, type EventSelector, type EventType, type ExchangeRate, type FEATURE_TYPES, type Feature, type FeatureCreate, type FeatureReset, type FeatureType, type FeatureUpdate, type FeatureUsageResult, type GRACE_PERIOD_DAYS, type GracePeriodDays, type KEY_PATTERN, type List, type ListQuery, type MOBILE_PREFIXES, type Metadata, type MobilePrefix, type PAYMENT_FAILURE_CODES, type PAYMENT_STATUSES, type PRODUCT_CATEGORIES, type Payment, type PaymentAttempt, type PaymentFailure, type PaymentFailureCode, type PaymentInstructions, type PaymentLink, type PaymentLinkCreate, type PaymentLinkUpdate, type PaymentMethodField, type PaymentSettlement, type PaymentStatus, type PaymentTestScenario, type Payout, type Plan, type PlanCheckoutCreate, type PlanCreate, type PlanFeatureValues, type PlanFeaturesInput, type PlanLinkCreate, type PlanPrice, type PlanPriceInput, type PlanUpdate, type Product, type ProductCategory, type ProductCheckoutCreate, type ProductCreate, type ProductLinkCreate, type ProductUpdate, type ProductVariant, type PublicCheckout, type PublicPaymentLink, type QuantityInput, type QuantitySettings, type RESET_FREQUENCIES, type RISK_LEVELS, type Refund, type RefundCreate, type RequestOptions, type ResetFrequency, type RetireInput, type Retirement, type RiskLevel, type SUBMIT_LABELS, type SUBSCRIPTION_STATUSES, type SubmitLabel, type Subscription, type SubscriptionCancel, type SubscriptionCreate, type SubscriptionRenewal, type SubscriptionStatus, type SubscriptionUpdate, type TAX_BEHAVIORS, type TaxBehavior, type Timestamp, type TokenKind, type USAGE_FEATURE_TYPES, type VariantInput, type WebhookEndpoint, type WebhookEndpointCreate, type WebhookEndpointUpdate, type WebhookEvent, type Weekday, type isLandlinePhone, type isMobilePhone, type nationalPhone, type tokenEnvironment, type tokenPrefix };
package/dist/client.js CHANGED
@@ -1,4 +1,4 @@
1
- import { n as segment, r as BilleteError, t as createTransport } from "./http-CG9YDQ6-.js";
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
@@ -98,5 +98,3 @@ list: (options) => http.request({
98
98
  }
99
99
  //#endregion
100
100
  export { BilleteError, createClient };
101
-
102
- //# sourceMappingURL=client.js.map
@@ -446,5 +446,4 @@ declare class BilleteError extends Error {
446
446
  }, status: number);
447
447
  }
448
448
  //#endregion
449
- export { ProductCheckoutCreate as A, VariantInput as B, PaymentLinkUpdate as C, PlanLinkCreate as D, PlanFeaturesInput as E, RefundCreate as F, PaymentMethodField as G, WebhookEndpointUpdate as H, RetireInput as I, PublicPaymentLink as J, PaymentTestScenario as K, SubscriptionCancel as L, ProductLinkCreate as M, ProductUpdate as N, PlanPriceInput as O, QuantityInput as P, SubscriptionCreate as R, PaymentLinkCreate as S, PlanCreate as T, CheckoutPaymentMethod as U, WebhookEndpointCreate as V, PaymentInstructions as W, CustomerUpdate as _, CheckoutCreate as a, FeatureUpdate as b, ClientCheckoutUpdate as c, CouponCreate as d, CouponUpdate as f, CustomerSessionCreate as g, CustomerInput as h, AfterCompletionInput as i, ProductCreate as j, PlanUpdate as k, ClientLinkCheckout as l, CustomerCreate as m, RequestOptions as n, CheckoutItemInput as o, CustomFieldInput as p, PublicCheckout as q, TransportOptions as r, ClientCheckoutPay as s, BilleteError as t, CollectInput as u, EventSelector as v, PlanCheckoutCreate as w, KEY_PATTERN as x, FeatureCreate as y, SubscriptionUpdate as z };
450
- //# sourceMappingURL=errors-oT7VMFhd.d.ts.map
449
+ export { ProductCheckoutCreate as A, VariantInput as B, PaymentLinkUpdate as C, PlanLinkCreate as D, PlanFeaturesInput as E, RefundCreate as F, PaymentMethodField as G, WebhookEndpointUpdate as H, RetireInput as I, PublicPaymentLink as J, PaymentTestScenario as K, SubscriptionCancel as L, ProductLinkCreate as M, ProductUpdate as N, PlanPriceInput as O, QuantityInput as P, SubscriptionCreate as R, PaymentLinkCreate as S, PlanCreate as T, CheckoutPaymentMethod as U, WebhookEndpointCreate as V, PaymentInstructions as W, CustomerUpdate as _, CheckoutCreate as a, FeatureUpdate as b, ClientCheckoutUpdate as c, CouponCreate as d, CouponUpdate as f, CustomerSessionCreate as g, CustomerInput as h, AfterCompletionInput as i, ProductCreate as j, PlanUpdate as k, ClientLinkCheckout as l, CustomerCreate as m, RequestOptions as n, CheckoutItemInput as o, CustomFieldInput as p, PublicCheckout as q, TransportOptions as r, ClientCheckoutPay as s, BilleteError as t, CollectInput as u, EventSelector as v, PlanCheckoutCreate as w, KEY_PATTERN as x, FeatureCreate as y, SubscriptionUpdate as z };
@@ -564,5 +564,4 @@ type WebhookEvent = { [TType in EventType]: {
564
564
  data: EventDataMap[TType];
565
565
  }; }[EventType];
566
566
  //#endregion
567
- export { Subscription as $, Payment as A, ENVIRONMENTS as At, Product as B, FEATURE_TYPES as C, MobilePrefix as Ct, FeatureUsageResult as D, isLandlinePhone as Dt, FeatureType as E, Timestamp as Et, PaymentStatus as F, RISK_LEVELS as G, ProductVariant as H, Payout as I, Retirement as J, Refund as K, Plan as L, PaymentFailure as M, TokenKind as Mt, PaymentLink as N, tokenEnvironment as Nt, PAYMENT_STATUSES as O, isMobilePhone as Ot, PaymentSettlement as P, tokenPrefix as Pt, SubmitLabel as Q, PlanFeatureValues as R, ExchangeRate as S, Metadata as St, FeatureReset as T, TaxBehavior as Tt, QuantitySettings as U, ProductCategory as V, RESET_FREQUENCIES as W, SUBMIT_LABELS as X, RiskLevel as Y, SUBSCRIPTION_STATUSES as Z, Customer as _, GRACE_PERIOD_DAYS as _t, Actor as a, API_ERROR_TYPES as at, CustomerSession as b, ListQuery as bt, BalanceAmount as c, ApiErrorType as ct, CheckoutItem as d, Address as dt, SubscriptionRenewal as et, CheckoutPurpose as f, AddressInput as ft, CustomField as g, Currency as gt, CurrencyAvailability as h, CURRENCIES as ht, WebhookEvent as i, Weekday as it, PaymentAttempt as j, Environment as jt, PRODUCT_CATEGORIES as k, nationalPhone as kt, CHECKOUT_PURPOSES as l, PAYMENT_FAILURE_CODES as lt, Coupon as m, BillingMonths as mt, EventDataMap as n, USAGE_FEATURE_TYPES as nt, AfterCompletion as o, ApiErrorBody as ot, CollectSettings as p, BILLING_MONTHS as pt, ResetFrequency as q, EventType as r, WebhookEndpoint as rt, Balance as s, ApiErrorEnvelope as st, EVENT_TYPES as t, SubscriptionStatus as tt, Checkout as u, PaymentFailureCode as ut, CustomerDetails as v, GracePeriodDays as vt, Feature as w, TAX_BEHAVIORS as wt, Event as x, MOBILE_PREFIXES as xt, CustomerFeature as y, List as yt, PlanPrice as z };
568
- //# sourceMappingURL=events-BJuSt_4W.d.ts.map
567
+ export { Subscription as $, Payment as A, ENVIRONMENTS as At, Product as B, FEATURE_TYPES as C, MobilePrefix as Ct, FeatureUsageResult as D, isLandlinePhone as Dt, FeatureType as E, Timestamp as Et, PaymentStatus as F, RISK_LEVELS as G, ProductVariant as H, Payout as I, Retirement as J, Refund as K, Plan as L, PaymentFailure as M, TokenKind as Mt, PaymentLink as N, tokenEnvironment as Nt, PAYMENT_STATUSES as O, isMobilePhone as Ot, PaymentSettlement as P, tokenPrefix as Pt, SubmitLabel as Q, PlanFeatureValues as R, ExchangeRate as S, Metadata as St, FeatureReset as T, TaxBehavior as Tt, QuantitySettings as U, ProductCategory as V, RESET_FREQUENCIES as W, SUBMIT_LABELS as X, RiskLevel as Y, SUBSCRIPTION_STATUSES as Z, Customer as _, GRACE_PERIOD_DAYS as _t, Actor as a, API_ERROR_TYPES as at, CustomerSession as b, ListQuery as bt, BalanceAmount as c, ApiErrorType as ct, CheckoutItem as d, Address as dt, SubscriptionRenewal as et, CheckoutPurpose as f, AddressInput as ft, CustomField as g, Currency as gt, CurrencyAvailability as h, CURRENCIES as ht, WebhookEvent as i, Weekday as it, PaymentAttempt as j, Environment as jt, PRODUCT_CATEGORIES as k, nationalPhone as kt, CHECKOUT_PURPOSES as l, PAYMENT_FAILURE_CODES as lt, Coupon as m, BillingMonths as mt, EventDataMap as n, USAGE_FEATURE_TYPES as nt, AfterCompletion as o, ApiErrorBody as ot, CollectSettings as p, BILLING_MONTHS as pt, ResetFrequency as q, EventType as r, WebhookEndpoint as rt, Balance as s, ApiErrorEnvelope as st, EVENT_TYPES as t, SubscriptionStatus as tt, Checkout as u, PaymentFailureCode as ut, CustomerDetails as v, GracePeriodDays as vt, Feature as w, TAX_BEHAVIORS as wt, Event as x, MOBILE_PREFIXES as xt, CustomerFeature as y, List as yt, PlanPrice as z };
@@ -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.1";
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,
@@ -120,5 +120,3 @@ function segment(value) {
120
120
  }
121
121
  //#endregion
122
122
  export { segment as n, BilleteError as r, createTransport as t };
123
-
124
- //# sourceMappingURL=http-CG9YDQ6-.js.map
package/dist/index.d.ts CHANGED
@@ -194,5 +194,4 @@ export declare function createBillete(options: BilleteOptions): {
194
194
  /** The server client: what `createBillete` returns. */
195
195
  type Billete = ReturnType<typeof createBillete>;
196
196
  //#endregion
197
- export { type API_ERROR_TYPES, type Actor, type Address, type AddressInput, type AfterCompletion, type AfterCompletionInput, type ApiErrorBody, type ApiErrorEnvelope, type ApiErrorType, type BILLING_MONTHS, type Balance, type BalanceAmount, type Billete, BilleteError, type BilleteOptions, type BillingMonths, type CHECKOUT_PURPOSES, type CURRENCIES, type Checkout, type CheckoutCreate, type CheckoutItem, type CheckoutItemInput, type CheckoutPaymentMethod, type CheckoutPurpose, type ClientCheckoutPay, type ClientCheckoutUpdate, type ClientLinkCheckout, type CollectInput, type CollectSettings, type Coupon, type CouponCreate, type CouponUpdate, type Currency, type CurrencyAvailability, type CustomField, type CustomFieldInput, type Customer, type CustomerCreate, type CustomerDetails, type CustomerFeature, type CustomerInput, type CustomerSession, type CustomerSessionCreate, type CustomerUpdate, type ENVIRONMENTS, type EVENT_TYPES, type Environment, type Event, type EventDataMap, type EventSelector, type EventType, type ExchangeRate, type FEATURE_TYPES, type Feature, type FeatureCreate, type FeatureReset, type FeatureType, type FeatureUpdate, type FeatureUsageResult, type GRACE_PERIOD_DAYS, type GracePeriodDays, type KEY_PATTERN, type List, type ListQuery, type MOBILE_PREFIXES, type Metadata, type MobilePrefix, type PAYMENT_FAILURE_CODES, type PAYMENT_STATUSES, type PRODUCT_CATEGORIES, type Payment, type PaymentAttempt, type PaymentFailure, type PaymentFailureCode, type PaymentInstructions, type PaymentLink, type PaymentLinkCreate, type PaymentLinkUpdate, type PaymentMethodField, type PaymentSettlement, type PaymentStatus, type PaymentTestScenario, type Payout, type Plan, type PlanCheckoutCreate, type PlanCreate, type PlanFeatureValues, type PlanFeaturesInput, type PlanLinkCreate, type PlanPrice, type PlanPriceInput, type PlanUpdate, type Product, type ProductCategory, type ProductCheckoutCreate, type ProductCreate, type ProductLinkCreate, type ProductUpdate, type ProductVariant, type PublicCheckout, type PublicPaymentLink, type QuantityInput, type QuantitySettings, type RESET_FREQUENCIES, type RISK_LEVELS, type Refund, type RefundCreate, type RequestOptions, type ResetFrequency, type RetireInput, type Retirement, type RiskLevel, type SUBMIT_LABELS, type SUBSCRIPTION_STATUSES, type SubmitLabel, type Subscription, type SubscriptionCancel, type SubscriptionCreate, type SubscriptionRenewal, type SubscriptionStatus, type SubscriptionUpdate, type TAX_BEHAVIORS, type TaxBehavior, type Timestamp, type TokenKind, type TransportOptions, type USAGE_FEATURE_TYPES, type VariantInput, type WebhookEndpoint, type WebhookEndpointCreate, type WebhookEndpointUpdate, type WebhookEvent, WebhookVerificationError, type Weekday, type isLandlinePhone, type isMobilePhone, type nationalPhone, type tokenEnvironment, type tokenPrefix, verifyWebhook };
198
- //# sourceMappingURL=index.d.ts.map
197
+ export { type API_ERROR_TYPES, type Actor, type Address, type AddressInput, type AfterCompletion, type AfterCompletionInput, type ApiErrorBody, type ApiErrorEnvelope, type ApiErrorType, type BILLING_MONTHS, type Balance, type BalanceAmount, type Billete, BilleteError, type BilleteOptions, type BillingMonths, type CHECKOUT_PURPOSES, type CURRENCIES, type Checkout, type CheckoutCreate, type CheckoutItem, type CheckoutItemInput, type CheckoutPaymentMethod, type CheckoutPurpose, type ClientCheckoutPay, type ClientCheckoutUpdate, type ClientLinkCheckout, type CollectInput, type CollectSettings, type Coupon, type CouponCreate, type CouponUpdate, type Currency, type CurrencyAvailability, type CustomField, type CustomFieldInput, type Customer, type CustomerCreate, type CustomerDetails, type CustomerFeature, type CustomerInput, type CustomerSession, type CustomerSessionCreate, type CustomerUpdate, type ENVIRONMENTS, type EVENT_TYPES, type Environment, type Event, type EventDataMap, type EventSelector, type EventType, type ExchangeRate, type FEATURE_TYPES, type Feature, type FeatureCreate, type FeatureReset, type FeatureType, type FeatureUpdate, type FeatureUsageResult, type GRACE_PERIOD_DAYS, type GracePeriodDays, type KEY_PATTERN, type List, type ListQuery, type MOBILE_PREFIXES, type Metadata, type MobilePrefix, type PAYMENT_FAILURE_CODES, type PAYMENT_STATUSES, type PRODUCT_CATEGORIES, type Payment, type PaymentAttempt, type PaymentFailure, type PaymentFailureCode, type PaymentInstructions, type PaymentLink, type PaymentLinkCreate, type PaymentLinkUpdate, type PaymentMethodField, type PaymentSettlement, type PaymentStatus, type PaymentTestScenario, type Payout, type Plan, type PlanCheckoutCreate, type PlanCreate, type PlanFeatureValues, type PlanFeaturesInput, type PlanLinkCreate, type PlanPrice, type PlanPriceInput, type PlanUpdate, type Product, type ProductCategory, type ProductCheckoutCreate, type ProductCreate, type ProductLinkCreate, type ProductUpdate, type ProductVariant, type PublicCheckout, type PublicPaymentLink, type QuantityInput, type QuantitySettings, type RESET_FREQUENCIES, type RISK_LEVELS, type Refund, type RefundCreate, type RequestOptions, type ResetFrequency, type RetireInput, type Retirement, type RiskLevel, type SUBMIT_LABELS, type SUBSCRIPTION_STATUSES, type SubmitLabel, type Subscription, type SubscriptionCancel, type SubscriptionCreate, type SubscriptionRenewal, type SubscriptionStatus, type SubscriptionUpdate, type TAX_BEHAVIORS, type TaxBehavior, type Timestamp, type TokenKind, type TransportOptions, type USAGE_FEATURE_TYPES, type VariantInput, type WebhookEndpoint, type WebhookEndpointCreate, type WebhookEndpointUpdate, type WebhookEvent, WebhookVerificationError, type Weekday, type isLandlinePhone, type isMobilePhone, type nationalPhone, type tokenEnvironment, type tokenPrefix, verifyWebhook };
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { n as segment, r as BilleteError, t as createTransport } from "./http-CG9YDQ6-.js";
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
  /**
@@ -269,5 +269,3 @@ verify: (payload, headers, secret) => verifyWebhook({
269
269
  }
270
270
  //#endregion
271
271
  export { BilleteError, WebhookVerificationError, createBillete, verifyWebhook };
272
-
273
- //# sourceMappingURL=index.js.map
@@ -15,5 +15,4 @@ export declare function verifyWebhook(input: {
15
15
  toleranceSeconds?: number;
16
16
  now?: Date;
17
17
  }): Promise<WebhookEvent>;
18
- //#endregion
19
- //# sourceMappingURL=webhooks.d.ts.map
18
+ //#endregion
package/dist/webhooks.js CHANGED
@@ -42,5 +42,3 @@ async function verifyWebhook(input) {
42
42
  }
43
43
  //#endregion
44
44
  export { WebhookVerificationError, verifyWebhook };
45
-
46
- //# sourceMappingURL=webhooks.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@boelabs/billete-sdk",
3
- "version": "0.3.1",
3
+ "version": "0.3.3",
4
4
  "description": "Billete SDK: server client, browser client and webhook verification",
5
5
  "keywords": [
6
6
  "billete",
@@ -1 +0,0 @@
1
- {"version":3,"file":"client.js","names":[],"sources":["../src/create-client.ts"],"sourcesContent":["import type {\n CheckoutPaymentMethod,\n ClientCheckoutPay,\n ClientCheckoutUpdate,\n ClientLinkCheckout,\n CurrencyAvailability,\n Customer,\n CustomerFeature,\n ExchangeRate,\n List,\n PublicCheckout,\n PublicPaymentLink,\n Subscription,\n} from \"@boelabs/billete-contracts/public\";\n\nimport { createTransport, segment } from \"./http\";\nimport type { RequestOptions, TransportOptions } from \"./http\";\n\nexport interface ClientOptions extends TransportOptions {\n /**\n * Token from `billete.customerSessions.create()` on your server. Needed only for the\n * `customer` methods; paying checkouts works without it.\n */\n customerSession?: string | (() => string | undefined);\n}\n\n/**\n * Browser client. It never holds a secret key: checkouts are addressed by their unguessable ID\n * and customer data requires a short-lived customer session minted by your server.\n *\n * ```ts\n * const billete = createClient({ customerSession: token });\n * const [subscription] = (await billete.customer.subscriptions()).data;\n * if (subscription?.renewal?.status === \"overdue\") showBanner(subscription.renewal);\n * ```\n */\nexport function createClient(options: ClientOptions = {}) {\n const session = options.customerSession;\n const http = createTransport({\n ...options,\n token: typeof session === \"function\" ? session : () => session,\n });\n const checkoutPath = (id: string) => `/v1/client/checkouts/${segment(id)}`;\n\n return {\n checkouts: {\n retrieve: (id: string, options?: RequestOptions) =>\n http.request<PublicCheckout>({ method: \"GET\", path: checkoutPath(id), options }),\n /** Changes quantity, variant, billing cycle or coupon on a payment link checkout. */\n update: (id: string, changes: ClientCheckoutUpdate, options?: RequestOptions) =>\n http.request<PublicCheckout>({\n method: \"PATCH\",\n path: checkoutPath(id),\n body: changes,\n options,\n }),\n /** Methods that can pay this checkout, with the converted amount and transfer details. */\n paymentMethods: (id: string, options?: RequestOptions) =>\n http.request<List<CheckoutPaymentMethod>>({\n method: \"GET\",\n path: `${checkoutPath(id)}/payment-methods`,\n options,\n }),\n /** Submits the payment; the returned checkout carries the payment status and any failure. */\n pay: (id: string, input: ClientCheckoutPay, options?: RequestOptions) =>\n http.request<PublicCheckout>({\n method: \"POST\",\n path: `${checkoutPath(id)}/pay`,\n body: input,\n options,\n }),\n },\n paymentLinks: {\n retrieve: (id: string, options?: RequestOptions) =>\n http.request<PublicPaymentLink>({\n method: \"GET\",\n path: `/v1/client/links/${segment(id)}`,\n options,\n }),\n createCheckout: (id: string, selection: ClientLinkCheckout = {}, options?: RequestOptions) =>\n http.request<PublicCheckout>({\n method: \"POST\",\n path: `/v1/client/links/${segment(id)}/checkouts`,\n body: selection,\n options,\n }),\n },\n exchangeRates: {\n /** Current official BCV rates, to show prices in bolívares. */\n list: (options?: RequestOptions) =>\n http.request<List<ExchangeRate>>({\n method: \"GET\",\n path: \"/v1/client/exchange-rates\",\n options,\n }),\n },\n currencies: {\n list: (options?: RequestOptions) =>\n http.request<List<CurrencyAvailability>>({\n method: \"GET\",\n path: \"/v1/client/currencies\",\n options,\n }),\n },\n /** The customer behind the session token. */\n customer: {\n retrieve: (options?: RequestOptions) =>\n http.request<Customer>({ method: \"GET\", path: \"/v1/client/customer\", options }),\n /** Subscriptions with their `renewal` block: due date, grace period and payment link. */\n subscriptions: (options?: RequestOptions) =>\n http.request<List<Subscription>>({\n method: \"GET\",\n path: \"/v1/client/customer/subscriptions\",\n options,\n }),\n features: (options?: RequestOptions) =>\n http.request<List<CustomerFeature>>({\n method: \"GET\",\n path: \"/v1/client/customer/features\",\n options,\n }),\n feature: (key: string, options?: RequestOptions) =>\n http.request<CustomerFeature>({\n method: \"GET\",\n path: `/v1/client/customer/features/${segment(key)}`,\n options,\n }),\n },\n };\n}\n\nexport type BilleteClient = ReturnType<typeof createClient>;\n"],"mappings":";;;;;;;;;;;;AAoCA,SAAgB,aAAa,UAAyB,CAAC,GAAG;CACxD,MAAM,UAAU,QAAQ;CACxB,MAAM,OAAO,gBAAgB;EAC3B,GAAG;EACH,OAAO,OAAO,YAAY,aAAa,gBAAgB;CACzD,CAAC;CACD,MAAM,gBAAgB,OAAe,wBAAwB,QAAQ,EAAE;CAEvE,OAAO;EACL,WAAW;GACT,WAAW,IAAY,YACrB,KAAK,QAAwB;IAAE,QAAQ;IAAO,MAAM,aAAa,EAAE;IAAG;GAAQ,CAAC;;GAEjF,SAAS,IAAY,SAA+B,YAClD,KAAK,QAAwB;IAC3B,QAAQ;IACR,MAAM,aAAa,EAAE;IACrB,MAAM;IACN;GACF,CAAC;;GAEH,iBAAiB,IAAY,YAC3B,KAAK,QAAqC;IACxC,QAAQ;IACR,MAAM,GAAG,aAAa,EAAE,EAAE;IAC1B;GACF,CAAC;;GAEH,MAAM,IAAY,OAA0B,YAC1C,KAAK,QAAwB;IAC3B,QAAQ;IACR,MAAM,GAAG,aAAa,EAAE,EAAE;IAC1B,MAAM;IACN;GACF,CAAC;EACL;EACA,cAAc;GACZ,WAAW,IAAY,YACrB,KAAK,QAA2B;IAC9B,QAAQ;IACR,MAAM,oBAAoB,QAAQ,EAAE;IACpC;GACF,CAAC;GACH,iBAAiB,IAAY,YAAgC,CAAC,GAAG,YAC/D,KAAK,QAAwB;IAC3B,QAAQ;IACR,MAAM,oBAAoB,QAAQ,EAAE,EAAE;IACtC,MAAM;IACN;GACF,CAAC;EACL;EACA,eAAe;;AAEb,OAAO,YACL,KAAK,QAA4B;GAC/B,QAAQ;GACR,MAAM;GACN;EACF,CAAC,EACL;EACA,YAAY,EACV,OAAO,YACL,KAAK,QAAoC;GACvC,QAAQ;GACR,MAAM;GACN;EACF,CAAC,EACL;;EAEA,UAAU;GACR,WAAW,YACT,KAAK,QAAkB;IAAE,QAAQ;IAAO,MAAM;IAAuB;GAAQ,CAAC;;GAEhF,gBAAgB,YACd,KAAK,QAA4B;IAC/B,QAAQ;IACR,MAAM;IACN;GACF,CAAC;GACH,WAAW,YACT,KAAK,QAA+B;IAClC,QAAQ;IACR,MAAM;IACN;GACF,CAAC;GACH,UAAU,KAAa,YACrB,KAAK,QAAyB;IAC5B,QAAQ;IACR,MAAM,gCAAgC,QAAQ,GAAG;IACjD;GACF,CAAC;EACL;CACF;AACF"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"http-CG9YDQ6-.js","names":[],"sources":["../src/errors.ts","../src/http.ts"],"sourcesContent":["import type { ApiErrorBody, ApiErrorType } from \"@boelabs/billete-contracts/public\";\n\n/** Any failed Billete request. `type`/`code` are stable and safe to branch on. */\nexport class BilleteError extends Error {\n readonly type: ApiErrorType | \"network\";\n readonly code: string;\n readonly status: number;\n readonly param?: string;\n readonly requestId?: string;\n\n constructor(\n body: Omit<ApiErrorBody, \"type\"> & { type: ApiErrorType | \"network\" },\n status: number,\n ) {\n super(body.message);\n this.name = \"BilleteError\";\n this.type = body.type;\n this.code = body.code;\n this.status = status;\n this.param = body.param;\n this.requestId = body.requestId;\n }\n}\n","import type { ApiErrorEnvelope, List } from \"@boelabs/billete-contracts/public\";\n\nimport { BilleteError } from \"./errors\";\n\n/** Replaced at build time with the package version; the source (tests, workspace) reads \"dev\". */\ndeclare const PACKAGE_VERSION: string | undefined;\nexport const SDK_VERSION = typeof PACKAGE_VERSION === \"string\" ? PACKAGE_VERSION : \"dev\";\nconst DEFAULT_BASE_URL = \"https://api.billete.run\";\nconst RETRYABLE_STATUS = new Set([429, 500, 502, 503, 504]);\n\nexport interface TransportOptions {\n /** Defaults to `BILLETE_BASE_URL` on the server, then `https://api.billete.run`. Use\n * `http://localhost:9999` in development. */\n baseUrl?: string;\n /** Custom fetch (tests, proxies). Defaults to the global fetch. */\n fetch?: typeof globalThis.fetch;\n timeoutMs?: number;\n /** Retries for network errors, 429, 5xx and in-progress duplicates. Mutations retry safely. */\n maxRetries?: number;\n}\n\nexport interface RequestOptions {\n /** Reuse to make a mutation safe to retry; generated automatically when omitted. */\n idempotencyKey?: string;\n signal?: AbortSignal;\n}\n\n/** Query parameters; `undefined` values are omitted. */\ntype Query = object;\n\ninterface Request {\n method: \"GET\" | \"POST\" | \"PATCH\" | \"PUT\" | \"DELETE\";\n path: string;\n query?: Query;\n body?: unknown;\n options?: RequestOptions;\n}\n\nfunction backoff(attempt: number, retryAfter: string | null) {\n const seconds = retryAfter ? Number(retryAfter) : Number.NaN;\n return Number.isFinite(seconds) ? seconds * 1_000 : Math.min(500 * 2 ** attempt, 8_000);\n}\n\nfunction pause(ms: number, signal?: AbortSignal) {\n return new Promise<void>((resolve, reject) => {\n const timer = setTimeout(resolve, ms);\n signal?.addEventListener(\n \"abort\",\n () => {\n clearTimeout(timer);\n reject(signal.reason);\n },\n { once: true },\n );\n });\n}\n\nfunction buildUrl(baseUrl: string, path: string, query?: Query) {\n const url = new URL(path, baseUrl);\n Object.entries(query ?? {})\n .filter(([, value]) => value !== undefined)\n .forEach(([key, value]) => url.searchParams.set(key, String(value)));\n return url;\n}\n\n/** A concurrent request with the same idempotency key is still running: worth retrying. */\nasync function inProgress(response: Response) {\n const body = (await response\n .clone()\n .json()\n .catch(() => null)) as ApiErrorEnvelope | null;\n return body?.error.code === \"idempotency_request_in_progress\";\n}\n\nasync function toError(response: Response): Promise<BilleteError> {\n const body = (await response.json().catch(() => null)) as ApiErrorEnvelope | null;\n return new BilleteError(\n body?.error ?? {\n type: \"internal\",\n code: \"invalid_response\",\n message: `Unexpected HTTP ${response.status} from Billete`,\n requestId: response.headers.get(\"X-Request-Id\") ?? undefined,\n },\n response.status,\n );\n}\n\nexport type Transport = ReturnType<typeof createTransport>;\n\nexport function createTransport(options: TransportOptions & { token?: () => string | undefined }) {\n const baseUrl = options.baseUrl ?? DEFAULT_BASE_URL;\n const fetchImpl =\n options.fetch ?? ((...args: Parameters<typeof fetch>) => globalThis.fetch(...args));\n const timeoutMs = options.timeoutMs ?? 30_000;\n const maxRetries = options.maxRetries ?? 2;\n\n async function send<T>(\n request: Request,\n idempotencyKey: string | undefined,\n attempt: number,\n ): Promise<T> {\n const token = options.token?.();\n const headers: Record<string, string> = {\n Accept: \"application/json\",\n \"X-Billete-Client\": `billete-js/${SDK_VERSION}`,\n ...(token ? { Authorization: `Bearer ${token}` } : {}),\n ...(request.body === undefined ? {} : { \"Content-Type\": \"application/json\" }),\n ...(idempotencyKey ? { \"Idempotency-Key\": idempotencyKey } : {}),\n };\n const signals = [AbortSignal.timeout(timeoutMs), request.options?.signal].filter(\n (signal): signal is AbortSignal => signal !== undefined,\n );\n const response = await fetchImpl(buildUrl(baseUrl, request.path, request.query), {\n method: request.method,\n headers,\n body: request.body === undefined ? undefined : JSON.stringify(request.body),\n signal: AbortSignal.any(signals),\n }).catch((error: unknown) => {\n if (request.options?.signal?.aborted) throw error;\n return error instanceof Error ? error : new Error(String(error));\n });\n\n const retryable =\n response instanceof Error ||\n RETRYABLE_STATUS.has(response.status) ||\n (response.status === 409 && (await inProgress(response)));\n if (retryable && attempt < maxRetries) {\n await pause(\n backoff(attempt, response instanceof Error ? null : response.headers.get(\"Retry-After\")),\n request.options?.signal,\n );\n return send(request, idempotencyKey, attempt + 1);\n }\n if (response instanceof Error) {\n throw new BilleteError(\n {\n type: \"network\",\n code: \"network_error\",\n message: `Could not reach Billete: ${response.message}`,\n },\n 0,\n );\n }\n if (!response.ok) throw await toError(response);\n return (response.status === 204 ? undefined : await response.json()) as T;\n }\n\n function request<T>(request: Request): Promise<T> {\n const idempotencyKey =\n request.method === \"GET\"\n ? undefined\n : (request.options?.idempotencyKey ?? globalThis.crypto.randomUUID());\n return send<T>(request, idempotencyKey, 0);\n }\n\n /** Walks every page of a list endpoint. */\n async function* paginate<T>(path: string, query: Query = {}): AsyncGenerator<T> {\n const page = await request<List<T>>({ method: \"GET\", path, query });\n yield* page.data;\n if (page.nextCursor) yield* paginate<T>(path, { ...query, cursor: page.nextCursor });\n }\n\n return { request, paginate };\n}\n\n/** Encodes a path segment (customer references may be merchant-defined external IDs). */\nexport function segment(value: string) {\n return encodeURIComponent(value);\n}\n"],"mappings":"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"}
package/dist/index.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","names":["runtime"],"sources":["../../contracts/src/environment.ts","../src/server.ts"],"sourcesContent":["/**\n * Environments and the credentials that name them. Kept free of runtime dependencies: the public\n * SDK bundles this module to check API keys.\n */\n\n/**\n * Where an account operates. `test` is always available and moves no money; `production`\n * charges for real. Every resource carries it, and secret keys name it: `sk_test_`, `sk_prod_`.\n */\nexport const ENVIRONMENTS = [\"test\", \"production\"] as const;\nexport type Environment = (typeof ENVIRONMENTS)[number];\n\n/** How tokens spell each environment: `sk_test_…`, `sk_prod_…`. */\nconst TOKEN_ENVIRONMENT_CODES = { test: \"test\", production: \"prod\" } as const satisfies Record<\n Environment,\n string\n>;\n\n/** Credentials that name their environment: secret keys and customer session tokens. */\nexport type TokenKind = \"sk\" | \"cst\";\n\n/** The prefix of a token for an environment, e.g. `sk_prod_`. */\nexport function tokenPrefix(kind: TokenKind, environment: Environment) {\n return `${kind}_${TOKEN_ENVIRONMENT_CODES[environment]}_`;\n}\n\n/**\n * The environment a token belongs to by its prefix, or `null` when it is not that kind of token.\n */\nexport function tokenEnvironment(kind: TokenKind, token: string): Environment | null {\n return (\n ENVIRONMENTS.find((environment) => token.startsWith(tokenPrefix(kind, environment))) ?? null\n );\n}\n","import { tokenEnvironment } from \"@boelabs/billete-contracts/environment\";\nimport type {\n Balance,\n Checkout,\n CheckoutCreate,\n Coupon,\n CouponCreate,\n CouponUpdate,\n CurrencyAvailability,\n Customer,\n CustomerCreate,\n CustomerFeature,\n CustomerSession,\n CustomerSessionCreate,\n CustomerUpdate,\n Environment,\n Event,\n EventType,\n ExchangeRate,\n Feature,\n FeatureCreate,\n FeatureUpdate,\n FeatureUsageResult,\n List,\n ListQuery,\n Payment,\n PaymentLink,\n PaymentLinkCreate,\n PaymentLinkUpdate,\n PaymentStatus,\n Payout,\n Plan,\n PlanCreate,\n PlanUpdate,\n Product,\n ProductCreate,\n ProductUpdate,\n RetireInput,\n Refund,\n RefundCreate,\n Subscription,\n SubscriptionCancel,\n SubscriptionCreate,\n SubscriptionStatus,\n SubscriptionUpdate,\n WebhookEndpoint,\n WebhookEndpointCreate,\n WebhookEndpointUpdate,\n WebhookEvent,\n} from \"@boelabs/billete-contracts/public\";\n\nimport { createTransport, segment } from \"./http\";\nimport type { RequestOptions, TransportOptions } from \"./http\";\nimport { verifyWebhook } from \"./webhooks\";\n\ntype Transport = ReturnType<typeof createTransport>;\ntype Filters<T> = ListQuery & T;\n\nfunction collection<TResource, TCreate, TUpdate, TFilters = object>(http: Transport, path: string) {\n return {\n create: (input: TCreate, options?: RequestOptions) =>\n http.request<TResource>({ method: \"POST\", path, body: input, options }),\n retrieve: (id: string, options?: RequestOptions) =>\n http.request<TResource>({ method: \"GET\", path: `${path}/${segment(id)}`, options }),\n update: (id: string, input: TUpdate, options?: RequestOptions) =>\n http.request<TResource>({\n method: \"PATCH\",\n path: `${path}/${segment(id)}`,\n body: input,\n options,\n }),\n list: (query: Filters<TFilters> = {} as Filters<TFilters>, options?: RequestOptions) =>\n http.request<List<TResource>>({ method: \"GET\", path, query, options }),\n /** Iterates every item across pages: `for await (const item of iterate())`. */\n iterate: (query: Filters<TFilters> = {} as Filters<TFilters>) =>\n http.paginate<TResource>(path, { ...query }),\n };\n}\n\nexport interface BilleteOptions extends TransportOptions {\n /**\n * Where this client operates: `test` or `production`. Required, and it must match the API key\n * (`sk_test_…` / `sk_prod_…`), so a key never runs in an environment it was not meant for.\n */\n environment: Environment;\n /** Defaults to the `BILLETE_API_KEY` environment variable. Never ship it to a browser. */\n apiKey?: string;\n /** API keys must never reach a browser. Set only for trusted desktop/server-like runtimes. */\n dangerouslyAllowBrowser?: boolean;\n}\n\nfunction fromEnv(name: string): string | undefined {\n const runtime = globalThis as { process?: { env?: Record<string, string | undefined> } };\n return runtime.process?.env?.[name];\n}\n\n/**\n * Server-side Billete client. Works in any runtime with `fetch` (Node, Bun, Deno, edge).\n *\n * ```ts\n * const billete = createBillete({ environment: \"test\" }); // reads BILLETE_API_KEY\n * const { allowed } = await billete.features.consume(\"user_123\", \"tokens\", 850);\n * ```\n */\nexport function createBillete(options: BilleteOptions) {\n const apiKey = options.apiKey ?? fromEnv(\"BILLETE_API_KEY\");\n if (!apiKey) {\n throw new TypeError(\"Billete needs an API key: pass apiKey or set BILLETE_API_KEY\");\n }\n const keyEnvironment = tokenEnvironment(\"sk\", apiKey);\n if (!keyEnvironment) {\n throw new TypeError(\"Billete expects an API key (sk_test_… or sk_prod_…)\");\n }\n if (keyEnvironment !== options.environment) {\n throw new TypeError(\n `This API key belongs to the ${keyEnvironment} environment, but environment is \"${options.environment}\"`,\n );\n }\n if (typeof window !== \"undefined\" && !options.dangerouslyAllowBrowser) {\n throw new Error(\n \"Never use an API key in the browser. Use createClient from @boelabs/billete-sdk/client instead.\",\n );\n }\n const http = createTransport({\n ...options,\n baseUrl: options.baseUrl ?? fromEnv(\"BILLETE_BASE_URL\"),\n token: () => apiKey,\n });\n\n const customers = collection<Customer, CustomerCreate, CustomerUpdate, { email?: string }>(\n http,\n \"/v1/customers\",\n );\n\n const customerSessions = {\n /** Short-lived token for the client SDK to read one customer's billing state. */\n create: (input: CustomerSessionCreate, options?: RequestOptions) =>\n http.request<CustomerSession>({\n method: \"POST\",\n path: \"/v1/customer-sessions\",\n body: input,\n options,\n }),\n };\n\n const products = {\n ...collection<Product, ProductCreate, ProductUpdate, { active?: boolean; retired?: boolean }>(\n http,\n \"/v1/products\",\n ),\n retire: (id: string, input: RetireInput = {}, options?: RequestOptions) =>\n http.request<Product>({\n method: \"POST\",\n path: `/v1/products/${segment(id)}/retire`,\n body: input,\n options,\n }),\n retireVariant: (\n product: string,\n variant: string,\n input: RetireInput = {},\n options?: RequestOptions,\n ) =>\n http.request<Product>({\n method: \"POST\",\n path: `/v1/products/${segment(product)}/variants/${segment(variant)}/retire`,\n body: input,\n options,\n }),\n };\n const plans = collection<Plan, PlanCreate, PlanUpdate, { active?: boolean }>(http, \"/v1/plans\");\n const coupons = collection<Coupon, CouponCreate, CouponUpdate>(http, \"/v1/coupons\");\n const paymentLinks = {\n ...collection<PaymentLink, PaymentLinkCreate, PaymentLinkUpdate, { retired?: boolean }>(\n http,\n \"/v1/payment-links\",\n ),\n retire: (id: string, input: RetireInput = {}, options?: RequestOptions) =>\n http.request<PaymentLink>({\n method: \"POST\",\n path: `/v1/payment-links/${segment(id)}/retire`,\n body: input,\n options,\n }),\n };\n\n const customerFeature = (customer: string, key: string) =>\n `/v1/customers/${segment(customer)}/features/${segment(key)}`;\n const featureCrud = collection<Feature, FeatureCreate, FeatureUpdate>(http, \"/v1/features\");\n const features = {\n ...featureCrud,\n /** Current access to one feature. `customer` is a `cus_…` ID or your own `externalId`. */\n check: (customer: string, key: string, options?: RequestOptions) =>\n http.request<CustomerFeature>({\n method: \"GET\",\n path: customerFeature(customer, key),\n options,\n }),\n /** `true` when the customer can use a boolean feature (or has a limit feature). */\n isEnabled: async (customer: string, key: string, options?: RequestOptions) =>\n (\n await http.request<CustomerFeature>({\n method: \"GET\",\n path: customerFeature(customer, key),\n options,\n })\n ).enabled,\n /** Every feature and its usage for one customer. */\n listForCustomer: (customer: string, options?: RequestOptions) =>\n http.request<List<CustomerFeature>>({\n method: \"GET\",\n path: `/v1/customers/${segment(customer)}/features`,\n options,\n }),\n /**\n * Atomically consumes `amount` units. When the limit would be exceeded nothing is consumed\n * and `allowed` is false, so it is safe to call right before doing the work.\n */\n consume: (customer: string, key: string, amount = 1, options?: RequestOptions) =>\n http.request<FeatureUsageResult>({\n method: \"POST\",\n path: `${customerFeature(customer, key)}/usage`,\n body: { delta: amount },\n options,\n }),\n /** Gives back `amount` units (e.g. a folder was deleted). Usage never drops below zero. */\n release: (customer: string, key: string, amount = 1, options?: RequestOptions) =>\n http.request<FeatureUsageResult>({\n method: \"POST\",\n path: `${customerFeature(customer, key)}/usage`,\n body: { delta: -amount },\n options,\n }),\n /** Reports the absolute current usage (e.g. the customer now has 7 folders). */\n set: (customer: string, key: string, value: number, options?: RequestOptions) =>\n http.request<CustomerFeature>({\n method: \"PUT\",\n path: `${customerFeature(customer, key)}/usage`,\n body: { value },\n options,\n }),\n };\n\n const checkoutCrud = collection<Checkout, CheckoutCreate, never>(http, \"/v1/checkouts\");\n const checkouts = {\n create: checkoutCrud.create,\n retrieve: checkoutCrud.retrieve,\n list: checkoutCrud.list,\n iterate: checkoutCrud.iterate,\n expire: (id: string, options?: RequestOptions) =>\n http.request<Checkout>({\n method: \"POST\",\n path: `/v1/checkouts/${segment(id)}/expire`,\n options,\n }),\n };\n\n const subscriptionCrud = collection<\n Subscription,\n SubscriptionCreate,\n SubscriptionUpdate,\n { customer?: string; plan?: string; status?: SubscriptionStatus }\n >(http, \"/v1/subscriptions\");\n const subscriptions = {\n ...subscriptionCrud,\n /** Cancels at period end by default; pass `{ atPeriodEnd: false }` to end it now. */\n cancel: (id: string, input: SubscriptionCancel = {}, options?: RequestOptions) =>\n http.request<Subscription>({\n method: \"POST\",\n path: `/v1/subscriptions/${segment(id)}/cancel`,\n body: input,\n options,\n }),\n resume: (id: string, options?: RequestOptions) =>\n http.request<Subscription>({\n method: \"POST\",\n path: `/v1/subscriptions/${segment(id)}/resume`,\n options,\n }),\n };\n\n const paymentCrud = collection<\n Payment,\n never,\n never,\n { customer?: string; status?: PaymentStatus }\n >(http, \"/v1/payments\");\n const payments = {\n retrieve: paymentCrud.retrieve,\n list: paymentCrud.list,\n iterate: paymentCrud.iterate,\n refund: (id: string, input: RefundCreate = {}, options?: RequestOptions) =>\n http.request<Refund>({\n method: \"POST\",\n path: `/v1/payments/${segment(id)}/refunds`,\n body: input,\n options,\n }),\n listRefunds: (id: string, options?: RequestOptions) =>\n http.request<List<Refund>>({\n method: \"GET\",\n path: `/v1/payments/${segment(id)}/refunds`,\n options,\n }),\n };\n\n const balance = {\n retrieve: (options?: RequestOptions) =>\n http.request<Balance>({ method: \"GET\", path: \"/v1/balance\", options }),\n };\n\n const payoutCrud = collection<Payout, never, never>(http, \"/v1/payouts\");\n const payouts = {\n retrieve: payoutCrud.retrieve,\n list: payoutCrud.list,\n iterate: payoutCrud.iterate,\n };\n\n const endpoints = collection<WebhookEndpoint, WebhookEndpointCreate, WebhookEndpointUpdate>(\n http,\n \"/v1/webhook-endpoints\",\n );\n const webhookEndpoints = {\n ...endpoints,\n delete: (id: string, options?: RequestOptions) =>\n http.request<void>({\n method: \"DELETE\",\n path: `/v1/webhook-endpoints/${segment(id)}`,\n options,\n }),\n };\n\n const eventCrud = collection<Event, never, never, { type?: EventType }>(http, \"/v1/events\");\n const events = { retrieve: eventCrud.retrieve, list: eventCrud.list, iterate: eventCrud.iterate };\n\n const exchangeRates = {\n /** Current official BCV rates (USD/VES, EUR/VES). */\n list: (options?: RequestOptions) =>\n http.request<List<ExchangeRate>>({ method: \"GET\", path: \"/v1/exchange-rates\", options }),\n };\n const currencies = {\n list: (options?: RequestOptions) =>\n http.request<List<CurrencyAvailability>>({\n method: \"GET\",\n path: \"/v1/currencies\",\n options,\n }),\n };\n\n const webhooks = {\n /**\n * Verifies a webhook (Standard Webhooks signature) and returns the typed event.\n * Pass the raw request body exactly as received.\n */\n verify: (\n payload: string,\n headers: Headers | Record<string, string | string[] | undefined>,\n secret: string,\n ): Promise<WebhookEvent> => verifyWebhook({ payload, headers, secret }),\n };\n\n return {\n customers,\n customerSessions,\n products,\n plans,\n features,\n coupons,\n checkouts,\n paymentLinks,\n subscriptions,\n payments,\n balance,\n payouts,\n webhookEndpoints,\n events,\n exchangeRates,\n currencies,\n webhooks,\n };\n}\n\n/** The server client: what `createBillete` returns. */\nexport type Billete = ReturnType<typeof createBillete>;\n"],"mappings":";;;;;;;;;;;AASA,MAAa,eAAe,CAAC,QAAQ,YAAY;;AAIjD,MAAM,0BAA0B;CAAE,MAAM;CAAQ,YAAY;AAAO;;AASnE,SAAgB,YAAY,MAAiB,aAA0B;CACrE,OAAO,GAAG,KAAK,GAAG,wBAAwB,aAAa;AACzD;;;;AAKA,SAAgB,iBAAiB,MAAiB,OAAmC;CACnF,OACE,aAAa,MAAM,gBAAgB,MAAM,WAAW,YAAY,MAAM,WAAW,CAAC,CAAC,KAAK;AAE5F;;;ACyBA,SAAS,WAA2D,MAAiB,MAAc;CACjG,OAAO;EACL,SAAS,OAAgB,YACvB,KAAK,QAAmB;GAAE,QAAQ;GAAQ;GAAM,MAAM;GAAO;EAAQ,CAAC;EACxE,WAAW,IAAY,YACrB,KAAK,QAAmB;GAAE,QAAQ;GAAO,MAAM,GAAG,KAAK,GAAG,QAAQ,EAAE;GAAK;EAAQ,CAAC;EACpF,SAAS,IAAY,OAAgB,YACnC,KAAK,QAAmB;GACtB,QAAQ;GACR,MAAM,GAAG,KAAK,GAAG,QAAQ,EAAE;GAC3B,MAAM;GACN;EACF,CAAC;EACH,OAAO,QAA2B,CAAC,GAAwB,YACzD,KAAK,QAAyB;GAAE,QAAQ;GAAO;GAAM;GAAO;EAAQ,CAAC;;EAEvE,UAAU,QAA2B,CAAC,MACpC,KAAK,SAAoB,MAAM,EAAE,GAAG,MAAM,CAAC;CAC/C;AACF;AAcA,SAAS,QAAQ,MAAkC;CAEjD,OAAOA,WAAQ,SAAS,MAAM;AAChC;;;;;;;;;AAUA,SAAgB,cAAc,SAAyB;CACrD,MAAM,SAAS,QAAQ,UAAU,QAAQ,iBAAiB;CAC1D,IAAI,CAAC,QACH,MAAM,IAAI,UAAU,8DAA8D;CAEpF,MAAM,iBAAiB,iBAAiB,MAAM,MAAM;CACpD,IAAI,CAAC,gBACH,MAAM,IAAI,UAAU,qDAAqD;CAE3E,IAAI,mBAAmB,QAAQ,aAC7B,MAAM,IAAI,UACR,+BAA+B,eAAe,oCAAoC,QAAQ,YAAY,EACxG;CAEF,IAAI,OAAO,WAAW,eAAe,CAAC,QAAQ,yBAC5C,MAAM,IAAI,MACR,iGACF;CAEF,MAAM,OAAO,gBAAgB;EAC3B,GAAG;EACH,SAAS,QAAQ,WAAW,QAAQ,kBAAkB;EACtD,aAAa;CACf,CAAC;CAED,MAAM,YAAY,WAChB,MACA,eACF;CAEA,MAAM,mBAAmB;;AAEvB,SAAS,OAA8B,YACrC,KAAK,QAAyB;EAC5B,QAAQ;EACR,MAAM;EACN,MAAM;EACN;CACF,CAAC,EACL;CAEA,MAAM,WAAW;EACf,GAAG,WACD,MACA,cACF;EACA,SAAS,IAAY,QAAqB,CAAC,GAAG,YAC5C,KAAK,QAAiB;GACpB,QAAQ;GACR,MAAM,gBAAgB,QAAQ,EAAE,EAAE;GAClC,MAAM;GACN;EACF,CAAC;EACH,gBACE,SACA,SACA,QAAqB,CAAC,GACtB,YAEA,KAAK,QAAiB;GACpB,QAAQ;GACR,MAAM,gBAAgB,QAAQ,OAAO,EAAE,YAAY,QAAQ,OAAO,EAAE;GACpE,MAAM;GACN;EACF,CAAC;CACL;CACA,MAAM,QAAQ,WAA+D,MAAM,WAAW;CAC9F,MAAM,UAAU,WAA+C,MAAM,aAAa;CAClF,MAAM,eAAe;EACnB,GAAG,WACD,MACA,mBACF;EACA,SAAS,IAAY,QAAqB,CAAC,GAAG,YAC5C,KAAK,QAAqB;GACxB,QAAQ;GACR,MAAM,qBAAqB,QAAQ,EAAE,EAAE;GACvC,MAAM;GACN;EACF,CAAC;CACL;CAEA,MAAM,mBAAmB,UAAkB,QACzC,iBAAiB,QAAQ,QAAQ,EAAE,YAAY,QAAQ,GAAG;CAE5D,MAAM,WAAW;EACf,GAFkB,WAAkD,MAAM,cAE7D;;EAEb,QAAQ,UAAkB,KAAa,YACrC,KAAK,QAAyB;GAC5B,QAAQ;GACR,MAAM,gBAAgB,UAAU,GAAG;GACnC;EACF,CAAC;;EAEH,WAAW,OAAO,UAAkB,KAAa,aAE7C,MAAM,KAAK,QAAyB;GAClC,QAAQ;GACR,MAAM,gBAAgB,UAAU,GAAG;GACnC;EACF,CAAC,EAAA,CACD;;EAEJ,kBAAkB,UAAkB,YAClC,KAAK,QAA+B;GAClC,QAAQ;GACR,MAAM,iBAAiB,QAAQ,QAAQ,EAAE;GACzC;EACF,CAAC;;;;;EAKH,UAAU,UAAkB,KAAa,SAAS,GAAG,YACnD,KAAK,QAA4B;GAC/B,QAAQ;GACR,MAAM,GAAG,gBAAgB,UAAU,GAAG,EAAE;GACxC,MAAM,EAAE,OAAO,OAAO;GACtB;EACF,CAAC;;EAEH,UAAU,UAAkB,KAAa,SAAS,GAAG,YACnD,KAAK,QAA4B;GAC/B,QAAQ;GACR,MAAM,GAAG,gBAAgB,UAAU,GAAG,EAAE;GACxC,MAAM,EAAE,OAAO,CAAC,OAAO;GACvB;EACF,CAAC;;EAEH,MAAM,UAAkB,KAAa,OAAe,YAClD,KAAK,QAAyB;GAC5B,QAAQ;GACR,MAAM,GAAG,gBAAgB,UAAU,GAAG,EAAE;GACxC,MAAM,EAAE,MAAM;GACd;EACF,CAAC;CACL;CAEA,MAAM,eAAe,WAA4C,MAAM,eAAe;CACtF,MAAM,YAAY;EAChB,QAAQ,aAAa;EACrB,UAAU,aAAa;EACvB,MAAM,aAAa;EACnB,SAAS,aAAa;EACtB,SAAS,IAAY,YACnB,KAAK,QAAkB;GACrB,QAAQ;GACR,MAAM,iBAAiB,QAAQ,EAAE,EAAE;GACnC;EACF,CAAC;CACL;CAQA,MAAM,gBAAgB;EACpB,GAPuB,WAKvB,MAAM,mBAEY;;EAElB,SAAS,IAAY,QAA4B,CAAC,GAAG,YACnD,KAAK,QAAsB;GACzB,QAAQ;GACR,MAAM,qBAAqB,QAAQ,EAAE,EAAE;GACvC,MAAM;GACN;EACF,CAAC;EACH,SAAS,IAAY,YACnB,KAAK,QAAsB;GACzB,QAAQ;GACR,MAAM,qBAAqB,QAAQ,EAAE,EAAE;GACvC;EACF,CAAC;CACL;CAEA,MAAM,cAAc,WAKlB,MAAM,cAAc;CACtB,MAAM,WAAW;EACf,UAAU,YAAY;EACtB,MAAM,YAAY;EAClB,SAAS,YAAY;EACrB,SAAS,IAAY,QAAsB,CAAC,GAAG,YAC7C,KAAK,QAAgB;GACnB,QAAQ;GACR,MAAM,gBAAgB,QAAQ,EAAE,EAAE;GAClC,MAAM;GACN;EACF,CAAC;EACH,cAAc,IAAY,YACxB,KAAK,QAAsB;GACzB,QAAQ;GACR,MAAM,gBAAgB,QAAQ,EAAE,EAAE;GAClC;EACF,CAAC;CACL;CAEA,MAAM,UAAU,EACd,WAAW,YACT,KAAK,QAAiB;EAAE,QAAQ;EAAO,MAAM;EAAe;CAAQ,CAAC,EACzE;CAEA,MAAM,aAAa,WAAiC,MAAM,aAAa;CACvE,MAAM,UAAU;EACd,UAAU,WAAW;EACrB,MAAM,WAAW;EACjB,SAAS,WAAW;CACtB;CAMA,MAAM,mBAAmB;EACvB,GALgB,WAChB,MACA,uBAGW;EACX,SAAS,IAAY,YACnB,KAAK,QAAc;GACjB,QAAQ;GACR,MAAM,yBAAyB,QAAQ,EAAE;GACzC;EACF,CAAC;CACL;CAEA,MAAM,YAAY,WAAsD,MAAM,YAAY;CA6B1F,OAAO;EACL;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,QAAA;GA1Ce,UAAU,UAAU;GAAU,MAAM,UAAU;GAAM,SAAS,UAAU;EA0CjF;EACL,eAAA;;AAvCA,OAAO,YACL,KAAK,QAA4B;GAAE,QAAQ;GAAO,MAAM;GAAsB;EAAQ,CAAC,EAsC7E;EACZ,YAAA,EApCA,OAAO,YACL,KAAK,QAAoC;GACvC,QAAQ;GACR,MAAM;GACN;EACF,CAAC,EA+BM;EACT,UAAA;;;;;AAxBA,SACE,SACA,SACA,WAC0B,cAAc;GAAE;GAAS;GAAS;EAAO,CAAC,EAoB/D;CACT;AACF"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"webhooks.js","names":[],"sources":["../src/webhooks.ts"],"sourcesContent":["import type { WebhookEvent } from \"@boelabs/billete-contracts/public\";\n\nexport class WebhookVerificationError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"WebhookVerificationError\";\n }\n}\n\ntype HeaderSource = Headers | Record<string, string | string[] | undefined>;\n\nfunction header(headers: HeaderSource, name: string): string | undefined {\n if (headers instanceof Headers) return headers.get(name) ?? undefined;\n const entry = Object.entries(headers).find(([key]) => key.toLowerCase() === name)?.[1];\n return Array.isArray(entry) ? entry[0] : entry;\n}\n\nfunction fromBase64(value: string): Uint8Array<ArrayBuffer> {\n return Uint8Array.from(atob(value), (char) => char.charCodeAt(0));\n}\n\nfunction toBase64(bytes: ArrayBuffer): string {\n return btoa(String.fromCharCode(...new Uint8Array(bytes)));\n}\n\nfunction constantTimeEqual(left: string, right: string): boolean {\n const length = Math.max(left.length, right.length);\n const mismatch = Array.from(\n { length },\n (_, index) => left.charCodeAt(index) ^ right.charCodeAt(index),\n ).reduce((accumulator, value) => accumulator | value, left.length ^ right.length);\n return mismatch === 0;\n}\n\n/**\n * Verifies a Billete webhook using the Standard Webhooks scheme with Web Crypto, so it runs in\n * Node, Bun, Deno, Cloudflare Workers and other edge runtimes.\n */\nexport async function verifyWebhook(input: {\n payload: string;\n headers: HeaderSource;\n secret: string;\n toleranceSeconds?: number;\n now?: Date;\n}): Promise<WebhookEvent> {\n const id = header(input.headers, \"webhook-id\");\n const timestamp = header(input.headers, \"webhook-timestamp\");\n const signatures = header(input.headers, \"webhook-signature\");\n if (!id || !timestamp || !signatures) {\n throw new WebhookVerificationError(\"Missing webhook headers\");\n }\n\n const nowSeconds = Math.floor((input.now ?? new Date()).getTime() / 1_000);\n const tolerance = input.toleranceSeconds ?? 300;\n if (!/^\\d+$/.test(timestamp) || Math.abs(nowSeconds - Number(timestamp)) > tolerance) {\n throw new WebhookVerificationError(\"Webhook timestamp is outside the tolerance window\");\n }\n\n const key = await globalThis.crypto.subtle.importKey(\n \"raw\",\n fromBase64(input.secret.replace(/^whsec_/, \"\")),\n { name: \"HMAC\", hash: \"SHA-256\" },\n false,\n [\"sign\"],\n );\n const expected = toBase64(\n await globalThis.crypto.subtle.sign(\n \"HMAC\",\n key,\n new TextEncoder().encode(`${id}.${timestamp}.${input.payload}`),\n ),\n );\n const valid = signatures\n .split(\" \")\n .some(\n (candidate) => candidate.startsWith(\"v1,\") && constantTimeEqual(candidate.slice(3), expected),\n );\n if (!valid) throw new WebhookVerificationError(\"Invalid webhook signature\");\n return JSON.parse(input.payload) as WebhookEvent;\n}\n"],"mappings":";AAEA,IAAa,2BAAb,cAA8C,MAAM;CAClD,YAAY,SAAiB;EAC3B,MAAM,OAAO;EACb,KAAK,OAAO;CACd;AACF;AAIA,SAAS,OAAO,SAAuB,MAAkC;CACvE,IAAI,mBAAmB,SAAS,OAAO,QAAQ,IAAI,IAAI,KAAK,KAAA;CAC5D,MAAM,QAAQ,OAAO,QAAQ,OAAO,CAAC,CAAC,MAAM,CAAC,SAAS,IAAI,YAAY,MAAM,IAAI,CAAC,GAAG;CACpF,OAAO,MAAM,QAAQ,KAAK,IAAI,MAAM,KAAK;AAC3C;AAEA,SAAS,WAAW,OAAwC;CAC1D,OAAO,WAAW,KAAK,KAAK,KAAK,IAAI,SAAS,KAAK,WAAW,CAAC,CAAC;AAClE;AAEA,SAAS,SAAS,OAA4B;CAC5C,OAAO,KAAK,OAAO,aAAa,GAAG,IAAI,WAAW,KAAK,CAAC,CAAC;AAC3D;AAEA,SAAS,kBAAkB,MAAc,OAAwB;CAC/D,MAAM,SAAS,KAAK,IAAI,KAAK,QAAQ,MAAM,MAAM;CAKjD,OAJiB,MAAM,KACrB,EAAE,OAAO,IACR,GAAG,UAAU,KAAK,WAAW,KAAK,IAAI,MAAM,WAAW,KAAK,CAC/D,CAAC,CAAC,QAAQ,aAAa,UAAU,cAAc,OAAO,KAAK,SAAS,MAAM,MAC5D,MAAM;AACtB;;;;;AAMA,eAAsB,cAAc,OAMV;CACxB,MAAM,KAAK,OAAO,MAAM,SAAS,YAAY;CAC7C,MAAM,YAAY,OAAO,MAAM,SAAS,mBAAmB;CAC3D,MAAM,aAAa,OAAO,MAAM,SAAS,mBAAmB;CAC5D,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,YACxB,MAAM,IAAI,yBAAyB,yBAAyB;CAG9D,MAAM,aAAa,KAAK,OAAO,MAAM,uBAAO,IAAI,KAAK,EAAA,CAAG,QAAQ,IAAI,GAAK;CACzE,MAAM,YAAY,MAAM,oBAAoB;CAC5C,IAAI,CAAC,QAAQ,KAAK,SAAS,KAAK,KAAK,IAAI,aAAa,OAAO,SAAS,CAAC,IAAI,WACzE,MAAM,IAAI,yBAAyB,mDAAmD;CAGxF,MAAM,MAAM,MAAM,WAAW,OAAO,OAAO,UACzC,OACA,WAAW,MAAM,OAAO,QAAQ,WAAW,EAAE,CAAC,GAC9C;EAAE,MAAM;EAAQ,MAAM;CAAU,GAChC,OACA,CAAC,MAAM,CACT;CACA,MAAM,WAAW,SACf,MAAM,WAAW,OAAO,OAAO,KAC7B,QACA,KACA,IAAI,YAAY,CAAC,CAAC,OAAO,GAAG,GAAG,GAAG,UAAU,GAAG,MAAM,SAAS,CAChE,CACF;CAMA,IAAI,CALU,WACX,MAAM,GAAG,CAAC,CACV,MACE,cAAc,UAAU,WAAW,KAAK,KAAK,kBAAkB,UAAU,MAAM,CAAC,GAAG,QAAQ,CAEvF,GAAG,MAAM,IAAI,yBAAyB,2BAA2B;CAC1E,OAAO,KAAK,MAAM,MAAM,OAAO;AACjC"}