🚧 La documentacion de Bloque está en desarrollo

API de Checkout

La API de Checkout te permite crear links de pago (páginas alojadas) para tus clientes.

Hay dos tipos de checkout:

  • Carrito (payment_type: 'shopping_cart'): compra única con uno o más items
  • Suscripción (payment_type: 'subscription'): cobro recurrente con programación tipo cron

Creando un Checkout

Crea una sesión de checkout con items y URLs de redirección:

import { Bloque } from '@bloque/payments';

const bloque = new Bloque({
  secretKey: process.env.BLOQUE_SECRET_KEY!,
  mode: 'production',
});

const checkout = await bloque.checkout.create({
  name: 'Curso de React',
  description: 'Aprende React desde cero',
  image_url: 'https://example.com/course.jpg',
  items: [
    {
      name: 'Curso de React',
      amount: 2999,
      quantity: 1,
      image_url: 'https://example.com/course.jpg',
    },
  ],
  success_url: 'https://tuapp.com/success',
});

console.log('URL de Checkout:', checkout.url);
console.log('Client Secret:', checkout.client_secret);
// Redirigir cliente a checkout.url o pasar id + client_secret al frontend

Suscripciones (recurrente)

Para crear un checkout de suscripción, define payment_type: 'subscription' e incluye un objeto subscription.

const checkout = await bloque.checkout.create({
  name: 'Premium (mensual)',
  payment_type: 'subscription',
  items: [{ name: 'Premium', amount: 1999, quantity: 1 }],
  subscription: {
    type: 'cron',
    cron: '0 0 1 * *', // cada mes
    startDate: new Date().toISOString(),
  },
  success_url: 'https://tuapp.com/success',
});

Suscripción (avanzado)

El cronograma de suscripción soporta campos opcionales adicionales:

  • allocations: montos variables por ocurrencia (ej. trial a $0 y luego precio normal)\n- trial_days: retrasa el primer cobro N días\n- timezone: interpreta el cron en una zona horaria IANA (recomendado para cobros mensuales)\n- max_occurrences: límite máximo de ocurrencias generadas
const checkout = await bloque.checkout.create({
  name: 'Premium (mensual)',
  payment_type: 'subscription',
  items: [{ name: 'Premium', amount: 1999, quantity: 1 }],
  subscription: {
    type: 'cron',
    cron: '0 0 1 * *',
    timezone: 'America/Bogota',
    trial_days: 14,
    max_occurrences: 24,
    allocations: [
      { amount_cents: 0, currency: 'COP/2' },
      { amount_cents: 1999, currency: 'COP/2' },
    ],
  },
  success_url: 'https://tuapp.com/success',
});

Parámetros del Checkout

Parámetros Requeridos

{
  name: string;              // Nombre del checkout
  items: CheckoutItem[];     // Items a comprar
  success_url: string;       // URL de redirección después de éxito
}

Parámetros Opcionales

{
  description?: string;      // Descripción del checkout
  image_url?: string;        // URL de imagen del checkout
  payment_type?: 'shopping_cart' | 'subscription';
  subscription?: {
    type: 'cron';
    cron: string;
    startDate?: string;
    endDate?: string;
    allocations?: { amount_cents: number; currency: string }[];
    trial_days?: number;
    timezone?: string;
    max_occurrences?: number;
  };
  metadata?: Record<string, string | number | boolean>;
  merchant?: {
    name: string;
    theme: {
      primary: string;
      primary_foreground: string;
      background: string;
      surface: string;
      border: string;
      input_border: string;
    };
  };
  payment_methods?: ('card' | 'pse' | 'cash')[];
  expires_at?: string;       // Fecha de expiración (ISO 8601)
  cancel_url?: string;
  webhook_url?: string;      // Recibir payment.status.updated
  payout_route?: PayoutRoute[]; // Avanzado: rutar fondos a uno o más destinos
}

Avanzado: payout_route (ruteo de fondos)

payout_route define a dónde van los fondos después de que el pago se completa. Puedes rutear:

  • Un monto fijo a un destino, o
  • Un porcentaje del total a un destino

Cada destino es un union discriminado llamado route (por network):

  • polygon, kusama, tron, bep20 (por dirección)
  • bloque (hacia un account_urn de Bloque)

Costos de ruteo

  • network: 'bloque': $0 (sin costo adicional) después del pago
  • Otras redes (polygon, kusama, tron, bep20): $0.50 por “leg”

