🚧 La documentacion de Bloque está en desarrollo

Tarjetas Virtuales

Crea y gestiona tarjetas virtuales para pagos en línea usando el SDK de Bloque.

Descripción General

Las tarjetas virtuales proporcionan una forma segura de realizar pagos en línea sin exponer información financiera sensible. Las características incluyen:

  • Creación Instantánea: Las tarjetas se crean inmediatamente
  • Cumplimiento PCI: Manejo seguro de datos de tarjetas
  • Múltiples Tarjetas: Los usuarios pueden tener múltiples tarjetas
  • Saldos en Tiempo Real: Consulta saldos en múltiples activos
  • Historial de Transacciones: Seguimiento completo de transacciones

Creando una Tarjeta Virtual

Creación Básica

Crea una tarjeta virtual para un usuario:

create-card.ts
import { SDK } from '@bloque/sdk';

const bloque = new SDK({
  origin: 'tu-origen',
  auth: {
    type: 'apiKey',
    apiKey: process.env.BLOQUE_API_KEY!,
  },
  mode: 'production',
});

// Conectar a la sesión del usuario
const userSession = await bloque.connect('user-alias');

// Crear una tarjeta virtual
const card = await userSession.accounts.card.create({
  holderUrn: 'did:bloque:tu-origen:user-alias',
  name: 'Mi Tarjeta Virtual',
});

console.log('Tarjeta creada:', card.lastFour);
console.log('Estado:', card.status);
console.log('URL de detalles:', card.detailsUrl);

Parámetros

tipos.ts
interface CreateCardParams {
  holderUrn?: string;   // Optional user URN
  name?: string;        // Optional card name
  webhookUrl?: string;  // Optional webhook for events
  ledgerId?: string;    // Optional ledger account ID
  metadata?: Record<string, unknown>; // Custom metadata
}

Respuesta

tipos.ts
interface CardAccount {
  urn: string;                    // Unique resource name
  id: string;                     // Card account ID
  lastFour: string;               // Last four digits
  productType: 'CREDIT' | 'DEBIT';
  status: CardStatus;
  cardType: 'VIRTUAL' | 'PHYSICAL';
  detailsUrl: string;             // PCI-compliant details URL
  ownerUrn: string;
  ledgerId: string;
  webhookUrl: string | null;
  metadata?: Record<string, unknown>;
  createdAt: string;
  updatedAt: string;
  balance?: Record<string, TokenBalance>; // Only in list responses
}

Listando Tarjetas

Lista todas las cuentas de tarjetas de un usuario con sus saldos:

list-cards.ts
// Usando sesión conectada (recomendado)
const userSession = await bloque.connect('user-alias');
const cards = await userSession.accounts.card.list();

console.log(`Se encontraron ${cards.accounts.length} cuentas de tarjeta`);

cards.accounts.forEach((card) => {
  console.log('\nCard:', card.metadata?.name);
  console.log('Últimos cuatro:', card.lastFour);
  console.log('Estado:', card.status);

  if (card.balance) {
    Object.entries(card.balance).forEach(([token, balance]) => {
      console.log(`${token}: ${balance.current}`);
    });
  }
});

// Calcular balance total
const totalBalances: Record<string, bigint> = {};
cards.accounts.forEach(card => {
  if (card.balance) {
    Object.entries(card.balance).forEach(([token, balance]) => {
      if (!totalBalances[token]) {
        totalBalances[token] = BigInt(0);
      }
      totalBalances[token] += BigInt(balance.current);
    });
  }
});

Consultando Saldo

Obtiene el saldo actual de una tarjeta específica:

check-balance.ts
const balances = await bloque.accounts.card.balance({
  urn: 'did:bloque:account:card:usr-123:crd-456',
});

Object.entries(balances).forEach(([token, balance]) => {
  console.log(`${token}:`);
  console.log(`  Actual: ${balance.current}`);
  console.log(`  Pendiente: ${balance.pending}`);
  console.log(`  Total Entradas: ${balance.in}`);
  console.log(`  Total Salidas: ${balance.out}`);

  const net = BigInt(balance.in) - BigInt(balance.out);
  console.log(`  Neto: ${net.toString()}`);
});

