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.

How QR Code Payments Work
1

Generate QR Code

Your backend calls the QR code API with amount and reference

2

Display QR Code

Show the QR code to the customer on your payment page

3

Customer Scans

Customer opens their banking app and scans the QR code

4

Payment Processing

Payment is processed automatically through PromptPay

5

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.

Generate QR Code (Backend)
Example using Node.js/Next.js
// 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
Create an API endpoint to generate QR codes
// 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.

Display QR Code (Frontend)
React example with status polling
// 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 codes
  • qrcode - Node.js library for QR code generation
  • Use qr_image_url from API response for pre-generated images

Webhook Integration

Receive real-time payment notifications via webhooks instead of polling.

Webhook Handler
Handle payment status updates via webhooks
// 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 Payment 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

Related Documentation