Skip to content

Casos de Uso

Esta guía presenta ejemplos prácticos de cómo integrar Zelta Pay en diferentes escenarios de negocio. Cada caso de uso incluye código funcional que puedes adaptar a tu aplicación.

E-commerce

Crea links de pago vinculados a productos de tu catálogo:

:: tab Node.js

javascript
async function createProductPaymentLink(product, customer) {
  const response = await fetch('https://api-pay.zelta.dev/v1/payment-links', {
    method: 'POST',
    headers: {
      'X-API-Key': process.env.ZELTA_API_KEY,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      concept: `Compra: ${product.name}`,
      amount: product.priceInCents,
      customerName: customer.name,
      customerEmail: customer.email,
      isTest: false,
      redirectUrl: `https://mitienda.com/compra/gracias?product=${product.id}`,
      paymentMethods: ['card', 'yappy'],
      metadata: {
        productId: product.id,
        productName: product.name,
        sku: product.sku,
        customerId: customer.id,
        type: 'product-purchase'
      }
    })
  });

  return await response.json();
}

// Uso
const paymentLink = await createProductPaymentLink(
  { id: 'prod_001', name: 'Audífonos Bluetooth', priceInCents: 4500, sku: 'AUD-BT-001' },
  { id: 'cust_123', name: 'Ana Martinez', email: '[email protected]' }
);

::

:: tab Python

python
import requests
import os

def create_product_payment_link(product: dict, customer: dict) -> dict:
    response = requests.post(
        'https://api-pay.zelta.dev/v1/payment-links',
        headers={
            'X-API-Key': os.environ['ZELTA_API_KEY'],
            'Content-Type': 'application/json'
        },
        json={
            'concept': f'Compra: {product["name"]}',
            'amount': product['price_in_cents'],
            'customerName': customer['name'],
            'customerEmail': customer['email'],
            'isTest': False,
            'redirectUrl': f'https://mitienda.com/compra/gracias?product={product["id"]}',
            'paymentMethods': ['card', 'yappy'],
            'metadata': {
                'productId': product['id'],
                'productName': product['name'],
                'sku': product['sku'],
                'customerId': customer['id'],
                'type': 'product-purchase'
            }
        }
    )
    return response.json()

# Uso
result = create_product_payment_link(
    {'id': 'prod_001', 'name': 'Audífonos Bluetooth', 'price_in_cents': 4500, 'sku': 'AUD-BT-001'},
    {'id': 'cust_123', 'name': 'Ana Martinez', 'email': '[email protected]'}
)

::

Checkout de carrito de compras

Genera un link de pago consolidado para un carrito completo:

:: tab Node.js

javascript
async function createCartCheckout(cart, customer) {
  const itemsSummary = cart.items
    .map(item => `${item.name} x${item.quantity}`)
    .join(', ');

  const totalCents = cart.items.reduce(
    (sum, item) => sum + (item.priceInCents * item.quantity), 0
  );

  const response = await fetch('https://api-pay.zelta.dev/v1/payment-links', {
    method: 'POST',
    headers: {
      'X-API-Key': process.env.ZELTA_API_KEY,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      concept: `Orden - ${cart.items.length} productos`,
      amount: totalCents,
      customerName: customer.name,
      customerEmail: customer.email,
      isTest: false,
      redirectUrl: `https://mitienda.com/ordenes/${cart.id}/confirmacion`,
      metadata: {
        cartId: cart.id,
        itemCount: String(cart.items.length),
        itemsSummary: itemsSummary.substring(0, 200),
        customerId: customer.id,
        type: 'cart-checkout'
      }
    })
  });

  return await response.json();
}

::

:: tab Python

python
def create_cart_checkout(cart: dict, customer: dict) -> dict:
    items_summary = ', '.join(
        f'{item["name"]} x{item["quantity"]}' for item in cart['items']
    )

    total_cents = sum(
        item['price_in_cents'] * item['quantity'] for item in cart['items']
    )

    response = requests.post(
        'https://api-pay.zelta.dev/v1/payment-links',
        headers={
            'X-API-Key': os.environ['ZELTA_API_KEY'],
            'Content-Type': 'application/json'
        },
        json={
            'concept': f'Orden - {len(cart["items"])} productos',
            'amount': total_cents,
            'customerName': customer['name'],
            'customerEmail': customer['email'],
            'isTest': False,
            'redirectUrl': f'https://mitienda.com/ordenes/{cart["id"]}/confirmacion',
            'metadata': {
                'cartId': cart['id'],
                'itemCount': str(len(cart['items'])),
                'itemsSummary': items_summary[:200],
                'customerId': customer['id'],
                'type': 'cart-checkout'
            }
        }
    )
    return response.json()

::

Seguimiento de estado de órdenes

Consulta el estado de un link de pago para actualizar tu sistema:

:: tab Node.js

javascript
async function checkOrderPaymentStatus(paymentLinkId) {
  const response = await fetch(
    `https://api-pay.zelta.dev/v1/payment-links/${paymentLinkId}`,
    {
      headers: {
        'X-API-Key': process.env.ZELTA_API_KEY,
        'Content-Type': 'application/json'
      }
    }
  );

  const result = await response.json();

  if (!result.success) {
    throw new Error(`Error al consultar link: ${result.error.code}`);
  }

  const paymentLink = result.data.paymentLink;

  switch (paymentLink.status) {
    case 'pending':
      console.log('Pago pendiente - el cliente aún no ha pagado');
      break;
    case 'completed':
      console.log('Pago completado - actualizar orden como pagada');
      await updateOrderStatus(paymentLink.metadata.orderId, 'paid');
      break;
    case 'expired':
      console.log('Link expirado - notificar al cliente');
      await notifyCustomerExpiredLink(paymentLink.metadata.customerId);
      break;
    case 'cancelled':
      console.log('Link cancelado');
      break;
  }

  return paymentLink;
}

::

:: tab Python

python
def check_order_payment_status(payment_link_id: str) -> dict:
    response = requests.get(
        f'https://api-pay.zelta.dev/v1/payment-links/{payment_link_id}',
        headers={
            'X-API-Key': os.environ['ZELTA_API_KEY'],
            'Content-Type': 'application/json'
        }
    )

    result = response.json()

    if not result['success']:
        raise Exception(f'Error al consultar link: {result["error"]["code"]}')

    payment_link = result['data']['paymentLink']
    status = payment_link['status']

    if status == 'pending':
        print('Pago pendiente - el cliente aún no ha pagado')
    elif status == 'completed':
        print('Pago completado - actualizar orden como pagada')
        update_order_status(payment_link['metadata']['orderId'], 'paid')
    elif status == 'expired':
        print('Link expirado - notificar al cliente')
        notify_customer_expired_link(payment_link['metadata']['customerId'])
    elif status == 'cancelled':
        print('Link cancelado')

    return payment_link

::

Suscripciones

Pagos mensuales

Genera links de pago mensuales para tus suscriptores:

:: tab Node.js

javascript
async function createMonthlySubscriptionPayment(subscription) {
  const now = new Date();
  const monthNames = [
    'Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio',
    'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre'
  ];
  const period = `${monthNames[now.getMonth()]} ${now.getFullYear()}`;

  const response = await fetch('https://api-pay.zelta.dev/v1/payment-links', {
    method: 'POST',
    headers: {
      'X-API-Key': process.env.ZELTA_API_KEY,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      concept: `${subscription.planName} - ${period}`,
      amount: subscription.amountInCents,
      customerName: subscription.customerName,
      customerEmail: subscription.customerEmail,
      isTest: false,
      redirectUrl: `https://miapp.com/suscripciones/${subscription.id}`,
      metadata: {
        subscriptionId: subscription.id,
        planName: subscription.planName,
        billingPeriod: `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}`,
        type: 'subscription-renewal'
      }
    })
  });

  return await response.json();
}

// Generar links para todas las suscripciones activas
async function generateMonthlyBilling(subscriptions) {
  const results = [];

  for (const sub of subscriptions) {
    const result = await createMonthlySubscriptionPayment(sub);
    results.push({
      subscriptionId: sub.id,
      paymentLinkUrl: result.data?.paymentLink?.paymentLinkUrl,
      success: result.success
    });

    // Respetar rate limit (60 req/min)
    await new Promise(resolve => setTimeout(resolve, 1100));
  }

  return results;
}

::

:: tab Python

python
from datetime import datetime
import time

def create_monthly_subscription_payment(subscription: dict) -> dict:
    now = datetime.now()
    month_names = [
        'Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio',
        'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre'
    ]
    period = f'{month_names[now.month - 1]} {now.year}'

    response = requests.post(
        'https://api-pay.zelta.dev/v1/payment-links',
        headers={
            'X-API-Key': os.environ['ZELTA_API_KEY'],
            'Content-Type': 'application/json'
        },
        json={
            'concept': f'{subscription["plan_name"]} - {period}',
            'amount': subscription['amount_in_cents'],
            'customerName': subscription['customer_name'],
            'customerEmail': subscription['customer_email'],
            'isTest': False,
            'redirectUrl': f'https://miapp.com/suscripciones/{subscription["id"]}',
            'metadata': {
                'subscriptionId': subscription['id'],
                'planName': subscription['plan_name'],
                'billingPeriod': now.strftime('%Y-%m'),
                'type': 'subscription-renewal'
            }
        }
    )
    return response.json()

def generate_monthly_billing(subscriptions: list) -> list:
    """Generar links para todas las suscripciones activas."""
    results = []

    for sub in subscriptions:
        result = create_monthly_subscription_payment(sub)
        results.append({
            'subscriptionId': sub['id'],
            'paymentLinkUrl': result.get('data', {}).get('paymentLink', {}).get('paymentLinkUrl'),
            'success': result.get('success', False)
        })

        # Respetar rate limit (60 req/min)
        time.sleep(1.1)

    return results

::

Seguimiento de renovaciones

Lleva un control de qué suscripciones han pagado y cuáles necesitan seguimiento:

javascript
async function trackSubscriptionRenewals(subscriptions, paymentLinks) {
  const renewalStatus = [];

  for (const sub of subscriptions) {
    const link = paymentLinks.find(
      pl => pl.metadata?.subscriptionId === sub.id
    );

    if (!link) {
      renewalStatus.push({
        subscriptionId: sub.id,
        status: 'no-link',
        action: 'Crear link de pago'
      });
      continue;
    }

    switch (link.status) {
      case 'completed':
        renewalStatus.push({
          subscriptionId: sub.id,
          status: 'paid',
          action: 'Ninguna - renovación exitosa'
        });
        break;

      case 'pending':
        const expiresAt = new Date(link.expiresAt);
        const hoursLeft = (expiresAt - new Date()) / (1000 * 60 * 60);

        renewalStatus.push({
          subscriptionId: sub.id,
          status: 'pending',
          action: hoursLeft < 24
            ? 'Enviar recordatorio urgente'
            : 'Esperar pago del cliente'
        });
        break;

      case 'expired':
        renewalStatus.push({
          subscriptionId: sub.id,
          status: 'expired',
          action: 'Generar nuevo link y notificar al cliente'
        });
        break;
    }
  }

  return renewalStatus;
}

Cobro de servicios

Consultorías y servicios profesionales

javascript
async function createServicePaymentLink(service) {
  const response = await fetch('https://api-pay.zelta.dev/v1/payment-links', {
    method: 'POST',
    headers: {
      'X-API-Key': process.env.ZELTA_API_KEY,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      concept: `${service.type}: ${service.description}`,
      amount: service.amountInCents,
      customerName: service.clientName,
      customerEmail: service.clientEmail,
      isTest: false,
      redirectUrl: service.confirmationUrl,
      metadata: {
        serviceId: service.id,
        serviceType: service.type,
        professionalId: service.professionalId,
        scheduledDate: service.date,
        type: 'service-payment'
      }
    })
  });

  return await response.json();
}

// Ejemplo: cobro de consultoría
const result = await createServicePaymentLink({
  id: 'srv_001',
  type: 'Consultoría legal',
  description: 'Sesión de 1 hora - Derecho corporativo',
  amountInCents: 150000,
  clientName: 'Roberto Diaz',
  clientEmail: '[email protected]',
  professionalId: 'prof_045',
  date: '2026-03-20',
  confirmationUrl: 'https://miconsultorio.com/citas/confirmacion'
});