Historial de Transacciones

Listado Básico

Lista las transacciones de una tarjeta:

list-transactions.ts
const movements = await bloque.accounts.card.movements({
  urn: 'did:bloque:account:card:usr-123:crd-456',
  asset: 'DUSD/6',
});

movements.forEach((transaction) => {
  console.log(`${transaction.direction.toUpperCase()}: ${transaction.amount}`);
  console.log(`Date: ${transaction.created_at}`);

  if (transaction.details?.metadata?.merchant_name) {
    console.log(`Merchant: ${transaction.details.metadata.merchant_name}`);
  }
});

Con Filtros y Paginación

filter-transactions.ts
// Get recent incoming transactions
const recentIncoming = await bloque.accounts.card.movements({
  urn: 'did:bloque:account:card:usr-123:crd-456',
  asset: 'DUSD/6',
  limit: 50,
  direction: 'in', // Only incoming
  after: '2025-01-01T00:00:00Z',
});

// Get transactions from a specific date range
const dateRange = await bloque.accounts.card.movements({
  urn: 'did:bloque:account:card:usr-123:crd-456',
  asset: 'KSM/12',
  after: '2025-01-01T00:00:00Z',
  before: '2025-12-31T23:59:59Z',
  limit: 100,
});

// Buscar por referencia
const byReference = await bloque.accounts.card.movements({
  urn: 'did:bloque:account:card:usr-123:crd-456',
  asset: 'DUSD/6',
  reference: '0xbff43fa587...',
});

Ejemplo de Paginación

paginate-transactions.ts
async function getAllTransactions(cardUrn: string, asset: string) {
  const pageSize = 100;
  let allMovements = [];
  let hasMore = true;
  let lastDate: string | undefined;

  while (hasMore) {
    const movements = await bloque.accounts.card.movements({
      urn: cardUrn,
      asset,
      limit: pageSize,
      before: lastDate,
    });

    allMovements.push(...movements.data);
    console.log(`Se obtuvieron ${movements.data.length} transacciones`);

    if (movements.data.length < pageSize) {
      hasMore = false;
    } else {
      lastDate = movements.data[movements.data.length - 1].created_at;
    }
  }

  return allMovements;
}

Gestión de Tarjetas

Actualizar Nombre de Tarjeta

update-card.ts
const updatedCard = await bloque.accounts.card.updateMetadata({
  urn: 'did:bloque:account:card:usr-123:crd-456',
  metadata: {
    name: 'Mi Tarjeta de Negocio'
  }
});

console.log('Card name updated:', updatedCard.metadata?.name);

Estados de Tarjeta

Las tarjetas pueden estar en diferentes estados:

EstadoDescripciónPuede Transicionar A
creation_in_progressLa tarjeta se está creandoactive, creation_failed
creation_failedFalló la creación de la tarjeta-
activeLa tarjeta está activa y lista para usardisabled, frozen, deleted
disabledLa tarjeta ha sido deshabilitadadeleted
frozenLa tarjeta está temporalmente congeladaactive, disabled, deleted
deletedLa tarjeta ha sido eliminada-
stateDiagram-v2
    [*] --> creation_in_progress
    creation_in_progress --> active
    creation_in_progress --> creation_failed
    active --> disabled
    active --> frozen
    active --> deleted
    disabled --> deleted
    frozen --> active
    frozen --> disabled
    frozen --> deleted
    creation_failed --> [*]
    deleted --> [*]

Visualizando Detalles de Tarjeta

El campo detailsUrl proporciona una URL segura y compatible con PCI donde los usuarios pueden ver su número de tarjeta completo, CVV y fecha de expiración:

view-card-details.ts
const card = await bloque.accounts.card.create({
  holderUrn: userUrn,
  name: 'Mi Tarjeta',
});

// Redirect user to view card details
console.log('Ver detalles de tarjeta:', card.detailsUrl);
Seguridad

Nunca almacenes o registres números de tarjeta completos, CVVs u otros datos sensibles de tarjetas. Usa siempre el detailsUrl proporcionado para mostrar los detalles de la tarjeta a los usuarios.

Múltiples Tarjetas

