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.
Regularly rotate API keys (every 90 days recommended). Update and test before deactivating old keys.
Optimize Success Rates
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 ;
}
}
}
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