Blog

Integrace 8 min čtení

Integrace WayForPay v PHP: platební formulář, podpis a zpětné volání

WayForPay je praktický způsob, jak přijímat platby kartou v UAH, USD a EUR od ukrajinského podniku. Tento průvodce vás provede úplnou integrací v PHP: podepsání platebního formuláře, přijetí zpětného volání serveru, správné zodpovězení a potvrzení stavu objednávky.

Ilustrace článku: Integrace WayForPay v PHP: platební formulář, podpis a zpětné volání

Ve zkratce

  • Tok má tři části: podepsaný platební formulář, který odešle zákazníka na WayForPay, zpětné volání mezi servery (serviceUrl) a vaši vlastní stránku stavu objednávky.
  • Každá žádost a odpověď je podepsána pomocí HMAC_MD5 pomocí vašeho tajného klíče obchodníka; na polním pořádku záleží.
  • Označte objednávku jako zaplacenou pouze na základě ověřeného zpětného volání nebo odpovědi CHECK_STATUS, nikdy od zákazníka, který se na váš web vrací.
  • Na zpětné volání musíte odpovědět podepsanou odpovědí JSON „přijmout“, jinak se WayForPay neustále opakuje.
  • Udržujte částky, měnu a referenci objednávky ve své databázi a porovnejte je s tím, co říká zpětné volání.

Integrace WayForPay v PHP je malé množství kódu, ale má několik míst, kde tichá chyba znamená buď nezaplacené objednávky označené jako zaplacené, nebo placené objednávky, které se nikdy nesplní. Pokladnu WayForPay provozujeme na našich vlastních stránkách, včetně placených služeb na této webové stránce, a zavedli jsme ji do několika klientských obchodů. Níže je struktura, kterou používáme, s podrobnostmi, které dokumentace uvádí, ale lidé mají tendenci přelétávat.

Jak funguje platební tok

  1. Zákazník klikne na „Zaplatit“. Váš server vytvoří objednávku v databázi s unikátní orderReference, pevnou částkou a měnou.
  2. Váš server vytvoří pole platebního formuláře, podepíše je a vykreslí automaticky odeslaný HTML formulář, který se odešle na platební stránku WayForPay.
  3. Zákazník zaplatí na stránce WayForPay (karta, Apple Pay, Google Pay a další metody povolené ve vašem účtu obchodníka).
  4. WayForPay zavolá vaši serviceUrl s výsledkem transakce. Toto volání server-to-server je to, čemu důvěřujete.
  5. Zákazník je přesměrován na vaši returnUrl, kde ukazujete aktuální stav objednávky.

Krok 5 může proběhnout před dokončením kroku 4, takže na návratové stránce musí být uvedeno „potvrzuji vaši platbu“, dokud nebude zpětné volání zpracováno.

Co potřebujete z účtu obchodníka

  • merchantAccount – vaše přihlašovací údaje obchodníka.
  • merchantSecretKey – tajemství používané pro podpisy. Udržujte jej v proměnné prostředí nebo nesledovaném konfiguračním souboru, nikdy v git nebo v kódu front-end.
  • merchantDomainName – doména vašeho webu, jak je zaregistrována v účtu.
  • Měny a platební metody povolené pro váš účet.

Krok 1. Vytvořte a podepište platební formulář

Podpis je HMAC s MD5 nad vybranými poli spojenými středníkem. Pro nákup je objednávka: účet obchodníka, název domény obchodníka, reference objednávky, datum objednávky (časové razítko Unix), částka, měna, dále všechny názvy produktů, všechny počty produktů a ceny všech produktů.

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

Poté vykreslete formulář. Pole produktu jsou pole, takže používají příponu []:

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

Nejčastější chyby podpisu jsou: jiný formát čísla v podpisu a ve formuláři (50 vs 50.00), název produktu, který se po podpisu změní (oříznutý, s kódem HTML) a zapomenutí, že pořadí polí je pevně dané.

Krok 2. Přijměte zpětné volání na serviceUrl

Když platba dosáhne konečného stavu, WayForPay odešle výsledek na serviceUrl. Přečtěte si nezpracované tělo (je to JSON; některá nastavení dodávají také pole formuláře, takže zpracujte obojí) a ověřte podpis. Pro zpětné volání se počítá podle: obchodního účtu, reference objednávky, částky, měny, autorizačního kódu, masky karty, stavu transakce a kódu důvodu.

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

Pro srovnání použijte hash_equals, abyste se vyhnuli únikům načasování. Teprve poté, co je podpis platný, byste měli vyhledat objednávku a porovnat amount a currency s tím, co jste uložili. Pokud se liší, přihlaste se a neoznačujte objednávku jako zaplacenou.

Krok 3. Aktualizujte objednávku a odpovězte správně

Pole transactionStatus vám řekne, co se stalo: Approved znamená, že peníze jsou zachyceny; Declined, Expired a podobné hodnoty znamenají selhání; Pending nebo InProcessing znamená čekání; Refunded a Voided znamená, že peníze byly vráceny. Aktualizujte svou objednávku odpovídajícím způsobem v transakci a udělejte ji idempotentní: pokud je objednávka již zaplacena, nedělejte nic a přesto odpovězte úspěšně.

