+7 (987) 026-06-36
Написать в Telegram

Оставить заявку
stanislav_web

Прием платежей на сайте через Юкассу

Прием платежей на сайте через Юкассу Самое подробное описание процесса оплаты через сервис ЮKassa (ЮМани) с помощью прямых запросов к API через PHP cURL

Прием платежей на сайте через Юкассу

Самое подробное описание процесса оплаты через сервис ЮKassa (ЮМани) с помощью прямых запросов к API через PHP cURL

📅 02 апреля 2023

👁‍🗨 2808

🔥 0

Прием платежей на сайте через Юкассу

Данное решение по приёму платежей через юкассу было выполнено на системе CMS 1С-Битрикс, поэтому подключаемые файлы и используемые методы взяты оттуда, но понимая принцип работы и зная язык программирования, его можно кастомизировать без проблем. К тому же для удобства я оставил максимальное количество комментариев в коде.

Принцип работы

  1. Пользователь заходит на раздел /pay/, вводит данные в форму, нажимает оплатить
  2. Пользователя редиректит на страницу оплаты, он проводит оплату, нажимает "вернуться в магазин"
  3. Пользователя возвращает на /pay/?paid и под формой он получает уведомление "Если возникли вопросы, свяжитесь с техподдержкой."
  4. После совершения оплаты, Юкасса отправляет объект с данными о текущем платеже на адрес который указывается в личном кабинете Юкассы в разделе Интеграция -> HTTP-уведомления. В нашем примере на https://stanislav-web.com/pay/paid.php
  5. Мы проводим обработку полученных данных в файле paid.php и отправляем данные из описания платежа на почту

Последовательность настройки

  1. Получаем shop_id и secret_key в личном кабинете магазина
  2. Добавляем форму HTML и код PHP для отправки запроса в Юкассу на создание платежа
  3. Настраиваем в личном кабинете HTTP-уведомления (на скрине ползунок установлен только на получение информации об успешном платеже)
  4. Настраиваем код PHP на получение запроса об успешно созданном платеже и отправление на почту
Прием платежей на сайте через Юкассу
<? // index.php
require ($_SERVER['DOCUMENT_ROOT'] . '/bitrix/header.php');
$APPLICATION->SetTitle('Оплата');
$domain = ((!empty($_SERVER['HTTPS'])) ? 'https' : 'http') . '://' . $_SERVER['HTTP_HOST']; // Получаем адрес сайта
$curPage = $APPLICATION->GetCurPage(); // Получаем текущий раздел
?>

<div class="pay">
	<form action="/pay/" method="POST" class="pay-form" autocomplete="on"><!-- action="/pay/" - значит POST-запрос придёт на раздел /pay/ -->
		<input type="hidden" name="url" value="<?= $domain . $curPage . '?paid'; ?>"><!-- В ключ url массива $_POST передадим текущий раздел с GET-параметром ?paid, чтобы потом его передать в return_url, этот адрес будет установлен на кнопке возврата в магазин после совершения оплаты -->
		<input type="text" name="fio" class="pay-form__input" required placeholder="ФИО">
		<input type="text" name="dogovor" inputmode="tel" minlength="9" maxlength="9" class="pay-form__input" required placeholder="Номер договора">
		<input type="text" name="telefon" inputmode="tel" class="pay-form__input" required placeholder="Контактный номер телефона">
		<input type="email" name="email" class="pay-form__input" required placeholder="Email">
		<input type="number" name="summa" inputmode="tel" class="pay-form__input" required placeholder="Сумма оплаты">
		<input type="submit" class="pay-form__btn" value="Оплатить">
	</form>
	<? if (isset($_GET['paid'])) { ?><!-- Проверка на вышеустановленный GET-параметр, он будет установлен после возврата со страницы оплаты, тогда выведется блок ниже с классом pay__support -->
		<div class="pay__support">Если возникли вопросы, свяжитесь с техподдержкой.</div>
	<? } ?>
</div>

