🚧 Bloque documentation is under development

Checkout API

The Checkout API lets you create payment links (hosted checkout pages) for your customers.

There are two checkout types:

  • Shopping cart (payment_type: 'shopping_cart'): one-time purchase with one or more items
  • Subscription (payment_type: 'subscription'): recurring billing on a cron schedule

Creating a Checkout

Create a checkout session with items and redirect URLs:

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

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

const checkout = await bloque.checkout.create({
  name: 'React Course',
  description: 'Learn React from scratch',
  image_url: 'https://example.com/course.jpg',
  items: [
    {
      name: 'React Course',
      amount: 2999,
      quantity: 1,
      image_url: 'https://example.com/course.jpg',
    },
  ],
  success_url: 'https://yourapp.com/success',
});

console.log('Checkout URL:', checkout.url);
console.log('Client Secret:', checkout.client_secret);
// Redirect customer to checkout.url or pass id + client_secret to frontend

Subscriptions (recurring)

To create a subscription checkout, set payment_type: 'subscription' and include a subscription object.

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

Subscription (advanced)

The subscription schedule supports additional optional fields:

  • allocations: variable per-occurrence amounts (e.g. trial at $0 then normal price)\n- trial_days: delay the first charge by N days\n- timezone: interpret the cron in an IANA timezone (recommended for monthly billing)\n- max_occurrences: hard cap on generated occurrences
const checkout = await bloque.checkout.create({
  name: 'Premium (monthly)',
  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://yourapp.com/success',
});

Checkout Parameters

Required Parameters

{
  name: string;              // Checkout name
  items: CheckoutItem[];     // Items to purchase
  success_url: string;       // Redirect URL after success
}

Optional Parameters

{
  description?: string;      // Checkout description
  image_url?: string;        // Checkout image URL
  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;       // Expiration date (ISO 8601)
  cancel_url?: string;
  webhook_url?: string;      // Receive payment.status.updated
  payout_route?: PayoutRoute[]; // Advanced: route funds to one or more destinations
}

Advanced: payout_route (routing funds)

payout_route defines where the funds go after a payment is completed. You can route:

  • A fixed amount to a destination, or
  • A percentage of the total to a destination

Each destination is a discriminated union called route (by network):

  • polygon, kusama, tron, bep20 (address-based)
  • bloque (to a Bloque account_urn)

Routing costs

  • network: 'bloque': $0 (no additional cost) after the payment
  • Other networks (polygon, kusama, tron, bep20): $0.50 per leg

Example (fixed + percentage)

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

const checkout = await bloque.checkout.create({
  name: 'Marketplace order #123',
  items: [{ name: 'Order total', amount: 50000, quantity: 1 }],
  success_url: 'https://yourapp.com/success',
  payout_route: [
    {
      type: 'fixed',
      amount: 2000,
      route: {
        network: 'bloque',
        account_urn: 'did:bloque:accounts:...',
        alias: 'Platform fee',
      },
    },
    {
      type: 'percentage',
      percentage: 96,
      route: {
        network: 'polygon',
        address: '0xabc123...',
        alias: 'Seller',
      },
    },
  ],
});

::::tip If you’re not doing revenue splits / marketplace flows, you can ignore payout_route. ::::

Checkout Items

Each item in the checkout:

{
  name: string;        // Item name
  amount: number;      // Price in smallest currency unit
  quantity: number;    // Quantity
  image_url?: string;  // Item image URL
}

Complete Example

const checkout = await bloque.checkout.create({
  name: 'Shopping Cart',
  description: 'Your selected items',
  image_url: 'https://example.com/cart.jpg',
  items: [
    {
      name: 'Wireless Mouse',
      amount: 999,
      quantity: 1,
      image_url: 'https://example.com/mouse.jpg',
    },
    {
      name: 'USB Cable',
      amount: 499,
      quantity: 2,
      image_url: 'https://example.com/cable.jpg',
    },
  ],
  success_url: 'https://yourapp.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 hours
});

// Redirect user
res.redirect(checkout.url);

Retrieving a Checkout

Get checkout details:

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

console.log('Status:', checkout.status);
console.log('Amount:', checkout.amount_total);

Public vs authenticated retrieval

  • Public: use retrievePublic(urlId) for landing pages where you only need public fields.\n- JWT/client-secret: use retrieve(urlId) when you have authentication and need the full merchant view.
// Public (no auth)
const publicCheckout = await bloque.checkout.retrievePublic(urlId);

// Authenticated (requires JWT / client-secret flow)
const authedCheckout = await bloque.checkout.retrieve(urlId);

Canceling a Checkout

Cancel an active checkout:

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

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

Checkout Response

{
  id: string;                // Checkout ID
  urn: string;               // Checkout URN (e.g. did:bloque:payments:...)
  object: 'checkout';
  url: string;               // Payment URL
  client_secret?: string;    // Checkout-scoped JWT for browser-side auth (when using secretKey)
  status: 'pending' | 'paid' | 'expired' | 'deposited' | 'cancelled';
  amount_total: number;      // Total amount
  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

When creating a checkout with a secretKey, the response includes a client_secret field. This is a checkout-scoped JWT that should be passed to the frontend along with the checkoutId for browser-side authentication.

Next Steps