Skip to main content

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

Support

Need help? Check our: