Блог

Integrations 7 хв чытання

Інтэграцыя WayForPay на PHP: форма аплаты, подпіс і callback

WayForPay - практычны спосаб прымаць карты ў UAH, USD і EUR для ўкраінскага бізнэсу. У гэтым гайдзе пройдзем усю інтэграцыю на PHP: падпішам аплатную форму, прымем серверны callback, правільна адкажам на яго і пацвердзім статут замовы.

Вокладка артыкула: Інтэграцыя WayForPay на PHP: форма аплаты, подпіс і callback

Коратка

  • У патоку тры часткі: падпісаная форма, якая адпраўляе кліента на WayForPay, серверны callback (serviceUrl) і ваша старонка статусу замовы.
  • Кожны запыт і адказ падпісваецца HMAC_MD5 з сакрэтным ключом мерчанта, парадак палёў важны.
  • Пазначайце замову аплачанай толькі па правераным callback або адказе CHECK_STATUS, але не па вяртанні кліента на сайт.
  • На callback трэба адказаць падпісаным JSON-адказам "accept", інакш WayForPay будзе паўтараць запыт.
  • Захоўвайце суму, валюту і нумар замовы ў сваёй базе і спраўджвайце іх з дадзенымі callback.

Інтэграцыя WayForPay на PHP - гэта трохі кода, але ў некалькіх месцах ціхая памылка прыводзіць альбо да неаплачаных замоў, пазначаных аплачанымі, альбо да аплачаных замоў, якія ніхто не выканаў. Мы выкарыстоўваем WayForPay на ўласных сайтах (у тым ліку для платных паслуг на гэтым сайце) і падключалі яго ў некалькіх крамах кліентаў. Ніжэй - структура, якой мы карыстаемся, і дэталі, якія ёсць у дакументацыі, але на якія часта не зважаюць.

Як уладкованы плацежны струмень

  1. Кліент націскае «Аплаціць». Сервер стварае ў базе замову з унікальным orderReference, фіксаванай сумай і валютай.
  2. Сервер фармуе палі аплатнай формы, падпісвае іх і аддае HTML-форму з аўтаадпраўкай на старонку аплаты WayForPay.
  3. Кліент плаціць на старонцы WayForPay (карта, Apple Pay, Google Pay і іншыя метады, уключаныя ў кабінеце мерчанта).
  4. WayForPay выклікае ваш serviceUrl з вынікам транзакцыі. Менавіта гэтаму сервернаму выкліку вы давяраеце.
  5. Кліента вяртаюць на ваш returnUrl, дзе вы паказваеце бягучы статус замовы.

Пяты крок можа здарыцца раней завяршэння чацвёртага, таму старонка вяртання павінна паказваць "пацвярджаем аплату", пакуль callback не апрацаваны.

Што трэба з кабінета мерчанта

  • merchantAccount - лагін мерчанта.
  • merchantSecretKey - сакрэт для подпісаў. Захоўваеце яго ў зменнай асяроддзі або ў неадсочваным канфігу, але не ў git і не ў франтэндзе.
  • merchantDomainName - дамен сайта, як ён пазначаны ў кабінеце.
  • Уключаныя для вашага акаўнта валюты і спосабы аплаты.

Крок 1. Збярыце і падпішыце плацежную форму

Подпіс - гэта HMAC з MD5 ад абраных палёў, злучаных кропкай з коскай. Для пакупкі парадак такі: merchant account, дамен мерчанта, нумар замовы, дата замовы (Unix timestamp), сума, валюта, затым усе назвы тавараў, усе колькасці і ўсе цэны.

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

Затым адмалюйце форму. Палі тавараў - масівы, таму ў іх суфікс []:

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

Самыя частыя памылкі подпісу: розны фармат колькасці ў подпісы і ў форме (50 і 50.00), назва тавару, змененая пасля подпісу (абрэзана, экранавана), і забыты фіксаваны парадак палёў.

Крок 2. Прыміце callback на serviceUrl

Калі плацёж даходзіць да фінальнага стану, WayForPay адпраўляе вынік на serviceUrl. Чытайце сырое цела (гэта JSON, у некаторых канфігурацыях прыходзяць і палі формы, таму падтрымайце абодва варыянты) і правярайце подпіс. Для callback яна лічыцца па палях: merchant account, нумар замовы, сума, валюта, код аўтарызацыі, маска карты, статус транзакцыі і код прычыны.

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

Для параўнання выкарыстайце hash_equals, каб не было ўцечкі праз час. Толькі пасля валіднага подпісу шукайце замову і спраўджвайце amount і currency з тым, што вы захавалі. Калі значэнні адрозніваюцца, запішыце ў лог і не пазначайце замову аплачанай.

Крок 3. Абнавіце замову і адкажыце правільна

Поле transactionStatus кажа, што адбылося: Approved - грошы спісаны; Declined, Expired і падобныя - няўдача; Pending ці InProcessing - трэба чакаць; Refunded і Voided - грошы вярнуліся. Абнаўляйце замову ў транзакцыі і рабіце гэта ідэмпатэнтна: калі замова ўжо аплачаны, нічога не змяняйце, але ўсё роўна адказвайце поспехам.