Registro a eventos

javascript
async function createEventRegistrationLink(event, attendee) {
  const response = await fetch('https://api-pay.zelta.dev/v1/payment-links', {
    method: 'POST',
    headers: {
      'X-API-Key': process.env.ZELTA_API_KEY,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      concept: `Registro: ${event.name}`,
      amount: event.ticketPriceInCents,
      customerName: attendee.name,
      customerEmail: attendee.email,
      isTest: false,
      redirectUrl: `https://mieventos.com/registro/${event.id}/confirmacion`,
      paymentMethods: ['card', 'yappy'],
      metadata: {
        eventId: event.id,
        eventName: event.name,
        eventDate: event.date,
        ticketType: attendee.ticketType,
        attendeeId: attendee.id,
        type: 'event-registration'
      }
    })
  });

  return await response.json();
}

Integración con webhooks

Combina la creación de links de pago con para automatizar tu flujo completo.

Handler de pago exitoso

:: tab Node.js

javascript
// Procesar webhook de pago completado
app.post('/webhook/zelta-pay', express.raw({ type: 'application/json' }), async (req, res) => {
  const payload = JSON.parse(req.body);

  if (payload.type === 'payment.success') {
    const { metadata } = payload.paymentLink;

    switch (metadata?.type) {
      case 'product-purchase':
        await fulfillProductOrder(metadata.productId, metadata.customerId);
        await sendOrderConfirmationEmail(metadata.customerId);
        break;

      case 'cart-checkout':
        await processCartOrder(metadata.cartId);
        await updateInventory(metadata.cartId);
        await sendOrderConfirmationEmail(metadata.customerId);
        break;

      case 'subscription-renewal':
        await renewSubscription(metadata.subscriptionId, metadata.billingPeriod);
        await sendRenewalConfirmationEmail(metadata.subscriptionId);
        break;

      case 'service-payment':
        await confirmServiceAppointment(metadata.serviceId);
        await notifyProfessional(metadata.professionalId);
        break;

      case 'event-registration':
        await confirmEventRegistration(metadata.eventId, metadata.attendeeId);
        await sendEventTicket(metadata.attendeeId, metadata.eventId);
        break;

      default:
        console.log('Tipo de pago no reconocido:', metadata?.type);
    }
  }

  res.status(200).json({ received: true });
});

::

:: tab Python

python
from flask import Flask, request, jsonify

app = Flask(__name__)

@app.route('/webhook/zelta-pay', methods=['POST'])
def handle_webhook():
    payload = request.get_json()

    if payload.get('type') == 'payment.success':
        metadata = payload.get('paymentLink', {}).get('metadata', {})
        payment_type = metadata.get('type')

        if payment_type == 'product-purchase':
            fulfill_product_order(metadata['productId'], metadata['customerId'])
            send_order_confirmation_email(metadata['customerId'])

        elif payment_type == 'cart-checkout':
            process_cart_order(metadata['cartId'])
            update_inventory(metadata['cartId'])
            send_order_confirmation_email(metadata['customerId'])

        elif payment_type == 'subscription-renewal':
            renew_subscription(metadata['subscriptionId'], metadata['billingPeriod'])
            send_renewal_confirmation_email(metadata['subscriptionId'])

        elif payment_type == 'service-payment':
            confirm_service_appointment(metadata['serviceId'])
            notify_professional(metadata['professionalId'])

        elif payment_type == 'event-registration':
            confirm_event_registration(metadata['eventId'], metadata['attendeeId'])
            send_event_ticket(metadata['attendeeId'], metadata['eventId'])

    return jsonify({'received': True}), 200

::

Handler de renovación de suscripción

