Integracja WayForPay z PHP: formularz płatności, podpis i oddzwonienie
WayForPay to praktyczny sposób akceptowania płatności kartą w UAH, USD i EUR od ukraińskiej firmy. Ten przewodnik opisuje pełną integrację z PHP: podpisanie formularza płatności, otrzymanie wywołania zwrotnego serwera, prawidłową odpowiedź na nie i potwierdzenie statusu zamówienia.
W skrócie
- Przepływ składa się z trzech części: podpisanego formularza płatności, który wysyła klienta do WayForPay, wywołania zwrotnego między serwerami (
serviceUrl) oraz własnej strony statusu zamówienia. - Każde żądanie i odpowiedź są podpisane za pomocą HMAC_MD5 przy użyciu tajnego klucza sprzedawcy; kolejność w terenie ma znaczenie.
- Oznacz zamówienie jako opłacone tylko na podstawie zweryfikowanego oddzwonienia lub odpowiedzi
CHECK_STATUS, a nie od klienta powracającego na Twoją witrynę. - Musisz odpowiedzieć na wywołanie zwrotne, podpisując odpowiedź „akceptuj” w formacie JSON, w przeciwnym razie WayForPay będzie ponawiać próby.
- Przechowuj kwoty, walutę i numer zamówienia w swojej bazie danych i porównuj je z informacją zwrotną.
Integracja WayForPay z PHP to niewielka ilość kodu, ale zawiera kilka miejsc, w których cichy błąd oznacza albo niezapłacone zamówienia oznaczone jako opłacone, albo opłacone zamówienia, które nigdy nie zostaną zrealizowane. Prowadzimy realizację transakcji WayForPay na naszych własnych stronach, łącznie z usługami płatnymi na tej stronie, i połączyliśmy ją z kilkoma sklepami klientów. Poniżej znajduje się struktura, której używamy, ze szczegółami wymienionymi w dokumentacji, ale ludzie mają tendencję do przeglądania.
Jak działa przepływ płatności
- Klient klika „Zapłać”. Twój serwer tworzy w bazie danych zamówienie z unikalnym
orderReference, stałą kwotą i walutą. - Twój serwer tworzy pola formularza płatności, podpisuje je i renderuje automatycznie przesyłany formularz HTML, który jest publikowany na stronie płatności WayForPay.
- Klient płaci na stronie WayForPay (karta, Apple Pay, Google Pay i inne metody włączone na Twoim koncie sprzedawcy).
- WayForPay wywołuje Twój
serviceUrlz wynikiem transakcji. To połączenie serwer-serwer jest tym, czemu ufasz. - Klient zostaje przekierowany na Twój
returnUrl, gdzie pokazujesz aktualny status zamówienia.
Krok 5 może nastąpić przed zakończeniem kroku 4, dlatego na stronie zwrotnej musi znajdować się informacja „potwierdzam płatność” do czasu przetworzenia oddzwonienia.
Czego potrzebujesz z konta sprzedawcy
merchantAccount- Twój login sprzedawcy.merchantSecretKey- sekret używany do podpisów. Trzymaj go w zmiennej środowiskowej lub nieśledzonym pliku konfiguracyjnym, nigdy w git lub kodzie front-endu.merchantDomainName– domena Twojej witryny zarejestrowana na koncie.- Waluty i metody płatności włączone dla Twojego konta.
Krok 1. Zbuduj i podpisz formularz płatności
Podpis to HMAC z MD5 nad wybranymi polami połączonymi średnikiem. W przypadku zakupu zamówienie obejmuje: konto sprzedawcy, nazwę domeny sprzedawcy, numer zamówienia, datę zamówienia (znacznik czasu Unix), kwotę, walutę, następnie wszystkie nazwy produktów, wszystkie liczby produktów i wszystkie ceny produktów.
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);
Następnie wyrenderuj formularz. Pola produktów są tablicami, więc używają przyrostka []:
<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>
Najczęstsze błędy w podpisie to: inny format liczb w podpisie i formularzu (50 vs 50.00), nazwa produktu, która po podpisaniu zostaje zmieniona (przycięta, ze znacznikiem HTML) oraz zapomniano o ustalonej kolejności pól.
Krok 2. Odbierz wywołanie zwrotne na serviceUrl
Kiedy płatność osiągnie stan końcowy, WayForPay publikuje wynik w serviceUrl. Przeczytaj surową treść (jest to JSON; niektóre konfiguracje udostępniają również pola formularzy, więc obsłuż oba) i zweryfikuj podpis. W przypadku wywołania zwrotnego obliczane są na podstawie: konta sprzedawcy, numeru zamówienia, kwoty, waluty, kodu autoryzacyjnego, maski karty, statusu transakcji i kodu przyczyny.
$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;
}
Do porównania użyj hash_equals, aby uniknąć wycieków czasu. Dopiero po uprawomocnieniu się podpisu należy sprawdzić zamówienie i porównać amount i currency z tym, co przechowujesz. Jeśli się różnią, zarejestruj to i nie oznaczaj zamówienia jako opłaconego.
Krok 3. Zaktualizuj zamówienie i odpowiedz poprawnie
Pole transactionStatus informuje, co się stało: Approved oznacza, że pieniądze zostały przejęte; Declined, Expired i podobne wartości oznaczają awarię; Pending lub InProcessing oznaczają oczekiwanie; Refunded i Voided oznaczają, że pieniądze wróciły. Zaktualizuj odpowiednio swoje zamówienie w ramach transakcji i uczyń je idempotentnym: jeśli zamówienie zostało już opłacone, nie rób nic i nadal odpowiedz pomyślnie.
WayForPay wymaga podpisanej odpowiedzi, aby wiedzieć, że otrzymałeś wiadomość. Podpis znajduje się nad numerem zamówienia, słowem „ accept i aktualną godziną:
$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);
Jeśli Twój serwer nie odpowie w ten sposób, WayForPay potraktuje dostawę jako nieudaną i ponawia próbę, co oznacza, że otrzymasz kilka razy to samo wywołanie zwrotne. To kolejny powód sprawdzania idempotencji.
Krok 4. Potwierdź status za pomocą CHECK_STATUS
Wywołania zwrotne mogą zostać pominięte: serwer nie działał, zapora sieciowa zablokowała żądanie, wdrożenie przerwało trasę. WayForPay oferuje wywołanie API CHECK_STATUS, które poprzez jego odniesienie zwraca aktualny stan zamówienia. Użyj go w dwóch miejscach: na stronie zwrotnej, jeśli wywołanie zwrotne nie nadeszło po kilku sekundach, oraz w małym zadaniu cron, które ponownie sprawdza zamówienia, które pozostały w stanie „oczekującym” przez ponad, powiedzmy, piętnaście minut. Żądanie jest podpisane za pomocą HMAC_MD5 za pośrednictwem konta sprzedawcy i numeru zamówienia.
$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);
Zastosuj te same kontrole, co w przypadku wywołania zwrotnego: zweryfikuj podpis odpowiedzi, jeśli jest obecny, porównaj kwotę i walutę oraz idempotentnie zaktualizuj zamówienie.
Testowanie przed rozpoczęciem transmisji na żywo
- Korzystaj z trybu testowego i testuj karty z dokumentacji WayForPay, aż w Twoich logach pojawi się pełna płatność, odrzucenie i zwrot pieniędzy.
- Twój
serviceUrlmusi być dostępny z Internetu za pośrednictwem protokołu HTTPS. Na hoście lokalnym użyj tunelu lub wdróż w domenie tymczasowej. - Rejestruj każde wywołanie zwrotne wraz z jego surową treścią, wynikiem weryfikacji i wynikiem. Nigdy nie loguj tajnego klucza.
- Upewnij się, że ochrona WAF lub botów (na przykład w Cloudflare) nie kwestionuje ani nie blokuje żądań oddzwonienia WayForPay.
Typowe błędy
- Ufanie zwrotnemu adresowi URL. Klient może je otworzyć bez płacenia.
- Ponowne użycie odniesienia do zamówienia. Każda próba wymaga unikalnego odniesienia, w przeciwnym razie pomieszasz próby.
- Zmiana kwoty po podpisaniu lub pokazanie jednej ceny i podpisanie drugiej.
- Brak odpowiedzi na wywołanie zwrotne z podpisaną odpowiedzią akceptacji.
- Oznacz jako zapłacone przed sprawdzeniem kwoty i waluty.
- Kilka walut, jeden wspólny sekret i brak mapowania: zachowaj walutę w zamówieniu i zweryfikuj ją przy oddzwonieniu.
Jak możemy pomóc
Budujemy kompleksowe integracje płatności: WayForPay, Stripe, PayPal, rozliczenia cykliczne, zwroty pieniędzy i raporty uzgadniające. Jeśli masz już działającą kasę, ale nie masz pewności co do jej trybów awaryjnych, zarezerwuj analizę architektury, a my sprawdzimy obsługę wywołań zwrotnych, idempotencję i rejestrowanie. Podobny przewodnik dotyczący innego dostawcy znajdziesz w artykule Elementy webhook Stripe w PHP, a ogólne podejście znajdziesz w naszym poradniku dotyczącym integracji startowej.
Najczęściej zadawane pytania
Z jakiego podpisu korzysta WayForPay ?
HMAC_MD5 nad wybranymi polami połączonymi średnikiem przy użyciu tajnego klucza sprzedawcy. Kolejność pól jest stała i różni się dla formy płatności, oddzwonienia i odpowiedzi na oddzwonienie.
Co musi zwrócić punkt końcowy serviceUrl?
Odpowiedź JSON z OrderReference, statusem „accept”, bieżącym czasem uniksowym i podpisem obliczonym jako HMAC_MD5 „orderReference;accept;time”. Bez tego WayForPay ponawia próbę wywołania zwrotnego.
Czy mogę zaufać stronie returnUrl i oznaczyć zamówienie jako opłacone?
Nie. Przeglądarka klienta może otworzyć ten adres URL bez płacenia. Oznaczaj zamówienia jako opłacone dopiero po zweryfikowanym wywołaniu zwrotnym serviceUrl lub odpowiedzi CHECK_STATUS ze statusem Zatwierdzone i odpowiadającą kwotą i walutą.
Jak sprawdzić status zamówienia WayForPay ?
Wyślij podpisane żądanie CHECK_STATUS do API WayForPay, podając swoje konto sprzedawcy i numer zamówienia. Użyj go na stronie powrotu i w zadaniu cron, które ponownie sprawdza oczekujące zamówienia.
Dlaczego WayForPay wyświetla komunikat, że podpis jest nieprawidłowy?
Zwykle format kwoty różni się w podpisie od formularza (50 vs 50,00), nazwa produktu została zmieniona po podpisaniu, niewłaściwa kolejność pól lub użyto złego tajnego klucza.
Bezpłatna rozmowa odkrywcza
Bałagan w stacku albo zawieszona integracja - Integrono zaplanuje najmądrzejszy pierwszy ruch.
Dostajesz senior-ocenę pułapek budżetowych, zależności i tego, czy kupować, sklejać czy budować - zanim zacznie się płatna praca. CEO zostaje w pętli.