Blogg

Integrationer 7 min läsning

WayForPay-integrering i PHP: betalningsformulär, signatur och återuppringning

WayForPay är det praktiska sättet att acceptera kortbetalningar i UAH, USD och EUR från ett ukrainskt företag. Den här guiden går igenom den fullständiga integrationen i PHP: signering av betalningsformuläret, ta emot serveråteruppringning, besvara den korrekt och bekräfta orderstatus.

Omslagsbild: WayForPay-integrering i PHP: betalningsformulär, signatur och återuppringning

Kortfattat

  • Flödet har tre delar: ett signerat betalningsformulär som skickar kunden till WayForPay, en server-till-server-återuppringning (serviceUrl), och din egen orderstatussida.
  • Varje förfrågan och svar signeras med HMAC_MD5 med din säljarens hemliga nyckel; fältordning har betydelse.
  • Markera en beställning som betald endast från en verifierad återuppringning eller ett CHECK_STATUS svar, aldrig från kunden som återvänder till din webbplats.
  • Du måste svara på återuppringningen med ett signerat JSON "acceptera"-svar, annars fortsätter WayForPay att försöka igen.
  • Behåll belopp, valuta och orderreferens i din databas och jämför dem med vad återuppringningen säger.

En WayForPay integration i PHP är en liten mängd kod, men den har flera ställen där ett tyst misstag innebär att antingen obetalda beställningar markeras som betalda eller betalda beställningar som aldrig blir uppfyllda. Vi kör WayForPay utcheckning på våra egna webbplatser, inklusive betaltjänsterna på denna webbplats, och har kopplat den till flera kundbutiker. Nedan är strukturen vi använder, med detaljerna som dokumentationen listar men folk tenderar att skumma.

Hur betalningsflödet fungerar

  1. Kunden klickar på "Betala". Din server skapar en order i databasen med en unik orderReference, ett fast belopp och en valuta.
  2. Din server bygger betalningsformulärsfälten, signerar dem och återger ett HTML-formulär som skickas automatiskt till WayForPay-betalningssidan.
  3. Kunden betalar på WayForPay-sidan (kort, Apple Pay, Google Pay och andra metoder aktiverade på ditt säljarkonto).
  4. WayForPay anropar din serviceUrl med transaktionsresultatet. Detta server-till-server-anrop är vad du litar på.
  5. Kunden omdirigeras till din returnUrl, där du visar aktuell orderstatus.

Steg 5 kan ske innan steg 4 avslutas, så retursidan måste säga "bekräftar din betalning" tills återuppringningen har behandlats.

Vad du behöver från handlarkontot

  • merchantAccount - din handlarinloggning.
  • merchantSecretKey - hemligheten som används för signaturer. Behåll den i en miljövariabel eller en ospårad konfigurationsfil, aldrig i git eller i frontend-kod.
  • merchantDomainName - din webbplats domän, som registrerad på kontot.
  • Valutorna och betalningsmetoderna som har aktiverats för ditt konto.

Steg 1. Bygg och underteckna betalningsformuläret

Signaturen är en HMAC med MD5 över valda fält förenade med semikolon. För ett köp är beställningen: handlarkonto, säljarens domännamn, orderreferens, orderdatum (Unix-tidsstämpel), belopp, valuta, sedan alla produktnamn, alla produktantal och alla produktpriser.

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

Gör sedan formuläret. Produktfält är matriser, så de använder suffixet []:

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

De vanligaste signaturfelen är: ett annat nummerformat i signaturen och formuläret (50 vs 50.00), ett produktnamn som ändras (trimmas, HTML-escaped) efter signering och glömmer att ordningen på fälten är fixerad.

Steg 2. Ta emot återuppringningen på serviceUrl

När betalningen når ett slutgiltigt tillstånd publicerar WayForPay resultatet på serviceUrl. Läs råtexten (det är JSON; vissa inställningar levererar också formulärfält, så hantera båda) och verifiera signaturen. För återuppringningen beräknas den över: handlarkonto, orderreferens, belopp, valuta, auktoriseringskod, kortmask, transaktionsstatus och orsakskod.

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

Använd hash_equals för jämförelsen för att undvika tidsläckor. Först efter att signaturen är giltig bör du slå upp beställningen och jämföra amount och currency med det du lagrat. Om de skiljer sig, logga det och markera inte beställningen som betald.

Steg 3. Uppdatera beställningen och svara rätt

transactionStatus talar om vad som hände: Approved betyder att pengarna fångas; Declined, Expired och liknande värden betyder misslyckande; Pending eller InProcessing betyder vänta; Refunded och Voided betyder att pengarna gick tillbaka. Uppdatera din beställning i enlighet därmed, i en transaktion, och gör den idempotent: om beställningen redan är betald, gör ingenting och svarar ändå framgångsrikt.

