Blog

Integracje 8 min czytania

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.

Ilustracja okładkowa: Integracja WayForPay z PHP: formularz płatności, podpis i oddzwonienie

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

  1. Klient klika „Zapłać”. Twój serwer tworzy w bazie danych zamówienie z unikalnym orderReference, stałą kwotą i walutą.
  2. 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.
  3. Klient płaci na stronie WayForPay (karta, Apple Pay, Google Pay i inne metody włączone na Twoim koncie sprzedawcy).
  4. WayForPay wywołuje Twój serviceUrl z wynikiem transakcji. To połączenie serwer-serwer jest tym, czemu ufasz.
  5. 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 serviceUrl musi 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.