В конце прошлого года я был очень близок к тому, чтобы подключить на сайте оплату через CloudPayments, уже настроил API, всё закодил красиво, и сейчас хотел бы поделиться опытом с вами, как я это сделал.
Хочу обратить внимание, что в этом уроке нет никаких партнёрских ссылок и это не проплаченный пост. Я бы добавил партнёрские ссылки, но что-то было лень заморачиваться ради этого.
Кстати, если вы не планируете кодить всё сами, пишите нам, мы вам поможем.
Как весь процесс оплаты будет работать в общем?
- Люди будут заполнять данные карты прямо в форме на вашем сайте.
- При отправке этой формы мы будет отменять непосредственно отправку, затем при помощи JavaScript от CloudPayments преобразовывать данные карты в токен, и только затем отправлять форму.
- PHP-скрипт обработки отправки формы будет находиться у нас на сайте, мы будем производить первый запрос к API, который либо вернёт ошибку, либо успех, либо то, что нужно подтверждение оплаты по смс (3-D Secure).
- (дополнительно) Редиректим пользователя на форму от банка, где он подтверждает платёж.
- (дополнительно) Снова обрабатываем платёж через API, там уже либо успех, либо фейл.
Погнали!
Ах да, ловите ссылку на официальную документацию.
1. Форма оплаты на сайте
Преимущество CloudPayments перед например Робокассой в том, что форма ввода данных карты находится непосредственно на вашем сайте и вы можете закастомить её, как пожелаете.
<div id="errors"></div> <form id="cp-form" action="checkout.php" method="POST"> <input type="text" id="first_name" name="first_name" placeholder="Имя" /> <input type="email" id="email" name="email" placeholder="Email" /> <input type="text" id="ccNo" placeholder="Номер карты" /> <input type="number" id="expMonth" placeholder="Месяц" /> <input type="number" id="expYear" placeholder="Год" /> <input type="password" id="cvv" placeholder="CVV" /> <input name="token" type="hidden" value="" /> </form>
Самое главное, на что тут важно обратить внимание:
- Ни в коем случае не указываем атрибут name для полей карты.
- Также для поля CVV очень рекомендую установить
type="passowrd"
. - В блок
#errors
будем записывать ошибочки.
2. Создание токена при отправке формы
Возможно в предыдущем шаге вы могли заметить скрытое поле token
с пустым значением. В него мы преобразуем данные карты при помощи скрипта от CloudPayments. Для начала нужно этот скрипт подключить на сайт.
Так как мы говорим на этом блоге в основном о WordPress, то и скрипт будем подключать через хук wp_enqueue_scripts
.
add_action( 'wp_enqueue_scripts', function() { wp_enqueue_script( 'cp', 'https://checkout.cloudpayments.ru/checkout.js' ); } );
После этого напишем скрипт обработки формы:
// инициализируем Cloud Payments const checkout = new cp.Checkout({ publicId: 'ПУБЛИЧНЫЙ API КЛЮЧ', }); // обрабатываем отправку формы const paymentForm = document.getElementById( 'cp-form' ); paymentForm.addEventListener( 'submit', ( event ) => { // предотвращаем стандартную отправку event.preventDefault(); const form = event.target; const errContainer = document.getElementById( 'errors' ); // очищаем старые ошибки, если они есть errContainer.style.display = 'none'; errContainer.innerHTML = ''; // получаем данные карты const fieldValues = { cvv: form.querySelector( '#cvv' ).value, cardNumber: form.querySelector( '#ccNo' ).value, expDateMonth: form.querySelector( '#expMonth' ).value, expDateYear: form.querySelector( '#expYear' ).value, } // создаём токен (или по-научному платёжную криптограмму!) checkout.createPaymentCryptogram(fieldValues) .then((cryptogram) => { //console.log(cryptogram); // чисто почекать, что всё ок form.token.value = cryptogram; // записываем в скрытое поле form.submit(); // сабмитим форму ручками }).catch((errors) => { // стандартное сообщение об ошибке let errMessage = 'Что-то пошло не так...'; // каждая ошибка является свойством объекта // так что вы можете все их обработать по разному // errors.cardNumber, errors.cvv, errors.expDateMonth // например if( 'CardNumber_Empty' == errors.cardNumber ) { errMessage = 'Не могли бы вы указать номер карты?'; } errContainer.innerHTML = errMessage; errContainer.style.display = 'block'; }); } );
Как видите, в примере я решил не использовать jQuery и написал обработку на чистом JavaScript, если он пока вызывает у вас трудности, то вэлкам на мой видеокурс.
3. Обработка формы
По сути содержимое файла checkout.php
(или смотря куда вы ссылкаетесь с формы в шаге 1).
// не забываем подключить среду WordPress require_once( __DIR__ . '/wp-load.php' ); $publicID = 'ПУБЛИЧНЫЙ API КЛЮЧ'; $apiKey = 'СЕКРЕТНЫЙ API КЛЮЧ'; // вообще бы рекомендовал создать что-то типо заказа на сайте в этом моменте // $order_id = wp_insert_post( .... // update_post_meta( $order_id, 'ord_payer_email', ... // обрабатываем оплату $response = wp_remote_post( //'https://api.cloudpayments.ru/test', 'https://api.cloudpayments.ru/payments/cards/charge', array( 'method' => 'POST', 'timeout' => 45, 'headers' => array( 'Accept' => 'application/json', 'Content-Type' => 'application/json', 'Authorization' => 'Basic ' . base64_encode( "$publicID:$apiKey" ), ), 'body' => json_encode( array( 'Amount' => $amount, 'Currency' => 'USD', 'InvoiceId' => $order_id, 'IpAddress' => $ip, 'CardCryptogramPacket' => $_POST[ 'token' ], 'CultureName' => 'en-US', 'Payer' => array( 'FirstName' => $_POST[ 'first_name' ] ) ) ) ) ); // добавляем проверки, что запрос не улетел в ошибку if( is_wp_error( $response ) || 'OK' !== wp_remote_retrieve_response_message( $response ) ) { // обрабатываем ошибку } // не ошибка? продолжаем $body = json_decode( wp_remote_retrieve_body( $response ), true ); // это обработка 3-D Secure if( false == $body[ 'Success' ] ) { $MD = isset( $body[ 'Model' ][ 'TransactionId' ] ) && $body[ 'Model' ][ 'TransactionId' ] ? $body[ 'Model' ][ 'TransactionId' ] : false; $PaReq = isset( $body[ 'Model' ][ 'PaReq' ] ) && $body[ 'Model' ][ 'PaReq' ] ? $body[ 'Model' ][ 'PaReq' ] : false; $AcsUrl = isset( $body[ 'Model' ][ 'AcsUrl' ] ) && $body[ 'Model' ][ 'AcsUrl' ] ? $body[ 'Model' ][ 'AcsUrl' ] : false; if( $AcsUrl && $PaReq ) { // формируем HTML форму прямо тут! и редиректим! echo '<p>Редиректим...</p><form id="process3d" action="' . esc_url( $AcsUrl ) . '" method="POST"> <input type="hidden" name="MD" value="' . absint( $MD ) . '"> <input type="hidden" name="PaReq" value="' . esc_attr( $PaReq ) . '"> <input type="hidden" name="TermUrl" value="http://урл-на-вашем-сайте/3ds.php"> </form> <script type="text/javascript"> document.getElementById( 'process3d' ).submit(); </script>'; exit; } // всё ещё тут? значит какая-то ошибка и тут вам надо её обработать } // всё ещё тут? продолжаем! if( true == $body[ 'Success' ] ) { // Ура! оплата прошла, делаем то, что нужно }
Несколько моментов:
- Обратите внимание, что если вы не подключите сразу WordPress (1-2 строчка), то все функции WordPress в последующем коде wp_remote_post(), is_wp_error(), wp_remote_retrieve_response_message() и другие будут выплёвывать ошибку 500.
- Кроме того, если не понимаете, как строить запросы при помощи встроенного в WordPress HTTP API, то смотрите этот видеоурок.
- Может показаться, что редирект на подтверждение платежа выглядит странно (мы формируем HTML форму и сразу забмиттим её в JavaScript, строки 57-64), но это норм практика и вы неоднократно можете встретить её при оплате чего-либо на других сайтах.
Обработка 3-D Secure
Это уже содержимое файла 3ds.php
.
$publicID = 'ПУБЛИЧНЫЙ API КЛЮЧ'; $apiKey = 'СЕКРЕТНЫЙ API КЛЮЧ'; // обрабатываем платёж после ввода кода потрвеждения $response = wp_remote_post( 'https://api.cloudpayments.ru/payments/cards/post3ds', array( 'method' => 'POST', 'timeout' => 45, 'headers' => array( 'Accept' => 'application/json', 'Content-Type' => 'application/json', 'Authorization' => 'Basic ' . base64_encode( "$publicID:$apiKey" ), ), 'body' => json_encode( array( 'TransactionId' => $_POST[ 'MD' ], 'PaRes' => $_POST[ 'PaRes' ] ) ) ) ); // добавляем проверки, что запрос не улетел в ошибку if( is_wp_error( $response ) || 'OK' !== wp_remote_retrieve_response_message( $response ) ) { // обрабатываем ошибку } $body = json_decode( wp_remote_retrieve_body( $response ), true ); if( true == $body[ 'Success' ] ) { // Ура! всё круто } else { // Не круто... // Код ошибки можно кстати вытащить так: $ReasonCode = isset( $body[ 'Model' ][ 'ReasonCode' ] ) && $body[ 'Model' ][ 'ReasonCode' ] ? absint( $body[ 'Model' ][ 'ReasonCode' ] ) : 'declined'; }
И ещё, чтобы вам не пришлось искать тестовые номера карт, вот они.
Источник: Блог Миши Рудрастых