Blog

Intégrations 9 min de lecture

Intégration WayForPay en PHP : formulaire de paiement, signature et rappel

WayForPay est le moyen pratique d'accepter les paiements par carte en UAH, USD et EUR d'une entreprise ukrainienne. Ce guide présente l'intégration complète en PHP : signer le formulaire de paiement, recevoir le rappel du serveur, y répondre correctement et confirmer le statut de la commande.

Illustration de couverture : Intégration WayForPay en PHP : formulaire de paiement, signature et rappel

En bref

  • Le flux comporte trois parties : un formulaire de paiement signé qui envoie le client à WayForPay, un rappel de serveur à serveur (serviceUrl) et votre propre page d'état de la commande.
  • Chaque demande et réponse est signée avec HMAC_MD5 en utilisant votre clé secrète de commerçant ; l’ordre des champs est important.
  • Marquez une commande comme payée uniquement à partir d'un rappel vérifié ou d'une réponse CHECK_STATUS, jamais du client revenant sur votre site.
  • Vous devez répondre au rappel avec une réponse « accepter » JSON signée, sinon WayForPay continue de réessayer.
  • Conservez les montants, la devise et la référence de la commande dans votre base de données et comparez-les avec ce que dit le rappel.

Une intégration WayForPay dans PHP représente une petite quantité de code, mais elle comporte plusieurs endroits où une erreur silencieuse signifie soit des commandes impayées marquées comme payées, soit des commandes payées qui ne sont jamais exécutées. Nous gérons le paiement WayForPay sur nos propres sites, y compris les services payants de ce site Web, et l'avons connecté à plusieurs boutiques clientes. Vous trouverez ci-dessous la structure que nous utilisons, avec les détails que la documentation répertorie mais que les gens ont tendance à parcourir.

Comment fonctionne le flux de paiement

  1. Le client clique sur « Payer ». Votre serveur crée une commande dans la base de données avec un orderReference unique, un montant fixe et une devise.
  2. Votre serveur crée les champs du formulaire de paiement, les signe et affiche un formulaire HTML à soumission automatique qui est publié sur la page de paiement WayForPay.
  3. Le client paie sur la page WayForPay (carte, Apple Pay, Google Pay et autres méthodes activées dans votre compte marchand).
  4. WayForPay appelle votre serviceUrl avec le résultat de la transaction. Cet appel de serveur à serveur est ce à quoi vous faites confiance.
  5. Le client est redirigé vers votre returnUrl, où vous affichez le statut actuel de la commande.

L'étape 5 peut avoir lieu avant la fin de l'étape 4, la page de retour doit donc indiquer « confirmant votre paiement » jusqu'à ce que le rappel soit traité.

Ce dont vous avez besoin du compte marchand

  • merchantAccount - votre identifiant de commerçant.
  • merchantSecretKey - le secret utilisé pour les signatures. Conservez-le dans une variable d'environnement ou dans un fichier de configuration non suivi, jamais dans git ou dans le code front-end.
  • merchantDomainName - le domaine de votre site, tel qu'enregistré dans le compte.
  • Les devises et modes de paiement activés pour votre compte.

Étape 1. Créez et signez le formulaire de paiement

La signature est un HMAC avec MD5 sur les champs sélectionnés reliés par un point-virgule. Pour un achat, la commande est : le compte marchand, le nom de domaine du marchand, la référence de la commande, la date de la commande (horodatage Unix), le montant, la devise, puis tous les noms de produits, tous les nombres de produits et tous les prix des produits.

function wfpSignature(array $parts, string $secret): string
{
    return hash_hmac('md5', implode(';', $parts), $secret);
}

$orderReference = 'ORD-' . $orderId . '-' . time();
$orderDate      = time();
$amount         = '50.00';          // string with a dot, the same value everywhere
$currency       = 'EUR';
$products       = [['name' => 'Consultation, 1 hour', 'count' => 1, 'price' => '50.00']];

$parts = [$merchantAccount, $merchantDomain, $orderReference, $orderDate, $amount, $currency];
foreach ($products as $p) { $parts[] = $p['name']; }
foreach ($products as $p) { $parts[] = (string) $p['count']; }
foreach ($products as $p) { $parts[] = $p['price']; }

$signature = wfpSignature($parts, $secretKey);

Ensuite, restituez le formulaire. Les champs Product sont des tableaux, ils utilisent donc le suffixe [] :

