From c0d210206fb37624888d6f7b8aba92a6e7366063 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Thu, 2 Apr 2020 18:12:43 +0530 Subject: [PATCH 001/345] 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 frappe/integrations/doctype/paytm_settings/__init__.py create mode 100644 frappe/integrations/doctype/paytm_settings/paytm_settings.js create mode 100644 frappe/integrations/doctype/paytm_settings/paytm_settings.json create mode 100644 frappe/integrations/doctype/paytm_settings/paytm_settings.py create mode 100644 frappe/integrations/doctype/paytm_settings/test_paytm_settings.py diff --git a/frappe/integrations/doctype/paytm_settings/__init__.py b/frappe/integrations/doctype/paytm_settings/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/integrations/doctype/paytm_settings/paytm_settings.js b/frappe/integrations/doctype/paytm_settings/paytm_settings.js new file mode 100644 index 0000000000..d51a6e4a25 --- /dev/null +++ b/frappe/integrations/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/frappe/integrations/doctype/paytm_settings/paytm_settings.json b/frappe/integrations/doctype/paytm_settings/paytm_settings.json new file mode 100644 index 0000000000..e57c8a192c --- /dev/null +++ b/frappe/integrations/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/frappe/integrations/doctype/paytm_settings/paytm_settings.py b/frappe/integrations/doctype/paytm_settings/paytm_settings.py new file mode 100644 index 0000000000..74d51d1dcc --- /dev/null +++ b/frappe/integrations/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/frappe/integrations/doctype/paytm_settings/test_paytm_settings.py b/frappe/integrations/doctype/paytm_settings/test_paytm_settings.py new file mode 100644 index 0000000000..77a16c82ae --- /dev/null +++ b/frappe/integrations/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 8831bc6531bad3925526ea0d4e9f345cfbd89610 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Wed, 8 Apr 2020 18:49:48 +0530 Subject: [PATCH 002/345] feat: blink checkout --- .../doctype/paytm_settings/checksum.py | 143 ++++++++++++++++++ .../includes/integrations/paytm_checkout.js | 42 +++++ 2 files changed, 185 insertions(+) create mode 100644 frappe/integrations/doctype/paytm_settings/checksum.py create mode 100644 frappe/templates/includes/integrations/paytm_checkout.js diff --git a/frappe/integrations/doctype/paytm_settings/checksum.py b/frappe/integrations/doctype/paytm_settings/checksum.py new file mode 100644 index 0000000000..95fb7dc164 --- /dev/null +++ b/frappe/integrations/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/frappe/templates/includes/integrations/paytm_checkout.js b/frappe/templates/includes/integrations/paytm_checkout.js new file mode 100644 index 0000000000..4cf070b60b --- /dev/null +++ b/frappe/templates/includes/integrations/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 72e88b344033d8512651770c1f170324fa66d08d Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Fri, 10 Apr 2020 12:50:54 +0530 Subject: [PATCH 003/345] feat: create transaction token --- .../doctype/paytm_settings/paytm_settings.py | 150 +++++++++++++++++- .../pages/integrations/paytm_checkout.html | 26 +++ .../pages/integrations/paytm_checkout.py | 105 ++++++++++++ 3 files changed, 279 insertions(+), 2 deletions(-) create mode 100644 frappe/templates/pages/integrations/paytm_checkout.html create mode 100644 frappe/templates/pages/integrations/paytm_checkout.py diff --git a/frappe/integrations/doctype/paytm_settings/paytm_settings.py b/frappe/integrations/doctype/paytm_settings/paytm_settings.py index 74d51d1dcc..1f9e508460 100644 --- a/frappe/integrations/doctype/paytm_settings/paytm_settings.py +++ b/frappe/integrations/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/frappe/templates/pages/integrations/paytm_checkout.html b/frappe/templates/pages/integrations/paytm_checkout.html new file mode 100644 index 0000000000..2aedf31d65 --- /dev/null +++ b/frappe/templates/pages/integrations/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/frappe/templates/pages/integrations/paytm_checkout.py b/frappe/templates/pages/integrations/paytm_checkout.py new file mode 100644 index 0000000000..a17873bc80 --- /dev/null +++ b/frappe/templates/pages/integrations/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 3f6459edbb384d1529fe350bd93bd413c4b4f136 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Thu, 16 Apr 2020 15:48:37 +0530 Subject: [PATCH 004/345] fix: paytm integration --- .pylintrc | 2 -- frappe/integrations/doctype/paytm_settings/paytm_settings.json | 3 +-- 2 files changed, 1 insertion(+), 4 deletions(-) delete mode 100644 .pylintrc diff --git a/.pylintrc b/.pylintrc deleted file mode 100644 index c11c0ab6a3..0000000000 --- a/.pylintrc +++ /dev/null @@ -1,2 +0,0 @@ -disable=access-member-before-definition -disable=no-member \ No newline at end of file diff --git a/frappe/integrations/doctype/paytm_settings/paytm_settings.json b/frappe/integrations/doctype/paytm_settings/paytm_settings.json index e57c8a192c..eb68f0ab79 100644 --- a/frappe/integrations/doctype/paytm_settings/paytm_settings.json +++ b/frappe/integrations/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 b0a34c602d9954f9b1d77fae918ce167a2ba799e Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Thu, 16 Apr 2020 15:51:11 +0530 Subject: [PATCH 005/345] fix: initiate payment via standard checkout --- .../pages/integrations/paytm_checkout.html | 47 ++++++----- .../pages/integrations/paytm_checkout.py | 78 ++----------------- 2 files changed, 33 insertions(+), 92 deletions(-) diff --git a/frappe/templates/pages/integrations/paytm_checkout.html b/frappe/templates/pages/integrations/paytm_checkout.html index 2aedf31d65..772e16ecb8 100644 --- a/frappe/templates/pages/integrations/paytm_checkout.html +++ b/frappe/templates/pages/integrations/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 - -

+{% extends "templates/web.html" %} -{% endblock %} {% block style %} +{% block title %} Payment {% endblock %} + +{%- block header -%} + + Merchant Checkout Page + +{% endblock %} + +{% block script %} + +{% endblock %} + +{%- block page_content -%} + +

Please do not refresh this page...