Ejemplo (fijo + porcentaje)

import type { PayoutRoute } from '@bloque/payments';

const checkout = await bloque.checkout.create({
  name: 'Orden marketplace #123',
  items: [{ name: 'Total orden', amount: 50000, quantity: 1 }],
  success_url: 'https://tuapp.com/success',
  payout_route: [
    {
      type: 'fixed',
      amount: 2000,
      route: {
        network: 'bloque',
        account_urn: 'did:bloque:accounts:...',
        alias: 'Fee plataforma',
      },
    },
    {
      type: 'percentage',
      percentage: 96,
      route: {
        network: 'polygon',
        address: '0xabc123...',
        alias: 'Seller',
      },
    },
  ],
});

::::tip Si no estás haciendo revenue splits / marketplace, puedes ignorar payout_route. ::::

Items del Checkout

Cada item en el checkout:

{
  name: string;        // Nombre del item
  amount: number;      // Precio en la unidad más pequeña
  quantity: number;    // Cantidad
  image_url?: string;  // URL de imagen del item
}

Ejemplo Completo

const checkout = await bloque.checkout.create({
  name: 'Carrito de Compras',
  description: 'Tus items seleccionados',
  image_url: 'https://example.com/cart.jpg',
  items: [
    {
      name: 'Mouse Inalámbrico',
      amount: 999,
      quantity: 1,
      image_url: 'https://example.com/mouse.jpg',
    },
    {
      name: 'Cable USB',
      amount: 499,
      quantity: 2,
      image_url: 'https://example.com/cable.jpg',
    },
  ],
  success_url: 'https://tuapp.com/success?session_id={CHECKOUT_SESSION_ID}',
  merchant: {
    name: 'Bloque',
    theme: {
      primary: '#10b981',
      primary_foreground: '#ffffff',
      background: '#f8fafc',
      surface: '#ffffff',
      border: '#d1d5db',
      input_border: '#10b98166',
    },
  },
  payment_methods: ['card', 'pse', 'cash'],
  payeer: {
    name: 'Nestor Cortina',
    email: 'nestor@bloque.team',
  },
  metadata: {
    user_id: '12345',
    campaign: 'summer_sale',
    discount_applied: true,
  },
  expires_at: new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(), // 24 horas
});

// Redirigir usuario
res.redirect(checkout.url);

Recuperar un Checkout

Obtener detalles del checkout:

const checkout = await bloque.checkout.retrieve('checkout_123');

console.log('Estado:', checkout.status);
console.log('Monto:', checkout.amount_total);

Recuperación pública vs autenticada

  • Pública: usa retrievePublic(urlId) para páginas públicas donde solo necesitas campos públicos.\n- JWT/client-secret: usa retrieve(urlId) cuando tienes autenticación y necesitas la vista completa.
// Pública (sin auth)
const publicCheckout = await bloque.checkout.retrievePublic(urlId);

// Autenticada (requiere JWT / client-secret)
const authedCheckout = await bloque.checkout.retrieve(urlId);

Cancelar un Checkout

Cancelar un checkout activo:

const checkout = await bloque.checkout.cancel('checkout_123');

console.log('Estado:', checkout.status); // 'cancelled'

Respuesta del Checkout

{
  id: string;                // ID del checkout
  urn: string;               // URN del checkout (ej: did:bloque:payments:...)
  object: 'checkout';
  url: string;               // URL de pago
  client_secret?: string;    // JWT con alcance de checkout para auth en navegador (al usar secretKey)
  status: 'pending' | 'paid' | 'expired' | 'deposited' | 'cancelled';
  amount_total: number;      // Monto total
  amount_subtotal: number;   // Subtotal
  asset: string;
  items: CheckoutItem[];
  payment_type: 'shopping_cart' | 'subscription';
  subscription?: {
    type: 'cron';
    cron: string;
    startDate?: string;
    endDate?: string;
    status?: 'active' | 'expired' | 'eliminated' | 'paid';
  };
  metadata?: Metadata;
  created_at: string;
  updated_at: string;
  expires_at: string | null;
}
Client Secret

Al crear un checkout con secretKey, la respuesta incluye un campo client_secret. Este es un JWT con alcance de checkout que debe pasarse al frontend junto con el checkoutId para autenticación en el navegador.

Próximos Pasos