From 5c616eb27da2ed87d74d5f1f6e77d4bff0fa2949 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Thu, 2 Apr 2020 18:12:43 +0530 Subject: [PATCH 01/23] feat: paytm integration --- .../doctype/paytm_settings/__init__.py | 0 .../doctype/paytm_settings/paytm_settings.js | 8 ++++ .../paytm_settings/paytm_settings.json | 46 +++++++++++++++++++ .../doctype/paytm_settings/paytm_settings.py | 10 ++++ .../paytm_settings/test_paytm_settings.py | 10 ++++ 5 files changed, 74 insertions(+) create mode 100644 payments/payment_gateways/doctype/paytm_settings/__init__.py create mode 100644 payments/payment_gateways/doctype/paytm_settings/paytm_settings.js create mode 100644 payments/payment_gateways/doctype/paytm_settings/paytm_settings.json create mode 100644 payments/payment_gateways/doctype/paytm_settings/paytm_settings.py create mode 100644 payments/payment_gateways/doctype/paytm_settings/test_paytm_settings.py diff --git a/payments/payment_gateways/doctype/paytm_settings/__init__.py b/payments/payment_gateways/doctype/paytm_settings/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/payments/payment_gateways/doctype/paytm_settings/paytm_settings.js b/payments/payment_gateways/doctype/paytm_settings/paytm_settings.js new file mode 100644 index 0000000..d51a6e4 --- /dev/null +++ b/payments/payment_gateways/doctype/paytm_settings/paytm_settings.js @@ -0,0 +1,8 @@ +// Copyright (c) 2020, Frappe Technologies and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Paytm Settings', { + // refresh: function(frm) { + + // } +}); diff --git a/payments/payment_gateways/doctype/paytm_settings/paytm_settings.json b/payments/payment_gateways/doctype/paytm_settings/paytm_settings.json new file mode 100644 index 0000000..e57c8a1 --- /dev/null +++ b/payments/payment_gateways/doctype/paytm_settings/paytm_settings.json @@ -0,0 +1,46 @@ +{ + "actions": [], + "creation": "2020-04-02 00:11:22.846697", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "merchant_id", + "merchant_key" + ], + "fields": [ + { + "fieldname": "merchant_id", + "fieldtype": "Data", + "label": "Merchant ID" + }, + { + "fieldname": "merchant_key", + "fieldtype": "Data", + "hidden": 1, + "label": "Merchant Key" + } + ], + "issingle": 1, + "links": [], + "modified": "2020-04-02 00:11:22.846697", + "modified_by": "Administrator", + "module": "Integrations", + "name": "Paytm Settings", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/payments/payment_gateways/doctype/paytm_settings/paytm_settings.py b/payments/payment_gateways/doctype/paytm_settings/paytm_settings.py new file mode 100644 index 0000000..74d51d1 --- /dev/null +++ b/payments/payment_gateways/doctype/paytm_settings/paytm_settings.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +# import frappe +from frappe.model.document import Document + +class PaytmSettings(Document): + pass diff --git a/payments/payment_gateways/doctype/paytm_settings/test_paytm_settings.py b/payments/payment_gateways/doctype/paytm_settings/test_paytm_settings.py new file mode 100644 index 0000000..77a16c8 --- /dev/null +++ b/payments/payment_gateways/doctype/paytm_settings/test_paytm_settings.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies and Contributors +# See license.txt +from __future__ import unicode_literals + +# import frappe +import unittest + +class TestPaytmSettings(unittest.TestCase): + pass From 692c54bf3cf56853322156fb39844b9704d77d3d Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Wed, 8 Apr 2020 18:49:48 +0530 Subject: [PATCH 02/23] feat: blink checkout --- .../doctype/paytm_settings/checksum.py | 143 ++++++++++++++++++ payments/templates/includes/paytm_checkout.js | 42 +++++ 2 files changed, 185 insertions(+) create mode 100644 payments/payment_gateways/doctype/paytm_settings/checksum.py create mode 100644 payments/templates/includes/paytm_checkout.js diff --git a/payments/payment_gateways/doctype/paytm_settings/checksum.py b/payments/payment_gateways/doctype/paytm_settings/checksum.py new file mode 100644 index 0000000..95fb7dc --- /dev/null +++ b/payments/payment_gateways/doctype/paytm_settings/checksum.py @@ -0,0 +1,143 @@ +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): + # 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_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]): + respons_dict = {} + exit() + value = params[key] + params_string.append('' if value == 'null' else str(value)) + return '|'.join(params_string) + + +def __pad__(s): return s + (BLOCK_SIZE - len(s) % + BLOCK_SIZE) * chr(BLOCK_SIZE - len(s) % BLOCK_SIZE) + + +def __unpad__(s): return s[0:-ord(s[-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', + "CD5ndX8VVjlzjWbbYoAtKQIlvtXPypQYOg0Fi2AUYKXZA5XSHiRF0FDj7vQu66S8MHx9NaDZ/uYm3WBOWHf+sDQAmTyxqUipA7i1nILlxrk=")) + + # print(generate_checksum(params, "xxxxxxxxxxxxxxxx")) diff --git a/payments/templates/includes/paytm_checkout.js b/payments/templates/includes/paytm_checkout.js new file mode 100644 index 0000000..4cf070b --- /dev/null +++ b/payments/templates/includes/paytm_checkout.js @@ -0,0 +1,42 @@ +function onScriptLoad() { + console.log('inside on load') + var config = { + root: '', + flow: 'DEFAULT', + data: { + orderId: '{{ order_id}}', + token: '{{ token }}', + tokenType: 'TXN_TOKEN', + amount: '{{ amount }}' + }, + handler: { + notifyMerchant: function(eventName, data) { + // notify about the state of the payment page ( invalid token , session expire , cancel transaction) + console.log('notifyMerchant handler function called'); + console.log('eventName => ', eventName); + console.log('data => ', data); + }, + transactionStatus: function transactionStatus(paymentStatus) { + // provide information to merchant about the payment status. + console.log('transaction status handler function called'); + console.log('paymentStatus => ', paymentStatus); + } + } + }; + + $('.paytm-loading').addClass('hidden'); + if (window.Paytm && window.Paytm.CheckoutJS) { + window.Paytm.CheckoutJS.onLoad(function excecuteAfterCompleteLoad() { + // initialze configuration using init method + window.Paytm.CheckoutJS.init(config) + .then(function onSuccess() { + // after successfully updating configuration, invoke Blink Checkout + window.Paytm.CheckoutJS.invoke(); + }) + .catch(function onError(error) { + console.log('inside the error window') + console.log('error => ', error); + }); + }); + } +} From 3d2c2a583b0779e99766658d65eebcdbafcc65b8 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Fri, 10 Apr 2020 12:50:54 +0530 Subject: [PATCH 03/23] feat: create transaction token --- .../doctype/paytm_settings/paytm_settings.py | 150 +++++++++++++++++- payments/templates/pages/paytm_checkout.html | 26 +++ payments/templates/pages/paytm_checkout.py | 105 ++++++++++++ 3 files changed, 279 insertions(+), 2 deletions(-) create mode 100644 payments/templates/pages/paytm_checkout.html create mode 100644 payments/templates/pages/paytm_checkout.py diff --git a/payments/payment_gateways/doctype/paytm_settings/paytm_settings.py b/payments/payment_gateways/doctype/paytm_settings/paytm_settings.py index 74d51d1..1f9e508 100644 --- a/payments/payment_gateways/doctype/paytm_settings/paytm_settings.py +++ b/payments/payment_gateways/doctype/paytm_settings/paytm_settings.py @@ -3,8 +3,154 @@ # For license information, please see license.txt from __future__ import unicode_literals -# import frappe +import frappe from frappe.model.document import Document +from frappe import _ +from six.moves.urllib.parse import urlencode +from frappe.utils import get_url, call_hook_method, cint, flt +from frappe.integrations.utils import make_get_request, make_post_request, create_request_log, create_payment_gateway +from frappe.utils import get_request_site_address + +import json +import requests class PaytmSettings(Document): - pass + supported_currencies = ["INR"] + + def validate(self): + create_payment_gateway('Paytm') + call_hook_method('payment_gateway_enabled', gateway='Paytm') + if not self.flags.ignore_mandatory: + self.validate_paytm_credentails() + + def validate_paytm_credentails(self): + if self.merchant_id and self.merchant_key: + pass + # header = {"Authorization": "Bearer {0}".format(self.get_password(fieldname="secret_key", raise_exception=False))} + # try: + # make_get_request(url="https://api.stripe.com/v1/charges", headers=header) + # except Exception: + # frappe.throw(_("Seems Publishable Key or Secret Key is wrong !!!")) + + def validate_transaction_currency(self, currency): + if currency not in self.supported_currencies: + frappe.throw(_("Please select another payment method. Stripe does not support transactions in currency '{0}'").format(currency)) + + def get_payment_url(self, **kwargs): + '''Return payment url with several params''' + # create unique order id by making it equal to the integration request + integration_request = create_request_log(kwargs, "Host", "Paytm") + kwargs.update(dict(order_id=integration_request.name)) + + return get_url("./integrations/paytm_checkout?{0}".format(urlencode(kwargs))) + + def create_request(self, data): + self.data = frappe._dict(data) + + try: + self.integration_request = create_request_log(self.data, "Host", "Paytm") + return self.generate_transaction_token() + + except Exception: + frappe.log_error(frappe.get_traceback()) + return{ + "redirect_to": frappe.redirect_to_message(_('Server Error'), _("It seems that there is an issue with the server's stripe configuration. In case of failure, the amount will get refunded to your account.")), + "status": 401 + } + + def generate_transaction_token(self): + import stripe + try: + charge = stripe.Charge.create(amount=cint(flt(self.data.amount)*100), currency=self.data.currency, source=self.data.stripe_token_id, description=self.data.description, receipt_email=self.data.payer_email) + + if charge.captured == True: + self.integration_request.db_set('status', 'Completed', update_modified=False) + self.flags.status_changed_to = "Completed" + + else: + frappe.log_error(charge.failure_message, 'Stripe Payment not completed') + + except Exception: + frappe.log_error(frappe.get_traceback()) + + return self.finalize_request() + + + def finalize_request(self): + redirect_to = self.data.get('redirect_to') or None + redirect_message = self.data.get('redirect_message') or None + status = self.integration_request.status + + if self.flags.status_changed_to == "Completed": + if self.data.reference_doctype and self.data.reference_docname: + custom_redirect_to = None + try: + custom_redirect_to = frappe.get_doc(self.data.reference_doctype, + self.data.reference_docname).run_method("on_payment_authorized", self.flags.status_changed_to) + except Exception: + frappe.log_error(frappe.get_traceback()) + + if custom_redirect_to: + redirect_to = custom_redirect_to + + redirect_url = 'payment-success' + + if self.redirect_url: + redirect_url = self.redirect_url + redirect_to = None + else: + redirect_url = 'payment-failed' + + if redirect_to: + redirect_url += '?' + urlencode({'redirect_to': redirect_to}) + if redirect_message: + redirect_url += '&' + urlencode({'redirect_message': redirect_message}) + + return { + "redirect_to": redirect_url, + "status": status + } + +def generate_transaction_token(payment_details, order_id, merchant_id, merchant_key): + + # initialize a dictionary + paytmParams = dict() + + redirect_uri = get_request_site_address(True) + "?cmd=frappe.templates.pages.integrations.paytm_checkout.get_transaction_status" + + # body parameters + paytmParams["body"] = get_paytm_params(payment_details, order_id, merchant_id, merchant_key) + + checksum = generate_checksum_by_str(json.dumps(paytmParams["body"]), merchant_key) + + paytmParams["head"] = { + "signature" : checksum + } + + post_data = json.dumps(paytmParams) + + url = "https://securegw-stage.paytm.in/theia/api/v1/initiateTransaction?mid={0}&orderId={1}".format(merchant_id, order_id) + + response = requests.post(url, data = post_data, headers = {"Content-type": "application/json"}).json() + return response['body'].get('txnToken') + +def get_paytm_params(payment_details, order_id, merchant_id, merchant_key): + return { + "requestType" : "Payment", + "mid" : merchant_id, + "websiteName" : "WEBSTAGING", + "orderId" : order_id, + "callbackUrl" : redirect_uri, + "txnAmount" : { + "value" : flt(payment_details['amount'], 2), + "currency" : "INR", + }, + "userInfo" : { + "custId" : payment_details['payer_email'], + }, + } + +def get_gateway_controller(doctype, docname): + reference_doc = frappe.get_doc(doctype, docname) + gateway_controller = frappe.db.get_value("Payment Gateway", reference_doc.payment_gateway, "gateway_controller") + return gateway_controller \ No newline at end of file diff --git a/payments/templates/pages/paytm_checkout.html b/payments/templates/pages/paytm_checkout.html new file mode 100644 index 0000000..2aedf31 --- /dev/null +++ b/payments/templates/pages/paytm_checkout.html @@ -0,0 +1,26 @@ +{% extends "templates/web.html" %} {% block title %} Payment {% endblock %} {%- +block header -%}{% endblock %} {% block script %} + + +{% endblock %} {%- block page_content -%} +
+

