Как настроить эквайринг в Django. Пример с банком Тинькофф
Предпологается, что у вас уже имеется аккаунт зарегистрированной компании в тинькофф бизнес https://business.tinkoff.ru/
Значит открыт расчётный счёт и подключена облачная касса, с помощью которых мы будем принимать и обрабатывать платежи на сайте.
Какие данные нам для этого понадобятся?
1) Заходим во вкладку Магазины и создаём Интернет-Магазин
2) заходим в карточку созданного магазина
3)во вкладке способы оплаты подключаем нужные способы
4)заходим во вкладку терминалы, и видим два терминала тестовый и рабочий. Начнём работать с тестовым терминалом тот, что слева. т.е. в своём коде будем указывать его данные. Кнопку настроить можно не трогать, мы вручную(через код) в методе Init с помощью json, будем указывать нужные нам страницы успеха и страницы ошибок. А пока нажимаем на кнопку Тестировать
5) Здесь указаны карточки с помощью которых мы сможем получить нужные тесты при оплате.
6) Во вкладке Формирование чека дополнительные тесты для чеков.
Прежде чем приступить к реализации интеграции по API стоит понять общий принцип работы.
Вы создаёте функционал приёма заказов на Вашем сайте с соответствующей формой, по событию Submit, которой вы получаете id пользователя(допустим на вашем сайте есть авторизация), контактные данные, id выбранного товара и количество, цена с учётом скидок и промокодов т.е. в совокупности вы получаете ID этого заказа, который сохраняется в вашей базе данных. И это совершенно обычная процедура.
И в шаблоне, в атрибуте action этой формы, ведущего в представление во Views, вы создаете запрос метода Init, на инициализацию с необходимыми параметрами(которые получаются в request обычно через POST, обрабатываются необходимым нам образом, и отправляются в json формате через POST на url банка, который принимает метод Init в обработку), в массиве POST передаются ID заказа, сумма, описание и прочая информация в том числе для формирования кассового чека.
Другими словами, вы отправляете этот запрос на сервер Тинькофф Банка через конечную точку API. нужные Url и формат json для запросов нужно узнать в их документации по API
https://www.tinkoff.ru/kassa/develop/api/payments/
а также здесь
https://acdn.tinkoff.ru/static/documents/merchant_api_protocoI_eacq.pdf
Сервер Тинькофф Банка обрабатывает запрос и формирует ответ, содержащий платежный URL и другую необходимую информацию, которую мы тоже получаем в json формате, и самое главное для нас сейчас это этот URL.
response = requests.post(url, json=payment_data)
Получив этот url и сделав редирект на него, мы отправляем пользователя на сайт банка в котором он заполнит платёжную форму либо сделает оплату по QR коду.
Поэтому Вам не нужно городить огород с принятием платёжных данных от пользователя, номера карты или qr кодов, эта операция по формированию платёжной формы привязанной по переданному ID заказа в нашей системе, и ответственность за её конфиденциальность и безопасность, ложиться на плечи банка.
Другими словами, Сервер Тинькофф Банка в качестве результата отправленного метода init, сгенерирует для вас платежный URL, по которому вы сможете перенаправить пользователя на платежную форму. Этот URL-адрес обычно содержит уникальный идентификатор платежа или другой идентификатор, который можно использовать для обратной связи платежа с вашим заказом. Получив после инициации платежа данные с нужным URL, Вы только перенаправляете пользователя на этот URL-адрес платежа, где он вводит свои платежные реквизиты и завершает транзакцию.
После завершения транзакции сервер Тинькофф Банка обратно редиректит пользователя на ваш сайт(и также в POST передаёт на ваш сервер детали транзакции), на соответствующие страницы вашего сайта, которые были указаны в json запросе во время Init или в настройках терминала в WEB интерфейсе на сайте банка.
Страница успеха SuccessURL, Страница ошибок FailURL, Страница уведомлений NotificationURL
подробнее о всех параметрах запроса по каждому методу, например init смотрим на их сайте
https://www.tinkoff.ru/kassa/develop/api/payments/init-description/
уведомление, отправленное сервером Тинькофф Банка, обычно содержит тот же идентификатор или идентификатор платежа, а также другие сведения о транзакции, такие как статус транзакции, сумма и валюта. Вы можете использовать эту информацию для обновления записей и уведомления пользователя о статусе транзакции.
в файле настроек проекта settings.py добавляем секретные константы(лучше конечно подобные вещи делать через переменные окружения environ.Env()). Для примера просто указываем их напрямую:
нужные нам данные указаны, на сайте банка, (изображение 4).
Файл settings.py
TERMINALPASSWORD='xxxxxxxxxxxxx'
Вот пример файла views.py фрагмент для отправки метода Init в банк
☯
Terminal:
⌕
≡
✕
#описание метода в документации #https://www.tinkoff.ru/kassa/develop/api/payments/init-description/ # Define the payment information as a dictionary # Replace with your Tinkoff Merchant Terminal Key terminal_key = settings.TERMINALKEY # Replace with your Tinkoff Merchant Secret Key secret_key = settings.TERMINALPASSWORD values = { 'Amount': str(cert.price*100)+'.00',#*100 потому что указывается сумма в копейках/ и в других методах почему то идёт сразу с .00 а здесь без. глюк матрицы тинькофф... 'Description': str(cert.product.title)+' ('+str(cert.count)+') шт.', # The order description 'OrderId': str(cert.number_cert), 'Password': secret_key, 'TerminalKey': terminal_key } # Concatenate all values in the correct order concatenated_values = ''.join([values[key] for key in (values.keys())]) # Calculate the hash using SHA-256 algorithm hash_object = hashlib.sha256(concatenated_values.encode('utf-8')) token = hash_object.hexdigest() logger.debug('shop.views(1159) buy token {} ',token) payment_data = { 'TerminalKey':terminal_key, 'OrderId': str(cert.number_cert), 'Amount': str(int(cert.price*100)),#*100 потому что указывается сумма в копейках "Description": str(cert.product.title)+' ('+str(cert.count)+') шт.', # The order description "Language": "ru", # The language code (ru or en) "PayType": "O", # The payment type (O for one-time payment) "Recurrent": "N", # Indicates whether the payment is recurrent (N for no) # "CustomerKey": "1234567890", # The customer key (optional) 'Token':token, 'DATA': { 'Phone': cert.investor.phone, 'Email': cert.investor.email, }, 'PaymentMethod': { 'Type': 'Mobile', 'Data': {}, }, # данные чека 'Receipt': { 'Phone': str(cert.investor.phone), 'Email': str(cert.investor.email), 'Taxation':'usn_income',#упрощёнка 'Items':[{ #https://www.tinkoff.ru/kassa/develop/api/receipt/#Items 'Name':str(cert.product.title), 'Quantity':str(cert.count), 'Amount': str(int(cert.price*100)), 'Tax':'none',#без НДС 'Price':str(int(cert.product.price*100)), },] }, # your receipt data "SuccessURL": str(request.scheme+'://'+request.get_host()+"/youSuccess_path/?you_get="+str(your.pk)), # The URL for successful payments # "NotificationURL":request.scheme+'://'+request.get_host()+request.get_full_path()+'&CertId='+str(cert.pk), # The URL for payment notifications "FailURL": str(request.scheme+'://'+request.get_host()+"/youFailURL_path/?you_get="+str(your.pk)), The URL for failed payments } #путь по которому мы отправляем свой запрос, прописан в документации банка url = "https://securepay.tinkoff.ru/v2/payments/" response = requests.post(url, json=payment_data) logger.debug('shop.views Buy (1143) Tinkoff response {}',response.json()) if response.json()['Success']: payment_url = response.json()['PaymentURL'] # Redirect the user to the payment form Certificate.objects.filter(id=cert.id).update(PaymentId=response.json()['PaymentId']) # отправляем пользователя на платёжную форму return redirect(payment_url) else: # result = False message = response.json()['Message']+' '+response.json()['Details'] messages.error(request, message) logger.debug('shop.views(1191) buy response payment_url response {} ',response.json())
при этом в response мы получаем не только url но и следующие поля:
☯
Terminal:
⌕
≡
✕
response.json() == {'Success': True, 'Error Code': '0', 'TerminalKey': '111111111111DEMO', 'Status': 'NEW', 'PaymentId': '222222222', 'OrderId' : '333333', 'Amount': 1000000, 'PaymentURL':'https://securepayments.tinkoff.ru/xxxxxxxx' }
например мы можем сразу сохранить в объект, поле Идентификатор платежа PaymentId, чтобы опперативно изменить статус платежа, и/или оформить возврат при такой необходимости.
на странице успеха или провала мы можем дополнительно вызвать метод CheckOrder, чтобы получить необходимую информацию создадим для этого свой метод.
Обратите внимание на код выше, что хэш токена в методе Init формируется из 'Amount'+'Description'+'OrderId'+'Password'+'TerminalKey'
Именно в этом порядке и именно из этих полей.
А очерёдность и данные для Токена в хэшированном виде для метода CheckOrder уже такие: 'OrderId'+'Password'+'TerminalKey'
Таким образом для каждого метода может быть свой токен, конкретно эти данные, не было видно в их документации на момент моего решения этого вопроса, уточнили в тех.поддержке. До этого я ставил одинаковые токены в этих методах и получал ошибку, которая приходила мне в response деталях платежа.
p.s. Мне гораздо удобнее пользовать для этих целей logger, которое устанавливается в окружение через pip чем принтами
# logger.debug('shop.views(229) response_data {} ',response_data)
☯
Terminal:
⌕
≡
✕
user@itdid:~ $ cat log.log ... 2023-03-16T18:59:53.615397+0300 views shop.views(229) response_data {'Success': False, 'ErrorCode': '204', 'Message': 'Неверные параметры.', 'Details': 'Неверный токен. Проверьте пару TerminalKey/SecretKey.'} ....
Давайте посмотрим теперь на код метода CheckOrder, где ID заказа вы указываете из вашей базы, а пароль от терминала и сам ключ терминала также берётся на веб интерфейсе в вашем личном кабинете, которые мы уже прописали в settings.py выше.
☯
Terminal:
⌕
≡
✕
def CheckOrder(obj): try: url = "https://securepay.tinkoff.ru/v2/CheckOrder" # Replace with your Tinkoff Merchant Terminal Key terminal_key = settings.TERMINALKEY # Replace with your Tinkoff Merchant Secret Key secret_key = settings.TERMINALPASSWORD values = { # 'Amount': str(obj.price*100),#*100 потому что указывается сумма в копейках # 'Description': str(obj.product.title)+' ('+str(obj.count)+') шт.', # The order description 'OrderId': str(obj.number_cert), 'Password': secret_key, 'TerminalKey': terminal_key } concatenated_values = ''.join([values[key] for key in (values.keys())]) # logger.debug('shop.views(283) CheckOrder concatenated_values {} ',concatenated_values) # Calculate the hash using SHA-256 algorithm hash_object = hashlib.sha256(concatenated_values.encode('utf-8')) token = hash_object.hexdigest() logger.debug('shop.views(286) CheckOrder token {} ',token) # Make a request to the Tinkoff Merchant API endpoint response = requests.post(url, json={'TerminalKey': terminal_key, 'OrderId': obj.number_cert, 'Token': token}) logger.debug('shop.views(289) CheckOrder ID_CERT {} response {}',str(obj.number_cert),response.status_code) # Check the response status code if response.status_code == requests.codes.ok: # Parse the JSON response response_data = response.json() # logger.debug('shop.views(244) CheckOrder response_data {} ',response_data) # Check the payment status if response_data['Success']: payments = response_data['Payments'][0] # logger.debug('shop.views(249) CheckOrder payments {} ',payments) if payments['Success']: # messages.success(request, 'Оплата произведена '+payment_status+', вся необходимая информация отправлена Вам на email') message='Произведена оплата '+payments['Message'] logger.debug('shop.views(253) CheckOrder message {} payment_status {}',message, payments['Status']) else: message='Недоразумение при оплате '+payments['Message'] logger.debug('shop.views(256) CheckOrder message {} payment_status {}',message, payments['Status']) return {'status':payments['Success'],'message':message} else: # Handle the error response error_message = response_data['Message']+' '+response_data['Details'] # messages.error(request, 'Оплата не произведена '+error_message) logger.debug('(ERR)shop.views(311) CheckOrder number_cert {} err {}',str(obj.number_cert),error_message) message = error_message return {'status':False,'message':message} else: # Handle the request error logger.error('(ERR)shop.views(319) CheckOrder ID_CERT {} Ошибка при проверке статуса платежа Request error: {}',str(obj.number_cert),response.status_code) message='Оплата не произведена '+response.status_code return {'status':False,'message':message} except Exception as err: logger.debug('(ERR)shop.views(326) CheckOrder response{} GetState err {}',response,err)
Подытожим мысль, после оплаты, сайт банка направляет пользователя обратно на наш сайт, на соответствующую страницу, в зависимости от результата либо это страница успеха, либо провала, т.е. на те самые страницы, которые мы передали в методе Init. Наша задача только обработать этот результат чтобы сделать необходимые операции на своём сайте и уведомить пользователя об этом.
-
- Roman Sakhno
Комментарии (0):
Для добавления комментариев надо войти в систему.
Если Вы ещё не зарегистрированы на сайте, то сначала зарегистрируйтесь.