javascript
async function handleSubscriptionRenewalWebhook(payload) {
  const { metadata } = payload.paymentLink;

  if (metadata?.type !== 'subscription-renewal') return;

  const subscriptionId = metadata.subscriptionId;
  const billingPeriod = metadata.billingPeriod;

  // 1. Extender la suscripción
  await db.subscription.update({
    where: { id: subscriptionId },
    data: {
      status: 'active',
      currentPeriodEnd: getNextPeriodEnd(billingPeriod),
      lastPaymentDate: new Date()
    }
  });

  // 2. Registrar el pago
  await db.payment.create({
    data: {
      subscriptionId,
      amount: payload.paymentLink.amount,
      billingPeriod,
      paymentLinkId: payload.paymentLink.id,
      paidAt: new Date()
    }
  });

  // 3. Notificar al cliente
  await sendEmail({
    to: payload.paymentLink.customerEmail,
    subject: 'Renovación exitosa',
    body: `Tu suscripción ha sido renovada para ${billingPeriod}.`
  });
}

Analítica

Analítica de pagos

Consulta tus links de pago para generar reportes:

:: tab Node.js

javascript
async function getPaymentAnalytics(startDate, endDate) {
  const response = await fetch(
    `https://api-pay.zelta.dev/v1/payment-links?startDate=${startDate}&endDate=${endDate}`,
    {
      headers: {
        'X-API-Key': process.env.ZELTA_API_KEY,
        'Content-Type': 'application/json'
      }
    }
  );

  const result = await response.json();
  const links = result.data.paymentLinks;

  const analytics = {
    total: links.length,
    completed: links.filter(l => l.status === 'completed').length,
    pending: links.filter(l => l.status === 'pending').length,
    expired: links.filter(l => l.status === 'expired').length,
    totalAmountCollected: links
      .filter(l => l.status === 'completed')
      .reduce((sum, l) => sum + l.amount, 0),
    conversionRate: 0,
    averageAmount: 0,
    byPaymentMethod: {}
  };

  if (analytics.total > 0) {
    analytics.conversionRate = (analytics.completed / analytics.total * 100).toFixed(2);
  }

  if (analytics.completed > 0) {
    analytics.averageAmount = Math.round(analytics.totalAmountCollected / analytics.completed);
  }

  // Desglose por método de pago
  links.filter(l => l.status === 'completed').forEach(link => {
    const methods = link.paymentMethods || ['unknown'];
    methods.forEach(method => {
      if (!analytics.byPaymentMethod[method]) {
        analytics.byPaymentMethod[method] = { count: 0, amount: 0 };
      }
      analytics.byPaymentMethod[method].count++;
      analytics.byPaymentMethod[method].amount += link.amount;
    });
  });

  return analytics;
}

::

:: tab Python

python
def get_payment_analytics(start_date: str, end_date: str) -> dict:
    response = requests.get(
        f'https://api-pay.zelta.dev/v1/payment-links?startDate={start_date}&endDate={end_date}',
        headers={
            'X-API-Key': os.environ['ZELTA_API_KEY'],
            'Content-Type': 'application/json'
        }
    )

    result = response.json()
    links = result['data']['paymentLinks']

    completed = [l for l in links if l['status'] == 'completed']
    total_collected = sum(l['amount'] for l in completed)

    analytics = {
        'total': len(links),
        'completed': len(completed),
        'pending': len([l for l in links if l['status'] == 'pending']),
        'expired': len([l for l in links if l['status'] == 'expired']),
        'totalAmountCollected': total_collected,
        'conversionRate': f'{(len(completed) / len(links) * 100):.2f}' if links else '0',
        'averageAmount': round(total_collected / len(completed)) if completed else 0
    }

    return analytics

::

Historial de pagos por cliente

javascript
async function getCustomerPaymentHistory(customerEmail) {
  const response = await fetch(
    `https://api-pay.zelta.dev/v1/payment-links?customerEmail=${encodeURIComponent(customerEmail)}`,
    {
      headers: {
        'X-API-Key': process.env.ZELTA_API_KEY,
        'Content-Type': 'application/json'
      }
    }
  );

  const result = await response.json();

  return result.data.paymentLinks.map(link => ({
    id: link.id,
    concept: link.concept,
    amount: link.amount,
    status: link.status,
    createdAt: link.createdAt,
    metadata: link.metadata
  }));
}

Buenas prácticas

Manejo de errores

Centraliza el manejo de errores en una función reutilizable:

javascript
class ZeltaPayError extends Error {
  constructor(code, message, details = []) {
    super(message);
    this.code = code;
    this.details = details;
  }
}