Los usuarios pueden tener múltiples tarjetas para diferentes propósitos:

multiple-cards.ts
const userUrn = 'did:bloque:tu-origen:user-alias';
const userSession = await bloque.connect(userUrn);

// Tarjeta personal
const personalCard = await userSession.accounts.card.create({
  holderUrn: userUrn,
  name: 'Tarjeta Personal',
});

// Tarjeta de negocio
const businessCard = await userSession.accounts.card.create({
  holderUrn: userUrn,
  name: 'Tarjeta Negocio',
});

// Tarjeta de viaje
const travelCard = await userSession.accounts.card.create({
  holderUrn: userUrn,
  name: 'Gastos de Viaje',
});

console.log('Se crearon 3 tarjetas:');
console.log('- Personal:', personalCard.lastFour);
console.log('- Business:', businessCard.lastFour);
console.log('- Travel:', travelCard.lastFour);

Manejo de Errores

Siempre maneja los errores apropiadamente:

error-handling.ts
try {
  const card = await bloque.accounts.card.create({
    holderUrn: userUrn,
    name: cardName,
  });

  console.log('Tarjeta creada:', card.urn);

} catch (error) {
  if (error instanceof Error) {
    console.error('Card creation failed:', error.message);

    // Handle specific errors
    if (error.message.includes('not found')) {
      // User doesn't exist
    } else if (error.message.includes('unauthorized')) {
      // API key issues
    }
  }

  throw error;
}

Ejemplo Completo

setup-user-card.ts
import { SDK } from '@bloque/sdk';

const bloque = new SDK({
  origin: 'tu-origen',
  auth: {
    type: 'apiKey',
    apiKey: process.env.BLOQUE_API_KEY!,
  },
  mode: 'production',
});

async function setupUserCard(userUrn: string) {
  try {
    // Conectar a la sesión del usuario
    const userSession = await bloque.connect(userUrn);

    // Crear una tarjeta virtual
    const card = await userSession.accounts.card.create({
      holderUrn: userUrn,
      name: 'Tarjeta Principal',
    });

    console.log('✓ Tarjeta creada:', card.lastFour);

    // Consultar saldo
    const balances = await bloque.accounts.card.balance({
      urn: card.urn,
    });

    console.log('✓ Saldos actuales:');
    Object.entries(balances).forEach(([token, balance]) => {
      console.log(`  ${token}: ${balance.current}`);
    });

    // Get recent transactions
    const movements = await bloque.accounts.card.movements({
      urn: card.urn,
      asset: 'DUSD/6',
      limit: 10,
    });

    console.log(`✓ Se encontraron ${movements.data.length} transacciones recientes`);

    return { success: true, card };

  } catch (error) {
    console.error('✗ Setup failed:', error);
    throw error;
  }
}

Controles de Gasto

Los controles de gasto determinan cómo se procesan las transacciones con tarjeta, incluyendo qué cuentas se debitan, cómo funciona la conversión multicurrency y cómo se enrutan los fondos. El modo de control de gasto se configura a través del metadata de la tarjeta al crearla o actualizarla.

Control de Gasto por Defecto

El modo por defecto debita una única cuenta de tarjeta. Soporta conversión automática de moneda cuando la moneda de la transacción difiere del activo de la tarjeta.

create-card-default.ts
const card = await userSession.accounts.card.create({
  urn: userUrn,
  name: 'Tarjeta Estándar',
  metadata: {
    spending_control: 'default',      // Opcional, es el valor por defecto
    default_asset: 'DUSD/6',          // Activo principal de la tarjeta
    fallback_asset: 'KSM/12',         // Usado cuando el activo principal no está disponible
    currency_asset_map: {             // Mapea monedas de transacción a activos preferidos
      USD: ['DUSD/6'],
      COP: ['COPM/2'],
      EUR: ['EURc/6'],
    },
  },
});
CampoTipoRequeridoDescripción
spending_controlstringNoEstablecer como "default" (o no incluir).
default_assetstringNoActivo principal (ej. "DUSD/6"). Por defecto "DUSD/6".
fallback_assetstringNoActivo de respaldo cuando el principal no está disponible.
currency_asset_mapobjectNoMapea códigos de moneda ISO 4217 a un array de identificadores de activos.
whatsapp_notificationbooleanNoHabilitar notificaciones de compra por WhatsApp. Por defecto true.

Control de Gasto Inteligente

El gasto inteligente permite enrutamiento multi-bolsillo basado en Códigos de Categoría de Comercio (MCC). Las transacciones se enrutan a subcuentas (bolsillos) según la categoría del comercio, y el excedente se maneja por bolsillos de menor prioridad.

create-card-smart.ts
const card = await userSession.accounts.card.create({
  urn: userUrn,
  name: 'Tarjeta Inteligente',
  metadata: {
    spending_control: 'smart',
    priority_mcc: [
      'did:bloque:account:card:usr-xxx:pocket-food',
      'did:bloque:account:card:usr-xxx:pocket-transport',
      'did:bloque:account:card:usr-xxx:pocket-main',
    ],
    mcc_whitelist: {
      'did:bloque:account:card:usr-xxx:pocket-food': ['5411', '5412', '5812', '5814'],
      'did:bloque:account:card:usr-xxx:pocket-transport': ['4111', '4121', '4131'],
      // Sin whitelist para pocket-main = propósito general (acepta cualquier MCC)
    },
    default_asset: 'DUSD/6',
    fallback_asset: 'KSM/12',
  },
});
CampoTipoRequeridoDescripción
spending_controlstringDebe ser "smart".
priority_mccstring[]URNs de bolsillos en orden de prioridad. Los bolsillos de mayor prioridad se debitan primero.
mcc_whitelistobjectMapea URN de bolsillo a un array de códigos MCC permitidos, o una URL que retorna el array. Bolsillos sin entrada aceptan cualquier MCC.
default_assetstringNoActivo principal. Por defecto "DUSD/6".
fallback_assetstringNoActivo de respaldo cuando el principal no está disponible.
currency_asset_mapobjectNoMapea códigos de moneda a activos preferidos.

Cómo funciona el enrutamiento:

  1. El MCC del comercio se compara contra la whitelist de cada bolsillo en orden de prioridad
  2. Los bolsillos elegibles se debitan comenzando por el de mayor prioridad
  3. Si un bolsillo tiene fondos insuficientes, el remanente pasa al siguiente bolsillo elegible
  4. Los bolsillos sin entrada en la whitelist actúan como receptores generales
  5. Los reembolsos se enrutan de vuelta a los bolsillos originales que fueron debitados, proporcionalmente
flowchart LR
    TX[Transacción MCC 5411] --> P1{Bolsillo Comida}
    P1 -->|Cubre monto total| Done[Debitar Comida]
    P1 -->|Fondos insuficientes| P2{Bolsillo Principal}
    P2 --> Split[Débito dividido]
Whitelists de MCC

Puedes proporcionar las whitelists de MCC como arrays en línea o como URLs que apuntan a un array JSON. Usar URLs facilita actualizar las whitelists sin modificar el metadata de la tarjeta.

Configuración de Comisiones

Las comisiones se pueden configurar en dos niveles usando el array spending_fees. Cada comisión puede ser incondicional o estar controlada por una regla que evalúa el contexto de la transacción al momento de la autorización.

Prioridad de Resolución (Fusión por Nombre)

Las comisiones se fusionan a través de tres capas por fee_name. Cada capa puede sobreescribir comisiones específicas de la capa inferior y agregar nuevas. Las comisiones que no se redefinen en una capa superior siempre se preservan.

CapaFuenteDescripción
BasePor defectoComisiones predeterminadas (1.44% interchange + 2% FX). Siempre presentes a menos que se sobreescriban explícitamente por nombre.
+Metadata de origenspending_fees en el metadata del origen. Puede sobreescribir valores por defecto y agregar nuevas comisiones para todas las tarjetas.
+Metadata de tarjetaspending_fees en el metadata de la cuenta de tarjeta. Puede sobreescribir comisiones del origen/defecto y agregar nuevas para esta tarjeta específica.

Por ejemplo, si los valores por defecto definen interchange y fx, el origen agrega premium, y la tarjeta sobreescribe fx:

