Backend Integration Guide
This guide demonstrates how to process FramePay tokens on your backend and complete payment transactions using the Payments AI API.
Overview
Your backend is responsible for:
- 🔑 Securely storing API credentials
- 👤 Creating customers
- 💳 Creating payment instruments (card payments)
- 💰 Processing transactions
- ✅ Handling payment results
- 🔔 Processing webhooks
Security Note: Never expose your API secret key to the frontend. All payment processing must happen on your backend.
Prerequisites
Before you begin, ensure you have:
- API Secret Key - Get this from your Payments AI dashboard
- Organization ID - Provided after merchant registration
- Backend server - Node.js, Python, PHP, Ruby, etc.
Environment Setup
Configuration
Store your credentials securely using environment variables:
# Payments AI Configuration
PAYMENTS_AI_API_KEY=sk_live_your_secret_key_here
PAYMENTS_AI_ORG_ID=org_your_organization_id
PAYMENTS_AI_WEBSITE_ID=web_your_website_id
# Environment
NODE_ENV=production
# API URLs
PAYMENTS_AI_API_URL=https://api.payments.ai
# Use staging for development
# PAYMENTS_AI_API_URL=https://staging-api.payments.ai
Node.js/Express Setup
// config/payments-ai.js
require('dotenv').config();
const config = {
apiKey: process.env.PAYMENTS_AI_API_KEY,
organizationId: process.env.PAYMENTS_AI_ORG_ID,
websiteId: process.env.PAYMENTS_AI_WEBSITE_ID,
apiUrl: process.env.NODE_ENV === 'production'
? 'https://api.payments.ai'
: 'https://staging-api.payments.ai'
};
if (!config.apiKey || !config.organizationId) {
throw new Error('Missing required Payments AI configuration');
}
module.exports = config;
HTTP Client Setup
Axios (Node.js)
// lib/payments-ai-client.js
const axios = require('axios');
const config = require('../config/payments-ai');
const paymentsAI = axios.create({
baseURL: config.apiUrl,
headers: {
'Authorization': `ApiKey ${config.apiKey}`,
'Content-Type': 'application/json'
},
timeout: 30000 // 30 seconds
});
// Add request interceptor for logging
paymentsAI.interceptors.request.use(
(config) => {
console.log(`API Request: ${config.method.toUpperCase()} ${config.url}`);
return config;
},
(error) => {
console.error('API Request Error:', error);
return Promise.reject(error);
}
);
// Add response interceptor for error handling
paymentsAI.interceptors.response.use(
(response) => response,
(error) => {
if (error.response) {
console.error('API Error:', {
status: error.response.status,
data: error.response.data
});
} else {
console.error('Network Error:', error.message);
}
return Promise.reject(error);
}
);
module.exports = paymentsAI;
Python (requests)
# lib/payments_ai_client.py
import os
import requests
from typing import Dict, Any
class PaymentsAIClient:
def __init__(self):
self.api_key = os.getenv('PAYMENTS_AI_API_KEY')
self.organization_id = os.getenv('PAYMENTS_AI_ORG_ID')
self.api_url = os.getenv('PAYMENTS_AI_API_URL', 'https://api.payments.ai')
if not self.api_key or not self.organization_id:
raise ValueError('Missing Payments AI configuration')
self.headers = {
'Authorization': f'ApiKey {self.api_key}',
'Content-Type': 'application/json'
}
def post(self, endpoint: str, data: Dict[str, Any]) -> Dict[str, Any]:
url = f'{self.api_url}{endpoint}'
response = requests.post(url, json=data, headers=self.headers, timeout=30)
response.raise_for_status()
return response.json()
def get(self, endpoint: str) -> Dict[str, Any]:
url = f'{self.api_url}{endpoint}'
response = requests.get(url, headers=self.headers, timeout=30)
response.raise_for_status()
return response.json()
# Create singleton instance
payments_ai = PaymentsAIClient()
API Operations
Create Customer
// services/customer-service.js
const paymentsAI = require('../lib/payments-ai-client');
const config = require('../config/payments-ai');
async function createCustomer(customerData) {
try {
const response = await paymentsAI.post(
`/v1/organizations/${config.organizationId}/customers`,
{
email: customerData.email,
firstName: customerData.firstName,
lastName: customerData.lastName,
phoneNumber: customerData.phoneNumber,
primaryAddress: {
firstName: customerData.firstName,
lastName: customerData.lastName,
address: customerData.address.address,
city: customerData.address.city,
region: customerData.address.region,
country: customerData.address.country,
zip: customerData.address.postalCode
}
}
);
console.log('Customer created:', response.data.id);
return response.data;
} catch (error) {
console.error('Failed to create customer:', error);
throw new Error('Customer creation failed');
}
}
module.exports = { createCustomer };
Create Payment Instrument
Used for card payments only:
// services/payment-instrument-service.js
const paymentsAI = require('../lib/payments-ai-client');
const config = require('../config/payments-ai');
async function createPaymentInstrument(customerId, tokenId) {
try {
const response = await paymentsAI.post(
`/v1/organizations/${config.organizationId}/customers/${customerId}/payment-instruments`,
{
token: tokenId
}
);
console.log('Payment instrument created:', response.data.id);
return response.data;
} catch (error) {
console.error('Failed to create payment instrument:', error);
throw new Error('Payment instrument creation failed');
}
}
module.exports = { createPaymentInstrument };
Create Transaction
// services/transaction-service.js
const paymentsAI = require('../lib/payments-ai-client');
const config = require('../config/payments-ai');
async function createTransaction(transactionData) {
try {
const response = await paymentsAI.post(
`/v1/organizations/${config.organizationId}/transactions`,
{
amount: transactionData.amount,
currency: transactionData.currency,
customerId: transactionData.customerId,
type: 'sale', // or 'authorize'
paymentInstruction: transactionData.paymentInstruction,
billingAddress: transactionData.billingAddress,
metadata: transactionData.metadata || {}
}
);
console.log('Transaction created:', response.data.id);
return response.data;
} catch (error) {
console.error('Failed to create transaction:', error);
if (error.response?.status === 400) {
throw new Error(error.response.data.message || 'Transaction declined');
}
throw new Error('Transaction failed');
}
}
module.exports = { createTransaction };
Payment Processing Endpoints
Card Payment Endpoint
// routes/payments.js
const express = require('express');
const router = express.Router();
const { createCustomer } = require('../services/customer-service');
const { createPaymentInstrument } = require('../services/payment-instrument-service');
const { createTransaction } = require('../services/transaction-service');
router.post('/card', async (req, res) => {
const { tokenId, amount, currency, customer } = req.body;
// Validate request
if (!tokenId || !amount || !currency || !customer) {
return res.status(400).json({
success: false,
message: 'Missing required fields'
});
}
try {
// Step 1: Create customer
const customerRecord = await createCustomer({
email: customer.email,
firstName: customer.firstName,
lastName: customer.lastName,
phoneNumber: customer.phoneNumber,
address: customer.address
});
// Step 2: Create payment instrument from token
const paymentInstrument = await createPaymentInstrument(
customerRecord.id,
tokenId
);
// Step 3: Create transaction
const transaction = await createTransaction({
amount,
currency,
customerId: customerRecord.id,
paymentInstruction: {
paymentInstrumentId: paymentInstrument.id
},
billingAddress: {
firstName: customer.firstName,
lastName: customer.lastName,
address: customer.address.address,
city: customer.address.city,
region: customer.address.region,
country: customer.address.country,
zip: customer.address.postalCode,
email: customer.email,
phoneNumber: customer.phoneNumber
},
metadata: {
source: 'web-checkout',
paymentMethod: 'card'
}
});
// Check transaction result
if (transaction.result === 'approved') {
res.json({
success: true,
transactionId: transaction.id,
customerId: customerRecord.id,
message: 'Payment successful'
});
} else {
res.status(400).json({
success: false,
message: `Payment ${transaction.result}`,
reason: transaction.declineReason,
transactionId: transaction.id
});
}
} catch (error) {
console.error('Card payment error:', error);
res.status(500).json({
success: false,
message: error.message || 'Payment processing failed'
});
}
});
module.exports = router;
Wallet Payment Endpoint
// routes/payments.js (continued)
router.post('/wallet', async (req, res) => {
const { tokenId, paymentMethod, amount, currency, customer } = req.body;
// Validate request
if (!tokenId || !amount || !currency || !customer) {
return res.status(400).json({
success: false,
message: 'Missing required fields'
});
}
try {
// Step 1: Create customer (with fallback values for incomplete wallet data)
const customerRecord = await createCustomer({
email: customer.email || '[email protected]',
firstName: customer.firstName || 'Customer',
lastName: customer.lastName || 'Name',
phoneNumber: customer.phoneNumber || '',
address: {
address: customer.address.address || '',
city: customer.address.city || '',
region: customer.address.region || '',
country: customer.address.country || 'US',
postalCode: customer.address.postalCode || ''
}
});
// Step 2: Create transaction with token directly (skip payment instrument)
const transaction = await createTransaction({
amount,
currency,
customerId: customerRecord.id,
paymentInstruction: {
token: tokenId // Use token directly for wallet payments
},
billingAddress: {
firstName: customer.firstName || 'Customer',
lastName: customer.lastName || 'Name',
address: customer.address.address,
city: customer.address.city,
region: customer.address.region,
country: customer.address.country,
zip: customer.address.postalCode,
email: customer.email,
phoneNumber: customer.phoneNumber
},
metadata: {
source: 'web-checkout',
paymentMethod: paymentMethod || 'digital-wallet'
}
});
// Handle approval redirect (PayPal)
if (transaction.approvalUrl) {
return res.json({
success: true,
approvalUrl: transaction.approvalUrl,
transactionId: transaction.id,
message: 'Approval required'
});
}
// Check transaction result
if (transaction.result === 'approved') {
res.json({
success: true,
transactionId: transaction.id,
customerId: customerRecord.id,
message: 'Payment successful'
});
} else {
res.status(400).json({
success: false,
message: `Payment ${transaction.result}`,
reason: transaction.declineReason,
transactionId: transaction.id
});
}
} catch (error) {
console.error('Wallet payment error:', error);
res.status(500).json({
success: false,
message: error.message || 'Payment processing failed'
});
}
});
Error Handling
Comprehensive Error Handler
// middleware/error-handler.js
function errorHandler(err, req, res, next) {
console.error('Error:', err);
// Payments AI API errors
if (err.response) {
const status = err.response.status;
const data = err.response.data;
switch (status) {
case 400:
return res.status(400).json({
success: false,
message: data.message || 'Bad request',
details: data.details
});
case 401:
return res.status(500).json({
success: false,
message: 'Authentication failed'
});
case 404:
return res.status(404).json({
success: false,
message: 'Resource not found'
});
case 422:
return res.status(422).json({
success: false,
message: 'Validation error',
details: data.details
});
case 429:
return res.status(429).json({
success: false,
message: 'Too many requests. Please try again later.'
});
default:
return res.status(500).json({
success: false,
message: 'Payment processing failed'
});
}
}
// Generic error
res.status(500).json({
success: false,
message: err.message || 'Internal server error'
});
}
module.exports = errorHandler;
Security Best Practices
1. Validate Requests
// middleware/validate-payment.js
function validatePaymentRequest(req, res, next) {
const { tokenId, amount, currency, customer } = req.body;
// Validate token
if (!tokenId || typeof tokenId !== 'string') {
return res.status(400).json({
success: false,
message: 'Invalid token'
});
}
// Validate amount
if (!amount || typeof amount !== 'number' || amount <= 0) {
return res.status(400).json({
success: false,
message: 'Invalid amount'
});
}
// Validate currency
const validCurrencies = ['USD', 'EUR', 'GBP', 'CAD', 'AUD'];
if (!currency || !validCurrencies.includes(currency)) {
return res.status(400).json({
success: false,
message: 'Invalid currency'
});
}
// Validate customer data
if (!customer || !customer.email || !customer.firstName || !customer.lastName) {
return res.status(400).json({
success: false,
message: 'Invalid customer data'
});
}
next();
}
module.exports = validatePaymentRequest;
2. Rate Limiting
// middleware/rate-limit.js
const rateLimit = require('express-rate-limit');
const paymentLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 10, // 10 requests per window
message: {
success: false,
message: 'Too many payment attempts. Please try again later.'
},
standardHeaders: true,
legacyHeaders: false
});
module.exports = paymentLimiter;
3. CORS Configuration
// middleware/cors.js
const cors = require('cors');
const corsOptions = {
origin: process.env.NODE_ENV === 'production'
? ['https://yourdomain.com']
: ['http://localhost:3000', 'http://localhost:5173'],
methods: ['POST'],
credentials: true,
optionsSuccessStatus: 200
};
module.exports = cors(corsOptions);
Complete Server Setup
Express Server
// server.js
require('dotenv').config();
const express = require('express');
const cors = require('./middleware/cors');
const errorHandler = require('./middleware/error-handler');
const paymentRoutes = require('./routes/payments');
const app = express();
const PORT = process.env.PORT || 3000;
// Middleware
app.use(cors);
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// Health check
app.get('/health', (req, res) => {
res.json({ status: 'ok' });
});
// Payment routes
app.use('/api/payments', paymentRoutes);
// Error handler (must be last)
app.use(errorHandler);
// Start server
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
console.log(`Environment: ${process.env.NODE_ENV}`);
});
Webhook Integration
Webhook Endpoint
// routes/webhooks.js
const express = require('express');
const router = express.Router();
const crypto = require('crypto');
router.post('/payments-ai', express.raw({type: 'application/json'}), (req, res) => {
const signature = req.headers['x-webhook-signature'];
const webhookSecret = process.env.PAYMENTS_AI_WEBHOOK_SECRET;
// Verify webhook signature
const hmac = crypto.createHmac('sha256', webhookSecret);
const digest = hmac.update(req.body).digest('hex');
if (digest !== signature) {
console.error('Invalid webhook signature');
return res.status(401).send('Invalid signature');
}
const event = JSON.parse(req.body);
console.log('Webhook received:', event.type);
// Handle webhook events
switch (event.type) {
case 'transaction.approved':
handleTransactionApproved(event.data);
break;
case 'transaction.declined':
handleTransactionDeclined(event.data);
break;
case 'transaction.refunded':
handleTransactionRefunded(event.data);
break;
default:
console.log('Unhandled event type:', event.type);
}
res.sendStatus(200);
});
async function handleTransactionApproved(transaction) {
console.log('Transaction approved:', transaction.id);
// Update order status, send confirmation email, etc.
}
async function handleTransactionDeclined(transaction) {
console.log('Transaction declined:', transaction.id);
// Notify customer, update order status, etc.
}
async function handleTransactionRefunded(transaction) {
console.log('Transaction refunded:', transaction.id);
// Update order status, process refund, etc.
}
module.exports = router;
For more information, see Webhook Setup Guide.
Testing
Test Mode
Use sandbox credentials for testing:
PAYMENTS_AI_API_KEY=sk_sandbox_test_key
PAYMENTS_AI_API_URL=https://staging-api.payments.ai
Test Request
curl -X POST http://localhost:3000/api/payments/card \
-H "Content-Type: application/json" \
-d '{
"tokenId": "tok_test_123456",
"amount": 99.99,
"currency": "USD",
"customer": {
"email": "[email protected]",
"firstName": "John",
"lastName": "Doe",
"phoneNumber": "+1234567890",
"address": {
"address": "123 Test St",
"city": "Test City",
"region": "TC",
"country": "US",
"postalCode": "12345"
}
}
}'
Logging and Monitoring
Structured Logging
// lib/logger.js
const winston = require('winston');
const logger = winston.createLogger({
level: process.env.LOG_LEVEL || 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json()
),
transports: [
new winston.transports.File({ filename: 'error.log', level: 'error' }),
new winston.transports.File({ filename: 'combined.log' })
]
});
if (process.env.NODE_ENV !== 'production') {
logger.add(new winston.transports.Console({
format: winston.format.simple()
}));
}
module.exports = logger;
Usage
const logger = require('./lib/logger');
logger.info('Payment initiated', {
tokenId: 'tok_xxx',
amount: 99.99,
currency: 'USD'
});
logger.error('Payment failed', {
error: error.message,
tokenId: 'tok_xxx'
});
Best Practices
✅ Do
- Store API keys in environment variables
- Validate all incoming requests
- Implement rate limiting
- Log payment attempts and errors
- Use HTTPS in production
- Verify webhook signatures
- Handle errors gracefully
- Test with sandbox credentials first
❌ Don't
- Expose API keys in code or frontend
- Skip input validation
- Log sensitive data (full card numbers, etc.)
- Process payments without proper error handling
- Skip webhook verification
- Use production keys in development
- Ignore API errors
Next Steps
- 📖 Payment Flows Guide - Understand card vs wallet flows
- 🔐 Client-Side Integration - Frontend setup
- 🎯 Complete Example - Full implementation
- 📚 API Documentation - Complete API reference
- 🔔 Webhook Setup - Configure webhooks
Support
Need help? Check our: