Vue d’ensemble
Le flux de paiement est simple :- Le client crée une commande → Sauvegardez comme
PENDING - Créez un lien de paiement → Obtenez
paymentRef - Sauvegardez
paymentRefavec la commande (important !) - Redirigez le client vers la page de paiement
- Le client paie et revient
- Vérifiez le statut du paiement et mettez à jour la commande
Implémentation complète
Étape 1 : Création de la commande
Lorsqu’un client passe une commande, sauvegardez-la avec le statutPENDING :
async function createOrder(customerData, items, total) {
// Générer un ID de commande unique
const orderId = generateUniqueId(); // ex. "ORD-2025-001234"
// Sauvegarder la commande en base de données
const order = await db.orders.create({
id: orderId,
customerId: customerData.id,
items: items,
totalAmount: total,
status: "PENDING", // Important : commencer par PENDING
paymentRef: null, // Sera défini après la création du lien de paiement
createdAt: new Date(),
updatedAt: new Date(),
});
return order;
}
Critique : Chaque commande doit avoir un ID unique et doit commencer avec le statut
PENDINGÉtape 2 : Créer un lien de paiement
const express = require("express");
const fetch = require("node-fetch");
const app = express();
app.use(express.json());
// 1. Créer la commande et rediriger vers le paiement
app.post("/checkout", async (req, res) => {
const { items, total, customerId } = req.body;
try {
// Créer la commande en base de données
const orderId = generateOrderId();
await db.orders.create({
id: orderId,
customerId: customerId,
items: items,
total: total,
status: "PENDING",
});
// Créer le lien de paiement
const response = await fetch(
"https://api.oneclickdz.com/v3/ocpay/createLink",
{
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Access-Token": process.env.ONECLICK_API_KEY,
},
body: JSON.stringify({
productInfo: {
title: `Order ${orderId}`,
amount: total,
},
redirectUrl: `https://yoursite.com/orders/${orderId}`,
}),
}
);
const data = await response.json();
// IMPORTANT : Sauvegarder paymentRef avec la commande
await db.orders.update(orderId, {
paymentRef: data.data.paymentRef,
});
// Rediriger vers la page de paiement
res.redirect(data.data.paymentUrl);
} catch (error) {
console.error("Checkout error:", error);
res.status(500).json({ error: "Checkout failed" });
}
});
// 2. Page de statut de commande
app.get("/orders/:orderId", async (req, res) => {
const { orderId } = req.params;
try {
const order = await db.orders.findOne({ id: orderId });
if (!order) {
return res.status(404).send("Order not found");
}
// Check payment if still pending
if (order.status === "PENDING" && order.paymentRef) {
const statusResponse = await fetch(
`https://api.oneclickdz.com/v3/ocpay/checkPayment/${order.paymentRef}`,
{
headers: { "X-Access-Token": process.env.ONECLICK_API_KEY },
}
);
const statusData = await statusResponse.json();
if (statusData.data.status === "CONFIRMED") {
await db.orders.update(orderId, { status: "PAID" });
order.status = "PAID";
} else if (statusData.data.status === "FAILED") {
await db.orders.update(orderId, { status: "FAILED" });
order.status = "FAILED";
}
}
res.render("order-status", { order });
} catch (error) {
console.error("Error:", error);
res.status(500).send("Error loading order");
}
});
// 3. Endpoint de vérification manuelle du paiement
app.post("/orders/:orderId/check", async (req, res) => {
const { orderId } = req.params;
try {
const order = await db.orders.findOne({ id: orderId });
if (!order || !order.paymentRef) {
return res.status(404).json({ error: "Order not found" });
}
const response = await fetch(
`https://api.oneclickdz.com/v3/ocpay/checkPayment/${order.paymentRef}`,
{
headers: { "X-Access-Token": process.env.ONECLICK_API_KEY },
}
);
const data = await response.json();
// Mettre à jour le statut de la commande
if (data.data.status === "CONFIRMED") {
await db.orders.update(orderId, { status: "PAID" });
} else if (data.data.status === "FAILED") {
await db.orders.update(orderId, { status: "FAILED" });
}
res.json({ status: data.data.status });
} catch (error) {
console.error("Check error:", error);
res.status(500).json({ error: "Check failed" });
}
});
function generateOrderId() {
return `ORD-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
}
app.listen(3000);
from flask import Flask, request, redirect, render_template, jsonify
import requests
import os
import time
import random
import string
app = Flask(__name__)
# 1. Créer la commande et rediriger vers le paiement
@app.route('/checkout', methods=['POST'])
def checkout():
data = request.json
items = data['items']
total = data['total']
customer_id = data['customerId']
try:
order_id = generate_order_id()
db.orders.create({
'id': order_id,
'customerId': customer_id,
'items': items,
'total': total,
'status': 'PENDING'
})
response = requests.post(
'https://api.oneclickdz.com/v3/ocpay/createLink',
headers={
'Content-Type': 'application/json',
'X-Access-Token': os.getenv('ONECLICK_API_KEY')
},
json={
'productInfo': {
'title': f'Order {order_id}',
'amount': total
},
'redirectUrl': f'https://yoursite.com/orders/{order_id}'
}
)
data = response.json()
# IMPORTANT : Sauvegarder paymentRef avec la commande
db.orders.update(order_id, {
'paymentRef': data['data']['paymentRef']
})
return redirect(data['data']['paymentUrl'])
except Exception as e:
print(f'Checkout error: {e}')
return jsonify({'error': 'Checkout failed'}), 500
# 2. Page de statut de commande
@app.route('/orders/<order_id>')
def order_status(order_id):
try:
order = db.orders.find_one({'id': order_id})
if not order:
return 'Order not found', 404
if order['status'] == 'PENDING' and order.get('paymentRef'):
status_response = requests.get(
f"https://api.oneclickdz.com/v3/ocpay/checkPayment/{order['paymentRef']}",
headers={'X-Access-Token': os.getenv('ONECLICK_API_KEY')}
)
status_data = status_response.json()
if status_data['data']['status'] == 'CONFIRMED':
db.orders.update(order_id, {'status': 'PAID'})
order['status'] = 'PAID'
elif status_data['data']['status'] == 'FAILED':
db.orders.update(order_id, {'status': 'FAILED'})
order['status'] = 'FAILED'
return render_template('order_status.html', order=order)
except Exception as e:
print(f'Error: {e}')
return 'Error loading order', 500
def generate_order_id():
timestamp = int(time.time())
random_str = ''.join(random.choices(string.ascii_lowercase + string.digits, k=9))
return f'ORD-{timestamp}-{random_str}'
if __name__ == '__main__':
app.run(port=3000)
<?php
// checkout.php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$data = json_decode(file_get_contents('php://input'), true);
$items = $data['items'];
$total = $data['total'];
$customerId = $data['customerId'];
try {
$orderId = generateOrderId();
$db->orders->create([
'id' => $orderId,
'customerId' => $customerId,
'items' => $items,
'total' => $total,
'status' => 'PENDING'
]);
$ch = curl_init('https://api.oneclickdz.com/v3/ocpay/createLink');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
'X-Access-Token: ' . getenv('ONECLICK_API_KEY')
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
'productInfo' => [
'title' => "Order $orderId",
'amount' => $total
],
'redirectUrl' => "https://yoursite.com/orders.php?id=$orderId"
]));
$response = curl_exec($ch);
$data = json_decode($response, true);
// IMPORTANT : Sauvegarder paymentRef avec la commande
$db->orders->update($orderId, [
'paymentRef' => $data['data']['paymentRef']
]);
header('Location: ' . $data['data']['paymentUrl']);
exit;
} catch (Exception $e) {
error_log('Checkout error: ' . $e->getMessage());
http_response_code(500);
echo json_encode(['error' => 'Checkout failed']);
}
}
function generateOrderId() {
$timestamp = time();
$random = bin2hex(random_bytes(5));
return "ORD-{$timestamp}-{$random}";
}
?>
Frontend : Page de statut de commande
Page HTML simple avec bouton de vérification manuelle :<!DOCTYPE html>
<html>
<head>
<title>Statut de la commande</title>
<style>
.status-pending { color: #f59e0b; }
.status-paid { color: #10b981; }
.status-failed { color: #ef4444; }
</style>
</head>
<body>
<h1>Commande #<span id="orderId"></span></h1>
<h2>Statut : <span id="status" class="status-pending">PENDING</span></h2>
<button id="checkBtn" onclick="checkPayment()">Check Payment Status</button>
<p id="message"></p>
<script>
const orderId = window.location.pathname.split("/").pop();
document.getElementById("orderId").textContent = orderId;
async function checkPayment() {
const btn = document.getElementById("checkBtn");
const msg = document.getElementById("message");
btn.disabled = true;
msg.textContent = "Vérification...";
try {
const response = await fetch(`/orders/${orderId}/check`, {
method: "POST",
});
const data = await response.json();
const statusEl = document.getElementById("status");
statusEl.textContent = data.status;
statusEl.className = `status-${data.status.toLowerCase()}`;
if (data.status === "CONFIRMED") {
msg.textContent = "✅ Paiement confirmé ! Actualisation...";
setTimeout(() => location.reload(), 2000);
} else if (data.status === "FAILED") {
msg.textContent = "❌ Paiement échoué";
} else {
msg.textContent = "⏳ Paiement toujours en attente";
}
} catch (error) {
msg.textContent = "Erreur lors de la vérification";
} finally {
btn.disabled = false;
}
}
// Vérification automatique au chargement de la page
if (document.getElementById("status").textContent === "PENDING") {
checkPayment();
}
</script>
</body>
</html>
Schéma de base de données
Schéma simple pour les commandes :CREATE TABLE orders (
id VARCHAR(50) PRIMARY KEY,
customer_id VARCHAR(50) NOT NULL,
total INTEGER NOT NULL,
status VARCHAR(20) NOT NULL DEFAULT 'PENDING',
payment_ref VARCHAR(50),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_payment_ref (payment_ref),
INDEX idx_status (status)
);
Points clés
Important : - Sauvegardez toujours
paymentRef avec votre commande - Vérifiez le statut du paiement au retour du client - Ne traitez les commandes qu’avec le statut CONFIRMED - Les liens de paiement expirent après 20 minutesProchaines étapes
Continuer vers le suivi du statut
Apprenez à automatiser la vérification du statut avec des tâches cron