ComisiónPor defecto+ Origen+ Tarjeta (final)
interchange1.44%1.44%1.44%
fx2.00%2.00%1.50% (sobreescrito por tarjeta)
premium0.50%0.50%
Las Comisiones Base No Se Pueden Eliminar

Las comisiones de capas inferiores siempre se preservan. Una tarjeta u origen puede cambiar el value, type, account_urn o rule de una comisión existente redefiniéndola con el mismo fee_name, pero no puede eliminar una comisión por completo.

Definiendo Comisiones a Nivel de Origen

Las comisiones a nivel de origen aplican a todas las tarjetas bajo el origen. Se configuran a través del panel de Bloque o la API:

origin-fee-configuration.ts
// Metadata del origen (configurado via panel de Bloque o API)
{
  spending_fees: [
    {
      fee_name: 'interchange',
      account_urn: 'urn:bloque:treasury:interchange',
      type: 'percentage',
      value: 0.0144,       // 1.44% del monto de la transacción
      category: 'interchange',
    },
    {
      fee_name: 'fx',
      account_urn: 'urn:bloque:treasury:fx',
      type: 'percentage',
      value: 0.02,          // 2% — controla el spread de conversión
      category: 'fx',
      rule: 'fx_conversion',
    },
    {
      fee_name: 'high_ticket_fx',
      account_urn: 'urn:bloque:treasury:fx',
      type: 'percentage',
      value: 0.035,         // 3.5% — solo en liquidaciones USD de alto valor
      category: 'fx',
      rule: 'amount_range_usd',
      rule_params: { min: 500, max: 5000 },
    },
    {
      fee_name: 'apple_wallet',
      account_urn: 'urn:bloque:treasury:wallet',
      type: 'percentage',
      value: 0.01,          // 1% — solo para transacciones de Apple Wallet
      rule: 'wallet',
      rule_params: { wallet_name: 'Apple' },
    },
  ],
}

Definiendo Comisiones a Nivel de Tarjeta

Las comisiones a nivel de tarjeta sobreescriben las del origen para una tarjeta específica. Se configuran en metadata.spending_fees al crear o actualizar la tarjeta:

card-fee-override.ts
const card = await userSession.accounts.card.create({
  urn: userUrn,
  name: 'Tarjeta Premium',
  metadata: {
    spending_fees: [
      {
        fee_name: 'interchange',
        account_urn: 'urn:bloque:treasury:interchange',
        type: 'percentage',
        value: 0.01,         // 1% — interchange reducido para esta tarjeta
        category: 'interchange',
      },
      {
        fee_name: 'fx',
        account_urn: 'urn:bloque:treasury:fx',
        type: 'percentage',
        value: 0.015,        // 1.5% — spread FX reducido para esta tarjeta
        category: 'fx',
        rule: 'fx_conversion',
      },
    ],
  },
});
Sobreescritura de Comisiones por Tarjeta

Las comisiones a nivel de tarjeta se fusionan por fee_name. Solo necesitas incluir las comisiones que deseas sobreescribir o agregar — todas las demás comisiones del origen y los valores por defecto se preservan automáticamente.

Tipos de Comisión

types.ts
type SpendingFeeCategory = 'fx' | 'interchange' | 'custom';

interface SpendingFee {
  fee_name: string;           // Identificador único (ej. "interchange", "fx")
  account_urn: string;        // Cuenta destino para la comisión
  type: 'percentage' | 'flat';
  value: number;              // Tasa (0.02 = 2%) para porcentaje, o monto para fijo
  category?: SpendingFeeCategory; // Propósito de la comisión (por defecto "custom")
  rule?: string;              // Opcional: nombre de regla condicional
  rule_params?: Record<string, unknown>; // Opcional: parámetros para la regla
}
Tipo de ComisiónSignificado de valueEjemplo
percentageFracción del monto de la transacción0.0144 = 1.44%
flatMonto fijo en el activo de la transacción100000 (escalado)
CategoríaPropósito
fxControla el spread del tipo de cambio en conversiones de moneda. Múltiples comisiones fx se suman.
interchangeComisión de intercambio estándar.
customValor por defecto para cualquier otra comisión.
Categoría FX y Spread de Conversión

