Complete end-to-end examples showing a full payment flow: authenticate, initiate payment, redirect customer, and verify on return.
<?php
// PaymentController.php (Laravel)
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;
class PaymentController extends Controller
{
private string $baseUrl;
private string $clientId;
private string $secretKey;
private Client $http;
public function __construct()
{
$this->baseUrl = config('services.quatapay.base_url');
$this->clientId = config('services.quatapay.client_id');
$this->secretKey = config('services.quatapay.secret_key');
$this->http = new Client(['timeout' => 30]);
}
/**
* Step 1: Get access token (cache it for 50 minutes)
*/
private function getAccessToken(): string
{
return cache()->remember('quatapay_token', now()->addMinutes(50), function () {
$res = $this->http->post($this->baseUrl . '/authentication', [
'json' => [
'client_id' => $this->clientId,
'secret_id' => $this->secretKey,
],
]);
$data = json_decode($res->getBody(), true);
return $data['data']['token'];
});
}
/**
* Step 2: Initiate payment — redirect customer to payment page
*/
public function checkout(Request $request)
{
$orderId = 'ORDER-' . uniqid();
$token = $this->getAccessToken();
try {
$res = $this->http->post($this->baseUrl . '/payment/create', [
'json' => [
'amount' => number_format($request->amount, 2, '.', ''),
'currency' => 'XAF',
'return_url' => route('payment.success'),
'cancel_url' => route('payment.cancel'),
'custom' => $orderId,
],
'headers' => [
'Authorization' => 'Bearer ' . $token,
'Accept' => 'application/json',
],
]);
$data = json_decode($res->getBody(), true);
return redirect()->away($data['data']['payment_url']);
} catch (RequestException $e) {
return back()->with('error', 'Payment initiation failed. Please try again.');
}
}
/**
* Step 3: Verify payment after customer returns
*/
public function success(Request $request)
{
$payToken = $request->query('token');
$token = $this->getAccessToken();
try {
$res = $this->http->get($this->baseUrl . '/payment/success/' . $payToken, [
'headers' => [
'Authorization' => 'Bearer ' . $token,
'Accept' => 'application/json',
],
]);
$data = json_decode($res->getBody(), true);
if ($data['type'] === 'success') {
$trxId = $data['data']['trx_id'];
$orderId = $data['data']['custom'];
// Mark order paid in your DB
return view('payment.success', compact('trxId', 'orderId'));
}
} catch (RequestException $e) {
// Log error
}
return view('payment.failed');
}
}
// routes/payment.js (Node.js + Express)
const express = require('express');
const router = express.Router();
const BASE_URL = process.env.QUATAPAY_BASE_URL;
const CLIENT_ID = process.env.QUATAPAY_CLIENT_ID;
const SECRET_KEY = process.env.QUATAPAY_SECRET_KEY;
let cachedToken = null;
let tokenExpiry = 0;
async function getAccessToken() {
if (cachedToken && Date.now() < tokenExpiry) return cachedToken;
const res = await fetch(`${BASE_URL}/authentication`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ client_id: CLIENT_ID, secret_id: SECRET_KEY }),
});
const data = await res.json();
cachedToken = data.data.token;
tokenExpiry = Date.now() + 50 * 60 * 1000; // 50 minutes
return cachedToken;
}
// Step 2: Initiate payment
router.post('/checkout', async (req, res) => {
const token = await getAccessToken();
const orderId = 'ORDER-' + Date.now();
const response = await fetch(`${BASE_URL}/payment/create`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Accept': 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
amount: parseFloat(req.body.amount).toFixed(2),
currency: 'XAF',
return_url: `${process.env.APP_URL}/payment/success`,
cancel_url: `${process.env.APP_URL}/payment/cancel`,
custom: orderId,
}),
});
const data = await response.json();
res.redirect(data.data.payment_url);
});
// Step 3: Verify payment
router.get('/success', async (req, res) => {
const { token: payToken } = req.query;
const token = await getAccessToken();
const response = await fetch(`${BASE_URL}/payment/success/${payToken}`, {
headers: { 'Authorization': `Bearer ${token}`, 'Accept': 'application/json' },
});
const data = await response.json();
if (data.type === 'success') {
const { trx_id, custom } = data.data;
// Mark order paid in your DB
return res.render('payment-success', { trxId: trx_id, orderId: custom });
}
res.render('payment-failed');
});
module.exports = router;
// pages/api/payment/initiate.js (Next.js API route)
let cachedToken = null;
let tokenExpiry = 0;
async function getAccessToken() {
if (cachedToken && Date.now() < tokenExpiry) return cachedToken;
const res = await fetch(`${process.env.QUATAPAY_BASE_URL}/authentication`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
client_id: process.env.QUATAPAY_CLIENT_ID,
secret_id: process.env.QUATAPAY_SECRET_KEY,
}),
});
const data = await res.json();
cachedToken = data.data.token;
tokenExpiry = Date.now() + 50 * 60 * 1000;
return cachedToken;
}
// POST /api/payment/initiate
export default async function initiateHandler(req, res) {
if (req.method !== 'POST') return res.status(405).end();
const token = await getAccessToken();
const orderId = `ORDER-${Date.now()}`;
const response = await fetch(`${process.env.QUATAPAY_BASE_URL}/payment/create`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Accept': 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
amount: parseFloat(req.body.amount).toFixed(2),
currency: 'XAF',
return_url: `${process.env.NEXT_PUBLIC_APP_URL}/payment/success`,
cancel_url: `${process.env.NEXT_PUBLIC_APP_URL}/payment/cancel`,
custom: orderId,
}),
});
const data = await response.json();
return res.json({ payment_url: data.data.payment_url, orderId });
}
// -------------------------------------------------------
// pages/payment/success.jsx (client page)
// -------------------------------------------------------
// export default function PaymentSuccess({ query }) {
// // token comes from query string after redirect
// // Call /api/payment/verify?token=xxx from useEffect
// }
// pages/api/payment/verify.js
export async function verifyHandler(req, res) {
const { token: payToken } = req.query;
const token = await getAccessToken();
const response = await fetch(
`${process.env.QUATAPAY_BASE_URL}/payment/success/${payToken}`,
{ headers: { 'Authorization': `Bearer ${token}`, 'Accept': 'application/json' } }
);
const data = await response.json();
if (data.type === 'success') {
// Mark order paid in your DB
return res.json({ success: true, trxId: data.data.trx_id, orderId: data.data.custom });
}
return res.json({ success: false });
}
# views.py (Django)
import requests
from django.shortcuts import redirect, render
from django.core.cache import cache
from django.conf import settings
import uuid
BASE_URL = settings.QUATAPAY_BASE_URL
CLIENT_ID = settings.QUATAPAY_CLIENT_ID
SECRET_KEY = settings.QUATAPAY_SECRET_KEY
def get_access_token():
token = cache.get("quatapay_token")
if token:
return token
response = requests.post(f"{BASE_URL}/authentication", json={
"client_id": CLIENT_ID,
"secret_id": SECRET_KEY,
})
data = response.json()
token = data["data"]["token"]
cache.set("quatapay_token", token, timeout=3000) # 50 minutes
return token
def checkout(request):
if request.method != "POST":
return redirect("home")
amount = request.POST.get("amount")
order_id = f"ORDER-{uuid.uuid4().hex[:12].upper()}"
token = get_access_token()
response = requests.post(f"{BASE_URL}/payment/create",
json={
"amount": f"{float(amount):.2f}",
"currency": "XAF",
"return_url": request.build_absolute_uri("/payment/success/"),
"cancel_url": request.build_absolute_uri("/payment/cancel/"),
"custom": order_id,
},
headers={
"Authorization": f"Bearer {token}",
"Accept": "application/json",
},
timeout=30,
)
data = response.json()
if data.get("type") == "success":
return redirect(data["data"]["payment_url"])
return render(request, "payment_error.html")
def payment_success(request):
pay_token = request.GET.get("token")
token = get_access_token()
response = requests.get(f"{BASE_URL}/payment/success/{pay_token}",
headers={
"Authorization": f"Bearer {token}",
"Accept": "application/json",
},
timeout=30,
)
data = response.json()
if data.get("type") == "success":
trx_id = data["data"]["trx_id"]
order_id = data["data"]["custom"]
# Mark order paid in your DB
return render(request, "payment_success.html", {
"trx_id": trx_id, "order_id": order_id
})
return render(request, "payment_failed.html")
# .env QUATAPAY_BASE_URL=https://quatapay.com/pay/api/v1 QUATAPAY_CLIENT_ID=your_client_id_here QUATAPAY_SECRET_KEY=your_secret_key_here