Complete Working Example
This page provides a full, production-ready implementation of FramePay with Payments AI, including both frontend and backend code.
Overview
This example demonstrates:
- ✅ Card and wallet payment support
- ✅ Proper payment flow handling
- ✅ Error handling and validation
- ✅ CSP configuration
- ✅ Production-ready code structure
- ✅ Modern UI with responsive design
Project Structure
payment-integration/
├── frontend/
│ ├── index.html
│ ├── css/
│ │ └── style.css
│ └── js/
│ └── payment.js
├── backend/
│ ├── server.js
│ ├── config/
│ │ └── payments-ai.js
│ ├── lib/
│ │ └── payments-ai-client.js
│ ├── routes/
│ │ └── payments.js
│ ├── services/
│ │ ├── customer-service.js
│ │ ├── payment-instrument-service.js
│ │ └── transaction-service.js
│ └── middleware/
│ ├── validate-payment.js
│ └── error-handler.js
├── .env
└── package.json
Backend Implementation
1. Environment Configuration
# .env
PAYMENTS_AI_API_KEY=sk_sandbox_your_secret_key
PAYMENTS_AI_ORG_ID=org_your_organization_id
PAYMENTS_AI_WEBSITE_ID=web_your_website_id
PAYMENTS_AI_API_URL=https://staging-api.payments.ai
NODE_ENV=development
PORT=3000
2. Configuration
// backend/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.PAYMENTS_AI_API_URL || 'https://api.payments.ai',
};
if (!config.apiKey || !config.organizationId) {
throw new Error('Missing required Payments AI configuration');
}
module.exports = config;
3. API Client
// backend/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,
});
paymentsAI.interceptors.response.use(
(response) => response,
(error) => {
if (error.response) {
console.error('API Error:', {
status: error.response.status,
message: error.response.data,
});
}
return Promise.reject(error);
}
);
module.exports = paymentsAI;
4. Services
// backend/services/customer-service.js
const paymentsAI = require('../lib/payments-ai-client');
const config = require('../config/payments-ai');
async function createCustomer(customerData) {
const customerAddress = {
address: customerData.address.address,
city: customerData.address.city,
region: customerData.address.region,
country: customerData.address.country,
zip: customerData.address.postalCode || customerData.address.zip,
};
// Only include address2 if it's provided
if (customerData.address.address2) {
customerAddress.address2 = customerData.address.address2;
}
const response = await paymentsAI.post(
`/v1/organizations/${config.organizationId}/customers`,
{
email: customerData.email,
firstName: customerData.firstName,
lastName: customerData.lastName,
phoneNumber: customerData.phoneNumber,
primaryAddress: {
customerAddress,
},
isEnhancedDueDiligenceRequired: false,
}
);
return response.data;
}
module.exports = { createCustomer };
// backend/services/payment-instrument-service.js
const paymentsAI = require('../lib/payments-ai-client');
const config = require('../config/payments-ai');
async function createPaymentInstrument(customerId, tokenId) {
const response = await paymentsAI.post(
`/v1/organizations/${config.organizationId}/customers/${customerId}/payment-instruments`,
{ token: tokenId }
);
return response.data;
}
module.exports = { createPaymentInstrument };
// backend/services/transaction-service.js
const paymentsAI = require('../lib/payments-ai-client');
const config = require('../config/payments-ai');
async function createTransaction(transactionData) {
const response = await paymentsAI.post(
`/v1/organizations/${config.organizationId}/transactions`,
{
amount: transactionData.amount,
currency: transactionData.currency,
customerId: transactionData.customerId,
type: 'sale',
paymentInstruction: transactionData.paymentInstruction,
billingAddress: transactionData.billingAddress,
metadata: transactionData.metadata || {},
}
);
return response.data;
}
module.exports = { createTransaction };
5. Payment Routes
// backend/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');
// Card payment
router.post('/card', async (req, res) => {
const { tokenId, amount, currency, customer } = req.body;
try {
// Create customer
const customerRecord = await createCustomer(customer);
// Create payment instrument
const paymentInstrument = await createPaymentInstrument(
customerRecord.id,
tokenId
);
// 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',
},
});
if (transaction.result === 'approved') {
res.json({
success: true,
transactionId: transaction.id,
message: 'Payment successful',
});
} else {
res.status(400).json({
success: false,
message: `Payment ${transaction.result}`,
reason: transaction.declineReason,
});
}
} catch (error) {
console.error('Card payment error:', error);
res.status(500).json({
success: false,
message: error.message || 'Payment processing failed',
});
}
});
// Wallet payment
router.post('/wallet', async (req, res) => {
const { tokenId, paymentMethod, amount, currency, customer } = req.body;
try {
// Create customer with fallback values
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 || '',
},
});
// Create transaction with token directly
const transaction = await createTransaction({
amount,
currency,
customerId: customerRecord.id,
paymentInstruction: {
token: tokenId, // Use token directly
},
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
if (transaction.approvalUrl) {
return res.json({
success: true,
approvalUrl: transaction.approvalUrl,
transactionId: transaction.id,
});
}
if (transaction.result === 'approved') {
res.json({
success: true,
transactionId: transaction.id,
message: 'Payment successful',
});
} else {
res.status(400).json({
success: false,
message: `Payment ${transaction.result}`,
reason: transaction.declineReason,
});
}
} catch (error) {
console.error('Wallet payment error:', error);
res.status(500).json({
success: false,
message: error.message || 'Payment processing failed',
});
}
});
module.exports = router;
6. Server
// backend/server.js
require('dotenv').config();
const express = require('express');
const cors = require('cors');
const paymentRoutes = require('./routes/payments');
const app = express();
const PORT = process.env.PORT || 3000;
// Middleware
app.use(
cors({
origin:
process.env.NODE_ENV === 'production'
? ['https://yourdomain.com']
: ['http://localhost:5173', 'http://localhost:3000'],
credentials: true,
})
);
app.use(express.json());
app.use(express.static('frontend'));
// Routes
app.use('/api/payments', paymentRoutes);
// Error handler
app.use((err, req, res, next) => {
console.error(err);
res.status(500).json({
success: false,
message: 'Internal server error',
});
});
app.listen(PORT, () => {
console.log(`Server running on http://localhost:${PORT}`);
});
7. Package.json
{
"name": "framepay-payments-ai-example",
"version": "1.0.0",
"description": "Complete FramePay integration with Payments AI",
"main": "backend/server.js",
"scripts": {
"start": "node backend/server.js",
"dev": "nodemon backend/server.js"
},
"dependencies": {
"axios": "^1.6.0",
"cors": "^2.8.5",
"dotenv": "^16.3.1",
"express": "^4.18.2"
},
"devDependencies": {
"nodemon": "^3.0.1"
}
}