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
}
]
}| Champ | Obligatoire | Description |
|---|---|---|
description | Oui | Texte affiché au client |
access | Oui | Votre token GoalPay |
reference | Oui | Votre référence unique (ex: CMD-12345) |
amount | Oui | Montant en Ariary (entier) |
currency | Oui | Toujours "Ar" |
metadata | Oui | Liste des articles affichés au client lors du paiement. Obligatoire dans chaque objet labelunit_pricequantityExemple 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_referencedans 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.successest reçu - Répond immédiatement 200 OK
Processus de paiement en 3 étapes
- 1.Votre serveur envoie une requête à l’API GoalPay avec le montant et la référence → reçoit un
payment_link - 2.Vous redirigez le client vers ce lien (ou affichez le QR code)
- 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é.