Las comisiones con category: "fx" controlan directamente el spread del tipo de cambio aplicado durante las conversiones de moneda. Por ejemplo, una comisión fx con value: 0.02 resulta en un spread del 2% (tasa multiplicada por 0.98). Si múltiples comisiones tienen category: "fx", sus valores se suman para determinar el spread total. Si no se configuran comisiones con categoría fx, se usa el spread por defecto del 2%.

Reglas Condicionales de Comisiones

Las comisiones sin campo rule siempre se aplican. Las comisiones con una regla se evalúan contra la solicitud completa de la transacción al momento de la autorización.

ReglaDescripciónParámetros
fx_conversionSe aplica cuando la transacción requirió conversión de moneda (el activo de gasto difiere de la moneda de la transacción).Ninguno
amount_range_usdSe aplica cuando el monto de liquidación en USD cae dentro de un rango.min?: number, max?: number
walletSe aplica cuando la transacción se realizó a través de una billetera de tokenización específica (coincidencia parcial, sin distinción de mayúsculas).wallet_name: string
Cálculo de Comisiones

Las comisiones se calculan usando aritmética BigInt para evitar problemas de precisión de punto flotante. Durante reembolsos parciales (ajustes), las comisiones se recalculan proporcionalmente basándose en el monto del reembolso relativo a la autorización original.

Desglose de Comisiones en Eventos

Cada webhook de transacción exitosa incluye un objeto fee_breakdown mostrando exactamente qué comisiones se aplicaron:

types.ts
interface FeeBreakdown {
  total_fees: string;          // Comisiones totales como string BigInt escalado
  fees: Array<{
    fee_name: string;          // Identificador de la comisión
    amount: string;            // Monto de la comisión como string BigInt escalado
    rate: number;              // La tasa o valor fijo utilizado
  }>;
}

Webhooks de Tarjeta

Cuando proporcionas un webhookUrl durante la creación de la tarjeta, tu endpoint recibe notificaciones POST en tiempo real para cada evento de transacción — compras, rechazos y ajustes (reembolsos).

Payload del Webhook

Todos los eventos de tarjeta comparten esta estructura:

types.ts
interface CardWebhookPayload {
  account_urn: string;         // URN de la cuenta de tarjeta
  transaction_id: string;      // Identificador único de la transacción
  type: 'authorization' | 'adjustment';
  direction: 'debit' | 'credit';
  event: CardEventType;
  amount?: string;             // Monto escalado como string BigInt
  asset?: string;              // Activo utilizado (ej. "DUSD/6")
  local_amount?: number;       // Monto en la moneda local del comercio
  local_currency?: string;     // Código de moneda ISO 4217 (ej. "USD", "COP")
  exchange_rate?: number;      // Tasa de conversión aplicada (1 si coincidencia directa)
  merchant?: MerchantInfo;
  medium?: TransactionMedium;
  fee_breakdown?: FeeBreakdown;
  reason?: string;             // Presente en eventos de rechazo
}

Tipos de Evento

EventoTipoDirecciónDescripción
purchaseauthorizationdebitCompra con tarjeta autorizada y debitada.
rejected_insufficient_fundsauthorizationdebitCompra rechazada — saldo insuficiente.
rejected_creditauthorizationcreditAutorización de crédito rechazada (no soportada).
rejected_currencyauthorizationdebitCompra rechazada — moneda no soportada.
credit_adjustmentadjustmentcreditReembolso o ajuste de crédito procesado.
debit_adjustmentadjustmentdebitAjuste de débito procesado.

Ejemplo de Evento de Compra

purchase-webhook.json
{
  "account_urn": "did:bloque:account:card:usr-abc:crd-123",
  "transaction_id": "ctx-200kXoaEJLNzcsvNxY1pmBO7fEx",
  "type": "authorization",
  "direction": "debit",
  "event": "purchase",
  "amount": "50000000",
  "asset": "DUSD/6",
  "local_amount": 50.0,
  "local_currency": "USD",
  "exchange_rate": 1,
  "merchant": {
    "name": "AMAZON.COM",
    "mcc": "5411",
    "city": "Seattle",
    "country": "USA"
  },
  "fee_breakdown": {
    "total_fees": "720000",
    "fees": [
      { "fee_name": "interchange", "amount": "720000", "rate": 0.0144 }
    ]
  }
}

