Skip to main content

Overview

The payment flow is simple:
  1. Customer creates order → Save as PENDING
  2. Create payment link → Get paymentRef
  3. Save paymentRef with order (important!)
  4. Redirect customer to payment page
  5. Customer pays and returns
  6. Check payment status and update order

Complete Implementation

Step 1: Order Creation

When a customer places an order, save it with a PENDING 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 PENDING
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);

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 minutes

Next Steps

Continue to Status Polling

Learn how to automate status checking with cron jobs