Payment Forms with QR Codes
Learn how to integrate QR code generation into your payment forms. Customers scan the QR code with their PromptPay-enabled banking app to complete payments.
Overview
QR code payments work differently from traditional card forms. Instead of collecting card details, you generate a QR code that customers scan with their banking app. The payment is processed automatically through the PromptPay network.
Client Information:
Client name, bank name, and bank account number are optional. If you have this information, you can provide it for better tracking. If not provided, our system will automatically handle and retrieve it when the payment is processed. For merchant operations, use the metadata field to include client IDs, profiles, or merchant account information.
Generate QR Code
Your backend calls the QR code API with amount and reference
Display QR Code
Show the QR code to the customer on your payment page
Customer Scans
Customer opens their banking app and scans the QR code
Payment Processing
Payment is processed automatically through PromptPay
Receive Notification
You receive a webhook notification when payment completes
Backend Integration
Generate QR codes on your backend server to keep your API keys secure.
// Backend: Generate QR Code
const response = await fetch('https://api.altafinex.com/v1/qr-codes', {
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_SECRET_KEY',
'Content-Type': 'application/json',
},
body: JSON.stringify({
amount: 10000,
currency: 'THB',
reference: 'ORDER-12345',
// Optional: Client information (system will handle if not provided)
client_name: 'John Doe',
client_bank_name: 'Bangkok Bank',
client_bank_account: '1234567890',
// Optional: Metadata for merchant/client tracking
metadata: {
client_id: 'CLIENT-12345',
client_profile: 'premium',
merchant_account_id: 'MERCHANT-67890',
},
expiry_minutes: 15,
}),
});
const { qr_code_id, qr_image_url, qr_data, payment_id } = await response.json();
// Return QR code data to frontend
return {
qr_code_id,
qr_image_url,
qr_data,
payment_id,
};// Backend API Route: /api/generate-qr
import { NextApiRequest, NextApiResponse } from 'next';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method !== 'POST') {
return res.status(405).json({ error: 'Method not allowed' });
}
const { amount, currency, reference } = req.body;
try {
// Generate QR code via Altafinex API
const response = await fetch('https://api.altafinex.com/v1/qr-codes', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.ALTAFINEX_SECRET_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
amount,
currency: currency || 'THB',
reference,
// Optional: Client information (system will handle if not provided)
...(req.body.client_name && { client_name: req.body.client_name }),
...(req.body.client_bank_name && { client_bank_name: req.body.client_bank_name }),
...(req.body.client_bank_account && { client_bank_account: req.body.client_bank_account }),
// Optional: Metadata
metadata: req.body.metadata || {},
expiry_minutes: 15,
}),
});
if (!response.ok) {
throw new Error('Failed to generate QR code');
}
const qrData = await response.json();
// Store payment_id in your database for status tracking
// await savePayment(qrData.payment_id, { amount, reference });
return res.status(200).json(qrData);
} catch (error) {
console.error('QR generation error:', error);
return res.status(500).json({ error: 'Failed to generate QR code' });
}
}Frontend Integration
Display QR codes on your frontend and poll for payment status updates.
// Frontend: Display QR Code
import { useState, useEffect } from 'react';
import QRCode from 'qrcode.react'; // or any QR code library
function PaymentPage() {
const [qrData, setQrData] = useState(null);
const [paymentStatus, setPaymentStatus] = useState('pending');
useEffect(() => {
// Generate QR code when component mounts
fetch('/api/generate-qr', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
amount: 10000,
currency: 'THB',
reference: 'ORDER-12345',
// Optional: Client information (system will handle if not provided)
client_name: 'John Doe',
client_bank_name: 'Bangkok Bank',
client_bank_account: '1234567890',
// Optional: Metadata
metadata: {
client_id: 'CLIENT-12345',
merchant_account_id: 'MERCHANT-67890',
},
}),
})
.then(res => res.json())
.then(data => {
setQrData(data);
// Start polling for payment status
pollPaymentStatus(data.payment_id);
});
}, []);
const pollPaymentStatus = async (paymentId) => {
const interval = setInterval(async () => {
const res = await fetch(`/api/payment-status/${paymentId}`);
const { status } = await res.json();
if (status === 'succeeded' || status === 'failed') {
clearInterval(interval);
setPaymentStatus(status);
}
}, 3000); // Poll every 3 seconds
};
if (!qrData) return <div>Loading QR code...</div>;
return (
<div className="payment-container">
<h2>Scan to Pay</h2>
<div className="qr-code">
{/* Option 1: Use pre-generated image */}
<img src={qrData.qr_image_url} alt="Payment QR Code" />
{/* Option 2: Generate QR code client-side */}
{/* <QRCode value={qrData.qr_data} size={256} /> */}
</div>
<p>Amount: ฿100.00</p>
<p>Status: {paymentStatus}</p>
<p className="expiry">QR code expires in 15 minutes</p>
</div>
);
}QR Code Libraries:
qrcode.react- React component for generating QR codesqrcode- Node.js library for QR code generation- Use
qr_image_urlfrom API response for pre-generated images
Webhook Integration
Receive real-time payment notifications via webhooks instead of polling.
// Backend: Webhook Handler
import { NextApiRequest, NextApiResponse } from 'next';
import crypto from 'crypto';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method !== 'POST') {
return res.status(405).json({ error: 'Method not allowed' });
}
// Verify webhook signature
const signature = req.headers['x-altafinex-signature'];
const payload = JSON.stringify(req.body);
const expectedSignature = crypto
.createHmac('sha256', process.env.ALTAFINEX_WEBHOOK_SECRET)
.update(payload)
.digest('hex');
if (signature !== expectedSignature) {
return res.status(401).json({ error: 'Invalid signature' });
}
const { event, data } = req.body;
if (event === 'payment.succeeded') {
const { payment_id, amount, reference } = data;
// Update payment status in your database
await updatePaymentStatus(payment_id, 'succeeded');
// Send notification to customer
await sendPaymentConfirmation(reference, amount);
} else if (event === 'payment.failed') {
const { payment_id, reason } = data;
await updatePaymentStatus(payment_id, 'failed');
}
return res.status(200).json({ received: true });
}Best Practice:
Always verify webhook signatures to ensure requests are from Altafinex. Use webhooks as the primary method for payment status updates, with polling as a backup.
Best Practices
- •QR Code Size: Display QR codes at least 200x200 pixels for easy scanning
- •Expiry Handling: Monitor QR code expiry and regenerate if needed before customer scans
- •Status Updates: Use webhooks for real-time updates, but also poll payment status as a backup mechanism
- •Error Handling: Handle expired QR codes gracefully and provide option to generate a new one
- •Client Information: Client name, bank name, and account number are optional. If you have this information, provide it for better tracking. If not, our system will handle it automatically.
- •Metadata for Tracking: Use metadata field to store client IDs, merchant account IDs, or any other business-specific information for tracking and reporting.
- •User Experience: Show clear instructions for customers on how to scan the QR code with their banking app