Ejemplo de Evento de Reembolso

refund-webhook.json
{
  "account_urn": "did:bloque:account:card:usr-abc:crd-123",
  "transaction_id": "ctx-adj-456",
  "type": "adjustment",
  "direction": "credit",
  "event": "credit_adjustment",
  "amount": "25000000",
  "asset": "DUSD/6",
  "local_amount": 25.0,
  "local_currency": "USD",
  "exchange_rate": 1,
  "merchant": {
    "name": "AMAZON.COM",
    "mcc": "5411",
    "city": "Seattle",
    "country": "USA"
  },
  "fee_breakdown": {
    "total_fees": "360000",
    "fees": [
      { "fee_name": "interchange", "amount": "360000", "rate": 0.0144 }
    ]
  }
}

Ejemplo de Evento de Rechazo

rejection-webhook.json
{
  "account_urn": "did:bloque:account:card:usr-abc:crd-123",
  "transaction_id": "ctx-789",
  "type": "authorization",
  "direction": "debit",
  "event": "rejected_insufficient_funds",
  "required_usd": 150.0,
  "reason": "Insufficient funds"
}

Manejando Webhooks

card-webhook-handler.ts
import express from 'express';

const app = express();

app.post('/webhooks/cards', express.json(), (req, res) => {
  const payload = req.body;

  console.log(`[${payload.transaction_id}] ${payload.event}${payload.type}/${payload.direction}`);

  switch (payload.event) {
    case 'purchase':
      console.log(`Compra: ${payload.local_amount} ${payload.local_currency}`);
      console.log(`Comercio: ${payload.merchant?.name}`);
      console.log(`Comisiones: ${payload.fee_breakdown?.total_fees}`);
      // Actualizar registros, notificar usuario, etc.
      break;

    case 'credit_adjustment':
      console.log(`Reembolso: ${payload.local_amount} ${payload.local_currency}`);
      // Procesar reembolso en tu sistema
      break;

    case 'debit_adjustment':
      console.log(`Ajuste de débito: ${payload.local_amount} ${payload.local_currency}`);
      break;

    case 'rejected_insufficient_funds':
      console.log(`Rechazado: ${payload.reason}`);
      // Notificar al usuario de la transacción fallida
      break;

    case 'rejected_currency':
      console.log(`Moneda no soportada: ${payload.currency}`);
      break;

    case 'rejected_credit':
      console.log(`Crédito rechazado: ${payload.reason}`);
      break;
  }

  res.status(200).send('OK');
});
Idempotencia

La entrega de webhooks usa claves de idempotencia basadas en ${type}-${transaction_id}. Tu handler debe ser idempotente — procesar el mismo evento dos veces debe producir el mismo resultado.

Notificaciones de WhatsApp

Los eventos de compra también pueden activar notificaciones de WhatsApp al titular de la tarjeta si whatsapp_notification está habilitado en el metadata de la tarjeta y el usuario tiene un número de teléfono registrado.

Mejores Prácticas

  1. Sesiones de Usuario: Siempre conéctate a una sesión de usuario
  2. KYC Primero: Asegúrate de que los usuarios completen la verificación KYC
  3. Nombres Significativos: Ayuda a los usuarios a identificar sus tarjetas
  4. Verificar Estado: Verifica el estado de la tarjeta antes de usarla
  5. Seguridad: Nunca almacenes números de tarjeta completos
  6. Manejo de Errores: Usa bloques try-catch
  7. Probar en Sandbox: Prueba exhaustivamente antes de producción
  8. Webhooks Idempotentes: Siempre maneja entregas duplicadas de forma segura
  9. Transparencia de Comisiones: Incluye el desglose de comisiones en los recibos mostrados a los usuarios
  10. Presupuestos Inteligentes: Usa controles de gasto por bolsillo para ayudar a los usuarios a gestionar presupuestos por categoría

Próximos Pasos