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
Links de pago para productos
Crea links de pago vinculados a productos de tu catálogo:
:: tab Node.js
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
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
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
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
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
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
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
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:
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
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
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
// 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
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
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
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
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
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:
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:
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:
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:
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
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
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:
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