<form id="wfp" method="post" action="https://secure.wayforpay.com/pay" accept-charset="utf-8">
  <input type="hidden" name="merchantAccount" value="...">
  <input type="hidden" name="merchantAuthType" value="SimpleSignature">
  <input type="hidden" name="merchantDomainName" value="example.com">
  <input type="hidden" name="merchantSignature" value="<?= htmlspecialchars($signature) ?>">
  <input type="hidden" name="orderReference" value="<?= htmlspecialchars($orderReference) ?>">
  <input type="hidden" name="orderDate" value="<?= $orderDate ?>">
  <input type="hidden" name="amount" value="50.00">
  <input type="hidden" name="currency" value="EUR">
  <input type="hidden" name="productName[]" value="Consultation, 1 hour">
  <input type="hidden" name="productCount[]" value="1">
  <input type="hidden" name="productPrice[]" value="50.00">
  <input type="hidden" name="returnUrl" value="https://example.com/pay/return">
  <input type="hidden" name="serviceUrl" value="https://example.com/pay/callback">
</form>
<script>document.getElementById('wfp').submit();</script>

Les erreurs de signature les plus courantes sont : un format numérique différent dans la signature et le formulaire (50 vs 50.00), un nom de produit modifié (tronqué, avec échappement HTML) après la signature et l'oubli que l'ordre des champs est fixe.

Étape 2. Recevez le rappel sur serviceUrl

Lorsque le paiement atteint son état final, WayForPay publie le résultat sur serviceUrl. Lisez le corps brut (il s'agit de JSON ; certaines configurations fournissent également des champs de formulaire, donc gérez les deux) et vérifiez la signature. Pour le rappel, il est calculé sur : le compte marchand, la référence de la commande, le montant, la devise, le code d'autorisation, le masque de la carte, le statut de la transaction et le code motif.

$raw  = file_get_contents('php://input');
$data = json_decode($raw, true) ?: $_POST;

$line = implode(';', [
    $data['merchantAccount'] ?? '',
    $data['orderReference'] ?? '',
    $data['amount'] ?? '',
    $data['currency'] ?? '',
    $data['authCode'] ?? '',
    $data['cardPan'] ?? '',
    $data['transactionStatus'] ?? '',
    $data['reasonCode'] ?? '',
]);
$expected = hash_hmac('md5', $line, $secretKey);

if (!hash_equals(strtolower($expected), strtolower((string) ($data['merchantSignature'] ?? '')))) {
    http_response_code(400);
    exit;
}

Utilisez hash_equals pour la comparaison afin d'éviter les fuites de synchronisation. Ce n'est qu'une fois la signature valide que vous devrez rechercher la commande et comparer amount et currency avec ce que vous avez stocké. S'ils diffèrent, enregistrez-le et ne marquez pas la commande comme payée.

Étape 3. Mettez à jour la commande et répondez correctement

Le champ transactionStatus vous indique ce qui s'est passé : Approved signifie que l'argent a été capturé ; Les valeurs Declined, Expired et similaires signifient un échec ; Pending ou InProcessing signifie attendre ; Refunded et Voided signifie que l'argent a été restitué. Mettez à jour votre commande en conséquence, dans une transaction, et rendez-la idempotente : si la commande est déjà payée, ne faites rien et répondez quand même avec succès.

WayForPay nécessite une réponse signée afin de savoir que vous avez reçu le message. La signature porte la référence de la commande, le mot accept et l'heure actuelle :

$time   = time();
$status = 'accept';
$reply  = [
    'orderReference' => $data['orderReference'],
    'status'         => $status,
    'time'           => $time,
    'signature'      => hash_hmac('md5', "{$data['orderReference']};{$status};{$time}", $secretKey),
];

header('Content-Type: application/json');
echo json_encode($reply);

Si votre serveur ne répond pas ainsi, WayForPay considère la livraison comme un échec et réessaye, ce qui signifie que vous recevrez plusieurs fois le même rappel. C’est une autre raison pour le contrôle d’idempotence.

Étape 4. Confirmez le statut avec CHECK_STATUS

Des rappels peuvent être manqués : votre serveur était en panne, un pare-feu a bloqué la requête, un déploiement a interrompu la route. WayForPay propose un appel API CHECK_STATUS qui renvoie l'état actuel d'une commande par sa référence. Utilisez-le à deux endroits : sur la page de retour, si le rappel n'est pas arrivé après quelques secondes, et dans une petite tâche cron qui revérifie les commandes bloquées "en attente" pendant plus de, disons, quinze minutes. La demande est signée avec HMAC_MD5 sur le compte marchand et la référence de la commande.

$payload = [
    'transactionType'   => 'CHECK_STATUS',
    'merchantAccount'   => $merchantAccount,
    'orderReference'    => $orderReference,
    'merchantSignature' => hash_hmac('md5', "{$merchantAccount};{$orderReference}", $secretKey),
    'apiVersion'        => 1,
];

$ch = curl_init('https://api.wayforpay.com/api');
curl_setopt_array($ch, [
    CURLOPT_POST           => true,
    CURLOPT_POSTFIELDS     => json_encode($payload),
    CURLOPT_HTTPHEADER     => ['Content-Type: application/json'],
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT        => 15,
]);
$result = json_decode((string) curl_exec($ch), true);

Effectuez les mêmes vérifications que pour un rappel : vérifiez la signature de la réponse lorsqu'elle est présente, comparez le montant et la devise, et mettez à jour la commande de manière idempotente.

Tester avant de mettre en ligne

  • Utilisez le mode test et les cartes de test de la documentation WayForPay jusqu'à ce qu'un paiement intégral, un refus et un remboursement soient tous visibles dans vos journaux.
  • Votre serviceUrl doit être accessible depuis Internet via HTTPS. Sur localhost, utilisez un tunnel ou déployez sur un domaine intermédiaire.
  • Enregistrez chaque rappel avec son corps brut, le résultat de la vérification et le résultat. Ne consignez jamais la clé secrète.
  • Assurez-vous que la protection WAF ou contre les robots (par exemple dans Cloudflare) ne conteste pas ou ne bloque pas les demandes de rappel de WayForPay.

Erreurs courantes

  • Faire confiance à l'URL de retour. Le client peut l'ouvrir sans payer.
  • Réutilisation d'une référence de commande. Chaque tentative nécessite une référence unique, sinon vous confondrez les tentatives.
  • Changer le montant après la signature, ou afficher un prix et en signer un autre.
  • Ne pas répondre au rappel avec la réponse d'acceptation signée.
  • Marquez comme payé avant de vérifier le montant et la devise.
  • Plusieurs devises, un secret partagé et pas de mappage : conservez la devise dans l'ordre et vérifiez-la au rappel.

Comment pouvons-nous vous aider

Nous construisons des intégrations de paiement de bout en bout : WayForPay, Stripe, PayPal, facturation récurrente, remboursements et rapports de rapprochement. Si vous disposez déjà d'une caisse fonctionnelle mais que vous n'êtes pas sûr de ses modes de défaillance, réservez une analyse d'architecture et nous examinerons la gestion des rappels, l'idempotence et la journalisation. Pour un guide similaire sur un autre fournisseur, consultez les webhooks Stripe en PHP, et pour l'approche globale, lisez notre playbook d'intégrations de démarrage.

Questions fréquentes

Quelle signature WayForPay utilise-t-il ?

HMAC_MD5 sur les champs sélectionnés joints par un point-virgule, à l'aide de la clé secrète du commerçant. L'ordre des champs est fixe et diffère pour le formulaire de paiement, le rappel et la réponse de rappel.

Que doit renvoyer le point de terminaison serviceUrl ?

Une réponse JSON avec orderReference, le statut « accepter », l'heure Unix actuelle et une signature calculée comme HMAC_MD5 de « orderReference;accept;time ». Sans cela, WayForPay réessaye le rappel.

Puis-je faire confiance à la page returnUrl pour marquer une commande comme payée ?

Non. Le navigateur du client peut ouvrir cette URL sans payer. Marquez les commandes comme payées uniquement après un rappel serviceUrl vérifié ou une réponse CHECK_STATUS avec un statut Approuvé et un montant et une devise correspondants.

Comment puis-je vérifier le statut d'une commande WayForPay ?

Envoyez une demande CHECK_STATUS signée à l'API WayForPay avec votre compte marchand et la référence de la commande. Utilisez-le sur la page de retour et dans une tâche cron qui revérifie les commandes bloquées en attente.

Pourquoi WayForPay indique-t-il que la signature n'est pas valide ?

Habituellement, le format du montant diffère entre la signature et le formulaire (50 contre 50,00), un nom de produit a été modifié après la signature, l'ordre des champs est erroné ou une mauvaise clé secrète est utilisée.

Appel découverte gratuit

Apportez une pile désordonnée ou une intégration bloquée - Integrono tracera le premier pas le plus intelligent.

Vous obtenez un avis senior sur les pièges budgétaires, les dépendances et s'il faut acheter, assembler ou construire - avant le début du travail payant. Le PDG reste dans la boucle.