WayForPay vyžaduje podepsanou odpověď, aby věděl, že jste zprávu obdrželi. Podpis je přes odkaz na objednávku, slovo accept a aktuální čas:

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

Pokud váš server neodpoví takto, WayForPay považuje doručení za neúspěšné a zopakuje to, což znamená, že obdržíte stejné zpětné volání několikrát. To je další důvod pro kontrolu idempotence.

Krok 4. Potvrďte stav pomocí CHECK_STATUS

Zpětná volání mohou chybět: váš server byl mimo provoz, firewall zablokoval požadavek, nasazení přerušilo trasu. WayForPay nabízí volání API CHECK_STATUS, které vrací aktuální stav objednávky pomocí jejího odkazu. Použijte jej na dvou místech: na návratové stránce, pokud zpětné volání nedorazilo po několika sekundách, a v malé cronové úloze, která znovu kontroluje objednávky uvízlé ve stavu „nevyřízeno“ déle než řekněme patnáct minut. Požadavek je podepsán pomocí HMAC_MD5 přes účet obchodníka a referenci objednávky.

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

Použijte stejné kontroly jako u zpětného volání: ověřte podpis odpovědi, je-li přítomen, porovnejte částku a měnu a aktualizujte objednávku idempotentně.

Testování před spuštěním

  • Používejte testovací režim a testovací karty z dokumentace WayForPay, dokud se ve vašich protokolech nezobrazí úplná platba, odmítnutí a vrácení peněz.
  • Vaše serviceUrl musí být dostupná z internetu přes HTTPS. Na localhost použijte tunel nebo nasaďte do pracovní domény.
  • Zaznamenejte každé zpětné volání s jeho nezpracovaným tělem, výsledkem ověření a výsledkem. Nikdy nezapisujte tajný klíč.
  • Ujistěte se, že ochrana WAF nebo bot (například v Cloudflare) nezpochybňuje nebo neblokuje požadavky WayForPay na zpětné volání.

Časté chyby

  • Důvěřování návratové URL. Zákazník si jej může otevřít bez placení.
  • Opětovné použití reference objednávky. Každý pokus vyžaduje jedinečnou referenci, jinak pokusy zaměníte.
  • Změna částky po podpisu nebo zobrazení jedné ceny a podepsání jiné.
  • Neodpovídá na zpětné volání s podepsanou odpovědí přijetí.
  • Označit jako zaplacené před kontrolou částky a měny.
  • Několik měn, jedno sdílené tajemství a žádné mapování: měnu udržujte v objednávce a ověřte ji při zpětném volání.

Jak můžeme pomoci

Vytváříme integraci plateb od začátku do konce: WayForPay, Stripe, PayPal, opakující se fakturace, refundace a zprávy o odsouhlasení. Pokud již máte fungující pokladnu, ale nejste si jisti jejími způsoby selhání, objednejte si analýzu architektury a my zkontrolujeme zpracování zpětných volání, idempotenci a protokolování. Podobnou příručku pro jiného poskytovatele naleznete v tématu Stripe webhooky v PHP a celkový přístup naleznete v naší příručce pro integraci spouštění.

Časté otázky

Jaký podpis používá WayForPay ?

HMAC_MD5 přes vybraná pole spojená středníkem pomocí tajného klíče obchodníka. Pořadí polí je pevné a liší se pro platební formulář, zpětné volání a odpověď na zpětné volání.

Co musí vrátit koncový bod serviceUrl?

Odpověď JSON s orderReference, stavem "accept", aktuálním Unixovým časem a podpisem vypočítaným jako HMAC_MD5 z "orderReference;accept;time". Bez něj WayForPay zopakuje zpětné volání.

Mohu důvěřovat stránce returnUrl, že označí objednávku jako zaplacenou?

Ne. Prohlížeč zákazníka může tuto adresu URL otevřít bez placení. Označte objednávky jako zaplacené až po ověřeném zpětném volání serviceUrl nebo odpovědi CHECK_STATUS se stavem Schváleno a odpovídající částkou a měnou.

Jak zkontroluji stav objednávky WayForPay ?

Odešlete podepsanou žádost CHECK_STATUS do rozhraní API WayForPay se svým účtem obchodníka a referenčním číslem objednávky. Použijte jej na návratové stránce a v úloze cron, která znovu kontroluje nevyřízené objednávky.

Proč WayForPay říká, že podpis je neplatný?

Obvykle se formát částky mezi podpisem a formulářem liší (50 vs 50,00), název produktu byl po podpisu změněn, pořadí polí je špatné nebo je použit nesprávný tajný klíč.

Volání bezplatného zjišťování

Přineste chaotický stack nebo zaseknutou integraci - Integrono namapuje nejchytřejší první krok.

Dostanete seniorský pohled na rozpočtové pasti, závislosti a na to, zda koupit, slepit nebo postavit - než začne placená práce. CEO zůstává v okruhu.