async function zeltaPayRequest(endpoint, options = {}) {
  const response = await fetch(`https://api-pay.zelta.dev${endpoint}`, {
    ...options,
    headers: {
      'X-API-Key': process.env.ZELTA_API_KEY,
      'Content-Type': 'application/json',
      ...options.headers
    }
  });

  const result = await response.json();

  if (!result.success) {
    throw new ZeltaPayError(
      result.error.code,
      result.error.message,
      result.error.details || []
    );
  }

  return result.data;
}

Rate limiting

Respeta el límite de 60 solicitudes por minuto implementando un control de tasa:

javascript
class RateLimiter {
  constructor(maxRequests = 60, windowMs = 60000) {
    this.maxRequests = maxRequests;
    this.windowMs = windowMs;
    this.requests = [];
  }

  async waitForSlot() {
    const now = Date.now();
    this.requests = this.requests.filter(t => now - t < this.windowMs);

    if (this.requests.length >= this.maxRequests) {
      const oldestRequest = this.requests[0];
      const waitTime = this.windowMs - (now - oldestRequest) + 100;
      await new Promise(resolve => setTimeout(resolve, waitTime));
    }

    this.requests.push(Date.now());
  }
}

const limiter = new RateLimiter();

async function createPaymentLinkWithRateLimit(data) {
  await limiter.waitForSlot();

  return await zeltaPayRequest('/v1/payment-links', {
    method: 'POST',
    body: JSON.stringify(data)
  });
}

Caché

Si consultas frecuentemente el estado de los links, implementa un caché para reducir llamadas a la API:

javascript
const cache = new Map();
const CACHE_TTL = 30000; // 30 segundos

async function getPaymentLinkCached(paymentLinkId) {
  const cacheKey = `pl_${paymentLinkId}`;
  const cached = cache.get(cacheKey);

  if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
    return cached.data;
  }

  const data = await zeltaPayRequest(`/v1/payment-links/${paymentLinkId}`);

  cache.set(cacheKey, { data, timestamp: Date.now() });
  return data;
}

Caché y estados de pago

No confíes solo en el caché para determinar si un pago fue completado. Usa para recibir notificaciones en tiempo real cuando cambie el estado de un pago.

Logging

Registra las interacciones con la API para facilitar la depuración:

javascript
async function zeltaPayRequestWithLogging(endpoint, options = {}) {
  const requestId = crypto.randomUUID();
  const startTime = Date.now();

  console.log(JSON.stringify({
    type: 'zelta_pay_request',
    requestId,
    endpoint,
    method: options.method || 'GET',
    timestamp: new Date().toISOString()
  }));

  try {
    const result = await zeltaPayRequest(endpoint, options);

    console.log(JSON.stringify({
      type: 'zelta_pay_response',
      requestId,
      success: true,
      durationMs: Date.now() - startTime
    }));

    return result;
  } catch (error) {
    console.error(JSON.stringify({
      type: 'zelta_pay_error',
      requestId,
      code: error.code,
      message: error.message,
      durationMs: Date.now() - startTime
    }));

    throw error;
  }
}

Testing

Tests unitarios

Verifica la lógica de tu integración con tests unitarios:

:: tab Node.js

javascript
import { describe, it, expect, vi } from 'vitest';

describe('createProductPaymentLink', () => {
  it('debe crear un link con los datos correctos del producto', async () => {
    const mockFetch = vi.fn().mockResolvedValue({
      json: () => Promise.resolve({
        success: true,
        data: {
          paymentLink: {
            id: 'pl_test_123',
            paymentLinkUrl: 'https://pay.zelta.dev/test123',
            status: 'pending'
          }
        }
      })
    });

    global.fetch = mockFetch;

    const product = { id: 'prod_001', name: 'Test Product', priceInCents: 5000, sku: 'TST-001' };
    const customer = { id: 'cust_001', name: 'Test User', email: '[email protected]' };

    const result = await createProductPaymentLink(product, customer);

    expect(result.success).toBe(true);
    expect(result.data.paymentLink.status).toBe('pending');

    const requestBody = JSON.parse(mockFetch.mock.calls[0][1].body);
    expect(requestBody.amount).toBe(5000);
    expect(requestBody.metadata.productId).toBe('prod_001');
  });

  it('debe manejar errores de validación', async () => {
    global.fetch = vi.fn().mockResolvedValue({
      json: () => Promise.resolve({
        success: false,
        error: { code: 'ERR_VALIDATION_FAILED', message: 'Validation failed' }
      })
    });

    const product = { id: 'prod_001', name: 'Test', priceInCents: 50, sku: 'TST' };
    const customer = { id: 'cust_001', name: 'Test', email: '[email protected]' };

    const result = await createProductPaymentLink(product, customer);
    expect(result.success).toBe(false);
    expect(result.error.code).toBe('ERR_VALIDATION_FAILED');
  });
});

