Overview
The payment flow is simple:- Customer creates order → Save as
PENDING - Create payment link → Get
paymentRef - Save
paymentRefwith order (important!) - Redirect customer to payment page
- Customer pays and returns
- Check payment status and update order
Complete Implementation
Step 1: Order Creation
When a customer places an order, save it with aPENDING status:
async function createOrder(customerData, items, total) {
// Generate unique order ID
const orderId = generateUniqueId(); // e.g., "ORD-2025-001234"
// Save order to database
const order = await db.orders.create({
id: orderId,
customerId: customerData.id,
items: items,
totalAmount: total,
status: "PENDING", // Important: Start as PENDING
paymentRef: null, // Will be set after creating payment link
createdAt: new Date(),
updatedAt: new Date(),
});
return order;
}
Critical: Every order must have a unique ID and must start with status
PENDINGStep 2: Create Payment Link
const express = require("express");
const fetch = require("node-fetch");
const app = express();
app.use(express.json());
// 1. Create order and redirect to payment
app.post("/checkout", async (req, res) => {
const { items, total, customerId } = req.body;
try {
// Create order in database
const orderId = generateOrderId();
await db.orders.create({
id: orderId,
customerId: customerId,
items: items,
total: total,
status: "PENDING",
});
// Create payment link
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: Save paymentRef with order
await db.orders.update(orderId, {
paymentRef: data.data.paymentRef,
});
// Redirect to payment page
res.redirect(data.data.paymentUrl);
} catch (error) {
console.error("Checkout error:", error);
res.status(500).json({ error: "Checkout failed" });
}
});
// 2. Order status page
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. Manual payment check endpoint
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();
// Update order status
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. Create order and redirect to payment
@app.route('/checkout', methods=['POST'])
def checkout():
data = request.json
items = data['items']
total = data['total']
customer_id = data['customerId']
try:
# Create order in database
order_id = generate_order_id()
db.orders.create({
'id': order_id,
'customerId': customer_id,
'items': items,
'total': total,
'status': 'PENDING'
})
# Create payment link
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: Save paymentRef with order
db.orders.update(order_id, {
'paymentRef': data['data']['paymentRef']
})
# Redirect to payment page
return redirect(data['data']['paymentUrl'])
except Exception as e:
print(f'Checkout error: {e}')
return jsonify({'error': 'Checkout failed'}), 500
# 2. Order status page
@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
# Check payment if still pending
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
# 3. Manual payment check endpoint
@app.route('/orders/<order_id>/check', methods=['POST'])
def check_order(order_id):
try:
order = db.orders.find_one({'id': order_id})
if not order or not order.get('paymentRef'):
return jsonify({'error': 'Order not found'}), 404
response = requests.get(
f"https://api.oneclickdz.com/v3/ocpay/checkPayment/{order['paymentRef']}",
headers={'X-Access-Token': os.getenv('ONECLICK_API_KEY')}
)
data = response.json()
# Update order status
if data['data']['status'] == 'CONFIRMED':
db.orders.update(order_id, {'status': 'PAID'})
elif data['data']['status'] == 'FAILED':
db.orders.update(order_id, {'status': 'FAILED'})
return jsonify({'status': data['data']['status']})
except Exception as e:
print(f'Check error: {e}')
return jsonify({'error': 'Check failed'}), 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 {
// Create order in database
$orderId = generateOrderId();
$db->orders->create([
'id' => $orderId,
'customerId' => $customerId,
'items' => $items,
'total' => $total,
'status' => 'PENDING'
]);
// Create payment link
$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: Save paymentRef with order
$db->orders->update($orderId, [
'paymentRef' => $data['data']['paymentRef']
]);
// Redirect to payment page
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']);
}
}
// order-status.php
$orderId = $_GET['id'] ?? null;
if (!$orderId) {
http_response_code(404);
echo 'Order not found';
exit;
}
try {
$order = $db->orders->findOne(['id' => $orderId]);
if (!$order) {
http_response_code(404);
echo 'Order not found';
exit;
}
// Check payment if still pending
if ($order['status'] === 'PENDING' && !empty($order['paymentRef'])) {
$ch = curl_init(
"https://api.oneclickdz.com/v3/ocpay/checkPayment/{$order['paymentRef']}"
);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'X-Access-Token: ' . getenv('ONECLICK_API_KEY')
]);
$response = curl_exec($ch);
$statusData = json_decode($response, true);
if ($statusData['data']['status'] === 'CONFIRMED') {
$db->orders->update($orderId, ['status' => 'PAID']);
$order['status'] = 'PAID';
} elseif ($statusData['data']['status'] === 'FAILED') {
$db->orders->update($orderId, ['status' => 'FAILED']);
$order['status'] = 'FAILED';
}
}
// Display order status page
include 'templates/order_status.php';
} catch (Exception $e) {
error_log('Error: ' . $e->getMessage());
http_response_code(500);
echo 'Error loading order';
}
// check-payment.php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$orderId = $_GET['id'] ?? null;
try {
$order = $db->orders->findOne(['id' => $orderId]);
if (!$order || empty($order['paymentRef'])) {
http_response_code(404);
echo json_encode(['error' => 'Order not found']);
exit;
}
$ch = curl_init(
"https://api.oneclickdz.com/v3/ocpay/checkPayment/{$order['paymentRef']}"
);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'X-Access-Token: ' . getenv('ONECLICK_API_KEY')
]);
$response = curl_exec($ch);
$data = json_decode($response, true);
// Update order status
if ($data['data']['status'] === 'CONFIRMED') {
$db->orders->update($orderId, ['status' => 'PAID']);
} elseif ($data['data']['status'] === 'FAILED') {
$db->orders->update($orderId, ['status' => 'FAILED']);
}
echo json_encode(['status' => $data['data']['status']]);
} catch (Exception $e) {
error_log('Check error: ' . $e->getMessage());
http_response_code(500);
echo json_encode(['error' => 'Check failed']);
}
}
function generateOrderId() {
$timestamp = time();
$random = bin2hex(random_bytes(5));
return "ORD-{$timestamp}-{$random}";
}
?>
Frontend: Order Status Page
Simple HTML page with manual check button:<!DOCTYPE html>
<html>
<head>
<title>Order Status</title>
<style>
.status-pending {
color: #f59e0b;
}
.status-paid {
color: #10b981;
}
.status-failed {
color: #ef4444;
}
</style>
</head>
<body>
<h1>Order #<span id="orderId"></span></h1>
<h2>Status: <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 = "Checking...";
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 = "✅ Payment confirmed! Refreshing...";
setTimeout(() => location.reload(), 2000);
} else if (data.status === "FAILED") {
msg.textContent = "❌ Payment failed";
} else {
msg.textContent = "⏳ Payment still pending";
}
} catch (error) {
msg.textContent = "Error checking payment";
} finally {
btn.disabled = false;
}
}
// Auto-check when page loads
if (document.getElementById("status").textContent === "PENDING") {
checkPayment();
}
</script>
</body>
</html>
Database Schema
Simple schema for orders: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)
);
Key Points
Important: - Always save
paymentRef with your order - Check payment
status when customer returns - Only fulfill orders with status CONFIRMED -
Payment links expire after 20 minutesNext Steps
Continue to Status Polling
Learn how to automate status checking with cron jobs

