Intégration GoalPay

Acceptez les paiements en Ariary (MGA) en quelques minutes.

Prérequis

  • Compte marchand GoalPay actif
  • Token d’accès (commence toujours par TGP_)
  • Un serveur capable d’envoyer des requêtes HTTP et de recevoir des webhooks

1. Créer une commande de paiement

Requête à envoyer depuis votre serveur (jamais depuis le navigateur) :

POST https://api.goalpay.pro/api/payement/service
{
  "description": "Commande #20201320",
  "access": "TGP_XXXXXXXXXXXXXXXXX",
  "reference": "CMD-20201320",
  "amount": 125000,
  "currency": "Ar",
  "metadata": [
    {
      "label": "T-shirt Premium",
      "unit_price": 75000,
      "quantity": 1,
    },
    {
      "label": "Frais de livraison",
      "unit_price": 50000,
      "quantity": 1
    }
  ]
}
ChampObligatoireDescription
descriptionOuiTexte affiché au client
accessOuiVotre token GoalPay
referenceOuiVotre référence unique (ex: CMD-12345)
amountOuiMontant en Ariary (entier)
currencyOuiToujours "Ar"
metadataOui

Liste des articles affichés au client lors du paiement.

Obligatoire dans chaque objet
labelunit_pricequantity

Exemple complet :

metadata: [
  {
    "label": "T-shirt noir - Taille M",
    "unit_price": 45000,
    "quantity": 2,
  }
]

Le token ne doit jamais apparaître côté client (navigateur).

2. Réponse de GoalPay

{
  "message": "Success",
  "data": {
    "checkout_url": "https://goalpay.pro/payement/checkout/...",
    "expires_in_minutes": 10,
    "order_reference": "REF_74275670285582638"
  }
}
  • Redirigez immédiatement le client vers checkout_url
  • Le lien expire dans 10 minutes
  • Stockez order_reference dans votre base de données

3. Webhook – Confirmation officielle du paiement

Le webhook est la seule source fiable pour savoir si un paiement a réellement réussi. Les pages de redirection (success/canceled/failed) sont uniquement pour l’expérience utilisateur.

URLs à configurer dans votre espace marchand

  • URL de succès : https://votredomaine.com/payment/success
  • URL d’annulation : https://votredomaine.com/payment/canceled
  • URL d’échec : https://votredomaine.com/payment/failed
  • URL Webhook (obligatoire) : https://votredomaine.com/webhook/goalpay

payment.success → Paiement réussi

Cet événement est envoyé lorsque le paiement a été effectué avec succès par l’utilisateur. À ce moment, vous pouvez valider la commande, enregistrer le paiement dans votre base de données et activer le service ou livrer le produit.

payment.success → Paiement réussi

{
  "event": "payment.success",
  "data": {
    "order_reference": "REF_74275670285582638",
    "reference": "PAN_1772716154773",
    "amount": 125000,
    "currency": "Ar",
    "description": "Panier 154773 - 1 article"
  }
}

payment.failed → Paiement échoué

Cet événement est envoyé lorsque la tentative de paiement a échoué (solde insuffisant, refus de transaction, etc.). Vous pouvez informer l’utilisateur et lui proposer de réessayer le paiement.

payment.failed → Paiement échoué

{
  "event": "payment.failed",
  "data": {
    "order_reference": "REF_74275670285582638",
    "reference": "PAN_1772716154773",
    "amount": 125000,
    "currency": "Ar",
    "description": "Panier 154773 - 1 article",
    "error": "Fonds insuffisants"
  }
}

payment.canceled → Paiement annulé

Cet événement est envoyé lorsque l’utilisateur annule volontairement le paiement avant de le confirmer. Dans ce cas, la commande reste non payée et l’utilisateur peut relancer le paiement plus tard.

payment.canceled → Paiement annulé

{
  "event": "payment.canceled",
  "data": {
    "order_reference": "REF_74275670285582638",
    "reference": "PAN_1772716154773",
    "amount": 125000,
    "currency": "Ar",
    "description": "Panier 154773 - 1 article",
    "error": "Paiement annulé"
  }
}

payment.expired → Paiement expiré

Cet événement est envoyé lorsque le paiement n’a pas été effectué dans le délai prévu. La transaction est alors considérée comme expirée et l’utilisateur devra créer un nouveau paiement s’il souhaite continuer.

payment.expired → Paiement expiré

{
  "event": "payment.expired",
  "data": {
    "order_reference": "REF_74275670285582638",
    "reference": "PAN_1772716154773",
    "amount": 125000,
    "currency": "Ar",
    "description": "Panier 154773 - 1 article",
    "error": "Temps de paiement dépassé"
  }
}

Exemple webhook Node.js / Express (simple et sécurisé)

// Webhook GoalPay – Node.js + Express
const express = require("express");
const crypto = require("crypto");
const { db } = require("./db"); // Adapter selon ton projet
const { orders } = require("./schema"); // Adapter selon ton projet

const app = express();
app.use(express.json());

const WEBHOOK_SECRET = process.env.GOALPAY_WEBHOOK_SECRET;

if (!WEBHOOK_SECRET) {
  console.error("GOALPAY_WEBHOOK_SECRET manquant dans .env");
}

app.post("/webhook/goalpay", async (req, res) => {
  try {
    const signature = req.headers["x-gpay-signature"];
    const payload = req.body;

    if (!payload || !signature) {
      return res.status(400).send("Bad Request");
    }

    // Vérification HMAC
    const expected = crypto
      .createHmac("sha256", WEBHOOK_SECRET)
      .update(`${WEBHOOK_SECRET}`, "utf8")
      .digest("hex");

    if (signature !== expected) {
      return res.status(401).send("Invalid signature");
    }

    const { reference, amount, order_reference, curency, description } =
      payload.data || {};

    console.log("Event :", payload.event);
    console.log("Reference commande :", reference);
    console.log("GoalPay order reference :", order_reference);
    console.log("Montant :", amount);
    console.log("Devise :", curency);
    console.log("Description :", description);

    if (!order_reference) {
      return res.status(400).send("Missing order_reference");
    }

    // Trouver la commande
    const record = await db
      .select({ id: orders.id, status: orders.status })
      .from(orders)
      .where({ goalpayOrderRef: order_reference })
      .limit(1);

    if (!record.length) {
      console.warn(`Commande non trouvée pour order_reference ${order_reference}`);
      return res.status(404).send("Not found");
    }

    const currentOrder = record[0];

    // Déterminer le nouveau status
    let newStatus;
    switch (payload.event) {
      case "payment.success":
        newStatus = "success";
        break;
      case "payment.failed":
        newStatus = "failed";
        break;
      case "payment.canceled":
        newStatus = "canceled";
        break;
      case "payment.expired":
        newStatus = "expired";
        break;
      default:
        newStatus = currentOrder.status;
    }

    console.log(`Nouveau status : ${newStatus}`);

    // Mettre à jour si changement
    if (newStatus !== currentOrder.status) {
      await db.update(orders)
        .set({ status: newStatus })
        .where({ id: currentOrder.id });
    }

    res.status(200).json({ received: true, status: newStatus });
  } catch (err) {
    console.error("Erreur webhook GoalPay :", err);
    res.status(500).send("Internal Server Error");
  }
});

Ce que fait ce code :

  • Vérifie que la requête vient bien de GoalPay (via le header de signature)
  • Évite de traiter deux fois le même événement
  • Met à jour votre base de données uniquement quand payment.success est reçu
  • Répond immédiatement 200 OK

Processus de paiement en 3 étapes

  1. 1.
    Votre serveur envoie une requête à l’API GoalPay avec le montant et la référence → reçoit un payment_link
  2. 2.
    Vous redirigez le client vers ce lien (ou affichez le QR code)
  3. 3.
    Dès que le client paye, GoalPay appelle votre webhook → vous mettez à jour la commande comme paid

C’est tout. Aucun code côté client. Paiement sécurisé et instantané.