+ Loading Payment System + +

+ +{% endblock %} {% block style %} + +{% endblock %} diff --git a/payments/templates/pages/paytm_checkout.py b/payments/templates/pages/paytm_checkout.py new file mode 100644 index 0000000..a17873b --- /dev/null +++ b/payments/templates/pages/paytm_checkout.py @@ -0,0 +1,105 @@ +# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors +# License: GNU General Public License v3. See license.txt +from __future__ import unicode_literals +import frappe +from frappe import _ +from frappe.utils import flt, cint +import json +from six import string_types +from frappe.integrations.doctype.paytm_settings.checksum import generate_checksum_by_str +from frappe.utils import get_request_site_address +import requests + +no_cache = 1 + +expected_keys = ('amount', 'title', 'description', 'reference_doctype', 'reference_docname', + 'payer_name', 'payer_email') + +def get_context(context): + context.no_cache = 1 + merchant_id, merchant_key = get_paytm_credentials() + + try: + doc = frappe.get_doc("Integration Request", frappe.form_dict['order_id']) + payment_details = json.loads(doc.data) + context.token = generate_transaction_token(payment_details, doc.name, merchant_id, merchant_key) + context.order_id = doc.name + + + for key in expected_keys: + context[key] = payment_details[key] + + context['amount'] = flt(context['amount'], 2) + context.host = 'https://securegw-stage.paytm.in' + context.mid = merchant_id + + except Exception as e: + frappe.log_error() + frappe.redirect_to_message(_('Invalid Token'), + _('Seems token you are using is invalid!'), + http_status_code=400, indicator_color='red') + + frappe.local.flags.redirect_location = frappe.local.response.location + raise frappe.Redirect + +def get_paytm_credentials(): + return frappe.db.get_value("Paytm Settings", None, ['merchant_id', 'merchant_key']) + +@frappe.whitelist(allow_guest=True) +def make_payment(paytm_payment_id, options, reference_doctype, reference_docname, token): + data = {} + + if isinstance(options, string_types): + data = json.loads(options) + + data.update({ + "paytm_payment_id": paytm_payment_id, + "reference_docname": reference_docname, + "reference_doctype": reference_doctype, + "token": token + }) + + data = frappe.get_doc("Paytm Settings").create_request(data) + frappe.db.commit() + return data + +def generate_transaction_token(payment_details, order_id, merchant_id, merchant_key): + + # initialize a dictionary + paytmParams = dict() + + redirect_uri = get_request_site_address(True) + "?cmd=frappe.templates.pages.integrations.paytm_checkout.get_transaction_status" + + # body parameters + paytmParams["body"] = { + "requestType" : "Payment", + "mid" : merchant_id, + "websiteName" : "WEBSTAGING", + "orderId" : order_id, + "callbackUrl" : redirect_uri, + "txnAmount" : { + "value" : flt(payment_details['amount'], 2), + "currency" : "INR", + }, + "userInfo" : { + "custId" : payment_details['payer_email'], + }, + } + + checksum = generate_checksum_by_str(json.dumps(paytmParams["body"]), merchant_key) + + paytmParams["head"] = { + "signature" : checksum + } + + print(paytmParams) + post_data = json.dumps(paytmParams) + + url = "https://securegw-stage.paytm.in/theia/api/v1/initiateTransaction?mid={0}&orderId={1}".format(merchant_id, order_id) + + response = requests.post(url, data = post_data, headers = {"Content-type": "application/json"}).json() + return response['body'].get('txnToken') + +@frappe.whitelist(allow_guest=True) +def get_transaction_status(): + print(vars(frappe.form_dict)) \ No newline at end of file From b9c311f3619379807cef3e4764727ec2834e58a8 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Thu, 16 Apr 2020 15:48:37 +0530 Subject: [PATCH 04/23] fix: paytm integration --- .../doctype/paytm_settings/paytm_settings.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/payments/payment_gateways/doctype/paytm_settings/paytm_settings.json b/payments/payment_gateways/doctype/paytm_settings/paytm_settings.json index e57c8a1..eb68f0a 100644 --- a/payments/payment_gateways/doctype/paytm_settings/paytm_settings.json +++ b/payments/payment_gateways/doctype/paytm_settings/paytm_settings.json @@ -17,13 +17,12 @@ { "fieldname": "merchant_key", "fieldtype": "Data", - "hidden": 1, "label": "Merchant Key" } ], "issingle": 1, "links": [], - "modified": "2020-04-02 00:11:22.846697", + "modified": "2020-04-07 19:57:24.126640", "modified_by": "Administrator", "module": "Integrations", "name": "Paytm Settings", From a4f54c9ea57784417540cb06c7d7a65e44005288 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Thu, 16 Apr 2020 15:51:11 +0530 Subject: [PATCH 05/23] fix: initiate payment via standard checkout --- payments/templates/pages/paytm_checkout.html | 45 ++++++----- payments/templates/pages/paytm_checkout.py | 78 ++------------------ 2 files changed, 32 insertions(+), 91 deletions(-) diff --git a/payments/templates/pages/paytm_checkout.html b/payments/templates/pages/paytm_checkout.html index 2aedf31..772e16e 100644 --- a/payments/templates/pages/paytm_checkout.html +++ b/payments/templates/pages/paytm_checkout.html @@ -1,26 +1,35 @@ -{% extends "templates/web.html" %} {% block title %} Payment {% endblock %} {%- -block header -%}{% endblock %} {% block script %} - - -{% endblock %} {%- block page_content -%} -
-

- Loading Payment System - -

+{% endblock %} + +{%- block page_content -%} + +

Please do not refresh this page...

