Blog

Integrari 9 min de citit

Integrarea WayForPay în PHP: formular de plată, semnătură și apel invers

WayForPay este modalitatea practică de a accepta plăți cu cardul în UAH, USD și EUR de la o companie din Ucraina. Acest ghid parcurge integrarea completă în PHP: semnarea formularului de plată, primirea apelului de server, răspunsul corect la acesta și confirmarea stării comenzii.

Imagine de copertă: Integrarea WayForPay în PHP: formular de plată, semnătură și apel invers

Pe scurt

  • Fluxul are trei părți: un formular de plată semnat care trimite clientul la WayForPay, un apel invers de la server la server (serviceUrl) și propria pagină de stare a comenzii.
  • Fiecare cerere și răspuns este semnat cu HMAC_MD5 folosind cheia dvs. secretă de comerciant; ordinea câmpului contează.
  • Marcați o comandă ca plătită numai dintr-un apel invers verificat sau dintr-un răspuns CHECK_STATUS, niciodată de la clientul care se întoarce pe site-ul dvs.
  • Trebuie să răspundeți la apel invers cu un răspuns de „acceptare” JSON semnat, altfel WayForPay continuă să încerce din nou.
  • Păstrați sumele, moneda și referința comenzii în baza de date și comparați-le cu ceea ce spune apelul înapoi.

O integrare WayForPay în PHP este o cantitate mică de cod, dar are mai multe locuri în care o greșeală silențioasă înseamnă fie comenzi neplătite marcate ca plătite, fie comenzi plătite care nu sunt niciodată onorate. Rulăm WayForPay checkout pe propriile noastre site-uri, inclusiv serviciile plătite de pe acest site web, și l-am conectat la mai multe magazine clienți. Mai jos este structura pe care o folosim, cu detaliile pe care le enumeră în documentație, dar oamenii au tendința de a le trece.

Cum funcționează fluxul de plată

  1. Clientul face clic pe „Plătește”. Serverul dvs. creează o comandă în baza de date cu o orderReference unică, o sumă fixă și o monedă.
  2. Serverul dvs. creează câmpurile formularului de plată, le semnează și redă un formular HTML cu trimitere automată care se postează pe pagina de plată WayForPay.
  3. Clientul plătește pe pagina WayForPay (card, Apple Pay, Google Pay și alte metode activate în contul dvs. de comerciant).
  4. WayForPay vă apelează serviceUrl cu rezultatul tranzacției. Acest apel de la server la server este ceea ce aveți încredere.
  5. Clientul este redirecționat către returnUrl, unde afișați starea curentă a comenzii.

Pasul 5 se poate întâmpla înainte ca pasul 4 să se încheie, așa că pe pagina de retur trebuie să scrie „confirmarea plății” până când apelul înapoi a fost procesat.

Ce ai nevoie din contul de comerciant

  • merchantAccount - autentificarea dvs. de comerciant.
  • merchantSecretKey - secretul folosit pentru semnături. Păstrați-l într-o variabilă de mediu sau într-un fișier de configurare neurmărit, niciodată în git sau în codul front-end.
  • merchantDomainName - domeniul site-ului dvs., așa cum este înregistrat în cont.
  • Monedele și metodele de plată activate pentru contul dvs.

Pasul 1. Creați și semnați formularul de plată

Semnătura este un HMAC cu MD5 peste câmpurile selectate unite prin punct și virgulă. Pentru o achiziție, comanda este: contul de comerciant, numele de domeniu al comerciantului, referința comenzii, data comenzii (marca temporală Unix), suma, moneda, apoi toate numele produselor, toate numărul de produse și toate prețurile produselor.

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);

Apoi redați forma. Câmpurile de produs sunt matrice, deci folosesc sufixul []:

<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>

Cele mai frecvente erori de semnătură sunt: un format de număr diferit în semnătură și formular (50 vs 50.00), un nume de produs care este schimbat (decupat, HTML-escaped) după semnare și uitarea că ordinea câmpurilor este fixă.

Pasul 2. Primiți apelul înapoi pe serviceUrl

Când plata atinge o stare finală, WayForPay postează rezultatul în serviceUrl. Citiți corpul brut (este JSON; unele setări oferă și câmpuri de formular, deci gestionați ambele) și verificați semnătura. Pentru callback se calculează pe: cont de comerciant, referința comenzii, sumă, valută, cod de autorizare, mască de card, starea tranzacției și cod motiv.

$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;
}

Utilizați hash_equals pentru comparație pentru a evita scurgerile de sincronizare. Numai după ce semnătura este valabilă ar trebui să căutați comanda și să comparați amount și currency cu ceea ce ați stocat. Dacă acestea diferă, înregistrați-o și nu marcați comanda ca plătită.

Pasul 3. Actualizați comanda și răspundeți corect

Câmpul transactionStatus vă spune ce sa întâmplat: Approved înseamnă că banii sunt capturați; Declined, Expired și valori similare înseamnă eșec; Pending sau InProcessing înseamnă așteptare; Refunded și Voided înseamnă că banii s-au returnat. Actualizați-vă comanda în consecință, într-o tranzacție, și faceți-o idempotent: dacă comanda este deja plătită, nu faceți nimic și răspundeți totuși cu succes.

