Bootstrap

Как настроить эквайринг в Django. Пример с банком Тинькофф

Как настроить эквайринг в 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

TERMINALKEY='111111111111DEMO'
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 чем принтами

# response_data = response.json()
# 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 и индексируемой прямой ссылкой на сайт (http://itdid.ru)!

Добавляйтесь ко мне в друзья ВКонтакте: http://vk.com/sahroman.
Если Вы хотите дать оценку мне и моей работе, то напишите её в моей группе: http://vk.com/sahroman.

Если у Вас остались какие-либо вопросы, либо у Вас есть желание высказаться по поводу этой статьи, то Вы можете оставить свой комментарий внизу страницы.

Порекомендуйте эту статью друзьям:

Если Вам понравился сайт, то разместите ссылку на него (у себя на сайте, на форуме, в контакте):

  1. Кнопка:

    Она выглядит вот так: Как настроить свой компьютер

  2. Текстовая ссылка:

    Она выглядит вот так: Как настроить свой компьютер

  3. BB-код ссылки для форумов (например, можете поставить её в подписи):

Комментарии (0):

Для добавления комментариев надо войти в систему.
Если Вы ещё не зарегистрированы на сайте, то сначала зарегистрируйтесь.

крипто-донат, на развитие сайта itdid.ru:

В новом окне с терминалом itdid.ru, введите любую сумму: