diff --git a/app/api/helpers/checksum.py b/app/api/helpers/checksum.py new file mode 100644 index 0000000000..8095dbaf10 --- /dev/null +++ b/app/api/helpers/checksum.py @@ -0,0 +1,139 @@ +# Checksum module from https://github.com/Paytm-Payments/Paytm_Web_Sample_Kit_Python + +import base64 +import string +import random +import hashlib + +from Crypto.Cipher import AES + + +IV = "@@@@&&&&####$$$$" +BLOCK_SIZE = 16 + + +def generate_checksum(param_dict, merchant_key, salt=None): + params_string = __get_param_string__(param_dict) + salt = salt if salt else __id_generator__(4) + final_string = '%s|%s' % (params_string, salt) + + hasher = hashlib.sha256(final_string.encode()) + hash_string = hasher.hexdigest() + + hash_string += salt + + return __encode__(hash_string, IV, merchant_key) + + +def generate_refund_checksum(param_dict, merchant_key, salt=None): + for i in param_dict: + if("|" in param_dict[i]): + param_dict = {} + exit() + params_string = __get_param_string__(param_dict) + salt = salt if salt else __id_generator__(4) + final_string = '%s|%s' % (params_string, salt) + + hasher = hashlib.sha256(final_string.encode()) + hash_string = hasher.hexdigest() + + hash_string += salt + + return __encode__(hash_string, IV, merchant_key) + + +def generate_checksum_by_str(param_str, merchant_key, salt=None): + params_string = param_str + salt = salt if salt else __id_generator__(4) + final_string = '%s|%s' % (params_string, salt) + + hasher = hashlib.sha256(final_string.encode()) + hash_string = hasher.hexdigest() + + hash_string += salt + + return __encode__(hash_string, IV, merchant_key) + + +def verify_checksum(param_dict, merchant_key, checksum): + # Remove checksum + if 'CHECKSUMHASH' in param_dict: + param_dict.pop('CHECKSUMHASH') + + # Get salt + paytm_hash = __decode__(checksum, IV, merchant_key) + salt = paytm_hash[-4:] + calculated_checksum = generate_checksum(param_dict, merchant_key, salt=salt) + return calculated_checksum == checksum + + +def verify_checksum_by_str(param_str, merchant_key, checksum): + # Get salt + paytm_hash = __decode__(checksum, IV, merchant_key) + salt = paytm_hash[-4:] + calculated_checksum = generate_checksum_by_str(param_str, merchant_key, salt=salt) + return calculated_checksum == checksum + + +def __id_generator__(size=6, chars=string.ascii_uppercase + string.digits + string.ascii_lowercase): + return ''.join(random.choice(chars) for _ in range(size)) + + +def __get_param_string__(params): + params_string = [] + for key in sorted(params.keys()): + if("REFUND" in params[key] or "|" in params[key]): + exit() + value = params[key] + params_string.append('' if value == 'null' else str(value)) + return '|'.join(params_string) + + +def __pad__(string_literal): + return (string_literal + (BLOCK_SIZE - len(string_literal) % BLOCK_SIZE) * + chr(BLOCK_SIZE - len(string_literal) % BLOCK_SIZE)) + + +def __unpad__(string_literal): + return string_literal[0:-ord(string_literal[-1])] + + +def __encode__(to_encode, iv, key): + # Pad + to_encode = __pad__(to_encode) + # Encrypt + c = AES.new(key, AES.MODE_CBC, iv) + to_encode = c.encrypt(to_encode) + # Encode + to_encode = base64.b64encode(to_encode) + return to_encode.decode("UTF-8") + + +def __decode__(to_decode, iv, key): + # Decode + to_decode = base64.b64decode(to_decode) + # Decrypt + c = AES.new(key, AES.MODE_CBC, iv) + to_decode = c.decrypt(to_decode) + if type(to_decode) == bytes: + # convert bytes array to str. + to_decode = to_decode.decode() + # remove pad + return __unpad__(to_decode) + + +if __name__ == "__main__": + params = { + "MID": "mid", + "ORDER_ID": "order_id", + "CUST_ID": "cust_id", + "TXN_AMOUNT": "1", + "CHANNEL_ID": "WEB", + "INDUSTRY_TYPE_ID": "Retail", + "WEBSITE": "xxxxxxxxxxx" + } + + print(verify_checksum( + params, 'xxxxxxxxxxxxxxxx', + "CD5ndX8VVjlzjWbbYoAtKQIlvtXPypQYOg0Fi2AUYKXZA5XSHiRF0FDj7vQu6\ + 6S8MHx9NaDZ/uYm3WBOWHf+sDQAmTyxqUipA7i1nILlxrk=")) diff --git a/app/api/helpers/payment.py b/app/api/helpers/payment.py index b540124159..276bc4b4e0 100644 --- a/app/api/helpers/payment.py +++ b/app/api/helpers/payment.py @@ -7,6 +7,8 @@ from forex_python.converter import CurrencyRates from app.api.helpers.cache import cache +from app.settings import get_settings +from app.api.helpers import checksum from app.api.helpers.exceptions import ForbiddenException, ConflictException from app.api.helpers.utilities import represents_int from app.models.stripe_authorization import StripeAuthorization @@ -315,3 +317,17 @@ def charge_payment(order_identifier, token): }, ) return charge + + +class PaytmPaymentsManager(object): + """ + Class to manage PayTM payments + """ + + @staticmethod + def generate_checksum(paytm_params): + if get_settings()['paytm_mode'] == 'sandbox': + merchant_key = get_settings()['paytm_sandbox_secret'] + else: + merchant_key = get_settings()['paytm_live_secret'] + return checksum.generate_checksum_by_str(json.dumps(paytm_params["body"]), merchant_key) diff --git a/app/api/orders.py b/app/api/orders.py index 67344fc174..a385ffc0b3 100644 --- a/app/api/orders.py +++ b/app/api/orders.py @@ -1,6 +1,9 @@ import logging +import json import pytz from datetime import datetime +import requests + import omise from flask import request, jsonify, Blueprint, url_for, redirect @@ -10,6 +13,7 @@ from marshmallow_jsonapi.flask import Schema from sqlalchemy.orm.exc import NoResultFound +from app.settings import get_settings from app.api.bootstrap import api from app.api.data_layers.ChargesLayer import ChargesLayer from app.api.helpers.db import save_to_db, safe_query, safe_query_without_soft_deleted_entries @@ -22,7 +26,7 @@ send_notif_ticket_cancel from app.api.helpers.order import delete_related_attendees_for_order, set_expiry_for_order, \ create_pdf_tickets_for_holder, create_onsite_attendees_for_order -from app.api.helpers.payment import AliPayPaymentsManager, OmisePaymentsManager +from app.api.helpers.payment import AliPayPaymentsManager, OmisePaymentsManager, PaytmPaymentsManager from app.api.helpers.payment import PayPalPaymentsManager from app.api.helpers.permission_manager import has_access from app.api.helpers.permissions import jwt_required @@ -622,3 +626,45 @@ def omise_checkout(order_identifier): logging.info(f"Successful charge: {charge.id}. Order ID: {order_identifier}") return redirect(make_frontend_url('orders/{}/view'.format(order_identifier))) + + +@order_misc_routes.route('/orders//paytm/initiate-transaction', methods=['POST', 'GET']) +def initiate_transaction(order_identifier): + """ + Initiating a PayTM transaction to obtain the txn token + :param order_identifier: + :return: JSON response containing the signature & txn token + """ + order = safe_query(db, Order, 'identifier', order_identifier, 'identifier') + paytm_mode = get_settings()['paytm_mode'] + paytm_params = {} + # body parameters + paytm_params["body"] = { + "requestType": "Payment", + "mid": (get_settings()['paytm_sandbox_merchant'] if paytm_mode == 'sandbox' + else get_settings()['paytm_live_merchant']), + "websiteName": "eventyay", + "orderId": order.id, + "callbackUrl": "", + "txnAmount": { + "value": order.amount, + "currency": order.event.payment_currency, + }, + "userInfo": { + "custId": order.user.id, + }, + } + checksum = PaytmPaymentsManager.generate_checksum(paytm_params) + # head parameters + paytm_params["head"] = { + "signature" : checksum + } + post_data = json.dumps(paytm_params) + if paytm_mode == 'sandbox': + url = "https://securegw-stage.paytm.in/theia/api/v1/initiateTransaction?mid={}&orderId={}".\ + format(get_settings()['paytm_sandbox_merchant'], order.id) + else: + url = "https://securegw.paytm.in/theia/api/v1/initiateTransaction?mid={}&orderId={}".\ + format(get_settings()['paytm_sandbox_merchant'], order.id) + response = requests.post(url, data=post_data, headers={"Content-type": "application/json"}) + return response.json() diff --git a/app/api/schema/orders.py b/app/api/schema/orders.py index a1d03e0b31..d9fe5200dd 100644 --- a/app/api/schema/orders.py +++ b/app/api/schema/orders.py @@ -61,7 +61,7 @@ def initial_values(self, data): payment_mode = fields.Str( default="free", validate=validate.OneOf(choices=["free", "stripe", "paypal", "bank", - "cheque", "onsite", "omise", "alipay"]), + "cheque", "onsite", "omise", "alipay", "paytm"]), allow_none=True) paid_via = fields.Str(dump_only=True) is_billing_enabled = fields.Boolean(default=False) diff --git a/requirements/common.txt b/requirements/common.txt index 1d11f8174d..bac0f4f81f 100644 --- a/requirements/common.txt +++ b/requirements/common.txt @@ -32,6 +32,7 @@ stripe~=2.35.0 xhtml2pdf~=0.2 flask-caching~=1.4 forex-python~=1.5 +pycrypto~=2.6.1 oauth2~=1.9 qrcode~=6.1 python-magic~=0.4