+
+ {% for name, value in payment_details.items() %} + + {% endfor %} +
+ +{% endblock %} -{% endblock %} {% block style %} +{% block style %} -{% endblock %} +{% endblock %} \ No newline at end of file diff --git a/payments/templates/pages/paytm_checkout.py b/payments/templates/pages/paytm_checkout.py index a17873b..1c61d5c 100644 --- a/payments/templates/pages/paytm_checkout.py +++ b/payments/templates/pages/paytm_checkout.py @@ -6,8 +6,8 @@ from frappe import _ from frappe.utils import flt, cint import json from six import string_types -from frappe.integrations.doctype.paytm_settings.checksum import generate_checksum_by_str from frappe.utils import get_request_site_address +from frappe.integrations.doctype.paytm_settings.paytm_settings import get_paytm_params, get_paytm_config import requests no_cache = 1 @@ -17,21 +17,15 @@ expected_keys = ('amount', 'title', 'description', 'reference_doctype', 'referen def get_context(context): context.no_cache = 1 - merchant_id, merchant_key = get_paytm_credentials() + paytm_config = get_paytm_config() try: doc = frappe.get_doc("Integration Request", frappe.form_dict['order_id']) payment_details = json.loads(doc.data) - context.token = generate_transaction_token(payment_details, doc.name, merchant_id, merchant_key) - context.order_id = doc.name + context.payment_details = get_paytm_params(json.loads(doc.data), doc.name, paytm_config) - for key in expected_keys: - context[key] = payment_details[key] - - context['amount'] = flt(context['amount'], 2) - context.host = 'https://securegw-stage.paytm.in' - context.mid = merchant_id + context.url = paytm_config.url except Exception as e: frappe.log_error() @@ -40,66 +34,4 @@ def get_context(context): http_status_code=400, indicator_color='red') frappe.local.flags.redirect_location = frappe.local.response.location - raise frappe.Redirect - -def get_paytm_credentials(): - return frappe.db.get_value("Paytm Settings", None, ['merchant_id', 'merchant_key']) - -@frappe.whitelist(allow_guest=True) -def make_payment(paytm_payment_id, options, reference_doctype, reference_docname, token): - data = {} - - if isinstance(options, string_types): - data = json.loads(options) - - data.update({ - "paytm_payment_id": paytm_payment_id, - "reference_docname": reference_docname, - "reference_doctype": reference_doctype, - "token": token - }) - - data = frappe.get_doc("Paytm Settings").create_request(data) - frappe.db.commit() - return data - -def generate_transaction_token(payment_details, order_id, merchant_id, merchant_key): - - # initialize a dictionary - paytmParams = dict() - - redirect_uri = get_request_site_address(True) + "?cmd=frappe.templates.pages.integrations.paytm_checkout.get_transaction_status" - - # body parameters - paytmParams["body"] = { - "requestType" : "Payment", - "mid" : merchant_id, - "websiteName" : "WEBSTAGING", - "orderId" : order_id, - "callbackUrl" : redirect_uri, - "txnAmount" : { - "value" : flt(payment_details['amount'], 2), - "currency" : "INR", - }, - "userInfo" : { - "custId" : payment_details['payer_email'], - }, - } - - checksum = generate_checksum_by_str(json.dumps(paytmParams["body"]), merchant_key) - - paytmParams["head"] = { - "signature" : checksum - } - - print(paytmParams) - post_data = json.dumps(paytmParams) - - url = "https://securegw-stage.paytm.in/theia/api/v1/initiateTransaction?mid={0}&orderId={1}".format(merchant_id, order_id) - - response = requests.post(url, data = post_data, headers = {"Content-type": "application/json"}).json() - return response['body'].get('txnToken') - -@frappe.whitelist(allow_guest=True) -def get_transaction_status(): - print(vars(frappe.form_dict)) \ No newline at end of file + raise frappe.Redirect \ No newline at end of file From 3809d5be979ba97b5213614b6c97a3ef05b5afdb Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Thu, 16 Apr 2020 18:53:56 +0530 Subject: [PATCH 06/23] feat: add paytm config, transaction status --- .../paytm_settings/paytm_settings.json | 40 +++- .../doctype/paytm_settings/paytm_settings.py | 208 ++++++++++-------- 2 files changed, 149 insertions(+), 99 deletions(-) diff --git a/payments/payment_gateways/doctype/paytm_settings/paytm_settings.json b/payments/payment_gateways/doctype/paytm_settings/paytm_settings.json index eb68f0a..0540dc4 100644 --- a/payments/payment_gateways/doctype/paytm_settings/paytm_settings.json +++ b/payments/payment_gateways/doctype/paytm_settings/paytm_settings.json @@ -6,23 +6,55 @@ "engine": "InnoDB", "field_order": [ "merchant_id", - "merchant_key" + "merchant_key", + "staging", + "column_break_4", + "industry_type", + "website" ], "fields": [ { "fieldname": "merchant_id", "fieldtype": "Data", - "label": "Merchant ID" + "in_list_view": 1, + "label": "Merchant ID", + "reqd": 1 }, { "fieldname": "merchant_key", + "fieldtype": "Password", + "in_list_view": 1, + "label": "Merchant Key", + "reqd": 1 + }, + { + "default": "0", + "fieldname": "staging", + "fieldtype": "Check", + "label": "Staging" + }, + { + "depends_on": "eval: !doc.staging", + "fieldname": "industry_type", "fieldtype": "Data", - "label": "Merchant Key" + "label": "Industry Type", + "mandatory_depends_on": "eval: !doc.staging" + }, + { + "depends_on": "eval: !doc.staging", + "fieldname": "website", + "fieldtype": "Data", + "label": "Website", + "mandatory_depends_on": "eval: !doc.staging" + }, + { + "fieldname": "column_break_4", + "fieldtype": "Column Break" } ], "issingle": 1, "links": [], - "modified": "2020-04-07 19:57:24.126640", + "modified": "2020-04-16 14:16:19.687449", "modified_by": "Administrator", "module": "Integrations", "name": "Paytm Settings", diff --git a/payments/payment_gateways/doctype/paytm_settings/paytm_settings.py b/payments/payment_gateways/doctype/paytm_settings/paytm_settings.py index 1f9e508..10282d3 100644 --- a/payments/payment_gateways/doctype/paytm_settings/paytm_settings.py +++ b/payments/payment_gateways/doctype/paytm_settings/paytm_settings.py @@ -3,16 +3,18 @@ # For license information, please see license.txt from __future__ import unicode_literals +import json +import requests +from six.moves.urllib.parse import urlencode + import frappe from frappe.model.document import Document from frappe import _ -from six.moves.urllib.parse import urlencode -from frappe.utils import get_url, call_hook_method, cint, flt +from frappe.utils import get_url, call_hook_method, cint, flt, cstr from frappe.integrations.utils import make_get_request, make_post_request, create_request_log, create_payment_gateway from frappe.utils import get_request_site_address - -import json -import requests +from frappe.integrations.doctype.paytm_settings.checksum import generate_checksum, verify_checksum +from frappe.utils.password import get_decrypted_password class PaytmSettings(Document): supported_currencies = ["INR"] @@ -44,110 +46,126 @@ class PaytmSettings(Document): return get_url("./integrations/paytm_checkout?{0}".format(urlencode(kwargs))) - def create_request(self, data): - self.data = frappe._dict(data) - - try: - self.integration_request = create_request_log(self.data, "Host", "Paytm") - return self.generate_transaction_token() - - except Exception: - frappe.log_error(frappe.get_traceback()) - return{ - "redirect_to": frappe.redirect_to_message(_('Server Error'), _("It seems that there is an issue with the server's stripe configuration. In case of failure, the amount will get refunded to your account.")), - "status": 401 - } - - def generate_transaction_token(self): - import stripe - try: - charge = stripe.Charge.create(amount=cint(flt(self.data.amount)*100), currency=self.data.currency, source=self.data.stripe_token_id, description=self.data.description, receipt_email=self.data.payer_email) - - if charge.captured == True: - self.integration_request.db_set('status', 'Completed', update_modified=False) - self.flags.status_changed_to = "Completed" - - else: - frappe.log_error(charge.failure_message, 'Stripe Payment not completed') +def get_paytm_config(): + ''' Returns paytm config ''' - except Exception: - frappe.log_error(frappe.get_traceback()) + paytm_config = frappe.db.get_singles_dict('Paytm Settings') + paytm_config.update(dict(merchant_key=get_decrypted_password('Paytm Settings', 'Paytm Settings', 'merchant_key'))) - return self.finalize_request() + if cint(paytm_config.staging): + paytm_config.update(dict( + website="WEBSTAGING", + url='https://securegw-stage.paytm.in/order/process', + transaction_status_url='https://securegw-stage.paytm.in/order/status', + industry_type_id='RETAIL' + )) + else: + paytm_config.update(dict( + url='https://securegw.paytm.in/order/process', + transaction_status_url='https://securegw.paytm.in/order/status', + )) + return paytm_config - - def finalize_request(self): - redirect_to = self.data.get('redirect_to') or None - redirect_message = self.data.get('redirect_message') or None - status = self.integration_request.status - - if self.flags.status_changed_to == "Completed": - if self.data.reference_doctype and self.data.reference_docname: - custom_redirect_to = None - try: - custom_redirect_to = frappe.get_doc(self.data.reference_doctype, - self.data.reference_docname).run_method("on_payment_authorized", self.flags.status_changed_to) - except Exception: - frappe.log_error(frappe.get_traceback()) - - if custom_redirect_to: - redirect_to = custom_redirect_to - - redirect_url = 'payment-success' - - if self.redirect_url: - redirect_url = self.redirect_url - redirect_to = None - else: - redirect_url = 'payment-failed' - - if redirect_to: - redirect_url += '?' + urlencode({'redirect_to': redirect_to}) - if redirect_message: - redirect_url += '&' + urlencode({'redirect_message': redirect_message}) - - return { - "redirect_to": redirect_url, - "status": status - } - -def generate_transaction_token(payment_details, order_id, merchant_id, merchant_key): +def get_paytm_params(payment_details, order_id, paytm_config): # initialize a dictionary - paytmParams = dict() - - redirect_uri = get_request_site_address(True) + "?cmd=frappe.templates.pages.integrations.paytm_checkout.get_transaction_status" + paytm_params = dict() + + # redirect_uri = get_request_site_address(True) + "/api/method/frappe.integrations.doctype.paytm_settings.paytm_settings.get_transaction_status" + redirect_uri = "http://cf9b2bb1.ngrok.io/api/method/frappe.integrations.doctype.paytm_settings.paytm_settings.get_transaction_status" + + paytm_params.update({ + "MID" : paytm_config.merchant_id, + "WEBSITE" : paytm_config.website, + "INDUSTRY_TYPE_ID" : paytm_config.industry_type_id, + "CHANNEL_ID" : "WEB", + "ORDER_ID" : order_id, + "CUST_ID" : payment_details['payer_email'], + "EMAIL" : payment_details['payer_email'], + "TXN_AMOUNT" : cstr(flt(payment_details['amount'], 2)), + "CALLBACK_URL" : redirect_uri, + }) + + checksum = generate_checksum(paytm_params, paytm_config.merchant_key) + + paytm_params.update({ + "CHECKSUMHASH" : checksum + }) + + return paytm_params + +@frappe.whitelist(allow_guest=True) +def verify_transaction(**kwargs): + '''Verify checksum for received data in the callback and then verify the transaction''' + paytm_config = get_paytm_config() + received_data = frappe._dict(kwargs) + + print(received_data) + paytm_params = {} + for key, value in received_data.items(): + if key == 'CHECKSUMHASH': + paytm_checksum = value + else: + paytm_params[key] = value - # body parameters - paytmParams["body"] = get_paytm_params(payment_details, order_id, merchant_id, merchant_key) + # Verify checksum + is_valid_checksum = verify_checksum(paytm_params, paytm_config.merchant_key, paytm_checksum) - checksum = generate_checksum_by_str(json.dumps(paytmParams["body"]), merchant_key) + if is_valid_checksum and received_data['RESPCODE'] == '01': + verify_transaction_status(paytm_config, received_data['ORDERID']) + else: + frappe.respond_as_web_page("Payment Failed", + "Transaction failed to complete. Don't worry, in case of failure amount will get refunded to your account.", + http_status_code=401, indicator_color='red') + frappe.log_error("Order unsuccessful, received data:"+received_data, 'Paytm Payment Failed') - paytmParams["head"] = { - "signature" : checksum - } +def verify_transaction_status(paytm_config, order_id): + '''Verify transaction completion after checksum has been verified''' + paytm_params=dict( + MID=paytm_config.merchant_id, + ORDERID= order_id + ) - post_data = json.dumps(paytmParams) + checksum = generate_checksum(paytm_params, paytm_config.merchant_key) + paytm_params["CHECKSUMHASH"] = checksum - url = "https://securegw-stage.paytm.in/theia/api/v1/initiateTransaction?mid={0}&orderId={1}".format(merchant_id, order_id) + post_data = json.dumps(paytm_params) + url = paytm_config.transaction_status_url response = requests.post(url, data = post_data, headers = {"Content-type": "application/json"}).json() - return response['body'].get('txnToken') + print('transaction status response') + print(response) + finalize_request(order_id, response) + +def finalize_request(order_id, response): + request = frappe.db.get_value('Integration Request', order_id) + redirect_to = request.data.get('redirect_to') or None + redirect_message = request.data.get('redirect_message') or None + + if request.flags.status_changed_to == "Completed": + if request.data.reference_doctype and request.data.reference_docname: + custom_redirect_to = None + try: + custom_redirect_to = frappe.get_doc(request.data.reference_doctype, + request.data.reference_docname).run_method("on_payment_authorized", request.flags.status_changed_to) + except Exception: + frappe.log_error(frappe.get_traceback()) + + if custom_redirect_to: + redirect_to = custom_redirect_to + + redirect_url = 'payment-success' + else: + redirect_url = 'payment-failed' + + if redirect_to: + redirect_url += '?' + urlencode({'redirect_to': redirect_to}) + if redirect_message: + redirect_url += '&' + urlencode({'redirect_message': redirect_message}) -def get_paytm_params(payment_details, order_id, merchant_id, merchant_key): return { - "requestType" : "Payment", - "mid" : merchant_id, - "websiteName" : "WEBSTAGING", - "orderId" : order_id, - "callbackUrl" : redirect_uri, - "txnAmount" : { - "value" : flt(payment_details['amount'], 2), - "currency" : "INR", - }, - "userInfo" : { - "custId" : payment_details['payer_email'], - }, + "redirect_to": redirect_url, + "status": status } def get_gateway_controller(doctype, docname): From bab8edd5b65581928d00dfac182bdb659072d8b1 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Fri, 17 Apr 2020 10:02:54 +0530 Subject: [PATCH 07/23] fix: check for transaction success in the final stage --- .../doctype/paytm_settings/paytm_settings.py | 13 ++++--------- payments/templates/pages/paytm_checkout.py | 1 - 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/payments/payment_gateways/doctype/paytm_settings/paytm_settings.py b/payments/payment_gateways/doctype/paytm_settings/paytm_settings.py index 10282d3..3382aeb 100644 --- a/payments/payment_gateways/doctype/paytm_settings/paytm_settings.py +++ b/payments/payment_gateways/doctype/paytm_settings/paytm_settings.py @@ -100,7 +100,6 @@ def verify_transaction(**kwargs): paytm_config = get_paytm_config() received_data = frappe._dict(kwargs) - print(received_data) paytm_params = {} for key, value in received_data.items(): if key == 'CHECKSUMHASH': @@ -133,16 +132,14 @@ def verify_transaction_status(paytm_config, order_id): url = paytm_config.transaction_status_url response = requests.post(url, data = post_data, headers = {"Content-type": "application/json"}).json() - print('transaction status response') - print(response) finalize_request(order_id, response) -def finalize_request(order_id, response): +def finalize_request(order_id, transaction_response): request = frappe.db.get_value('Integration Request', order_id) redirect_to = request.data.get('redirect_to') or None redirect_message = request.data.get('redirect_message') or None - if request.flags.status_changed_to == "Completed": + if transaction_response['STATUS'] == "TXN_SUCCESS": if request.data.reference_doctype and request.data.reference_docname: custom_redirect_to = None try: @@ -163,10 +160,8 @@ def finalize_request(order_id, response): if redirect_message: redirect_url += '&' + urlencode({'redirect_message': redirect_message}) - return { - "redirect_to": redirect_url, - "status": status - } + frappe.local.response['type'] = 'redirect' + frappe.local.response['location'] = 'redirect_url' def get_gateway_controller(doctype, docname): reference_doc = frappe.get_doc(doctype, docname) diff --git a/payments/templates/pages/paytm_checkout.py b/payments/templates/pages/paytm_checkout.py index 1c61d5c..1fa947a 100644 --- a/payments/templates/pages/paytm_checkout.py +++ b/payments/templates/pages/paytm_checkout.py @@ -21,7 +21,6 @@ def get_context(context): try: doc = frappe.get_doc("Integration Request", frappe.form_dict['order_id']) - payment_details = json.loads(doc.data) context.payment_details = get_paytm_params(json.loads(doc.data), doc.name, paytm_config) From 62e182319ced3132d0d7f4d6a63417f3708cc04b Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Fri, 17 Apr 2020 13:34:26 +0530 Subject: [PATCH 08/23] fix: change status of integration request on completion --- .../doctype/paytm_settings/paytm_settings.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/payments/payment_gateways/doctype/paytm_settings/paytm_settings.py b/payments/payment_gateways/doctype/paytm_settings/paytm_settings.py index 3382aeb..39a9f42 100644 --- a/payments/payment_gateways/doctype/paytm_settings/paytm_settings.py +++ b/payments/payment_gateways/doctype/paytm_settings/paytm_settings.py @@ -135,7 +135,7 @@ def verify_transaction_status(paytm_config, order_id): finalize_request(order_id, response) def finalize_request(order_id, transaction_response): - request = frappe.db.get_value('Integration Request', order_id) + request = frappe.get_doc('Integration Request', order_id) redirect_to = request.data.get('redirect_to') or None redirect_message = request.data.get('redirect_message') or None @@ -144,8 +144,10 @@ def finalize_request(order_id, transaction_response): custom_redirect_to = None try: custom_redirect_to = frappe.get_doc(request.data.reference_doctype, - request.data.reference_docname).run_method("on_payment_authorized", request.flags.status_changed_to) + request.data.reference_docname).run_method("on_payment_authorized", 'Completed') + request.db_set('status', 'Completed') except Exception: + request.db_set('status', 'Failed') frappe.log_error(frappe.get_traceback()) if custom_redirect_to: @@ -153,6 +155,7 @@ def finalize_request(order_id, transaction_response): redirect_url = 'payment-success' else: + request.db_set('status', 'Failed') redirect_url = 'payment-failed' if redirect_to: From 9cf9839b1090e50b8654f39e3f7b8e367bd907e1 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Wed, 22 Apr 2020 11:40:08 +0530 Subject: [PATCH 09/23] fix: add finalize request to complete the transaction --- .../doctype/paytm_settings/paytm_settings.py | 33 ++++++++------- payments/templates/includes/paytm_checkout.js | 42 ------------------- payments/templates/pages/paytm_checkout.html | 4 +- payments/templates/pages/paytm_checkout.py | 3 -- 4 files changed, 22 insertions(+), 60 deletions(-) delete mode 100644 payments/templates/includes/paytm_checkout.js diff --git a/payments/payment_gateways/doctype/paytm_settings/paytm_settings.py b/payments/payment_gateways/doctype/paytm_settings/paytm_settings.py index 39a9f42..394cd9c 100644 --- a/payments/payment_gateways/doctype/paytm_settings/paytm_settings.py +++ b/payments/payment_gateways/doctype/paytm_settings/paytm_settings.py @@ -71,8 +71,8 @@ def get_paytm_params(payment_details, order_id, paytm_config): # initialize a dictionary paytm_params = dict() - # redirect_uri = get_request_site_address(True) + "/api/method/frappe.integrations.doctype.paytm_settings.paytm_settings.get_transaction_status" - redirect_uri = "http://cf9b2bb1.ngrok.io/api/method/frappe.integrations.doctype.paytm_settings.paytm_settings.get_transaction_status" + redirect_uri = get_request_site_address(True) + "/api/method/frappe.integrations.doctype.paytm_settings.paytm_settings.verify_transaction" + paytm_params.update({ "MID" : paytm_config.merchant_id, @@ -99,24 +99,28 @@ def verify_transaction(**kwargs): '''Verify checksum for received data in the callback and then verify the transaction''' paytm_config = get_paytm_config() received_data = frappe._dict(kwargs) + is_valid_checksum = False paytm_params = {} for key, value in received_data.items(): if key == 'CHECKSUMHASH': paytm_checksum = value + elif key == 'cmd': + continue else: paytm_params[key] = value - # Verify checksum - is_valid_checksum = verify_checksum(paytm_params, paytm_config.merchant_key, paytm_checksum) + if paytm_params and paytm_config and paytm_checksum: + # Verify checksum + is_valid_checksum = verify_checksum(paytm_params, paytm_config.merchant_key, paytm_checksum) if is_valid_checksum and received_data['RESPCODE'] == '01': verify_transaction_status(paytm_config, received_data['ORDERID']) else: frappe.respond_as_web_page("Payment Failed", - "Transaction failed to complete. Don't worry, in case of failure amount will get refunded to your account.", + "Transaction failed to complete. In case of any deductions, deducted amount will get refunded to your account.", http_status_code=401, indicator_color='red') - frappe.log_error("Order unsuccessful, received data:"+received_data, 'Paytm Payment Failed') + frappe.log_error("Order unsuccessful. Failed Response:"+cstr(received_data), 'Paytm Payment Failed') def verify_transaction_status(paytm_config, order_id): '''Verify transaction completion after checksum has been verified''' @@ -136,15 +140,16 @@ def verify_transaction_status(paytm_config, order_id): def finalize_request(order_id, transaction_response): request = frappe.get_doc('Integration Request', order_id) - redirect_to = request.data.get('redirect_to') or None - redirect_message = request.data.get('redirect_message') or None + transaction_data = frappe._dict(json.loads(request.data)) + redirect_to = transaction_data.get('redirect_to') or None + redirect_message = transaction_data.get('redirect_message') or None if transaction_response['STATUS'] == "TXN_SUCCESS": - if request.data.reference_doctype and request.data.reference_docname: + if transaction_data.reference_doctype and transaction_data.reference_docname: custom_redirect_to = None try: - custom_redirect_to = frappe.get_doc(request.data.reference_doctype, - request.data.reference_docname).run_method("on_payment_authorized", 'Completed') + custom_redirect_to = frappe.get_doc(transaction_data.reference_doctype, + transaction_data.reference_docname).run_method("on_payment_authorized", 'Completed') request.db_set('status', 'Completed') except Exception: request.db_set('status', 'Failed') @@ -153,10 +158,10 @@ def finalize_request(order_id, transaction_response): if custom_redirect_to: redirect_to = custom_redirect_to - redirect_url = 'payment-success' + redirect_url = '/integrations/payment-success' else: request.db_set('status', 'Failed') - redirect_url = 'payment-failed' + redirect_url = '/integrations/payment-failed' if redirect_to: redirect_url += '?' + urlencode({'redirect_to': redirect_to}) @@ -164,7 +169,7 @@ def finalize_request(order_id, transaction_response): redirect_url += '&' + urlencode({'redirect_message': redirect_message}) frappe.local.response['type'] = 'redirect' - frappe.local.response['location'] = 'redirect_url' + frappe.local.response['location'] = redirect_url def get_gateway_controller(doctype, docname): reference_doc = frappe.get_doc(doctype, docname) diff --git a/payments/templates/includes/paytm_checkout.js b/payments/templates/includes/paytm_checkout.js deleted file mode 100644 index 4cf070b..0000000 --- a/payments/templates/includes/paytm_checkout.js +++ /dev/null @@ -1,42 +0,0 @@ -function onScriptLoad() { - console.log('inside on load') - var config = { - root: '', - flow: 'DEFAULT', - data: { - orderId: '{{ order_id}}', - token: '{{ token }}', - tokenType: 'TXN_TOKEN', - amount: '{{ amount }}' - }, - handler: { - notifyMerchant: function(eventName, data) { - // notify about the state of the payment page ( invalid token , session expire , cancel transaction) - console.log('notifyMerchant handler function called'); - console.log('eventName => ', eventName); - console.log('data => ', data); - }, - transactionStatus: function transactionStatus(paymentStatus) { - // provide information to merchant about the payment status. - console.log('transaction status handler function called'); - console.log('paymentStatus => ', paymentStatus); - } - } - }; - - $('.paytm-loading').addClass('hidden'); - if (window.Paytm && window.Paytm.CheckoutJS) { - window.Paytm.CheckoutJS.onLoad(function excecuteAfterCompleteLoad() { - // initialze configuration using init method - window.Paytm.CheckoutJS.init(config) - .then(function onSuccess() { - // after successfully updating configuration, invoke Blink Checkout - window.Paytm.CheckoutJS.invoke(); - }) - .catch(function onError(error) { - console.log('inside the error window') - console.log('error => ', error); - }); - }); - } -} diff --git a/payments/templates/pages/paytm_checkout.html b/payments/templates/pages/paytm_checkout.html index 772e16e..77a2045 100644 --- a/payments/templates/pages/paytm_checkout.html +++ b/payments/templates/pages/paytm_checkout.html @@ -16,12 +16,14 @@ {%- block page_content -%} -

Please do not refresh this page...

+
+

Please do not refresh this page...

{% for name, value in payment_details.items() %} {% endfor %}
+
{% endblock %} diff --git a/payments/templates/pages/paytm_checkout.py b/payments/templates/pages/paytm_checkout.py index 1fa947a..5a55d02 100644 --- a/payments/templates/pages/paytm_checkout.py +++ b/payments/templates/pages/paytm_checkout.py @@ -12,9 +12,6 @@ import requests no_cache = 1 -expected_keys = ('amount', 'title', 'description', 'reference_doctype', 'reference_docname', - 'payer_name', 'payer_email') - def get_context(context): context.no_cache = 1 paytm_config = get_paytm_config() From e5834ba5b30f83ef1f0f2643cfc15e03c55738ae Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Wed, 22 Apr 2020 16:12:49 +0530 Subject: [PATCH 10/23] chore: remove initial validation check --- .../doctype/paytm_settings/checksum.py | 1 - .../doctype/paytm_settings/paytm_settings.py | 13 +------------ payments/templates/pages/paytm_checkout.py | 6 +----- 3 files changed, 2 insertions(+), 18 deletions(-) diff --git a/payments/payment_gateways/doctype/paytm_settings/checksum.py b/payments/payment_gateways/doctype/paytm_settings/checksum.py index 95fb7dc..b2e67bb 100644 --- a/payments/payment_gateways/doctype/paytm_settings/checksum.py +++ b/payments/payment_gateways/doctype/paytm_settings/checksum.py @@ -87,7 +87,6 @@ def __get_param_string__(params): params_string = [] for key in sorted(params.keys()): if("REFUND" in params[key] or "|" in params[key]): - respons_dict = {} exit() value = params[key] params_string.append('' if value == 'null' else str(value)) diff --git a/payments/payment_gateways/doctype/paytm_settings/paytm_settings.py b/payments/payment_gateways/doctype/paytm_settings/paytm_settings.py index 394cd9c..ed71202 100644 --- a/payments/payment_gateways/doctype/paytm_settings/paytm_settings.py +++ b/payments/payment_gateways/doctype/paytm_settings/paytm_settings.py @@ -11,7 +11,7 @@ import frappe from frappe.model.document import Document from frappe import _ from frappe.utils import get_url, call_hook_method, cint, flt, cstr -from frappe.integrations.utils import make_get_request, make_post_request, create_request_log, create_payment_gateway +from frappe.integrations.utils import create_request_log, create_payment_gateway from frappe.utils import get_request_site_address from frappe.integrations.doctype.paytm_settings.checksum import generate_checksum, verify_checksum from frappe.utils.password import get_decrypted_password @@ -22,17 +22,6 @@ class PaytmSettings(Document): def validate(self): create_payment_gateway('Paytm') call_hook_method('payment_gateway_enabled', gateway='Paytm') - if not self.flags.ignore_mandatory: - self.validate_paytm_credentails() - - def validate_paytm_credentails(self): - if self.merchant_id and self.merchant_key: - pass - # header = {"Authorization": "Bearer {0}".format(self.get_password(fieldname="secret_key", raise_exception=False))} - # try: - # make_get_request(url="https://api.stripe.com/v1/charges", headers=header) - # except Exception: - # frappe.throw(_("Seems Publishable Key or Secret Key is wrong !!!")) def validate_transaction_currency(self, currency): if currency not in self.supported_currencies: diff --git a/payments/templates/pages/paytm_checkout.py b/payments/templates/pages/paytm_checkout.py index 5a55d02..3018a9d 100644 --- a/payments/templates/pages/paytm_checkout.py +++ b/payments/templates/pages/paytm_checkout.py @@ -3,12 +3,8 @@ from __future__ import unicode_literals import frappe from frappe import _ -from frappe.utils import flt, cint import json -from six import string_types -from frappe.utils import get_request_site_address from frappe.integrations.doctype.paytm_settings.paytm_settings import get_paytm_params, get_paytm_config -import requests no_cache = 1 @@ -23,7 +19,7 @@ def get_context(context): context.url = paytm_config.url - except Exception as e: + except Exception: frappe.log_error() frappe.redirect_to_message(_('Invalid Token'), _('Seems token you are using is invalid!'), From e98b30ea368bd239d3af9af2d3095896779bdb89 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Fri, 22 May 2020 13:31:49 +0530 Subject: [PATCH 11/23] feat: added webhook hmac verification --- .../razorpay_settings/razorpay_settings.py | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/payments/payment_gateways/doctype/razorpay_settings/razorpay_settings.py b/payments/payment_gateways/doctype/razorpay_settings/razorpay_settings.py index 5e464d4..98ac022 100644 --- a/payments/payment_gateways/doctype/razorpay_settings/razorpay_settings.py +++ b/payments/payment_gateways/doctype/razorpay_settings/razorpay_settings.py @@ -64,6 +64,9 @@ from __future__ import unicode_literals import frappe from frappe import _ import json +import hmac +import sys +import hashlib from six.moves.urllib.parse import urlencode from frappe.model.document import Document from frappe.utils import get_url, call_hook_method, cint, get_timestamp @@ -317,6 +320,27 @@ class RazorpaySettings(Document): except Exception: frappe.log_error(frappe.get_traceback()) + def verify_signature(self, body, signature, key): + if sys.version_info[0] == 3: + key = bytes(key, 'utf-8') + body = bytes(body, 'utf-8') + + dig = hmac.new(key=key, + msg=body, + digestmod=hashlib.sha256) + + generated_signature = dig.hexdigest() + + if sys.version_info[0:3] < (2, 7, 7): + result = self.compare_string(generated_signature, signature) + else: + result = hmac.compare_digest(generated_signature, signature) + + if not result: + frappe.throw(_('Razorpay Signature Verification Failed'), exc=frappe.PermissionError) + + return result + def capture_payment(is_sandbox=False, sanbox_response=None): """ Verifies the purchase as complete by the merchant. From 4bc6899fa07015841640c7b8ec49f29b91b6e82a Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Fri, 29 May 2020 13:22:52 +0530 Subject: [PATCH 12/23] refactor: use six instead of sys.version_info --- .../doctype/razorpay_settings/razorpay_settings.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/payments/payment_gateways/doctype/razorpay_settings/razorpay_settings.py b/payments/payment_gateways/doctype/razorpay_settings/razorpay_settings.py index 98ac022..680225d 100644 --- a/payments/payment_gateways/doctype/razorpay_settings/razorpay_settings.py +++ b/payments/payment_gateways/doctype/razorpay_settings/razorpay_settings.py @@ -65,8 +65,8 @@ import frappe from frappe import _ import json import hmac -import sys import hashlib +import six from six.moves.urllib.parse import urlencode from frappe.model.document import Document from frappe.utils import get_url, call_hook_method, cint, get_timestamp @@ -321,7 +321,7 @@ class RazorpaySettings(Document): frappe.log_error(frappe.get_traceback()) def verify_signature(self, body, signature, key): - if sys.version_info[0] == 3: + if six.PY3: key = bytes(key, 'utf-8') body = bytes(body, 'utf-8') @@ -331,7 +331,7 @@ class RazorpaySettings(Document): generated_signature = dig.hexdigest() - if sys.version_info[0:3] < (2, 7, 7): + if six.PY2: result = self.compare_string(generated_signature, signature) else: result = hmac.compare_digest(generated_signature, signature) From e00a7284a5310351906c811af254ad8fe3ab86b8 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Fri, 29 May 2020 13:24:04 +0530 Subject: [PATCH 13/23] style: linting fixes --- .../doctype/razorpay_settings/razorpay_settings.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/payments/payment_gateways/doctype/razorpay_settings/razorpay_settings.py b/payments/payment_gateways/doctype/razorpay_settings/razorpay_settings.py index 680225d..b4f4f9d 100644 --- a/payments/payment_gateways/doctype/razorpay_settings/razorpay_settings.py +++ b/payments/payment_gateways/doctype/razorpay_settings/razorpay_settings.py @@ -325,9 +325,7 @@ class RazorpaySettings(Document): key = bytes(key, 'utf-8') body = bytes(body, 'utf-8') - dig = hmac.new(key=key, - msg=body, - digestmod=hashlib.sha256) + dig = hmac.new(key=key, msg=body, digestmod=hashlib.sha256) generated_signature = dig.hexdigest() From 7e70ad4cc475dd06156295cd128ab42f7c7c48f7 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Fri, 29 May 2020 14:09:05 +0530 Subject: [PATCH 14/23] feat: make verification function python 3 only --- .../doctype/razorpay_settings/razorpay_settings.py | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/payments/payment_gateways/doctype/razorpay_settings/razorpay_settings.py b/payments/payment_gateways/doctype/razorpay_settings/razorpay_settings.py index b4f4f9d..1d2f7f9 100644 --- a/payments/payment_gateways/doctype/razorpay_settings/razorpay_settings.py +++ b/payments/payment_gateways/doctype/razorpay_settings/razorpay_settings.py @@ -66,7 +66,6 @@ from frappe import _ import json import hmac import hashlib -import six from six.moves.urllib.parse import urlencode from frappe.model.document import Document from frappe.utils import get_url, call_hook_method, cint, get_timestamp @@ -321,18 +320,13 @@ class RazorpaySettings(Document): frappe.log_error(frappe.get_traceback()) def verify_signature(self, body, signature, key): - if six.PY3: - key = bytes(key, 'utf-8') - body = bytes(body, 'utf-8') + key = bytes(key, 'utf-8') + body = bytes(body, 'utf-8') dig = hmac.new(key=key, msg=body, digestmod=hashlib.sha256) generated_signature = dig.hexdigest() - - if six.PY2: - result = self.compare_string(generated_signature, signature) - else: - result = hmac.compare_digest(generated_signature, signature) + result = hmac.compare_digest(generated_signature, signature) if not result: frappe.throw(_('Razorpay Signature Verification Failed'), exc=frappe.PermissionError) From a31cd3a9649cb36ec2c4d2cdd993fa027cce955e Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Tue, 2 Jun 2020 12:33:01 +0530 Subject: [PATCH 15/23] fix: add docs link to the settings page dashboard --- .../doctype/paytm_settings/paytm_settings.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/payments/payment_gateways/doctype/paytm_settings/paytm_settings.js b/payments/payment_gateways/doctype/paytm_settings/paytm_settings.js index d51a6e4..fe2ee7c 100644 --- a/payments/payment_gateways/doctype/paytm_settings/paytm_settings.js +++ b/payments/payment_gateways/doctype/paytm_settings/paytm_settings.js @@ -2,7 +2,7 @@ // For license information, please see license.txt frappe.ui.form.on('Paytm Settings', { - // refresh: function(frm) { - - // } + refresh: function(frm) { + frm.dashboard.set_headline(__("For more information, {0}.", [`${__('Click here')}`])); + } }); From 7fc4702bcdb222cfff84f176f98c61d6de86f022 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Mon, 8 Jun 2020 17:06:48 +0530 Subject: [PATCH 16/23] fix: update the checksum logic --- .../doctype/paytm_settings/checksum.py | 186 ++++++------------ .../paytm_settings/paytm_settings.json | 40 ++-- .../doctype/paytm_settings/paytm_settings.py | 8 +- 3 files changed, 92 insertions(+), 142 deletions(-) diff --git a/payments/payment_gateways/doctype/paytm_settings/checksum.py b/payments/payment_gateways/doctype/paytm_settings/checksum.py index b2e67bb..32f976a 100644 --- a/payments/payment_gateways/doctype/paytm_settings/checksum.py +++ b/payments/payment_gateways/doctype/paytm_settings/checksum.py @@ -2,141 +2,79 @@ import base64 import string import random import hashlib +import sys from Crypto.Cipher import AES -IV = "@@@@&&&&####$$$$" +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) +if (sys.version_info > (3, 0)): + __pad__ = lambda s: bytes(s + (BLOCK_SIZE - len(s) % BLOCK_SIZE) * chr(BLOCK_SIZE - len(s) % BLOCK_SIZE), 'utf-8') +else: + __pad__ = lambda s: s + (BLOCK_SIZE - len(s) % BLOCK_SIZE) * chr(BLOCK_SIZE - len(s) % BLOCK_SIZE) + +__unpad__ = lambda s: s[0:-ord(s[-1])] + +def encrypt(input, key): + input = __pad__(input) + c = AES.new(key.encode("utf8"), AES.MODE_CBC, iv.encode("utf8")) + input = c.encrypt(input) + input = base64.b64encode(input) + return input.decode("UTF-8") + +def decrypt(encrypted, key): + encrypted = base64.b64decode(encrypted) + c = AES.new(key.encode("utf8"), AES.MODE_CBC, iv.encode("utf8")) + param = c.decrypt(encrypted) + if type(param) == bytes: + param = param.decode() + return __unpad__(param) + +def generateSignature(params, key): + if not type(params) is dict and not type(params) is str: + raise Exception("string or dict expected, " + str(type(params)) + " given") + if type(params) is dict: + params = getStringByParams(params) + return generateSignatureByString(params, key) + +def verifySignature(params, key, checksum): + if not type(params) is dict and not type(params) is str: + raise Exception("string or dict expected, " + str(type(params)) + " given") + if "CHECKSUMHASH" in params: + del params["CHECKSUMHASH"] + + if type(params) is dict: + params = getStringByParams(params) + return verifySignatureByString(params, key, checksum) + +def generateSignatureByString(params, key): + salt = generateRandomString(4) + return calculateChecksum(params, key, salt) + +def verifySignatureByString(params, key, checksum): + paytm_hash = decrypt(checksum, key) salt = paytm_hash[-4:] - calculated_checksum = generate_checksum( - param_dict, merchant_key, salt=salt) - return calculated_checksum == checksum + return paytm_hash == calculateHash(params, salt) +def generateRandomString(length): + chars = string.ascii_uppercase + string.digits + string.ascii_lowercase + return ''.join(random.choice(chars) for _ in range(length)) -def verify_checksum_by_str(param_str, 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_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): +def getStringByParams(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)) + value = params[key] if params[key] is not None and params[key].lower() != "null" else "" + params_string.append(str(value)) return '|'.join(params_string) +def calculateHash(params, salt): + finalString = '%s|%s' % (params, salt) + hasher = hashlib.sha256(finalString.encode()) + hashString = hasher.hexdigest() + salt + return hashString -def __pad__(s): return s + (BLOCK_SIZE - len(s) % - BLOCK_SIZE) * chr(BLOCK_SIZE - len(s) % BLOCK_SIZE) - - -def __unpad__(s): return s[0:-ord(s[-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', - "CD5ndX8VVjlzjWbbYoAtKQIlvtXPypQYOg0Fi2AUYKXZA5XSHiRF0FDj7vQu66S8MHx9NaDZ/uYm3WBOWHf+sDQAmTyxqUipA7i1nILlxrk=")) - - # print(generate_checksum(params, "xxxxxxxxxxxxxxxx")) +def calculateChecksum(params, key, salt): + hashString = calculateHash(params, salt) + return encrypt(hashString, key) \ No newline at end of file diff --git a/payments/payment_gateways/doctype/paytm_settings/paytm_settings.json b/payments/payment_gateways/doctype/paytm_settings/paytm_settings.json index 0540dc4..93fbd0d 100644 --- a/payments/payment_gateways/doctype/paytm_settings/paytm_settings.json +++ b/payments/payment_gateways/doctype/paytm_settings/paytm_settings.json @@ -9,7 +9,7 @@ "merchant_key", "staging", "column_break_4", - "industry_type", + "industry_type_id", "website" ], "fields": [ @@ -18,43 +18,55 @@ "fieldtype": "Data", "in_list_view": 1, "label": "Merchant ID", - "reqd": 1 + "reqd": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "merchant_key", "fieldtype": "Password", "in_list_view": 1, "label": "Merchant Key", - "reqd": 1 + "reqd": 1, + "show_days": 1, + "show_seconds": 1 }, { "default": "0", "fieldname": "staging", "fieldtype": "Check", - "label": "Staging" - }, - { - "depends_on": "eval: !doc.staging", - "fieldname": "industry_type", - "fieldtype": "Data", - "label": "Industry Type", - "mandatory_depends_on": "eval: !doc.staging" + "label": "Staging", + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "eval: !doc.staging", "fieldname": "website", "fieldtype": "Data", "label": "Website", - "mandatory_depends_on": "eval: !doc.staging" + "mandatory_depends_on": "eval: !doc.staging", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break_4", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 + }, + { + "depends_on": "eval: !doc.staging", + "fieldname": "industry_type_id", + "fieldtype": "Data", + "label": "Industry Type ID", + "mandatory_depends_on": "eval: !doc.staging", + "show_days": 1, + "show_seconds": 1 } ], "issingle": 1, "links": [], - "modified": "2020-04-16 14:16:19.687449", + "modified": "2020-06-08 13:36:09.703143", "modified_by": "Administrator", "module": "Integrations", "name": "Paytm Settings", diff --git a/payments/payment_gateways/doctype/paytm_settings/paytm_settings.py b/payments/payment_gateways/doctype/paytm_settings/paytm_settings.py index ed71202..c169a53 100644 --- a/payments/payment_gateways/doctype/paytm_settings/paytm_settings.py +++ b/payments/payment_gateways/doctype/paytm_settings/paytm_settings.py @@ -13,7 +13,7 @@ from frappe import _ from frappe.utils import get_url, call_hook_method, cint, flt, cstr from frappe.integrations.utils import create_request_log, create_payment_gateway from frappe.utils import get_request_site_address -from frappe.integrations.doctype.paytm_settings.checksum import generate_checksum, verify_checksum +from frappe.integrations.doctype.paytm_settings.checksum import generateSignature, verifySignature from frappe.utils.password import get_decrypted_password class PaytmSettings(Document): @@ -75,7 +75,7 @@ def get_paytm_params(payment_details, order_id, paytm_config): "CALLBACK_URL" : redirect_uri, }) - checksum = generate_checksum(paytm_params, paytm_config.merchant_key) + checksum = generateSignature(paytm_params, paytm_config.merchant_key) paytm_params.update({ "CHECKSUMHASH" : checksum @@ -101,7 +101,7 @@ def verify_transaction(**kwargs): if paytm_params and paytm_config and paytm_checksum: # Verify checksum - is_valid_checksum = verify_checksum(paytm_params, paytm_config.merchant_key, paytm_checksum) + is_valid_checksum = verifySignature(paytm_params, paytm_config.merchant_key, paytm_checksum) if is_valid_checksum and received_data['RESPCODE'] == '01': verify_transaction_status(paytm_config, received_data['ORDERID']) @@ -118,7 +118,7 @@ def verify_transaction_status(paytm_config, order_id): ORDERID= order_id ) - checksum = generate_checksum(paytm_params, paytm_config.merchant_key) + checksum = generateSignature(paytm_params, paytm_config.merchant_key) paytm_params["CHECKSUMHASH"] = checksum post_data = json.dumps(paytm_params) From e636ce9c24779b30d32656315f3b33147da08859 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Mon, 20 Jul 2020 23:08:50 +0530 Subject: [PATCH 17/23] fix(paytm-integration): use checksum library to generate/verify checksum --- .../doctype/paytm_settings/checksum.py | 80 ------------------- .../doctype/paytm_settings/paytm_settings.py | 2 +- 2 files changed, 1 insertion(+), 81 deletions(-) delete mode 100644 payments/payment_gateways/doctype/paytm_settings/checksum.py diff --git a/payments/payment_gateways/doctype/paytm_settings/checksum.py b/payments/payment_gateways/doctype/paytm_settings/checksum.py deleted file mode 100644 index 32f976a..0000000 --- a/payments/payment_gateways/doctype/paytm_settings/checksum.py +++ /dev/null @@ -1,80 +0,0 @@ -import base64 -import string -import random -import hashlib -import sys - -from Crypto.Cipher import AES - - -iv = '@@@@&&&&####$$$$' -BLOCK_SIZE = 16 - -if (sys.version_info > (3, 0)): - __pad__ = lambda s: bytes(s + (BLOCK_SIZE - len(s) % BLOCK_SIZE) * chr(BLOCK_SIZE - len(s) % BLOCK_SIZE), 'utf-8') -else: - __pad__ = lambda s: s + (BLOCK_SIZE - len(s) % BLOCK_SIZE) * chr(BLOCK_SIZE - len(s) % BLOCK_SIZE) - -__unpad__ = lambda s: s[0:-ord(s[-1])] - -def encrypt(input, key): - input = __pad__(input) - c = AES.new(key.encode("utf8"), AES.MODE_CBC, iv.encode("utf8")) - input = c.encrypt(input) - input = base64.b64encode(input) - return input.decode("UTF-8") - -def decrypt(encrypted, key): - encrypted = base64.b64decode(encrypted) - c = AES.new(key.encode("utf8"), AES.MODE_CBC, iv.encode("utf8")) - param = c.decrypt(encrypted) - if type(param) == bytes: - param = param.decode() - return __unpad__(param) - -def generateSignature(params, key): - if not type(params) is dict and not type(params) is str: - raise Exception("string or dict expected, " + str(type(params)) + " given") - if type(params) is dict: - params = getStringByParams(params) - return generateSignatureByString(params, key) - -def verifySignature(params, key, checksum): - if not type(params) is dict and not type(params) is str: - raise Exception("string or dict expected, " + str(type(params)) + " given") - if "CHECKSUMHASH" in params: - del params["CHECKSUMHASH"] - - if type(params) is dict: - params = getStringByParams(params) - return verifySignatureByString(params, key, checksum) - -def generateSignatureByString(params, key): - salt = generateRandomString(4) - return calculateChecksum(params, key, salt) - -def verifySignatureByString(params, key, checksum): - paytm_hash = decrypt(checksum, key) - salt = paytm_hash[-4:] - return paytm_hash == calculateHash(params, salt) - -def generateRandomString(length): - chars = string.ascii_uppercase + string.digits + string.ascii_lowercase - return ''.join(random.choice(chars) for _ in range(length)) - -def getStringByParams(params): - params_string = [] - for key in sorted(params.keys()): - value = params[key] if params[key] is not None and params[key].lower() != "null" else "" - params_string.append(str(value)) - return '|'.join(params_string) - -def calculateHash(params, salt): - finalString = '%s|%s' % (params, salt) - hasher = hashlib.sha256(finalString.encode()) - hashString = hasher.hexdigest() + salt - return hashString - -def calculateChecksum(params, key, salt): - hashString = calculateHash(params, salt) - return encrypt(hashString, key) \ No newline at end of file diff --git a/payments/payment_gateways/doctype/paytm_settings/paytm_settings.py b/payments/payment_gateways/doctype/paytm_settings/paytm_settings.py index c169a53..bfa9b6b 100644 --- a/payments/payment_gateways/doctype/paytm_settings/paytm_settings.py +++ b/payments/payment_gateways/doctype/paytm_settings/paytm_settings.py @@ -13,7 +13,7 @@ from frappe import _ from frappe.utils import get_url, call_hook_method, cint, flt, cstr from frappe.integrations.utils import create_request_log, create_payment_gateway from frappe.utils import get_request_site_address -from frappe.integrations.doctype.paytm_settings.checksum import generateSignature, verifySignature +from paytmchecksum import generateSignature, verifySignature from frappe.utils.password import get_decrypted_password class PaytmSettings(Document): From a7b3ae3bc2059d4640666d80d7eb51ea82d175f2 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Tue, 21 Jul 2020 00:12:06 +0530 Subject: [PATCH 18/23] fix: make the checkout page responsive for mobile view --- payments/templates/pages/paytm_checkout.html | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/payments/templates/pages/paytm_checkout.html b/payments/templates/pages/paytm_checkout.html index 77a2045..168f659 100644 --- a/payments/templates/pages/paytm_checkout.html +++ b/payments/templates/pages/paytm_checkout.html @@ -16,8 +16,9 @@ {%- block page_content -%} -
-

Please do not refresh this page...

+
+

Please do not refresh this page...

+
{% for name, value in payment_details.items() %} @@ -29,9 +30,14 @@ {% block style %} {% endblock %} \ No newline at end of file From 6b76559d0cab7e9510257efdfcf5866a2a914a6c Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Fri, 24 Jul 2020 20:19:35 +0530 Subject: [PATCH 19/23] fix(paytm-integration): simplify code and remove additional loop --- .../doctype/paytm_settings/paytm_settings.py | 20 +++++++------------ payments/templates/pages/paytm_checkout.py | 2 -- 2 files changed, 7 insertions(+), 15 deletions(-) diff --git a/payments/payment_gateways/doctype/paytm_settings/paytm_settings.py b/payments/payment_gateways/doctype/paytm_settings/paytm_settings.py index bfa9b6b..a166d24 100644 --- a/payments/payment_gateways/doctype/paytm_settings/paytm_settings.py +++ b/payments/payment_gateways/doctype/paytm_settings/paytm_settings.py @@ -25,7 +25,7 @@ class PaytmSettings(Document): def validate_transaction_currency(self, currency): if currency not in self.supported_currencies: - frappe.throw(_("Please select another payment method. Stripe does not support transactions in currency '{0}'").format(currency)) + frappe.throw(_("Please select another payment method. Paytm does not support transactions in currency '{0}'").format(currency)) def get_payment_url(self, **kwargs): '''Return payment url with several params''' @@ -87,29 +87,23 @@ def get_paytm_params(payment_details, order_id, paytm_config): def verify_transaction(**kwargs): '''Verify checksum for received data in the callback and then verify the transaction''' paytm_config = get_paytm_config() - received_data = frappe._dict(kwargs) + paytm_params = frappe._dict(kwargs) is_valid_checksum = False - paytm_params = {} - for key, value in received_data.items(): - if key == 'CHECKSUMHASH': - paytm_checksum = value - elif key == 'cmd': - continue - else: - paytm_params[key] = value + paytm_params.pop('cmd', None) + paytm_checksum = paytm_params.pop('CHECKSUMHASH', None) if paytm_params and paytm_config and paytm_checksum: # Verify checksum is_valid_checksum = verifySignature(paytm_params, paytm_config.merchant_key, paytm_checksum) - if is_valid_checksum and received_data['RESPCODE'] == '01': - verify_transaction_status(paytm_config, received_data['ORDERID']) + if is_valid_checksum and paytm_params.get('RESPCODE') == '01': + verify_transaction_status(paytm_config, paytm_params['ORDERID']) else: frappe.respond_as_web_page("Payment Failed", "Transaction failed to complete. In case of any deductions, deducted amount will get refunded to your account.", http_status_code=401, indicator_color='red') - frappe.log_error("Order unsuccessful. Failed Response:"+cstr(received_data), 'Paytm Payment Failed') + frappe.log_error("Order unsuccessful. Failed Response:"+cstr(paytm_params), 'Paytm Payment Failed') def verify_transaction_status(paytm_config, order_id): '''Verify transaction completion after checksum has been verified''' diff --git a/payments/templates/pages/paytm_checkout.py b/payments/templates/pages/paytm_checkout.py index 3018a9d..bc385b5 100644 --- a/payments/templates/pages/paytm_checkout.py +++ b/payments/templates/pages/paytm_checkout.py @@ -6,8 +6,6 @@ from frappe import _ import json from frappe.integrations.doctype.paytm_settings.paytm_settings import get_paytm_params, get_paytm_config -no_cache = 1 - def get_context(context): context.no_cache = 1 paytm_config = get_paytm_config() From b75528e762ca6d20d602c9559eb6c2e276119a77 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Fri, 24 Jul 2020 22:59:16 +0530 Subject: [PATCH 20/23] fix: minor changes --- .../payment_gateways/doctype/paytm_settings/paytm_settings.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/payments/payment_gateways/doctype/paytm_settings/paytm_settings.py b/payments/payment_gateways/doctype/paytm_settings/paytm_settings.py index a166d24..616c383 100644 --- a/payments/payment_gateways/doctype/paytm_settings/paytm_settings.py +++ b/payments/payment_gateways/doctype/paytm_settings/paytm_settings.py @@ -84,10 +84,9 @@ def get_paytm_params(payment_details, order_id, paytm_config): return paytm_params @frappe.whitelist(allow_guest=True) -def verify_transaction(**kwargs): +def verify_transaction(**paytm_params): '''Verify checksum for received data in the callback and then verify the transaction''' paytm_config = get_paytm_config() - paytm_params = frappe._dict(kwargs) is_valid_checksum = False paytm_params.pop('cmd', None) From 49b1f12027893a8a3d82d8164d74ef34a381f30d Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Fri, 4 Sep 2020 15:52:21 +0530 Subject: [PATCH 21/23] feat: add init client API --- .../doctype/razorpay_settings/razorpay_settings.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/payments/payment_gateways/doctype/razorpay_settings/razorpay_settings.py b/payments/payment_gateways/doctype/razorpay_settings/razorpay_settings.py index 1d2f7f9..5e7e75c 100644 --- a/payments/payment_gateways/doctype/razorpay_settings/razorpay_settings.py +++ b/payments/payment_gateways/doctype/razorpay_settings/razorpay_settings.py @@ -65,6 +65,7 @@ import frappe from frappe import _ import json import hmac +import razorpay import hashlib from six.moves.urllib.parse import urlencode from frappe.model.document import Document @@ -75,6 +76,11 @@ from frappe.integrations.utils import (make_get_request, make_post_request, crea class RazorpaySettings(Document): supported_currencies = ["INR"] + def init_client(self): + if self.api_key: + self.secret = self.get_password(fieldname="api_secret", raise_exception=False) + self.client = razorpay.Client(auth=(self.api_key, self.secret)) + def validate(self): create_payment_gateway('Razorpay') call_hook_method('payment_gateway_enabled', gateway='Razorpay') From 656d1a72d75a5b9ac9a61e04629e597c9b103405 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Mon, 7 Sep 2020 11:36:31 +0530 Subject: [PATCH 22/23] refactor: don't attach secret to self (#11428) --- .../doctype/razorpay_settings/razorpay_settings.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/payments/payment_gateways/doctype/razorpay_settings/razorpay_settings.py b/payments/payment_gateways/doctype/razorpay_settings/razorpay_settings.py index 5e7e75c..af7686c 100644 --- a/payments/payment_gateways/doctype/razorpay_settings/razorpay_settings.py +++ b/payments/payment_gateways/doctype/razorpay_settings/razorpay_settings.py @@ -78,8 +78,8 @@ class RazorpaySettings(Document): def init_client(self): if self.api_key: - self.secret = self.get_password(fieldname="api_secret", raise_exception=False) - self.client = razorpay.Client(auth=(self.api_key, self.secret)) + secret = self.get_password(fieldname="api_secret", raise_exception=False) + self.client = razorpay.Client(auth=(self.api_key, secret)) def validate(self): create_payment_gateway('Razorpay') From 5372351238bbf1454e1785f5f1770ced22984298 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Wed, 11 Nov 2020 16:31:47 +0530 Subject: [PATCH 23/23] feat(app): move /desk to /app --- payments/templates/pages/gcalendar-success.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/payments/templates/pages/gcalendar-success.html b/payments/templates/pages/gcalendar-success.html index 1ce9ba5..4138644 100644 --- a/payments/templates/pages/gcalendar-success.html +++ b/payments/templates/pages/gcalendar-success.html @@ -9,7 +9,7 @@ {{ _("Success") }}

{{ _("Your connection request to Google Calendar was successfully accepted") }}

-