Skip to main content

Overview

Follow these guidelines to build a robust integration and provide the best experience for your customers.

Security

Protect Your API Keys

Never expose API keys in client-side code or version control:
// ✅ Good - Use environment variables
const apiKey = process.env.CHEQPAY_API_KEY;

// ❌ Bad - Never hardcode
const apiKey = 'sk_live_abc123...';

Environment Variables

Store credentials securely:
# .env file (add to .gitignore)
CHEQPAY_API_KEY=sk_live_abc123xyz
// Load with dotenv
require('dotenv').config();

const config = {
  apiKey: process.env.CHEQPAY_API_KEY
};
Add .env to your .gitignore file. Never commit API keys to version control.

PCI Compliance

Never log, store, or display full card numbers. Use Cheqpay’s tokenization to keep card data secure.
Always use HTTPS for payment pages and API requests. Never accept card data over HTTP.
Use Content Security Policy headers to prevent XSS attacks on payment pages.
Regularly rotate API keys (every 90 days recommended). Update and test before deactivating old keys.

Optimize Success Rates

Include Device Information

Send device data with every payment to improve approval rates:
function getDeviceInformation() {
  return {
    ipAddress: req.ip,
    userAgent: req.headers['user-agent'],
    httpBrowserLanguage: req.headers['accept-language'],
    httpBrowserScreenWidth: req.body.screenWidth,
    httpBrowserScreenHeight: req.body.screenHeight,
    httpBrowserColorDepth: req.body.colorDepth,
    httpBrowserTimeDifference: new Date().getTimezoneOffset(),
    httpBrowserJavaEnabled: req.body.javaEnabled
  };
}

const payment = {
  // ... payment data
  deviceInformation: getDeviceInformation()
};

Save Payment Methods

Returning customers with saved cards have 3-5% higher approval rates:
// Save card during first payment
{
  paymentMethod: {
    type: 'card',
    options: { card: { /* ... */ } },
    persist: true  // Save for future use
  }
}

// Use saved card for subsequent payments
{
  paymentMethod: {
    type: 'payment_method_id',
    paymentMethodId: savedCard.id,
    cvc: '123'  // Still collect CVC
  }
}

Include Billing Address

Billing address verification improves approval rates:
{
  billingAddress: {
    address: 'Av. Reforma 123',
    city: 'Mexico City',
    state: 'CDMX',
    postalCode: '01000',
    country: 'MX'
  }
}

Consistent Customer Data

Use the same customer information across all payments:
// ✅ Good - Consistent data
{
  customer: {
    externalId: 'user_123',  // Always the same
    email: '[email protected]',
    firstName: 'María',
    lastName: 'González'
  }
}

// ❌ Bad - Changing data
{
  customer: {
    email: '[email protected]',  // Different email
    firstName: 'Maria',  // Different spelling
    // ...
  }
}

Idempotency

Always use externalId to prevent duplicate charges:
// ✅ Good - Use your order/transaction ID
{
  externalId: `order_${orderId}`,
  amount: 10000,
  // ...
}

// ❌ Bad - Random or missing ID
{
  amount: 10000,
  // Missing externalId
}

Safe Retries

With idempotency, you can safely retry failed requests:
async function createPaymentSafely(orderData) {
  const paymentData = {
    externalId: `order_${orderData.id}`,
    amount: orderData.total,
    currency: 'MXN',
    // ...
  };

  // If this fails and you retry, the same externalId
  // prevents duplicate charges
  return await createPaymentWithRetry(paymentData);
}

Monitor Payment Status

Check payment status when needed using the GET endpoint. For production systems, consider implementing efficient status checking patterns to minimize unnecessary API calls.

Error Handling

Show User-Friendly Messages

function getCustomerMessage(error) {
  const friendlyMessages = {
    'DECLINED': 'Payment couldn\'t be processed. Please try a different card.',
    'EXPIRED_CARD': 'This card has expired. Please use a different card.',
    'INVALID_CARD': 'Unable to process this card. Please check your details.',
    'INSUFFICIENT_FUNDS': 'Payment couldn\'t be completed. Please try another payment method.',
    'AUTHENTICATION_FAILED': 'We couldn\'t verify your identity. Please try again.'
  };

  return friendlyMessages[error.code] ||
         'Unable to process payment. Please try again or contact support.';
}

Implement Retry Logic

async function makePaymentWithRetry(data, maxRetries = 3) {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      return await createPayment(data);
    } catch (error) {
      // Only retry server errors
      if (error.status >= 500 && attempt < maxRetries - 1) {
        const delay = Math.pow(2, attempt) * 1000;
        await sleep(delay);
        continue;
      }
      throw error;
    }
  }
}

Performance Optimization

Cache Customer Data

const customerCache = new Map();

async function getCustomer(customerId) {
  // Check cache first
  if (customerCache.has(customerId)) {
    return customerCache.get(customerId);
  }

  // Fetch from API
  const customer = await cheqpay.customers.get(customerId);

  // Cache for 5 minutes
  customerCache.set(customerId, customer);
  setTimeout(() => customerCache.delete(customerId), 5 * 60 * 1000);

  return customer;
}