<?
if ( // Если в массив $_POST пришли все нижеперечисленные ключи
	!empty($_POST['fio'])
	&&
	!empty($_POST['dogovor'])
	&&
	!empty($_POST['telefon'])
	&&
	!empty($_POST['email'])
	&&
	!empty($_POST['summa'])
) {

	// Функция для генерации ключа идемпотентности, уникального значения операции на стороне сайта
	function gen_uuid() {
		return sprintf( '%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
			mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ),
			mt_rand( 0, 0xffff ),
			mt_rand( 0, 0x0fff ) | 0x4000,
			mt_rand( 0, 0x3fff ) | 0x8000,
			mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff )
		);
	};

	// Функция для отправки запроса в Юкассу
	function gateway($data) {
		$ch = curl_init('https://api.yookassa.ru/v3/payments');
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
		curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
		curl_setopt($ch, CURLOPT_HEADER, false);
		curl_setopt($ch, CURLOPT_USERPWD, 'shop_id:secret_key'); // Здесь указывается тестовый либо боевой shop_id и secret_key вашего магазина
		curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/json', 'Idempotence-Key: ' . gen_uuid()));
		curl_setopt($ch, CURLOPT_POST, 1);
		curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
		$res = curl_exec($ch);
		curl_close($ch);
		$response = json_decode($res, true);

		return $response;
	};

	// Собираем строку из данных в описание платежа, чтобы потом разбить и отправить на почту, больше у Юкассы нет полей для текста
	$description = $_POST['fio'] . ', ' . $_POST['dogovor'] . ', ' . $_POST['telefon'] . ', ' . $_POST['email'];

	// Генерируем рандомное значение от 0 до 1000000 млн для идентификатора заказа order_id
	$rand = rand(0, 1000000);

	// Собираем массив из данных
	$data = [
		'amount' => [
			'value' => $_POST['summa'],
			'currency' => 'RUB',
		],
		'capture' => true,
		'confirmation' => [
			'type' => 'redirect',
			'return_url' => $_POST['url'],
		],
		'description' => $description,
		'metadata' => [
	 		'order_id' => $rand,
	 	]
	];

	// Преобразовываем массив $data в JSON
	$data = json_encode($data, JSON_UNESCAPED_UNICODE);

	// Передаём аргументом массив $data в формате JSON в функцию gateway для отправки запроса в Юкассу
	$response = gateway($data);

	// Если в ответе от Юкассы мы получим какую-либо ошибку, в $response вернётся ключ type со значением error
	if (!empty($response['type'])) {

		// И если будет ошибка, выводим строку в виде блока с классом pay__support
		echo '<div class="pay__support">Произошла ошибка! Попробуйте снова или свяжитесь с техподдержкой.</div>';

	} else {

		// Если ошибок нет, производим редирект пользователя на страницу оплату, адрес которой придёт в ответе
		header('Location: ' . $response['confirmation']['confirmation_url'], true, 301);
		exit();

	};
};

?>

<? require ($_SERVER['DOCUMENT_ROOT'] . '/bitrix/footer.php'); ?>
Прием платежей на сайте через Юкассу
<? // /pay/paid.php
require ($_SERVER['DOCUMENT_ROOT'] . '/bitrix/modules/main/include/prolog_before.php');

// Получаем объект от Юкассы
$source = file_get_contents('php://input');
$requestBody = json_decode($source, true);

// Если получаем ответ об успешной оплате
if ($requestBody['event'] == 'payment.succeeded') {
	// Проводим манипуляции с полученными данными, формируем новый массив и отправляем на почту
	$descriptionExplode = explode(', ', $requestBody['object']['description']); // Здесь разбиваем ту самую строку, которую формировали и передавали в $description
	$fio = $descriptionExplode[0];
	$dogovor = $descriptionExplode[1];
	$telefon = $descriptionExplode[2];
	$email = $descriptionExplode[3];
	$summa = $requestBody['object']['amount']['value'] . ' ' . $requestBody['object']['amount']['currency'];

	$subject = 'Оплата на сайте';

	$message = '';
	$message .= '<b>ФИО: </b>' . $fio . '<br>';
	$message .= '<b>Номер договора: </b>' . $dogovor . '<br>';
	$message .= '<b>Контактный номер телефона: </b>' . $telefon . '<br>';
	$message .= '<b>Email: </b>' . $email . '<br>';
	$message .= '<b>Сумма оплаты: </b>' . $summa;

	$mail = 'iam@stanislav-web.com'; // Получатель письма

	$headers  = "Content-type: text/html; charset=utf-8\r\n";
	$headers .= "From:stanislav-web.com <from@stanislav-web.com>\r\n";
	$headers .= "Reply-To: from@stanislav-web.com\r\n";

	mail($mail, $subject, $message, $headers, '-f from@stanislav-web.com');

	// Такое тело будет у письма которое придёт на почту:
	// 
	// ФИО: Иванов Иван Иванович
	// Номер договора: 123456789
	// Контактный номер телефона: +7 (999) 888-77-66
	// Email: test@test.ru
	// Сумма оплаты: 777 RUB
}
?>

Если в ответе возвращается ошибка "Receipt is missing or illegal", значит отсутствует информации о чеке. Скорее всего в "боевом" магазине включена онлайн-касса.

В таком случае, массив из данных будет выглядеть вот так:

<?
// Собираем массив из данных
$data = [
	'amount' => [
		'value' => $post['summa'],
		'currency' => 'RUB',
	],
	'capture' => true,
	'confirmation' => [
		'type' => 'redirect',
		'return_url' => $post['url'],
	],
	'description' => $post['description'],
	'metadata' => [
		'order_id' => $post['id'],
	],
	'receipt' => [
		'customer' => [
			'email' => $post['email'],
		],
		'items' => [
			[
				'description' => $post['description'],
				'quantity' => '1.00',
				'amount' => [
					'value' => $post['summa'],
					'currency' => 'RUB'
				],
				'tax_system_code' => '1',
				'vat_code' => '1',
				'payment_mode' => 'full_payment',
				'payment_subject' => 'service'
			]
		]
	]
];
?>