Подключение оплаты через CloudPayments API

В конце прошлого года я был очень близок к тому, чтобы подключить на сайте оплату через CloudPayments, уже настроил API, всё закодил красиво, и сейчас хотел бы поделиться опытом с вами, как я это сделал.

Хочу обратить внимание, что в этом уроке нет никаких партнёрских ссылок и это не проплаченный пост. Я бы добавил партнёрские ссылки, но что-то было лень заморачиваться ради этого.

Кстати, если вы не планируете кодить всё сами, пишите нам, мы вам поможем.

Как весь процесс оплаты будет работать в общем?

  1. Люди будут заполнять данные карты прямо в форме на вашем сайте.
  2. При отправке этой формы мы будет отменять непосредственно отправку, затем при помощи JavaScript от CloudPayments преобразовывать данные карты в токен, и только затем отправлять форму.
  3. PHP-скрипт обработки отправки формы будет находиться у нас на сайте, мы будем производить первый запрос к API, который либо вернёт ошибку, либо успех, либо то, что нужно подтверждение оплаты по смс (3-D Secure).
  4. (дополнительно) Редиректим пользователя на форму от банка, где он подтверждает платёж.
  5. (дополнительно) Снова обрабатываем платёж через 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';
}

И ещё, чтобы вам не пришлось искать тестовые номера карт, вот они.

Миша

Впервые познакомился с WordPress в 2009 году. Организатор и спикер на конференциях WordCamp. Преподаватель в школе Нетология.

Пишите, если нужна помощь с сайтом или разработка с нуля.

Оставить комментарий

Если вы хотите добавить код, не забудьте обернуть его в <pre lang="php"></pre>, если же код меньше одной строчки, то можно и в <code></code>.

Нажав на кнопку, вы соглашаетесь с тем, как обрабатываются персональные данные.