Process Refunds Asynchronously

// ✅ Good - Process async
app.post('/v2/orders/:id/refund', async (req, res) => {
  const order = await db.orders.findById(req.params.id);

  // Queue refund for background processing
  await queue.add('process-refund', {
    orderId: order.id,
    paymentOrderId: order.paymentOrderId,
    amount: req.body.amount
  });

  res.json({ message: 'Refund initiated' });
});

// ❌ Bad - Blocks response
app.post('/v2/orders/:id/refund', async (req, res) => {
  const order = await db.orders.findById(req.params.id);

  // This blocks the response
  const refund = await cheqpay.refunds.create({
    paymentOrderId: order.paymentOrderId,
    amount: req.body.amount
  });

  res.json({ refund });
});

Logging and Monitoring

Log Important Events

function logPaymentEvent(event, data) {
  logger.info(event, {
    timestamp: new Date().toISOString(),
    externalId: data.externalId,
    amount: data.amount,
    status: data.status,
    customerId: data.customerId,
    // DON'T log: card numbers, CVCs, full API keys
  });
}

// Log payment lifecycle
logPaymentEvent('payment.initiated', paymentData);
logPaymentEvent('payment.completed', paymentResponse);
logPaymentEvent('order.fulfilled', { orderId, paymentId });

Monitor Key Metrics

const metrics = {
  paymentsCreated: 0,
  paymentsSucceeded: 0,
  paymentsFailed: 0,
  averageAmount: 0,
  averageProcessingTime: 0
};

function trackPayment(payment, duration) {
  metrics.paymentsCreated++;

  if (payment.status === 'COMPLETED') {
    metrics.paymentsSucceeded++;
  } else if (payment.status === 'FAILED') {
    metrics.paymentsFailed++;
  }

  metrics.averageAmount =
    (metrics.averageAmount + payment.amount) / 2;

  metrics.averageProcessingTime =
    (metrics.averageProcessingTime + duration) / 2;
}

Set Up Alerts

function checkHealthMetrics() {
  const successRate = metrics.paymentsSucceeded / metrics.paymentsCreated;

  if (successRate < 0.85) {  // Below 85% success
    sendAlert({
      severity: 'high',
      title: 'Low payment success rate',
      message: `Success rate: ${(successRate * 100).toFixed(2)}%`
    });
  }
}

// Check every 5 minutes
setInterval(checkHealthMetrics, 5 * 60 * 1000);

Integration Checklist

Before going live, ensure you have:
  • ✅ API keys stored in environment variables
  • ✅ HTTPS used on all payment pages
  • ✅ No card data logged or stored
  • ✅ API key rotation schedule
  • ✅ Card payments working
  • ✅ 3D Secure authentication implemented
  • ✅ Device information included
  • ✅ Billing address collected
  • ✅ Payment methods saved for returning customers
  • ✅ Customer-friendly error messages
  • ✅ Retry logic for temporary failures
  • ✅ Declined payment handling
  • ✅ Client-side validation
  • ✅ Error logging and monitoring
  • ✅ Clear payment instructions
  • ✅ Order status updates
  • ✅ Email confirmations
  • ✅ Refund policy communicated
  • ✅ Support contact information
  • ✅ Comprehensive logging
  • ✅ Metrics and monitoring
  • ✅ Alert thresholds set
  • ✅ Refund workflow tested
  • ✅ Support team trained

Code Organization

Separation of Concerns

// ✅ Good - Organized structure
// services/payment.service.js
class PaymentService {
  async createPayment(orderData) {
    const paymentData = this.buildPaymentRequest(orderData);
    const payment = await cheqpay.payments.create(paymentData);
    await this.recordPayment(payment);
    return payment;
  }
}

// controllers/payment.controller.js
class PaymentController {
  async processPayment(req, res) {
    const payment = await paymentService.createPayment(req.body);
    res.json({ payment });
  }
}

// handlers/payment.handler.js
class PaymentHandler {
  async handlePaymentCompleted(payment) {
    await orderService.fulfill(payment.externalId);
    await notificationService.sendConfirmation(payment.customerId);
  }
}

Configuration Management

// config/cheqpay.config.js
const config = {
  sandbox: {
    baseUrl: 'https://api.cheqpay.dev',
    apiKey: process.env.CHEQPAY_SANDBOX_KEY
  },
  production: {
    baseUrl: 'https://prod.cheqpay.mx',
    apiKey: process.env.CHEQPAY_PRODUCTION_KEY
  }
};

const env = process.env.NODE_ENV || 'sandbox';
module.exports = config[env];

Testing

Test Coverage

describe('Payment Service', () => {
  it('should create payment with valid data', async () => {
    const payment = await paymentService.createPayment(validData);
    expect(payment.status).toBe('COMPLETED');
  });

  it('should handle declined payments', async () => {
    await expect(
      paymentService.createPayment(declinedCardData)
    ).rejects.toThrow('DECLINED');
  });

  it('should prevent duplicate charges', async () => {
    const payment1 = await paymentService.createPayment(data);
    const payment2 = await paymentService.createPayment(data);
    expect(payment1.id).toBe(payment2.id);
  });
});

Next Steps