> ## Documentation Index
> Fetch the complete documentation index at: https://docs.oneclickdz.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Étape 2 : Flux de paiement

> Guide simple pour implémenter les paiements dans votre application

## Vue d'ensemble

Le flux de paiement est simple :

1. Le client crée une commande → Sauvegardez comme `PENDING`
2. Créez un lien de paiement → Obtenez `paymentRef`
3. **Sauvegardez `paymentRef` avec la commande** (important !)
4. Redirigez le client vers la page de paiement
5. Le client paie et revient
6. 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 statut `PENDING` :

```javascript theme={null}
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;
}
```

<Warning>
  **Critique** : Chaque commande doit avoir un ID unique et doit commencer avec le statut `PENDING`
</Warning>

### Étape 2 : Créer un lien de paiement

<CodeGroup>
  ```javascript Node.js / Express theme={null}
  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);
  ```

  ```python Python / Flask theme={null}
  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 PHP theme={null}
  <?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}";
  }
  ?>
  ```
</CodeGroup>

## Frontend : Page de statut de commande

Page HTML simple avec bouton de vérification manuelle :

```html theme={null}
<!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 :

```sql theme={null}
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

<Warning>
  **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 minutes
</Warning>

## Prochaines étapes

<Card title="Continuer vers le suivi du statut" icon="arrow-right" href="/fr/ocpay-guides/3-status-polling">
  Apprenez à automatiser la vérification du statut avec des tâches cron
</Card>