::

:: tab Python

python
import pytest
from unittest.mock import patch, MagicMock

def test_create_product_payment_link_success():
    mock_response = MagicMock()
    mock_response.json.return_value = {
        'success': True,
        'data': {
            'paymentLink': {
                'id': 'pl_test_123',
                'paymentLinkUrl': 'https://pay.zelta.dev/test123',
                'status': 'pending'
            }
        }
    }

    with patch('requests.post', return_value=mock_response) as mock_post:
        product = {'id': 'prod_001', 'name': 'Test Product', 'price_in_cents': 5000, 'sku': 'TST-001'}
        customer = {'id': 'cust_001', 'name': 'Test User', 'email': '[email protected]'}

        result = create_product_payment_link(product, customer)

        assert result['success'] is True
        assert result['data']['paymentLink']['status'] == 'pending'

        call_args = mock_post.call_args
        request_body = call_args[1]['json']
        assert request_body['amount'] == 5000
        assert request_body['metadata']['productId'] == 'prod_001'

def test_create_product_payment_link_validation_error():
    mock_response = MagicMock()
    mock_response.json.return_value = {
        'success': False,
        'error': {'code': 'ERR_VALIDATION_FAILED', 'message': 'Validation failed'}
    }

    with patch('requests.post', return_value=mock_response):
        product = {'id': 'prod_001', 'name': 'Test', 'price_in_cents': 50, 'sku': 'TST'}
        customer = {'id': 'cust_001', 'name': 'Test', 'email': '[email protected]'}

        result = create_product_payment_link(product, customer)
        assert result['success'] is False
        assert result['error']['code'] == 'ERR_VALIDATION_FAILED'

::

Tests de integración

Usa el modo de prueba (isTest: true) para ejecutar tests de integración contra la API real:

javascript
import { describe, it, expect } from 'vitest';

describe('Zelta Pay Integration', () => {
  it('debe crear y consultar un link de pago de prueba', async () => {
    // Crear link de prueba
    const createResponse = await fetch('https://api-pay.zelta.dev/v1/payment-links', {
      method: 'POST',
      headers: {
        'X-API-Key': process.env.ZELTA_API_KEY,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        concept: 'Test de integración',
        amount: 1000,
        customerName: 'Test User',
        isTest: true,
        metadata: { testId: 'integration-001' }
      })
    });

    const createResult = await createResponse.json();
    expect(createResult.success).toBe(true);

    const paymentLinkId = createResult.data.paymentLink.id;

    // Consultar el link creado
    const getResponse = await fetch(
      `https://api-pay.zelta.dev/v1/payment-links/${paymentLinkId}`,
      {
        headers: {
          'X-API-Key': process.env.ZELTA_API_KEY,
          'Content-Type': 'application/json'
        }
      }
    );

    const getResult = await getResponse.json();
    expect(getResult.success).toBe(true);
    expect(getResult.data.paymentLink.id).toBe(paymentLinkId);
    expect(getResult.data.paymentLink.isTest).toBe(true);
  });
});

Usa modo de prueba para tests

Siempre usa isTest: true en tus tests de integración. Los links de prueba se comportan idénticamente a los de producción pero no procesan pagos reales.

Siguientes pasos

  • -- Referencia completa para crear links de pago
  • -- Recibe notificaciones en tiempo real
  • -- API keys y rate limiting
  • -- Documentación completa de endpoints

Documentación oficial de Zelta