WayForPay kräver ett undertecknat svar så att det vet att du fått meddelandet. Signaturen är över orderreferens, ordet accept och aktuell tid:

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

Om din server inte svarar så här, behandlar WayForPay leveransen som misslyckad och försöker igen, vilket innebär att du får samma återuppringning flera gånger. Det är ytterligare en anledning till idempotenskontrollen.

Steg 4. Bekräfta statusen med CHECK_STATUS

Återuppringningar kan missas: din server låg nere, en brandvägg blockerade begäran, en utplacering bröt rutten. WayForPay erbjuder ett CHECK_STATUS API-anrop som returnerar det aktuella tillståndet för en beställning genom dess referens. Använd den på två ställen: på retursidan, om återuppringningen inte har kommit efter några sekunder, och i ett litet cron-jobb som återkontrollerar beställningar som har fastnat i "väntande" i mer än, säg, femton minuter. Begäran är undertecknad med HMAC_MD5 över handlarkonto och orderreferens.

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

Använd samma kontroller som för en återuppringning: verifiera svarssignaturen när den finns, jämför beloppet och valutan och uppdatera beställningen idempotent.

Testa innan du går live

  • Använd testläget och testkorten från WayForPay-dokumentationen tills en full betalning, ett avslag och en återbetalning har setts i dina loggar.
  • Din serviceUrl måste vara tillgänglig från internet via HTTPS. På localhost använd en tunnel eller distribuera till en staging-domän.
  • Logga varje återuppringning med dess råa innehåll, verifieringsresultatet och resultatet. Logga aldrig den hemliga nyckeln.
  • Se till att WAF- eller botskyddet (till exempel i Cloudflare) inte utmanar eller blockerar WayForPay återuppringningsförfrågningar.

Vanliga misstag

  • Litar på returadressen. Kunden kan öppna den utan att betala.
  • Återanvändning av en orderreferens. Varje försök behöver en unik referens, annars kommer du att blanda ihop försök.
  • Ändra beloppet efter signering, eller visa ett pris och signera ett annat.
  • Svarar inte på återuppringningen med det signerade godkännandesvaret.
  • Markera som betalt innan du kontrollerar belopp och valuta.
  • Flera valutor, en delad hemlighet och ingen mappning: håll valutan i ordning och verifiera den vid återuppringning.

Hur vi kan hjälpa till

Vi bygger betalningsintegrationer från början: WayForPay, Stripe, PayPal, återkommande fakturering, återbetalningar och avstämningsrapporter. Om du redan har en fungerande kassa men är osäker på dess fellägen, boka en arkitekturanalys så kommer vi att granska callback-hanteringen, idempotensen och loggningen. För en liknande guide för en annan leverantör se Stripe webhooks i PHP, och för det övergripande tillvägagångssättet, läs vår spelbok för startup integrations.

Vanliga frågor

Vilken signatur använder WayForPay ?

HMAC_MD5 över valda fält sammanfogade med ett semikolon, med hjälp av säljarens hemliga nyckel. Fältordern är fast och skiljer sig för betalningsformuläret, återuppringningen och återuppringningssvaret.

Vad måste serviceUrl-slutpunkten returnera?

Ett JSON-svar med orderReference, statusen "acceptera", den aktuella Unix-tiden och en signatur beräknad som HMAC_MD5 av "orderReference;accept;time". Utan det försöker WayForPay återuppringningen.

Kan jag lita på att returnUrl-sidan markerar en beställning som betald?

Nej. Kundens webbläsare kan öppna webbadressen utan att betala. Markera beställningar som betalda endast efter en verifierad serviceUrl-återuppringning eller ett CHECK_STATUS svar med en Godkänd status och matchande belopp och valuta.

Hur kontrollerar jag statusen för en WayForPay-order?

Skicka en signerad CHECK_STATUS förfrågan till WayForPay API med ditt säljarkonto och beställningsreferensen. Använd den på retursidan och i ett cron-jobb som kontrollerar beställningar som har fastnat i väntan.

Varför säger WayForPay att signaturen är ogiltig?

Vanligtvis skiljer sig beloppsformatet mellan signaturen och formuläret (50 vs 50,00), ett produktnamn ändrades efter signering, ordningen på fälten är felaktig eller fel hemlig nyckel används.

Gratis upptäcktssamtal

Ta med en rörig stack eller en fastnad integration - Integrono kartlägger det smartaste första steget.

Ni får en senior bedömning av budgetfällor, beroenden och om ni ska köpa, limma eller bygga - innan betalt arbete startar. CEO stannar i loopen.