+
+ {% for name, value in payment_details.items() %} + + {% endfor %} +
+ +{% endblock %} + +{% block style %} -{% endblock %} +{% endblock %} \ No newline at end of file diff --git a/frappe/templates/pages/integrations/paytm_checkout.py b/frappe/templates/pages/integrations/paytm_checkout.py index a17873bc80..1c61d5cab4 100644 --- a/frappe/templates/pages/integrations/paytm_checkout.py +++ b/frappe/templates/pages/integrations/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 9d71bdcd874451181c21b384be81f8e88f390500 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Thu, 16 Apr 2020 18:53:56 +0530 Subject: [PATCH 006/345] feat: add paytm config, transaction status --- .../paytm_settings/paytm_settings.json | 40 +++- .../doctype/paytm_settings/paytm_settings.py | 206 ++++++++++-------- 2 files changed, 148 insertions(+), 98 deletions(-) diff --git a/frappe/integrations/doctype/paytm_settings/paytm_settings.json b/frappe/integrations/doctype/paytm_settings/paytm_settings.json index eb68f0ab79..0540dc4948 100644 --- a/frappe/integrations/doctype/paytm_settings/paytm_settings.json +++ b/frappe/integrations/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/frappe/integrations/doctype/paytm_settings/paytm_settings.py b/frappe/integrations/doctype/paytm_settings/paytm_settings.py index 1f9e508460..10282d34ea 100644 --- a/frappe/integrations/doctype/paytm_settings/paytm_settings.py +++ b/frappe/integrations/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) +def get_paytm_config(): + ''' Returns paytm config ''' - try: - self.integration_request = create_request_log(self.data, "Host", "Paytm") - return self.generate_transaction_token() + paytm_config = frappe.db.get_singles_dict('Paytm Settings') + paytm_config.update(dict(merchant_key=get_decrypted_password('Paytm Settings', 'Paytm Settings', 'merchant_key'))) - 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 - } + 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 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): +def get_paytm_params(payment_details, order_id, paytm_config): # initialize a dictionary - paytmParams = dict() + 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) + "?cmd=frappe.templates.pages.integrations.paytm_checkout.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, + }) - # body parameters - paytmParams["body"] = get_paytm_params(payment_details, order_id, merchant_id, merchant_key) + checksum = generate_checksum(paytm_params, paytm_config.merchant_key) - checksum = generate_checksum_by_str(json.dumps(paytmParams["body"]), merchant_key) + paytm_params.update({ + "CHECKSUMHASH" : checksum + }) - paytmParams["head"] = { - "signature" : checksum - } + return paytm_params - post_data = json.dumps(paytmParams) +@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) - url = "https://securegw-stage.paytm.in/theia/api/v1/initiateTransaction?mid={0}&orderId={1}".format(merchant_id, order_id) + print(received_data) + paytm_params = {} + for key, value in received_data.items(): + if key == 'CHECKSUMHASH': + paytm_checksum = value + else: + paytm_params[key] = value + + # 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.", + http_status_code=401, indicator_color='red') + frappe.log_error("Order unsuccessful, received data:"+received_data, 'Paytm Payment Failed') + +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 + ) + + checksum = generate_checksum(paytm_params, paytm_config.merchant_key) + paytm_params["CHECKSUMHASH"] = checksum + + 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 cf5da1c787e1a24ef707d8541e26e5091421ee63 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Fri, 17 Apr 2020 10:02:54 +0530 Subject: [PATCH 007/345] fix: check for transaction success in the final stage --- .../doctype/paytm_settings/paytm_settings.py | 13 ++++--------- .../templates/pages/integrations/paytm_checkout.py | 1 - 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/frappe/integrations/doctype/paytm_settings/paytm_settings.py b/frappe/integrations/doctype/paytm_settings/paytm_settings.py index 10282d34ea..3382aeb6ed 100644 --- a/frappe/integrations/doctype/paytm_settings/paytm_settings.py +++ b/frappe/integrations/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/frappe/templates/pages/integrations/paytm_checkout.py b/frappe/templates/pages/integrations/paytm_checkout.py index 1c61d5cab4..1fa947a68f 100644 --- a/frappe/templates/pages/integrations/paytm_checkout.py +++ b/frappe/templates/pages/integrations/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 0cbf937763cf96ee2fd567aba550ebf0fe29b273 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Fri, 17 Apr 2020 13:34:26 +0530 Subject: [PATCH 008/345] fix: change status of integration request on completion --- .../integrations/doctype/paytm_settings/paytm_settings.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/frappe/integrations/doctype/paytm_settings/paytm_settings.py b/frappe/integrations/doctype/paytm_settings/paytm_settings.py index 3382aeb6ed..39a9f42aa0 100644 --- a/frappe/integrations/doctype/paytm_settings/paytm_settings.py +++ b/frappe/integrations/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 6eaefb35c7137352f19f2c7d153aa5c78d740bba Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Wed, 22 Apr 2020 11:40:08 +0530 Subject: [PATCH 009/345] fix: add finalize request to complete the transaction --- .../doctype/paytm_settings/paytm_settings.py | 33 ++++++++------- .../includes/integrations/paytm_checkout.js | 42 ------------------- .../pages/integrations/paytm_checkout.html | 4 +- .../pages/integrations/paytm_checkout.py | 3 -- .../website_theme/standard/standard.json | 20 --------- 5 files changed, 22 insertions(+), 80 deletions(-) delete mode 100644 frappe/templates/includes/integrations/paytm_checkout.js delete mode 100644 frappe/website/website_theme/standard/standard.json diff --git a/frappe/integrations/doctype/paytm_settings/paytm_settings.py b/frappe/integrations/doctype/paytm_settings/paytm_settings.py index 39a9f42aa0..394cd9c334 100644 --- a/frappe/integrations/doctype/paytm_settings/paytm_settings.py +++ b/frappe/integrations/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/frappe/templates/includes/integrations/paytm_checkout.js b/frappe/templates/includes/integrations/paytm_checkout.js deleted file mode 100644 index 4cf070b60b..0000000000 --- a/frappe/templates/includes/integrations/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/frappe/templates/pages/integrations/paytm_checkout.html b/frappe/templates/pages/integrations/paytm_checkout.html index 772e16ecb8..77a2045edf 100644 --- a/frappe/templates/pages/integrations/paytm_checkout.html +++ b/frappe/templates/pages/integrations/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/frappe/templates/pages/integrations/paytm_checkout.py b/frappe/templates/pages/integrations/paytm_checkout.py index 1fa947a68f..5a55d02ce9 100644 --- a/frappe/templates/pages/integrations/paytm_checkout.py +++ b/frappe/templates/pages/integrations/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() diff --git a/frappe/website/website_theme/standard/standard.json b/frappe/website/website_theme/standard/standard.json deleted file mode 100644 index a799f25425..0000000000 --- a/frappe/website/website_theme/standard/standard.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "button_gradients": 0, - "button_rounded_corners": 1, - "button_shadows": 0, - "creation": "2015-02-19 13:37:33.925909", - "custom": 0, - "docstatus": 0, - "doctype": "Website Theme", - "font_properties": "300,600", - "font_size": "", - "idx": 27, - "modified": "2020-04-21 02:10:31.761219", - "modified_by": "Administrator", - "module": "Website", - "name": "Standard", - "owner": "Administrator", - "theme": "Standard", - "theme_scss": "$enable-shadows: false;\n$enable-gradients: false;\n$enable-rounded: true;\n\n@import \"frappe/public/scss/website\";\n\n", - "theme_url": "/assets/css/standard_style.css" -} \ No newline at end of file From 25c9ac002290d070ab596a872faeba926f8317ad Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Wed, 22 Apr 2020 16:12:49 +0530 Subject: [PATCH 010/345] chore: remove initial validation check --- .../integrations/doctype/paytm_settings/checksum.py | 1 - .../doctype/paytm_settings/paytm_settings.py | 13 +------------ .../templates/pages/integrations/paytm_checkout.py | 6 +----- 3 files changed, 2 insertions(+), 18 deletions(-) diff --git a/frappe/integrations/doctype/paytm_settings/checksum.py b/frappe/integrations/doctype/paytm_settings/checksum.py index 95fb7dc164..b2e67bb5ad 100644 --- a/frappe/integrations/doctype/paytm_settings/checksum.py +++ b/frappe/integrations/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/frappe/integrations/doctype/paytm_settings/paytm_settings.py b/frappe/integrations/doctype/paytm_settings/paytm_settings.py index 394cd9c334..ed71202ea6 100644 --- a/frappe/integrations/doctype/paytm_settings/paytm_settings.py +++ b/frappe/integrations/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/frappe/templates/pages/integrations/paytm_checkout.py b/frappe/templates/pages/integrations/paytm_checkout.py index 5a55d02ce9..3018a9d65b 100644 --- a/frappe/templates/pages/integrations/paytm_checkout.py +++ b/frappe/templates/pages/integrations/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 7dbdb7c50bb37e0fa1e371032c4b2516d3089c4e Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Fri, 24 Apr 2020 11:18:35 +0530 Subject: [PATCH 011/345] fix: add pycryptodome for paytm checksum generation --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index 58f923d880..2587a2eceb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -66,3 +66,4 @@ watchdog==0.8.0 Werkzeug==0.16.1 xlrd==1.2.0 zxcvbn-python==4.4.24 +pycryptodome==3.9.7 \ No newline at end of file From 7a15f6f61d121901aa08c1f0bd1c3627bda0fe34 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Fri, 24 Apr 2020 15:09:11 +0530 Subject: [PATCH 012/345] revert: add pylint and standard json again --- .pylintrc | 2 ++ .../website_theme/standard/standard.json | 20 +++++++++++++++++++ 2 files changed, 22 insertions(+) create mode 100644 .pylintrc create mode 100644 frappe/website/website_theme/standard/standard.json diff --git a/.pylintrc b/.pylintrc new file mode 100644 index 0000000000..c11c0ab6a3 --- /dev/null +++ b/.pylintrc @@ -0,0 +1,2 @@ +disable=access-member-before-definition +disable=no-member \ No newline at end of file diff --git a/frappe/website/website_theme/standard/standard.json b/frappe/website/website_theme/standard/standard.json new file mode 100644 index 0000000000..a799f25425 --- /dev/null +++ b/frappe/website/website_theme/standard/standard.json @@ -0,0 +1,20 @@ +{ + "button_gradients": 0, + "button_rounded_corners": 1, + "button_shadows": 0, + "creation": "2015-02-19 13:37:33.925909", + "custom": 0, + "docstatus": 0, + "doctype": "Website Theme", + "font_properties": "300,600", + "font_size": "", + "idx": 27, + "modified": "2020-04-21 02:10:31.761219", + "modified_by": "Administrator", + "module": "Website", + "name": "Standard", + "owner": "Administrator", + "theme": "Standard", + "theme_scss": "$enable-shadows: false;\n$enable-gradients: false;\n$enable-rounded: true;\n\n@import \"frappe/public/scss/website\";\n\n", + "theme_url": "/assets/css/standard_style.css" +} \ No newline at end of file From f512fe8446f4fb8f9755099406a014ed0f04b8fd Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Thu, 7 May 2020 18:21:05 +0530 Subject: [PATCH 013/345] feat: help article feedback --- .../js/frappe/form/sidebar/form_sidebar.js | 13 ++++ .../frappe/form/templates/form_sidebar.html | 6 +- frappe/public/js/frappe/utils/common.js | 6 ++ frappe/public/less/sidebar.less | 3 +- frappe/templates/includes/feedback.html | 29 +++++++++ frappe/website/doctype/feedback/__init__.py | 0 frappe/website/doctype/feedback/feedback.js | 8 +++ frappe/website/doctype/feedback/feedback.json | 59 +++++++++++++++++++ frappe/website/doctype/feedback/feedback.py | 27 +++++++++ .../website/doctype/feedback/test_feedback.py | 10 ++++ .../help_article/templates/help_article.html | 5 +- 11 files changed, 163 insertions(+), 3 deletions(-) create mode 100644 frappe/templates/includes/feedback.html create mode 100644 frappe/website/doctype/feedback/__init__.py create mode 100644 frappe/website/doctype/feedback/feedback.js create mode 100644 frappe/website/doctype/feedback/feedback.json create mode 100644 frappe/website/doctype/feedback/feedback.py create mode 100644 frappe/website/doctype/feedback/test_feedback.py diff --git a/frappe/public/js/frappe/form/sidebar/form_sidebar.js b/frappe/public/js/frappe/form/sidebar/form_sidebar.js index a145e47149..9450fdc674 100644 --- a/frappe/public/js/frappe/form/sidebar/form_sidebar.js +++ b/frappe/public/js/frappe/form/sidebar/form_sidebar.js @@ -93,6 +93,19 @@ frappe.ui.form.Sidebar = Class.extend({ }); } + frappe.utils.get_feedback(this.frm.doc.name).then((res) => { + this.sidebar + .find(".helpful") + .html( + __("Helpful {0}", [String(res.message.helpful).bold()]) + ); + this.sidebar + .find(".not-helpful") + .html( + __("Not Helpful {0}", [String(res.message.not_helpful).bold()]) + ); + }); + this.sidebar .find(".modified-by") .html( diff --git a/frappe/public/js/frappe/form/templates/form_sidebar.html b/frappe/public/js/frappe/form/templates/form_sidebar.html index 30b2205bae..3e72684818 100644 --- a/frappe/public/js/frappe/form/templates/form_sidebar.html +++ b/frappe/public/js/frappe/form/templates/form_sidebar.html @@ -106,11 +106,15 @@ {% if(frappe.get_form_sidebar_extension) { %} - {{ frappe.get_form_sidebar_extension() }} + {{ frappe.get_form_sidebar_extension() }} {% } %}
    diff --git a/frappe/public/js/frappe/utils/common.js b/frappe/public/js/frappe/utils/common.js index 1cdabf23e0..2fda8ed27a 100644 --- a/frappe/public/js/frappe/utils/common.js +++ b/frappe/public/js/frappe/utils/common.js @@ -359,3 +359,9 @@ frappe.utils.get_page_view_count = function(route) { path: route }); }; + +frappe.utils.get_feedback = function(reference_name) { + return frappe.call("frappe.website.doctype.feedback.feedback.get_feedback_count", { + reference_name: reference_name + }); +}; \ No newline at end of file diff --git a/frappe/public/less/sidebar.less b/frappe/public/less/sidebar.less index 109b7a3209..b6ffb7e697 100644 --- a/frappe/public/less/sidebar.less +++ b/frappe/public/less/sidebar.less @@ -274,7 +274,8 @@ body[data-route^="Module"] .main-menu { .layout-side-section .form-sidebar { .modified-by, - .pageview-count { + .pageview-count, + .feedback { margin-bottom: 15px; } } diff --git a/frappe/templates/includes/feedback.html b/frappe/templates/includes/feedback.html new file mode 100644 index 0000000000..5d10c7b159 --- /dev/null +++ b/frappe/templates/includes/feedback.html @@ -0,0 +1,29 @@ + + + diff --git a/frappe/website/doctype/feedback/__init__.py b/frappe/website/doctype/feedback/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/website/doctype/feedback/feedback.js b/frappe/website/doctype/feedback/feedback.js new file mode 100644 index 0000000000..f5fa89b30e --- /dev/null +++ b/frappe/website/doctype/feedback/feedback.js @@ -0,0 +1,8 @@ +// Copyright (c) 2020, Frappe Technologies and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Feedback', { + // refresh: function(frm) { + + // } +}); diff --git a/frappe/website/doctype/feedback/feedback.json b/frappe/website/doctype/feedback/feedback.json new file mode 100644 index 0000000000..b04aefa462 --- /dev/null +++ b/frappe/website/doctype/feedback/feedback.json @@ -0,0 +1,59 @@ +{ + "actions": [], + "creation": "2020-05-07 16:42:27.234652", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "reference_doctype", + "reference_name", + "helpful" + ], + "fields": [ + { + "fieldname": "reference_doctype", + "fieldtype": "Link", + "label": "Reference DocType", + "options": "DocType" + }, + { + "fieldname": "helpful", + "fieldtype": "Select", + "label": "Helpful", + "options": "Yes\nNo" + }, + { + "fieldname": "reference_name", + "fieldtype": "Dynamic Link", + "label": "Reference Name", + "options": "reference_doctype", + "search_index": 1 + } + ], + "in_create": 1, + "links": [], + "modified": "2020-05-07 16:53:29.211653", + "modified_by": "Administrator", + "module": "Website", + "name": "Feedback", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "quick_entry": 1, + "read_only": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/frappe/website/doctype/feedback/feedback.py b/frappe/website/doctype/feedback/feedback.py new file mode 100644 index 0000000000..3b92118ec3 --- /dev/null +++ b/frappe/website/doctype/feedback/feedback.py @@ -0,0 +1,27 @@ +# -*- 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 Feedback(Document): + pass + +@frappe.whitelist(allow_guest=True) +def add_feedback(reference_doctype, reference_name, helpful): + frappe.get_doc({ + "doctype": "Feedback", + "reference_doctype": reference_doctype, + "reference_name": reference_name, + "helpful": helpful + }).insert(ignore_permissions=True) + +@frappe.whitelist() +def get_feedback_count(reference_name): + + return { + "helpful": frappe.db.count("Feedback", {"reference_name": reference_name, "helpful": "Yes"}), + "not_helpful": frappe.db.count("Feedback", {"reference_name": reference_name, "helpful": "No"}), + } \ No newline at end of file diff --git a/frappe/website/doctype/feedback/test_feedback.py b/frappe/website/doctype/feedback/test_feedback.py new file mode 100644 index 0000000000..9a31738d75 --- /dev/null +++ b/frappe/website/doctype/feedback/test_feedback.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 TestFeedback(unittest.TestCase): + pass diff --git a/frappe/website/doctype/help_article/templates/help_article.html b/frappe/website/doctype/help_article/templates/help_article.html index 913e6d6a92..6c59359785 100644 --- a/frappe/website/doctype/help_article/templates/help_article.html +++ b/frappe/website/doctype/help_article/templates/help_article.html @@ -19,6 +19,9 @@


    {{ _("More articles on {0}").format(category.name) }}

    +
    + {% include 'templates/includes/feedback.html' %} +

    Comments

    @@ -26,7 +29,7 @@
    {% endblock %} From c3a45de794a23f96ba208add27c518765d8c9d80 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Fri, 8 May 2020 11:39:40 +0530 Subject: [PATCH 014/345] fix: feedback restrict to help article --- .../js/frappe/form/sidebar/form_sidebar.js | 13 - .../frappe/form/templates/form_sidebar.html | 4 - frappe/public/js/frappe/utils/common.js | 6 - frappe/public/less/sidebar.less | 3 +- frappe/templates/includes/feedback.html | 29 - frappe/website/doctype/feedback/__init__.py | 0 frappe/website/doctype/feedback/feedback.js | 8 - frappe/website/doctype/feedback/feedback.json | 59 -- frappe/website/doctype/feedback/feedback.py | 27 - .../website/doctype/feedback/test_feedback.py | 10 - .../doctype/help_article/help_article.js | 15 + .../doctype/help_article/help_article.json | 559 +++++------------- .../doctype/help_article/help_article.py | 10 +- .../help_article/templates/help_article.html | 26 +- 14 files changed, 185 insertions(+), 584 deletions(-) delete mode 100644 frappe/templates/includes/feedback.html delete mode 100644 frappe/website/doctype/feedback/__init__.py delete mode 100644 frappe/website/doctype/feedback/feedback.js delete mode 100644 frappe/website/doctype/feedback/feedback.json delete mode 100644 frappe/website/doctype/feedback/feedback.py delete mode 100644 frappe/website/doctype/feedback/test_feedback.py diff --git a/frappe/public/js/frappe/form/sidebar/form_sidebar.js b/frappe/public/js/frappe/form/sidebar/form_sidebar.js index 9450fdc674..a145e47149 100644 --- a/frappe/public/js/frappe/form/sidebar/form_sidebar.js +++ b/frappe/public/js/frappe/form/sidebar/form_sidebar.js @@ -93,19 +93,6 @@ frappe.ui.form.Sidebar = Class.extend({ }); } - frappe.utils.get_feedback(this.frm.doc.name).then((res) => { - this.sidebar - .find(".helpful") - .html( - __("Helpful {0}", [String(res.message.helpful).bold()]) - ); - this.sidebar - .find(".not-helpful") - .html( - __("Not Helpful {0}", [String(res.message.not_helpful).bold()]) - ); - }); - this.sidebar .find(".modified-by") .html( diff --git a/frappe/public/js/frappe/form/templates/form_sidebar.html b/frappe/public/js/frappe/form/templates/form_sidebar.html index 3e72684818..497a6488e2 100644 --- a/frappe/public/js/frappe/form/templates/form_sidebar.html +++ b/frappe/public/js/frappe/form/templates/form_sidebar.html @@ -106,10 +106,6 @@
diff --git a/frappe/public/js/frappe/utils/common.js b/frappe/public/js/frappe/utils/common.js index 2fda8ed27a..1cdabf23e0 100644 --- a/frappe/public/js/frappe/utils/common.js +++ b/frappe/public/js/frappe/utils/common.js @@ -359,9 +359,3 @@ frappe.utils.get_page_view_count = function(route) { path: route }); }; - -frappe.utils.get_feedback = function(reference_name) { - return frappe.call("frappe.website.doctype.feedback.feedback.get_feedback_count", { - reference_name: reference_name - }); -}; \ No newline at end of file diff --git a/frappe/public/less/sidebar.less b/frappe/public/less/sidebar.less index b6ffb7e697..109b7a3209 100644 --- a/frappe/public/less/sidebar.less +++ b/frappe/public/less/sidebar.less @@ -274,8 +274,7 @@ body[data-route^="Module"] .main-menu { .layout-side-section .form-sidebar { .modified-by, - .pageview-count, - .feedback { + .pageview-count { margin-bottom: 15px; } } diff --git a/frappe/templates/includes/feedback.html b/frappe/templates/includes/feedback.html deleted file mode 100644 index 5d10c7b159..0000000000 --- a/frappe/templates/includes/feedback.html +++ /dev/null @@ -1,29 +0,0 @@ - - - diff --git a/frappe/website/doctype/feedback/__init__.py b/frappe/website/doctype/feedback/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/frappe/website/doctype/feedback/feedback.js b/frappe/website/doctype/feedback/feedback.js deleted file mode 100644 index f5fa89b30e..0000000000 --- a/frappe/website/doctype/feedback/feedback.js +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) 2020, Frappe Technologies and contributors -// For license information, please see license.txt - -frappe.ui.form.on('Feedback', { - // refresh: function(frm) { - - // } -}); diff --git a/frappe/website/doctype/feedback/feedback.json b/frappe/website/doctype/feedback/feedback.json deleted file mode 100644 index b04aefa462..0000000000 --- a/frappe/website/doctype/feedback/feedback.json +++ /dev/null @@ -1,59 +0,0 @@ -{ - "actions": [], - "creation": "2020-05-07 16:42:27.234652", - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "reference_doctype", - "reference_name", - "helpful" - ], - "fields": [ - { - "fieldname": "reference_doctype", - "fieldtype": "Link", - "label": "Reference DocType", - "options": "DocType" - }, - { - "fieldname": "helpful", - "fieldtype": "Select", - "label": "Helpful", - "options": "Yes\nNo" - }, - { - "fieldname": "reference_name", - "fieldtype": "Dynamic Link", - "label": "Reference Name", - "options": "reference_doctype", - "search_index": 1 - } - ], - "in_create": 1, - "links": [], - "modified": "2020-05-07 16:53:29.211653", - "modified_by": "Administrator", - "module": "Website", - "name": "Feedback", - "owner": "Administrator", - "permissions": [ - { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "System Manager", - "share": 1, - "write": 1 - } - ], - "quick_entry": 1, - "read_only": 1, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1 -} \ No newline at end of file diff --git a/frappe/website/doctype/feedback/feedback.py b/frappe/website/doctype/feedback/feedback.py deleted file mode 100644 index 3b92118ec3..0000000000 --- a/frappe/website/doctype/feedback/feedback.py +++ /dev/null @@ -1,27 +0,0 @@ -# -*- 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 Feedback(Document): - pass - -@frappe.whitelist(allow_guest=True) -def add_feedback(reference_doctype, reference_name, helpful): - frappe.get_doc({ - "doctype": "Feedback", - "reference_doctype": reference_doctype, - "reference_name": reference_name, - "helpful": helpful - }).insert(ignore_permissions=True) - -@frappe.whitelist() -def get_feedback_count(reference_name): - - return { - "helpful": frappe.db.count("Feedback", {"reference_name": reference_name, "helpful": "Yes"}), - "not_helpful": frappe.db.count("Feedback", {"reference_name": reference_name, "helpful": "No"}), - } \ No newline at end of file diff --git a/frappe/website/doctype/feedback/test_feedback.py b/frappe/website/doctype/feedback/test_feedback.py deleted file mode 100644 index 9a31738d75..0000000000 --- a/frappe/website/doctype/feedback/test_feedback.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2020, Frappe Technologies and Contributors -# See license.txt -from __future__ import unicode_literals - -# import frappe -import unittest - -class TestFeedback(unittest.TestCase): - pass diff --git a/frappe/website/doctype/help_article/help_article.js b/frappe/website/doctype/help_article/help_article.js index c56bda8e0b..0ca92aefbd 100644 --- a/frappe/website/doctype/help_article/help_article.js +++ b/frappe/website/doctype/help_article/help_article.js @@ -3,6 +3,21 @@ frappe.ui.form.on('Help Article', { refresh: function(frm) { + frm.dashboard.clear_headline(); + frm.dashboard.set_headline_alert(` +
+
+ + Helpful ${frm.doc.helpful} + +
+
+ + Not Helpful ${frm.doc.helpful} + +
+
+ `); } }); diff --git a/frappe/website/doctype/help_article/help_article.json b/frappe/website/doctype/help_article/help_article.json index 5957333724..ca659692c6 100644 --- a/frappe/website/doctype/help_article/help_article.json +++ b/frappe/website/doctype/help_article/help_article.json @@ -1,445 +1,158 @@ { - "allow_copy": 0, - "allow_guest_to_view": 1, - "allow_import": 1, - "allow_rename": 0, - "beta": 0, - "creation": "2014-10-30 14:25:53.780105", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 0, + "actions": [], + "allow_guest_to_view": 1, + "allow_import": 1, + "creation": "2014-10-30 14:25:53.780105", + "doctype": "DocType", + "field_order": [ + "title", + "category", + "published", + "column_break_4", + "author", + "level", + "section_break_7", + "content", + "likes", + "route", + "owner", + "feedback", + "helpful", + "cb_00", + "not_helpful" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "title", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 1, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Title", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "title", + "fieldtype": "Data", + "in_global_search": 1, + "label": "Title", + "reqd": 1, + "search_index": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "category", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 1, - "label": "Category", - "length": 0, - "no_copy": 0, - "options": "Help Category", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "category", + "fieldtype": "Link", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Category", + "options": "Help Category", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "published", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Published", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "default": "0", + "fieldname": "published", + "fieldtype": "Check", + "label": "Published" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_4", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "column_break_4", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "user_fullname", - "fieldname": "author", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Author", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "default": "user_fullname", + "fieldname": "author", + "fieldtype": "Data", + "label": "Author" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "level", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Level", - "length": 0, - "no_copy": 0, - "options": "Beginner\nIntermediate\nExpert", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "level", + "fieldtype": "Select", + "label": "Level", + "options": "Beginner\nIntermediate\nExpert" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "section_break_7", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "section_break_7", + "fieldtype": "Section Break" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "content", - "fieldtype": "Text Editor", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 1, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Content", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "content", + "fieldtype": "Text Editor", + "in_global_search": 1, + "label": "Content", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "likes", - "fieldtype": "Int", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Likes", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "likes", + "fieldtype": "Int", + "label": "Likes", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "route", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 1, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Route", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "route", + "fieldtype": "Data", + "in_global_search": 1, + "label": "Route" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "user", - "fieldname": "owner", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Owner", - "length": 0, - "no_copy": 0, - "options": "User", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "default": "user", + "fieldname": "owner", + "fieldtype": "Link", + "label": "Owner", + "options": "User" + }, + { + "collapsible": 1, + "fieldname": "feedback", + "fieldtype": "Section Break", + "label": "Feedback" + }, + { + "default": "0", + "fieldname": "helpful", + "fieldtype": "Int", + "in_list_view": 1, + "label": "Helpful", + "read_only": 1 + }, + { + "fieldname": "cb_00", + "fieldtype": "Column Break" + }, + { + "default": "0", + "fieldname": "not_helpful", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Not Helpful", + "read_only": 1 } - ], - "has_web_view": 1, - "hide_heading": 0, - "hide_toolbar": 0, - "icon": "icon-file-alt", - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_published_field": "published", - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2018-05-18 17:49:52.912440", - "modified_by": "Administrator", - "module": "Website", - "name": "Help Article", - "name_case": "", - "owner": "Administrator", + ], + "has_web_view": 1, + "icon": "icon-file-alt", + "is_published_field": "published", + "links": [], + "modified": "2020-05-08 10:48:19.997789", + "modified_by": "Administrator", + "module": "Website", + "name": "Help Article", + "owner": "Administrator", "permissions": [ { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 1, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Knowledge Base Editor", - "set_user_permissions": 0, - "share": 0, - "submit": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "import": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Knowledge Base Editor", "write": 1 - }, + }, { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 0, - "email": 0, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 0, - "read": 1, - "report": 0, - "role": "Knowledge Base Contributor", - "set_user_permissions": 0, - "share": 0, - "submit": 0, + "create": 1, + "read": 1, + "role": "Knowledge Base Contributor", "write": 1 - }, + }, { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 0, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 0, - "read": 1, - "report": 0, - "role": "Guest", - "set_user_permissions": 0, - "share": 0, - "submit": 0, - "write": 0 + "read": 1, + "role": "Guest" } - ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "title_field": "title", - "track_changes": 1, - "track_seen": 0 + ], + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "title", + "track_changes": 1 } \ No newline at end of file diff --git a/frappe/website/doctype/help_article/help_article.py b/frappe/website/doctype/help_article/help_article.py index 6220d0aff5..fa26cfef99 100644 --- a/frappe/website/doctype/help_article/help_article.py +++ b/frappe/website/doctype/help_article/help_article.py @@ -4,7 +4,7 @@ from __future__ import unicode_literals import frappe from frappe.website.website_generator import WebsiteGenerator -from frappe.utils import is_markdown, markdown +from frappe.utils import is_markdown, markdown, cint from frappe.website.utils import get_comment_list from frappe import _ @@ -100,3 +100,11 @@ def clear_website_cache(path=None): frappe.cache().delete_value("knowledge_base:category_sidebar") frappe.cache().delete_value("knowledge_base:faq") +@frappe.whitelist(allow_guest=True) +def add_feedback(article, helpful): + field = "helpful" + if helpful == "No": + field = "not_helpful" + + value = cint(frappe.db.get_value("Help Article", article, field)) + frappe.db.set_value("Help Article", article, field, value+1, update_modified=False) \ No newline at end of file diff --git a/frappe/website/doctype/help_article/templates/help_article.html b/frappe/website/doctype/help_article/templates/help_article.html index 6c59359785..4f71117dea 100644 --- a/frappe/website/doctype/help_article/templates/help_article.html +++ b/frappe/website/doctype/help_article/templates/help_article.html @@ -19,8 +19,13 @@


{{ _("More articles on {0}").format(category.name) }}

-
- {% include 'templates/includes/feedback.html' %} +
+
+

@@ -30,6 +35,23 @@ {% endblock %} From 2ea74dee36f1bbd094a7fa967fc50be26ba5e09d Mon Sep 17 00:00:00 2001 From: "Chinmay D. Pai" Date: Tue, 26 May 2020 18:47:17 +0530 Subject: [PATCH 015/345] fix: check for whitelist before calling from search search widget takes query as an input, but does not check whether the query function that is called is whitelisted, basically allowing anyone logged-in to call any function regardless of the whitelist. Signed-off-by: Chinmay D. Pai --- frappe/desk/search.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frappe/desk/search.py b/frappe/desk/search.py index c70b650945..1da2a3d0b5 100644 --- a/frappe/desk/search.py +++ b/frappe/desk/search.py @@ -6,6 +6,7 @@ from __future__ import unicode_literals import frappe, json from frappe.utils import cstr, unique, cint from frappe.permissions import has_permission +from frappe.handler import is_whitelisted from frappe import _ from six import string_types import re @@ -74,6 +75,7 @@ def search_widget(doctype, txt, query=None, searchfield=None, start=0, if query and query.split()[0].lower()!="select": # by method + is_whitelisted(query) frappe.response["values"] = frappe.call(query, doctype, txt, searchfield, start, page_length, filters, as_dict=as_dict) elif not query and doctype in standard_queries: From c4d4fc3574dcb0f716fc8ad5edb162f171a990fe Mon Sep 17 00:00:00 2001 From: "Chinmay D. Pai" Date: Tue, 26 May 2020 18:49:12 +0530 Subject: [PATCH 016/345] chore: add standard queries hooks to whitelist standard queries are used within the search widget, and now require to be whitelisted before they can be executed through the search widget. Signed-off-by: Chinmay D. Pai --- frappe/core/doctype/user/user.py | 1 + 1 file changed, 1 insertion(+) diff --git a/frappe/core/doctype/user/user.py b/frappe/core/doctype/user/user.py index 0c5ebc3ede..f571240454 100644 --- a/frappe/core/doctype/user/user.py +++ b/frappe/core/doctype/user/user.py @@ -811,6 +811,7 @@ def reset_password(user): frappe.clear_messages() return 'not found' +@frappe.whitelist() def user_query(doctype, txt, searchfield, start, page_len, filters): from frappe.desk.reportview import get_match_cond From 8c65d3581ebb634d913cc81a02dbda60fd278c4d Mon Sep 17 00:00:00 2001 From: Himanshu Date: Wed, 27 May 2020 13:39:29 +0530 Subject: [PATCH 017/345] Update frappe/website/doctype/help_article/templates/help_article.html Co-authored-by: Prssanna Desai --- frappe/website/doctype/help_article/templates/help_article.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/website/doctype/help_article/templates/help_article.html b/frappe/website/doctype/help_article/templates/help_article.html index 4f71117dea..08c8fa342d 100644 --- a/frappe/website/doctype/help_article/templates/help_article.html +++ b/frappe/website/doctype/help_article/templates/help_article.html @@ -48,7 +48,7 @@ frappe.ready(function() { callback: function(r) { $(".feedback")[0].disabled = true; $(".feedback")[1].disabled = true; - frappe.msgprint(__("Feedback Submitted.")); + frappe.msgprint(__("Thank you for your feedback!")); } }) }); From 0781d048d178aea71ccfdeeeb5d6caac3db42299 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Tue, 2 Jun 2020 12:33:01 +0530 Subject: [PATCH 018/345] fix: add docs link to the settings page dashboard --- .../integrations/doctype/paytm_settings/paytm_settings.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frappe/integrations/doctype/paytm_settings/paytm_settings.js b/frappe/integrations/doctype/paytm_settings/paytm_settings.js index d51a6e4a25..fe2ee7c952 100644 --- a/frappe/integrations/doctype/paytm_settings/paytm_settings.js +++ b/frappe/integrations/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 04c070d82d3bb1df4072a52f279478487a2511cc Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Thu, 4 Jun 2020 14:38:46 +0530 Subject: [PATCH 019/345] fix: add paytm settings link to the module page --- frappe/config/integrations.py | 5 +++++ frappe/templates/includes/login/login.js | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/frappe/config/integrations.py b/frappe/config/integrations.py index f41adc9ea4..a7ac20065f 100644 --- a/frappe/config/integrations.py +++ b/frappe/config/integrations.py @@ -27,6 +27,11 @@ def get_data(): "name": "Stripe Settings", "description": _("Stripe payment gateway settings"), }, + { + "type": "doctype", + "name": "Paytm Settings", + "description": _("Paytm payment gateway settings"), + }, ] }, { diff --git a/frappe/templates/includes/login/login.js b/frappe/templates/includes/login/login.js index 37b8acb328..793fcc1720 100644 --- a/frappe/templates/includes/login/login.js +++ b/frappe/templates/includes/login/login.js @@ -186,7 +186,7 @@ login.login_handlers = (function() { } else if(data.message == 'Password Reset'){ window.location.href = frappe.utils.sanitise_redirect(data.redirect_to); } else if(data.message=="No App") { - login.set_indicator("{{ _("Success") }}", 'green'); + login.set_indicator("{{ _('Success') }}", 'green'); if(localStorage) { var last_visited = localStorage.getItem("last_visited") From 59a1ee6a823ad6d29f4cb8951304ae67947f1941 Mon Sep 17 00:00:00 2001 From: prssanna Date: Thu, 4 Jun 2020 18:49:01 +0530 Subject: [PATCH 020/345] feat: date range in leaderboard --- frappe/desk/leaderboard.py | 13 +++-- frappe/desk/page/leaderboard/leaderboard.js | 57 ++++++++++----------- 2 files changed, 36 insertions(+), 34 deletions(-) diff --git a/frappe/desk/leaderboard.py b/frappe/desk/leaderboard.py index 1ebf32febe..e5654c853f 100644 --- a/frappe/desk/leaderboard.py +++ b/frappe/desk/leaderboard.py @@ -14,13 +14,16 @@ def get_leaderboards(): return leaderboards @frappe.whitelist() -def get_energy_point_leaderboard(from_date, company = None, field = None, limit = None): +def get_energy_point_leaderboard(date_range, company = None, field = None, limit = None): + filters = [ + ['type', '!=', 'Review'], + ] + if date_range: + date_range = frappe.parse_json(date_range) + filters.append(['creation', 'between', [date_range[0], date_range[1]]]) energy_point_users = frappe.db.get_all('Energy Point Log', fields = ['user as name', 'sum(points) as value'], - filters = [ - ['type', '!=', 'Review'], - ['creation', '>', from_date] - ], + filters = filters, group_by = 'user', order_by = 'value desc' ) diff --git a/frappe/desk/page/leaderboard/leaderboard.js b/frappe/desk/page/leaderboard/leaderboard.js index 4472a2978a..ecec7f4805 100644 --- a/frappe/desk/page/leaderboard/leaderboard.js +++ b/frappe/desk/page/leaderboard/leaderboard.js @@ -49,7 +49,7 @@ class Leaderboard { this.timespans = [ "This Week", "This Month", "This Quarter", "This Year", "Last Week", "Last Month", "Last Quarter", "Last Year", - "All Time", "Select From Date" + "All Time", "Select Date Range" ]; // for saving current selected filters @@ -113,7 +113,7 @@ class Leaderboard { return {"label": __(d), value: d }; }) ); - this.create_from_date_field(); + this.create_date_range_field(); this.type_select = this.page.add_select(__("Field"), this.options.selected_filter.map(d => { @@ -123,12 +123,12 @@ class Leaderboard { this.timespan_select.on("change", (e) => { this.options.selected_timespan = e.currentTarget.value; - if (this.options.selected_timespan === 'Select From Date') { - this.from_date_field.show(); + if (this.options.selected_timespan === 'Select Date Range') { + this.date_range_field.show(); } else { - this.from_date_field.hide(); - this.make_request(); + this.date_range_field.hide(); } + this.make_request(); }); this.type_select.on("change", (e) => { @@ -137,21 +137,21 @@ class Leaderboard { }); } - create_from_date_field() { + create_date_range_field() { let timespan_field = $(this.parent).find(`.frappe-control[data-original-title='Timespan']`); - this.from_date_field = $(`
`).insertAfter(timespan_field).hide(); + this.date_range_field = $(`
`).insertAfter(timespan_field).hide(); let date_field = frappe.ui.form.make_control({ df: { - fieldtype: 'Date', - fieldname: 'selected_from_date', - placeholder: frappe.datetime.month_start(), - default: frappe.datetime.month_start(), + fieldtype: 'DateRange', + fieldname: 'selected_date_range', + placeholder: "Date Range", + default: [frappe.datetime.month_start(), frappe.datetime.now_date()], input_class: 'input-sm', reqd: 1, change: () => { - this.selected_from_date = date_field.get_value(); - if (this.selected_from_date) this.make_request(); + this.selected_date_range = date_field.get_value(); + if (this.selected_date_range) this.make_request(); } }, parent: $(this.parent).find('.from-date-field'), @@ -225,7 +225,7 @@ class Leaderboard { frappe.call( this.leaderboard_config[this.options.selected_doctype].method, { - 'from_date': this.get_from_date(), + 'date_range': this.get_date_range(), 'company': this.options.selected_company, 'field': this.options.selected_filter_item, 'limit': this.leaderboard_limit, @@ -375,23 +375,22 @@ class Leaderboard { `); } - get_from_date() { + get_date_range() { let timespan = this.options.selected_timespan.toLowerCase(); let current_date = frappe.datetime.now_date(); - let get_from_date = { - "this week": frappe.datetime.week_start(), - "this month": frappe.datetime.month_start(), - "this quarter": frappe.datetime.quarter_start(), - "this year": frappe.datetime.year_start(), - "last week": frappe.datetime.add_days(current_date, -7), - "last month": frappe.datetime.add_months(current_date, -1), - "last quarter": frappe.datetime.add_months(current_date, -3), - "last year": frappe.datetime.add_months(current_date, -12), - "all time": "", - "select from date": this.selected_from_date || frappe.datetime.month_start() + let date_range_map = { + "this week": [frappe.datetime.week_start(), frappe.datetime.now_date()], + "this month": [frappe.datetime.month_start(), frappe.datetime.now_date()], + "this quarter": [frappe.datetime.quarter_start(), frappe.datetime.now_date()], + "this year": [frappe.datetime.year_start(), frappe.datetime.now_date()], + "last week": [frappe.datetime.add_days(current_date, -7), frappe.datetime.now_date()], + "last month": [frappe.datetime.add_months(current_date, -1), frappe.datetime.now_date()], + "last quarter": [frappe.datetime.add_months(current_date, -3), frappe.datetime.now_date()], + "last year": [frappe.datetime.add_months(current_date, -12), frappe.datetime.now_date()], + "all time": null, + "select date range": this.selected_date_range || [frappe.datetime.month_start(), frappe.datetime.now_date()] } - - return get_from_date[timespan]; + return date_range_map[timespan]; } } From a9a8fc953fa6a90be8718a9d5c1f9b16fa890e87 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Mon, 8 Jun 2020 17:06:48 +0530 Subject: [PATCH 021/345] fix: update the checksum logic --- .../doctype/paytm_settings/checksum.py | 172 ++++++------------ .../paytm_settings/paytm_settings.json | 40 ++-- .../doctype/paytm_settings/paytm_settings.py | 8 +- 3 files changed, 85 insertions(+), 135 deletions(-) diff --git a/frappe/integrations/doctype/paytm_settings/checksum.py b/frappe/integrations/doctype/paytm_settings/checksum.py index b2e67bb5ad..32f976ae18 100644 --- a/frappe/integrations/doctype/paytm_settings/checksum.py +++ b/frappe/integrations/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 +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) -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) +__unpad__ = lambda s: s[0:-ord(s[-1])] - hasher = hashlib.sha256(final_string.encode()) - hash_string = hasher.hexdigest() +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") - hash_string += salt +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) - return __encode__(hash_string, IV, merchant_key) +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 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) +def generateSignatureByString(params, key): + salt = generateRandomString(4) + return calculateChecksum(params, key, 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) +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/frappe/integrations/doctype/paytm_settings/paytm_settings.json b/frappe/integrations/doctype/paytm_settings/paytm_settings.json index 0540dc4948..93fbd0df09 100644 --- a/frappe/integrations/doctype/paytm_settings/paytm_settings.json +++ b/frappe/integrations/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/frappe/integrations/doctype/paytm_settings/paytm_settings.py b/frappe/integrations/doctype/paytm_settings/paytm_settings.py index ed71202ea6..c169a53246 100644 --- a/frappe/integrations/doctype/paytm_settings/paytm_settings.py +++ b/frappe/integrations/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 86508e7617e9b9d448599b6298dacf83f840d136 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Mon, 15 Jun 2020 14:53:30 +0530 Subject: [PATCH 022/345] fix: change to primary for button --- .../help_article/templates/help_article.html | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/frappe/website/doctype/help_article/templates/help_article.html b/frappe/website/doctype/help_article/templates/help_article.html index 08c8fa342d..01271af8b9 100644 --- a/frappe/website/doctype/help_article/templates/help_article.html +++ b/frappe/website/doctype/help_article/templates/help_article.html @@ -23,8 +23,8 @@
@@ -41,13 +41,19 @@ frappe.ready(function() { helpful: this.getAttribute("data-value"), } + let disable_button = function(btn) { + btn.classList.remove("btn-outline-primary"); + btn.classList.add("btn-light"); + btn.disabled = true + } + frappe.call({ btn: this, method: "frappe.website.doctype.help_article.help_article.add_feedback", args: args, callback: function(r) { - $(".feedback")[0].disabled = true; - $(".feedback")[1].disabled = true; + disable_button($(".feedback")[0]); + disable_button($(".feedback")[1]); frappe.msgprint(__("Thank you for your feedback!")); } }) From a72f8088972c7154e3400e59449de5bd2c5023e5 Mon Sep 17 00:00:00 2001 From: "Chinmay D. Pai" Date: Thu, 18 Jun 2020 18:09:27 +0530 Subject: [PATCH 023/345] fix: make cookies more secure * add HTTPOnly to sid, so sid will no longer be accessible through javascript. * mark all cookies Secure by default over HTTPS. * set SameSite to Strict for all cookies by default, preventing cross-origin cookie access. * remove redundant publish_realtime for setting csrf_token on the client-side. Signed-off-by: Chinmay D. Pai --- frappe/auth.py | 21 ++++++++++++++++----- frappe/public/js/frappe/desk.js | 9 --------- frappe/public/js/frappe/request.js | 4 ++-- frappe/sessions.py | 7 ------- frappe/website/js/website.js | 9 ++------- 5 files changed, 20 insertions(+), 30 deletions(-) diff --git a/frappe/auth.py b/frappe/auth.py index 1353acf10f..ab3624bee8 100644 --- a/frappe/auth.py +++ b/frappe/auth.py @@ -333,12 +333,20 @@ class CookieManager: # sid expires in 3 days expires = datetime.datetime.now() + datetime.timedelta(days=3) if frappe.session.sid: - self.cookies["sid"] = {"value": frappe.session.sid, "expires": expires} + self.set_cookie("sid", frappe.session.sid, expires=expires, httponly=True) if frappe.session.session_country: - self.cookies["country"] = {"value": frappe.session.get("session_country")} + self.set_cookie("country", frappe.session.session_country) - def set_cookie(self, key, value, expires=None): - self.cookies[key] = {"value": value, "expires": expires} + def set_cookie(self, key, value, expires=None, secure=False, httponly=False, samesite="Strict"): + if not secure: + secure = frappe.local.request.scheme == "https" + self.cookies[key] = { + "value": value, + "expires": expires, + "secure": secure, + "httponly": httponly, + "samesite": samesite + } def delete_cookie(self, to_delete): if not isinstance(to_delete, (list, tuple)): @@ -349,7 +357,10 @@ class CookieManager: def flush_cookies(self, response): for key, opts in self.cookies.items(): response.set_cookie(key, quote((opts.get("value") or "").encode('utf-8')), - expires=opts.get("expires")) + expires=opts.get("expires"), + secure=opts.get("secure"), + httponly=opts.get("httponly"), + samesite=opts.get("samesite")) # expires yesterday! expires = datetime.datetime.now() + datetime.timedelta(days=-1) diff --git a/frappe/public/js/frappe/desk.js b/frappe/public/js/frappe/desk.js index 79a78717cb..2e80dbfd85 100644 --- a/frappe/public/js/frappe/desk.js +++ b/frappe/public/js/frappe/desk.js @@ -101,15 +101,6 @@ frappe.Application = Class.extend({ frappe.ui.startup_setup_dialog.show(); } - // listen to csrf_update - frappe.realtime.on("csrf_generated", function(data) { - // handles the case when a user logs in again from another tab - // and it leads to invalid request in the current tab - if (data.csrf_token && data.sid===frappe.get_cookie("sid")) { - frappe.csrf_token = data.csrf_token; - } - }); - frappe.realtime.on("version-update", function() { var dialog = frappe.msgprint({ message:__("The application has been updated to a new version, please refresh this page"), diff --git a/frappe/public/js/frappe/request.js b/frappe/public/js/frappe/request.js index ea4de99249..e378499f35 100644 --- a/frappe/public/js/frappe/request.js +++ b/frappe/public/js/frappe/request.js @@ -125,7 +125,7 @@ frappe.request.call = function(opts) { message: __('The resource you are looking for is not available')}); }, 403: function(xhr) { - if (frappe.get_cookie('sid')==='Guest') { + if (frappe.session.user === 'Guest') { // session expired frappe.app.handle_session_expired(); } @@ -320,7 +320,7 @@ frappe.request.cleanup = function(opts, r) { if(r) { // session expired? - Guest has no business here! - if(r.session_expired || frappe.get_cookie("sid")==="Guest") { + if (r.session_expired || frappe.session.user === "Guest") { frappe.app.handle_session_expired(); return; } diff --git a/frappe/sessions.py b/frappe/sessions.py index d317d6caf3..7a018bb0aa 100644 --- a/frappe/sessions.py +++ b/frappe/sessions.py @@ -172,13 +172,6 @@ def generate_csrf_token(): frappe.local.session.data.csrf_token = frappe.generate_hash() frappe.local.session_obj.update(force=True) - # send sid and csrf token to the user - # handles the case when a user logs in again from another tab - # and it leads to invalid request in the current tab - frappe.publish_realtime(event="csrf_generated", - message={"sid": frappe.local.session.sid, "csrf_token": frappe.local.session.data.csrf_token}, - user=frappe.session.user, after_commit=True) - class Session: def __init__(self, user, resume=False, full_name=None, user_type=None): self.sid = cstr(frappe.form_dict.get('sid') or diff --git a/frappe/website/js/website.js b/frappe/website/js/website.js index b52a3fef63..f2c142fe4c 100644 --- a/frappe/website/js/website.js +++ b/frappe/website/js/website.js @@ -183,10 +183,6 @@ $.extend(frappe, { .html('

' +text+'
').appendTo(document.body); }, - get_sid: function() { - var sid = frappe.get_cookie("sid"); - return sid && sid !== "Guest"; - }, send_message: function(opts, btn) { return frappe.call({ type: "POST", @@ -212,8 +208,7 @@ $.extend(frappe, { }); }, render_user: function() { - var sid = frappe.get_cookie("sid"); - if(sid && sid!=="Guest") { + if (frappe.is_user_logged_in()) { $(".btn-login-area").toggle(false); $(".logged-in").toggle(true); $(".full-name").html(frappe.get_cookie("full_name")); @@ -323,7 +318,7 @@ $.extend(frappe, { return $(".navbar .search, .sidebar .search"); }, is_user_logged_in: function() { - return frappe.get_cookie("sid") && frappe.get_cookie("sid") !== "Guest"; + return frappe.session.user !== "Guest"; }, add_switch_to_desk: function() { $('.switch-to-desk').removeClass('hidden'); From a29fab70ac5653c81bda5d076a06db83789e47d0 Mon Sep 17 00:00:00 2001 From: "Chinmay D. Pai" Date: Thu, 18 Jun 2020 23:14:19 +0530 Subject: [PATCH 024/345] chore: improve logic for checking logged in user frappe.session.user sometimes gets set after the function runs, so current implementation would return undefined !== "Guest" as True, causing Guest user to be set as a logged-in user. Signed-off-by: Chinmay D. Pai --- frappe/website/js/website.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/website/js/website.js b/frappe/website/js/website.js index f2c142fe4c..cd9609cfd3 100644 --- a/frappe/website/js/website.js +++ b/frappe/website/js/website.js @@ -318,7 +318,7 @@ $.extend(frappe, { return $(".navbar .search, .sidebar .search"); }, is_user_logged_in: function() { - return frappe.session.user !== "Guest"; + return frappe.get_cookie("user_id") !== "Guest" && frappe.session.user !== "Guest"; }, add_switch_to_desk: function() { $('.switch-to-desk').removeClass('hidden'); From 2afe0cdaa48c88de5ad884f180eba9b3ae2d5215 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Mon, 22 Jun 2020 09:01:37 +0530 Subject: [PATCH 025/345] fix: Column mapping --- .../core/doctype/data_import/data_import.js | 4 +- frappe/core/doctype/data_import/importer.py | 6 ++- .../js/frappe/data_import/import_preview.js | 37 ++++++++++++++++--- 3 files changed, 39 insertions(+), 8 deletions(-) diff --git a/frappe/core/doctype/data_import/data_import.js b/frappe/core/doctype/data_import/data_import.js index 81a7bc9705..9ccf43436b 100644 --- a/frappe/core/doctype/data_import/data_import.js +++ b/frappe/core/doctype/data_import/data_import.js @@ -301,8 +301,8 @@ frappe.ui.form.on('Data Import', { events: { remap_column(changed_map) { let template_options = JSON.parse(frm.doc.template_options || '{}'); - template_options.remap_column = template_options.remap_column || {}; - Object.assign(template_options.remap_column, changed_map); + template_options.column_to_field_map = template_options.column_to_field_map || {}; + Object.assign(template_options.column_to_field_map, changed_map); frm.set_value('template_options', JSON.stringify(template_options)); frm.save().then(() => frm.trigger('import_file')); } diff --git a/frappe/core/doctype/data_import/importer.py b/frappe/core/doctype/data_import/importer.py index 4761652c70..09849dd5b9 100644 --- a/frappe/core/doctype/data_import/importer.py +++ b/frappe/core/doctype/data_import/importer.py @@ -766,8 +766,9 @@ class Header(Row): for j, header in enumerate(row): column_values = [get_item_at_index(r, j) for r in raw_data] + map_to_field = column_to_field_map.get(str(j)) column = Column( - j, header, self.doctype, column_values, column_to_field_map.get(header), self.seen + j, header, self.doctype, column_values, map_to_field, self.seen ) self.seen.append(header) self.columns.append(column) @@ -944,6 +945,9 @@ class Column: d.map_to_field = self.map_to_field d.date_format = self.date_format d.df = self.df + if hasattr(self.df, 'is_child_table_field'): + d.is_child_table_field = self.df.is_child_table_field + d.child_table_df = self.df.child_table_df d.skip_import = self.skip_import d.warnings = self.warnings return d diff --git a/frappe/public/js/frappe/data_import/import_preview.js b/frappe/public/js/frappe/data_import/import_preview.js index 7cf8431456..4edcb87aeb 100644 --- a/frappe/public/js/frappe/data_import/import_preview.js +++ b/frappe/public/js/frappe/data_import/import_preview.js @@ -245,11 +245,12 @@ frappe.data_import.ImportPreview = class ImportPreview { let fieldname; if (!df) { fieldname = null; + } else if (col.map_to_field) { + fieldname = col.map_to_field; + } else if (col.is_child_table_field) { + fieldname = `${col.child_table_df.fieldname}.${df.fieldname}`; } else { - fieldname = - df.parent === this.doctype - ? df.fieldname - : `${df.parent}:${df.fieldname}`; + fieldname = df.fieldname; } return [ { @@ -272,7 +273,7 @@ frappe.data_import.ImportPreview = class ImportPreview { label: __("Don't Import"), value: "Don't Import" } - ].concat(column_picker_fields.get_fields_as_options()), + ].concat(get_fields_as_options(this.doctype, column_picker_fields)), default: fieldname || "Don't Import", change() { changed.push(i); @@ -328,3 +329,29 @@ frappe.data_import.ImportPreview = class ImportPreview { }); } }; + +function get_fields_as_options(doctype, column_map) { + let keys = [doctype]; + frappe.meta.get_table_fields(doctype).forEach(df => { + keys.push(df.fieldname); + }); + // flatten array + return [].concat( + ...keys.map(key => { + return column_map[key].map(df => { + let label = df.label; + let value = df.fieldname; + if (doctype !== key) { + let table_field = frappe.meta.get_docfield(doctype, key); + label = `${df.label} (${table_field.label})`; + value = `${table_field.fieldname}.${df.fieldname}`; + } + return { + label, + value, + description: value + }; + }); + }) + ); +} \ No newline at end of file From b502bfff07da834745bde3018e4a2ebc316d3a4f Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Mon, 22 Jun 2020 09:02:07 +0530 Subject: [PATCH 026/345] fix: Serialize df manually --- frappe/core/doctype/data_import/importer.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/frappe/core/doctype/data_import/importer.py b/frappe/core/doctype/data_import/importer.py index 09849dd5b9..13684aaad3 100644 --- a/frappe/core/doctype/data_import/importer.py +++ b/frappe/core/doctype/data_import/importer.py @@ -607,7 +607,7 @@ class Row: self.warnings.append( { "row": self.row_number, - "field": df.as_dict(convert_dates_to_str=True), + "field": df_as_json(df), "message": msg, } ) @@ -622,7 +622,7 @@ class Row: self.warnings.append( { "row": self.row_number, - "field": df.as_dict(convert_dates_to_str=True), + "field": df_as_json(df), "message": msg, } ) @@ -635,7 +635,7 @@ class Row: { "row": self.row_number, "col": col.column_number, - "field": df.as_dict(convert_dates_to_str=True), + "field": df_as_json(df), "message": _("Value {0} must in {1} format").format( frappe.bold(value), frappe.bold(get_user_format(col.date_format)) ), @@ -646,7 +646,7 @@ class Row: return value def link_exists(self, value, df): - key = df.options + "::" + value + key = df.options + "::" + cstr(value) if Row.link_values_exist_map.get(key) is None: Row.link_values_exist_map[key] = frappe.db.exists(df.options, value) return Row.link_values_exist_map.get(key) @@ -1117,3 +1117,13 @@ def get_user_format(date_format): .replace("%m", "mm") .replace("%d", "dd") ) + +def df_as_json(df): + return { + 'fieldname': df.fieldname, + 'fieldtype': df.fieldtype, + 'label': df.label, + 'options': df.options, + 'parent': df.parent, + 'default': df.default + } From bbdc5e0db897f7fe7e5ce09b4a917142fe7e56be Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Mon, 22 Jun 2020 09:03:00 +0530 Subject: [PATCH 027/345] fix(exporter): Count for child table filters - Extract count method from list_view.js for reuse --- .../js/frappe/data_import/data_exporter.js | 8 +++--- frappe/public/js/frappe/db.js | 26 ++++++++++++++----- frappe/public/js/frappe/list/list_view.js | 24 +++-------------- 3 files changed, 27 insertions(+), 31 deletions(-) diff --git a/frappe/public/js/frappe/data_import/data_exporter.js b/frappe/public/js/frappe/data_import/data_exporter.js index 735237189d..6ee4e907fc 100644 --- a/frappe/public/js/frappe/data_import/data_exporter.js +++ b/frappe/public/js/frappe/data_import/data_exporter.js @@ -285,11 +285,9 @@ frappe.data_import.DataExporter = class DataExporter { } get_filters() { - return this.filter_group.get_filters().reduce((acc, filter) => { - return Object.assign(acc, { - [filter[1]]: [filter[2], filter[3]] - }); - }, {}); + return this.filter_group.get_filters().map(filter => { + return filter.slice(0, 4) + }); } get_multicheck_options(doctype, child_fieldname = null) { diff --git a/frappe/public/js/frappe/db.js b/frappe/public/js/frappe/db.js index 1b6fb0e438..cf716c67e5 100644 --- a/frappe/public/js/frappe/db.js +++ b/frappe/public/js/frappe/db.js @@ -91,12 +91,26 @@ frappe.db = { }); }, count: function(doctype, args={}) { - return new Promise(resolve => { - frappe.call({ - method: 'frappe.client.get_count', - type: 'GET', - args: Object.assign(args, { doctype }) - }).then(r => resolve(r.message)); + let filters = args.filters || {}; + const with_child_table_filter = Array.isArray(filters) && filters.some(filter => { + return filter[0] !== doctype; + }); + + const fields = [ + // cannot break this line as it adds extra \n's and \t's which breaks the query + `count(${with_child_table_filter ? 'distinct': ''} ${frappe.model.get_full_column_name('name', doctype)}) AS total_count` + ]; + + return frappe.call({ + type: 'GET', + method: 'frappe.desk.reportview.get', + args: { + doctype, + filters, + fields, + } + }).then(r => { + return r.message.values[0][0]; }); }, get_link_options(doctype, txt = '', filters={}) { diff --git a/frappe/public/js/frappe/list/list_view.js b/frappe/public/js/frappe/list/list_view.js index 9e1ba1b9bd..8da73b0dec 100644 --- a/frappe/public/js/frappe/list/list_view.js +++ b/frappe/public/js/frappe/list/list_view.js @@ -760,26 +760,10 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { let current_count = this.data.length; let count_without_children = this.data.uniqBy(d => d.name).length; - const filters = this.get_filters_for_args(); - const with_child_table_filter = filters.some(filter => { - return filter[0] !== this.doctype; - }); - - const fields = [ - // cannot break this line as it adds extra \n's and \t's which breaks the query - `count(${with_child_table_filter ? 'distinct': ''}${frappe.model.get_full_column_name('name', this.doctype)}) AS total_count` - ]; - - return frappe.call({ - type: 'GET', - method: this.method, - args: { - doctype: this.doctype, - filters, - fields, - } - }).then(r => { - this.total_count = r.message.values[0][0] || current_count; + return frappe.db.count(this.doctype, { + filters: this.get_filters_for_args() + }).then(total_count => { + this.total_count = total_count || current_count; let str = __('{0} of {1}', [current_count, this.total_count]); if (count_without_children !== current_count) { str = __('{0} of {1} ({2} rows with children)', [count_without_children, this.total_count, current_count]); From 010dbc402e4685aaf83a138c704c723a31c6c61a Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Mon, 22 Jun 2020 16:29:44 +0530 Subject: [PATCH 028/345] fix(google-calendar): reset sync token incase of invalid next sync token --- .../doctype/google_calendar/google_calendar.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/frappe/integrations/doctype/google_calendar/google_calendar.py b/frappe/integrations/doctype/google_calendar/google_calendar.py index fa2eea6ce1..c43f867711 100644 --- a/frappe/integrations/doctype/google_calendar/google_calendar.py +++ b/frappe/integrations/doctype/google_calendar/google_calendar.py @@ -12,6 +12,7 @@ from frappe import _ from frappe.model.document import Document from frappe.utils import get_request_site_address from googleapiclient.errors import HttpError +from frappe.utils.password import set_encrypted_password from frappe.utils import add_days, get_datetime, get_weekdays, now_datetime, add_to_date, get_time_zone from dateutil import parser from datetime import datetime, timedelta @@ -218,15 +219,23 @@ def sync_events_from_google_calendar(g_calendar, method=None, page_length=10): events = google_calendar.events().list(calendarId=account.google_calendar_id, maxResults=page_length, singleEvents=False, showDeleted=True, syncToken=sync_token).execute() except HttpError as err: - frappe.throw(_("Google Calendar - Could not fetch event from Google Calendar, error code {0}.").format(err.resp.status)) + msg = _("Google Calendar - Could not fetch event from Google Calendar, error code {0}.").format(err.resp.status) + + if err.resp.status == 410: + set_encrypted_password("Google Calendar", account.name, "", "next_sync_token") + frappe.db.commit() + msg += _(' Sync token was invalid and has been resetted, Retry syncing.') + frappe.msgprint(msg, title='Invalid Sync Token', indicator='blue') + else: + frappe.throw(msg) for event in events.get("items", []): results.append(event) if not events.get("nextPageToken"): if events.get("nextSyncToken"): - frappe.db.set_value("Google Calendar", account.name, "next_sync_token", events.get("nextSyncToken")) - frappe.db.commit() + account.next_sync_token = events.get("nextSyncToken") + account.save() break for idx, event in enumerate(results): From 25667cde1ed17deb9ee5f4cec6dac0deba6d56d7 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Mon, 22 Jun 2020 16:43:27 +0530 Subject: [PATCH 029/345] fix(google-calendar): pass page token to load the next set of events --- .../doctype/google_calendar/google_calendar.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/frappe/integrations/doctype/google_calendar/google_calendar.py b/frappe/integrations/doctype/google_calendar/google_calendar.py index c43f867711..d1cd62f84f 100644 --- a/frappe/integrations/doctype/google_calendar/google_calendar.py +++ b/frappe/integrations/doctype/google_calendar/google_calendar.py @@ -199,7 +199,7 @@ def check_google_calendar(account, google_calendar): except HttpError as err: frappe.throw(_("Google Calendar - Could not create Calendar for {0}, error code {1}.").format(account.name, err.resp.status)) -def sync_events_from_google_calendar(g_calendar, method=None, page_length=10): +def sync_events_from_google_calendar(g_calendar, method=None): """ Syncs Events from Google Calendar in Framework Calendar. Google Calendar returns nextSyncToken when all the events in Google Calendar are fetched. @@ -211,13 +211,14 @@ def sync_events_from_google_calendar(g_calendar, method=None, page_length=10): if not account.pull_from_google_calendar: return + sync_token = account.get_password(fieldname="next_sync_token", raise_exception=False) or None + events = frappe._dict() results = [] while True: try: # API Response listed at EOF - sync_token = account.get_password(fieldname="next_sync_token", raise_exception=False) or None - events = google_calendar.events().list(calendarId=account.google_calendar_id, maxResults=page_length, - singleEvents=False, showDeleted=True, syncToken=sync_token).execute() + events = google_calendar.events().list(calendarId=account.google_calendar_id, maxResults=2000, + pageToken=events.get("nextPageToken"), singleEvents=False, showDeleted=True, syncToken=sync_token).execute() except HttpError as err: msg = _("Google Calendar - Could not fetch event from Google Calendar, error code {0}.").format(err.resp.status) From 0aceebd5507d088410354e6bf55acdc54667b816 Mon Sep 17 00:00:00 2001 From: Himanshu Date: Wed, 24 Jun 2020 10:52:03 +0530 Subject: [PATCH 030/345] Update frappe/website/doctype/help_article/help_article.js Co-authored-by: Prssanna Desai --- frappe/website/doctype/help_article/help_article.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/website/doctype/help_article/help_article.js b/frappe/website/doctype/help_article/help_article.js index 0ca92aefbd..0a87b22e2c 100644 --- a/frappe/website/doctype/help_article/help_article.js +++ b/frappe/website/doctype/help_article/help_article.js @@ -14,7 +14,7 @@ frappe.ui.form.on('Help Article', {
- Not Helpful ${frm.doc.helpful} + Not Helpful ${frm.doc.not_helpful}
From aae9133594a915b19506144749674b567730b398 Mon Sep 17 00:00:00 2001 From: "Chinmay D. Pai" Date: Wed, 24 Jun 2020 11:15:24 +0530 Subject: [PATCH 031/345] fix: disallow access to signup page when disabled signup page is still accessible through the URL even when the button is not shown. this disables access to the page when signups are disabled. Signed-off-by: Chinmay D. Pai --- frappe/www/login.html | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/frappe/www/login.html b/frappe/www/login.html index ebbff748ec..f1131db8cf 100644 --- a/frappe/www/login.html +++ b/frappe/www/login.html @@ -70,20 +70,29 @@
From 5b828adab7d56d09d1745ff74c2870223b78d7a0 Mon Sep 17 00:00:00 2001 From: Chinmay Pai Date: Wed, 24 Jun 2020 11:31:10 +0530 Subject: [PATCH 032/345] chore: use default alignment for elements Co-authored-by: Suraj Shetty <13928957+surajshetty3416@users.noreply.github.com> --- frappe/www/login.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/www/login.html b/frappe/www/login.html index f1131db8cf..d57f126022 100644 --- a/frappe/www/login.html +++ b/frappe/www/login.html @@ -85,8 +85,8 @@
{{_("Signup Disabled")}}
-

{{_("Signups have been disabled for this website.")}}

- +

{{_("Signups have been disabled for this website.")}}

+ {%- endif -%}
-