Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
139 changes: 139 additions & 0 deletions app/api/helpers/checksum.py
Original file line number Diff line number Diff line change
@@ -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):

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

too many blank lines (3)

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="))
16 changes: 16 additions & 0 deletions app/api/helpers/payment.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
48 changes: 47 additions & 1 deletion app/api/orders.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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/<string:order_identifier>/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"] = {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

undefined name 'paytm_params'

"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)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

undefined name 'json'
undefined name '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()
2 changes: 1 addition & 1 deletion app/api/schema/orders.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions requirements/common.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down