WayForPay necesită un răspuns semnat, astfel încât să știe că ați primit mesajul. Semnătura este peste referința comenzii, cuvântul accept și ora curentă:

$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);

Dacă serverul dvs. nu răspunde astfel, WayForPay tratează livrarea ca eșuată și reîncearcă, ceea ce înseamnă că veți primi același apel invers de mai multe ori. Acesta este un alt motiv pentru verificarea idempotentei.

Pasul 4. Confirmați starea cu CHECK_STATUS

Apelurile inverse pot fi ratate: serverul dvs. a fost inactiv, un firewall a blocat cererea, o implementare a întrerupt ruta. WayForPay oferă un apel API CHECK_STATUS care returnează starea curentă a unei comenzi prin referință. Folosiți-l în două locuri: pe pagina de retur, dacă apelul înapoi nu a sosit după câteva secunde și într-o lucrare cron mică care verifică din nou comenzile blocate în „în așteptare” pentru mai mult de, să zicem, cincisprezece minute. Solicitarea este semnată cu HMAC_MD5 peste contul de comerciant și referința comenzii.

$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);

Aplicați aceleași verificări ca și pentru un apel invers: verificați semnătura răspunsului atunci când este prezentă, comparați suma și moneda și actualizați comanda în mod idempotent.

Testare înainte de a intra în direct

  • Utilizați modul de testare și cardurile de testare din documentația WayForPay până când o plată completă, un refuz și o rambursare au fost văzute în jurnalele dvs.
  • serviceUrl dvs. trebuie să fie accesibil de pe internet prin HTTPS. Pe localhost, utilizați un tunel sau implementați într-un domeniu intermediar.
  • Înregistrați fiecare apel invers cu corpul său brut, rezultatul verificării și rezultatul. Nu înregistrați niciodată cheia secretă.
  • Asigurați-vă că protecția WAF sau bot (de exemplu, în Cloudflare) nu contestă sau blochează cererile de apel invers de la WayForPay.

Greșeli comune

  • Aveți încredere în adresa URL de returnare. Clientul îl poate deschide fără să plătească.
  • Reutilizarea unei referințe de comandă. Fiecare încercare are nevoie de o referință unică, altfel vei amesteca încercările.
  • Modificarea sumei după semnare sau afișarea unui preț și semnarea altuia.
  • Nu răspunde la apel invers cu răspunsul de acceptare semnat.
  • Marcați ca plătit înainte de a verifica suma și moneda.
  • Mai multe monede, un secret comun și nicio mapare: păstrați moneda în ordine și verificați-o la apel invers.

Cum putem ajuta

Construim integrări de plată cap la cap: WayForPay, Stripe, PayPal, rapoarte de facturare recurentă, rambursări și reconciliere. Dacă aveți deja o procedură de checkout, dar nu sunteți sigur despre modurile de defecțiune ale acesteia, rezervați o analiză de arhitectură și vom revizui gestionarea apelului invers, idempotnța și înregistrarea în jurnal. Pentru un ghid similar pentru un alt furnizor, consultați Stripe webhooks în PHP, iar pentru abordarea generală, citiți manualul nostru de integrare de pornire.

Întrebări frecvente

Ce semnătură folosește WayForPay ?

HMAC_MD5 peste câmpurile selectate unite cu punct și virgulă, folosind cheia secretă a comerciantului. Ordinea de câmp este fixă și diferă pentru formularul de plată, apel invers și răspunsul de apel invers.

Ce trebuie să returneze punctul final serviceUrl?

Un răspuns JSON cu orderReference, starea „accept”, ora curentă Unix și o semnătură calculată ca HMAC_MD5 pentru „orderReference;accept;time”. Fără el, WayForPay reîncearcă apelul înapoi.

Pot avea încredere în pagina returnUrl pentru a marca o comandă ca plătită?

Nu. Browserul clientului poate deschide acea adresă URL fără a plăti. Marcați comenzile ca plătite numai după un apel invers verificat sau după un răspuns CHECK_STATUS cu starea Aprobat și suma și moneda corespunzătoare.

Cum verific starea unei comenzi WayForPay ?

Trimiteți o solicitare CHECK_STATUS semnată către API-ul WayForPay cu contul dvs. de comerciant și referința comenzii. Utilizați-l pe pagina de retur și într-o lucrare cron care verifică din nou comenzile blocate în așteptare.

De ce spune WayForPay că semnătura este nevalidă?

De obicei, formatul sumei diferă între semnătură și formă (50 vs 50,00), un nume de produs a fost modificat după semnare, ordinea câmpurilor este greșită sau este folosită cheia secretă greșită.

Apel de descoperire gratuit

Adu un stack haotic sau o integrare blocată - Integrono mapează cel mai inteligent prim pas.

Primești o evaluare senior pe capcane de buget, dependențe și dacă merită să cumperi, să lipești sau să construiești - înainte să înceapă munca plătită. CEO-ul rămâne în buclă.