Para desarrolladores
Cobros por QR, integrados desde su propio backend.
Ayni publica dos paquetes de código abierto en npm: uno para el servidor de su aplicación, que crea el cobro, y otro para el navegador de su cliente, que solo consulta si ya se pagó. Ninguno le exige hablar directamente con la API del banco.
Los paquetes
@aynicobros/node — para su servidor
Crea el cobro con la clave secreta de su comercio y devuelve el enlace de pago alojado por Ayni. Esa clave (ayn_live_… o ayn_test_…) puede crear cobros en su nombre: no debe llegar nunca al navegador, a una variable de entorno del lado del cliente, a un registro ni a un commit.
import { createClient, datePlusDays } from '@aynicobros/node';
import type {
AyniCobrosClient,
CreateChargeInput,
CreateChargeResult,
} from '@aynicobros/node';
const client: AyniCobrosClient = createClient({
apiKey: process.env.AYNI_SECRET_KEY!,
});
const input: CreateChargeInput = {
// Obligatorios.
reference: 'order-1042',
mode: 'live',
currency: 'BOB',
amount: '250.00',
dueDate: datePlusDays(3),
// Opcionales.
successUrl: '...',
cancelUrl: '...',
description: 'Pedido #1042',
modifyAmount: false,
singleUse: true,
payerName: 'María Quispe',
branchCode: '001',
metadata: { canal: 'tienda-online' },
accountId: '...',
};
const result: CreateChargeResult = await client.createCharge(input);
// No lanza excepciones: siempre devuelve un resultado que usted debe estrechar.
if (result.kind !== 'ok') {
// 'conflict' | 'unauthorized' | 'invalid' | 'rate-limited' | 'unavailable'
throw new Error('No se pudo crear el cobro: ' + result.kind);
}
// Redirija al comprador aquí.
return result.charge.checkoutUrl;Ninguno de los dos paquetes lanza excepciones: devuelven un resultado que usted debe estrechar. Solo kind: 'ok' trae los datos; el resto nombra el motivo. Si no lo mira, el error pasa en silencio en la única página donde el comprador lo ve.
El campo amount es un string decimal en unidades mayores: "250.00" son 250 Bs, no 25 000 centavos. Enviar unidades menores aquí sobrecobra 100 veces.
@aynicobros/js — para el navegador
Lee el estado de un cobro por su token público. No necesita ningún secreto: puede ejecutarse sin riesgo en el navegador de su cliente.
import { getCheckoutStatus, outcomeOf } from '@aynicobros/js';
import type { CheckoutResult, PaymentOutcome } from '@aynicobros/js';
const token = new URLSearchParams(location.search).get('ayni_ref');
if (!token) return;
const result: CheckoutResult = await getCheckoutStatus(token);
if (result.kind !== 'ok') {
// 'not-found' | 'rate-limited' | 'unavailable'
return;
}
// 'paid' | 'awaiting' | 'unpaid'
const outcome: PaymentOutcome = outcomeOf(result.checkout.status);Si su servidor no es Node
Solo publicamos paquetes para Node y para el navegador. Desde cualquier otro lenguaje el cobro se crea con una sola llamada HTTP: es la misma que hace @aynicobros/node por dentro.
Fíjese en el cálculo de dueDate: es un día del calendario boliviano. Un servidor en UTC que formatee «hoy» sin zona horaria va un día adelantado durante cuatro horas de cada día, y adelanta el vencimiento del cobro sin que nada falle.
import os
from datetime import datetime, timedelta
from zoneinfo import ZoneInfo
import requests
# Día calendario boliviano, no el del reloj de su servidor.
due = (datetime.now(ZoneInfo("America/La_Paz")) + timedelta(days=3)).date()
response = requests.post(
"https://api.aynicobros.com/v1/charges",
headers={
"authorization": f"Bearer {os.environ['AYNI_SECRET_KEY']}",
"content-type": "application/json",
},
json={
"reference": "order-1042",
"mode": "live",
"currency": "BOB",
"amount": "250.00", # string decimal, en unidades mayores
"dueDate": due.isoformat(), # yyyy-MM-dd
"successUrl": "...",
"cancelUrl": "...",
},
timeout=15,
)
if not response.ok:
# 401/403 clave inválida - 409 referencia repetida - 429 límite de tasa
raise RuntimeError("No se pudo crear el cobro: " + str(response.status_code))
checkout_url = response.json()["checkoutUrl"]import java.net.URI;
import java.net.http.*;
import java.time.*;
// Día calendario boliviano, no el del reloj de su servidor.
LocalDate due = LocalDate.now(ZoneId.of("America/La_Paz")).plusDays(3);
String body = """
{"reference":"order-1042","mode":"live","currency":"BOB",
"amount":"250.00","dueDate":"%s",
"successUrl":"...","cancelUrl":"..."}
""".formatted(due);
HttpRequest request = HttpRequest
.newBuilder(URI.create("https://api.aynicobros.com/v1/charges"))
.header("authorization", "Bearer " + System.getenv("AYNI_SECRET_KEY"))
.header("content-type", "application/json")
.timeout(Duration.ofSeconds(15))
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse<String> response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() / 100 != 2) {
// 401/403 clave inválida - 409 referencia repetida - 429 límite de tasa
throw new IllegalStateException("No se pudo crear el cobro: " + response.statusCode());
}
// response.body() trae el JSON con checkoutUrl y publicToken.using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text.Json;
// Día calendario boliviano, no el del reloj de su servidor.
var laPaz = TimeZoneInfo.FindSystemTimeZoneById("America/La_Paz");
var due = TimeZoneInfo.ConvertTime(DateTimeOffset.UtcNow, laPaz).AddDays(3);
var payload = new
{
reference = "order-1042",
mode = "live",
currency = "BOB",
amount = "250.00", // string decimal, unidades mayores
dueDate = due.ToString("yyyy-MM-dd"),
successUrl = "...",
cancelUrl = "...",
};
using var http = new HttpClient { Timeout = TimeSpan.FromSeconds(15) };
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
"Bearer",
Environment.GetEnvironmentVariable("AYNI_SECRET_KEY"));
var response = await http.PostAsJsonAsync(
"https://api.aynicobros.com/v1/charges", payload);
if (!response.IsSuccessStatusCode)
{
// 401/403 clave inválida - 409 referencia repetida - 429 límite de tasa
throw new InvalidOperationException(
"No se pudo crear el cobro: " + (int)response.StatusCode);
}
var charge = await response.Content.ReadFromJsonAsync<JsonElement>();
var checkoutUrl = charge.GetProperty("checkoutUrl").GetString();Cómo fluye un cobro
- Su servidor llama a
client.createCharge(...)con@aynicobros/nodey recibe un enlace de pago (checkoutUrl) y un token público (publicToken). - Usted redirige a su cliente a ese
checkoutUrl: la página de pago alojada por Ayni. - Ayni devuelve a su cliente a la
successUrlque usted definió, con el token en la URL (ayni_ref=<publicToken>). - El navegador de su cliente lee ese token y llama a
getCheckoutStatusde@aynicobros/jspara confirmar si el cobro se pagó.
Próximos pasos
Ambos paquetes están publicados en npm; instálelos con npm, pnpm o el gestor que ya use en su proyecto.
La referencia completa de la API todavía no está publicada; en cuanto lo esté, la enlazaremos desde aquí.
¿Integra con ayuda de una IA? Hay una guía de cómo pedírselo, y puede apuntarla directamente a aynicobros.com/llms.txt: todo lo anterior en un solo archivo de texto, escrito para que un modelo lo lea entero. Incluye una sección con lo que no existe, que es lo que un modelo inventa cuando nadie se lo dice.
¿Tiene dudas sobre la integración? Escríbanos por WhatsApp.