WayForPay патрабуе падпісаны адказ, каб ведаць, што вы атрымалі паведамленне. Подпіс лічыцца ад нумара замовы, слова accept і бягучага часу:

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

Калі сервер так не адказвае, WayForPay лічыць дастаўку няўдалай і паўтарае яе, таму вы атрымаеце адзін і той жа callback некалькі разоў. Гэта яшчэ адна прычына для праверкі ідэмпатэнтнасці.

Крок 4. Пацвердзіце статут праз CHECK_STATUS

Callback можна прапусціць: сервер ляжаў, файрвол заблакаваў запыт, дэплой зламаў маршрут. У WayForPay ёсць выклік API CHECK_STATUS, які вяртае бягучы стан замовы па ім нумару. Выкарыстоўвайце яго ў двух месцах: на старонцы звароту, калі callback не прыйшоў праз некалькі секунд, і ў невялікім cron-заданні, якое пераправярае замовы, якія завіслі ў "чакае", скажам, даўжэй пятнаццаці хвілін. Запыт падпісваецца HMAC_MD5 ад merchant account і нумары замовы.

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

Ужывайце тыя ж праверкі, што і для callback: правярайце подпіс адказу, калі ён ёсць, спраўджвайце суму і валюту і абнаўляйце замову ідэмпатэнтна.

Тэставанне перад запускам

  • Выкарыстоўвайце тэставы рэжым і тэставыя карты з дакументацыі WayForPay, пакуль у логах не з'явяцца поўны паспяховы плацёж, адмова і вяртанне.
  • Ваш serviceUrl павінен быць даступны з інтэрнэту праз HTTPS. Лакальна выкарыстоўвайце тунэль або выкаціце на staging-дамен.
  • Лагуйце кожны callback: сырое цела, вынік праверкі і вынік. Сакрэтны ключ у логі не пішыце ніколі.
  • Пераканайцеся, што WAF або абарона ад ботаў (напрыклад, у Cloudflare) не блакуе і не правярае капчай запыты callback ад WayForPay.

Частыя памылкі

  • Давер да return URL. Кліент можа адкрыць яго, не заплаціўшы.
  • Паўторнае выкарыстанне нумара замовы. Для кожнай спробы патрэбен унікальны нумар, інакш спробы пераблытаюцца.
  • Змяненне сумы пасля подпісу або паказ адной цаны пры подпісы другой.
  • Няма адказу на callback падпісаным адказам accept.
  • Пазнака "аплачана" да праверкі сумы і валюты.
  • Некалькі валют, адзін сакрэт і ніякай прывязкі: захоўвайце валюту ў замове і правярайце яе ў callback.

Чым мы можам дапамагчы

Мы робім плацежныя інтэграцыі пад ключ: WayForPay, Stripe, PayPal, рэкурэнтныя плацяжы, звароты і справаздачы зверкі. Калі ў вас ужо працуе аплата, але вы не ўпэўненыя ў сцэнарах збояў, замоўце архітэктурны аналіз, і мы праверым апрацоўку callback, ідэмпатэнтнасць і лагіраванне. Падобны гайд па іншым правайдэру- Stripe webhooks на PHP, а агульны падыход апісаны ў нашым плэйбуку інтэграцый для стартапаў.

Частыя пытанні

Які подпіс выкарыстоўвае WayForPay ?

HMAC_MD5 ад абраных палёў, злучаных кропкай з коскай, з сакрэтным ключом мерчанта. Парадак палёў фіксаваны і адрозніваецца для плацежнай формы, callback і адказу на callback.

Што мусіць вярнуць endpoint serviceUrl?

JSON з orderReference, статутам «accept», бягучым Unix-часам і подпісам HMAC_MD5 ад радка «orderReference;accept;time». Без такога адказу WayForPay паўтарае callback.

Ці можна па старонцы returnUrl адзначаць замову аплачанай?

Не. Браўзэр кліента можа адкрыць гэты адрас, не заплаціўшы. Пазначайце заказ аплачаным толькі пасля праверанага callback на serviceUrl або адказу CHECK_STATUS са статусам Approved і супадаючымі сумай і валютай.

Як праверыць статус замовы WayForPay ?

Адпраўце падпісаны запыт CHECK_STATUS у API WayForPay з вашым merchant account і нумарам замовы. Выкарыстоўвайце яго на старонцы вяртання і ў cron-заданні, якое пераправярае замовы ў статусе pending.

Чаму WayForPay піша, што подпіс няправільны?

Звычайна фармат сумы ў подпісы і ў форме адрозніваецца (50 і 50.00), назву тавара змянілі пасля подпісу, парадак палёў няслушны ці выкарыстоўваецца не той сакрэтны ключ.

Бясплатны званок для выяўлення

Прынясіце бязладны стэк ці завіслую інтэграцыю - Integrono намячае самы разумны першы крок.

Вы атрымліваеце экспертную ацэнку бюджэтных пастак, залежнасцей і таго, ці варта купляць, склейваць ці будаваць - да пачатку платнай працы. CEO застаецца ў кантуры.