From 32c5f7a011f66f234d82af1b1cec8b412a693627 Mon Sep 17 00:00:00 2001 From: Saurabh Date: Tue, 2 Oct 2018 22:11:43 +0530 Subject: [PATCH 1/8] provision to handel paypal and razoapay subscription charge notifications --- .../integration_request.json | 4 +-- .../paypal_settings/paypal_settings.py | 20 ++++++++++++++ .../razorpay_settings/razorpay_settings.py | 26 ++++++++++++++++--- 3 files changed, 45 insertions(+), 5 deletions(-) diff --git a/frappe/integrations/doctype/integration_request/integration_request.json b/frappe/integrations/doctype/integration_request/integration_request.json index f19da52e46..7d82ff491d 100644 --- a/frappe/integrations/doctype/integration_request/integration_request.json +++ b/frappe/integrations/doctype/integration_request/integration_request.json @@ -86,7 +86,7 @@ "label": "Status", "length": 0, "no_copy": 0, - "options": "\nQueued\nAuthorized\nCompleted\nCancelled\nFailed\n", + "options": "\nQueued\nAuthorized\nCompleted\nCancelled\nFailed\nSubscription Notification\n", "permlevel": 0, "precision": "", "print_hide": 0, @@ -252,7 +252,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2017-03-08 14:40:00.783063", + "modified": "2019-10-02 14:40:00.783063", "modified_by": "Administrator", "module": "Integrations", "name": "Integration Request", diff --git a/frappe/integrations/doctype/paypal_settings/paypal_settings.py b/frappe/integrations/doctype/paypal_settings/paypal_settings.py index c59a240911..80aadfd0bc 100644 --- a/frappe/integrations/doctype/paypal_settings/paypal_settings.py +++ b/frappe/integrations/doctype/paypal_settings/paypal_settings.py @@ -363,7 +363,27 @@ def manage_recurring_payment_profile_status(profile_id, action, args, url): }) response = make_post_request(url, data=args) + if response.get("ACK")[0] != "Success": frappe.throw(_("Failed while amending subscription")) +@frappe.whitelist(allow_guest=True) +def ipn_handler(): + data = frappe.local.form_dict + data.update({ + "payment_gateway": "PayPal" + }) + + doc = frappe.get_doc({ + "data": frappe.local.form_dict, + "doctype": "Integration Request", + "status": "Subscription Notification" + }).insert(ignore_permissions=True) + frappe.db.commit() + + frappe.enqueue(method='frappe.integrations.doctype.paypal_settings.paypal_settings.handle_subscription_notification', + queue='long', timeout=600, is_async=True, **{"doctype": "Integration Request", "docname": doc.name}) + +def handle_subscription_notification(doctype, docname): + call_hook_method("handle_subscription_notification", doctype=doctype, docname=docname) diff --git a/frappe/integrations/doctype/razorpay_settings/razorpay_settings.py b/frappe/integrations/doctype/razorpay_settings/razorpay_settings.py index 064d509574..a1029aa961 100644 --- a/frappe/integrations/doctype/razorpay_settings/razorpay_settings.py +++ b/frappe/integrations/doctype/razorpay_settings/razorpay_settings.py @@ -119,7 +119,6 @@ class RazorpaySettings(Document): "content-type": "application/json" } ) - if not resp.get('id'): frappe.log_error(str(resp), 'Razorpay Failed while creating subscription') except: @@ -329,5 +328,26 @@ def capture_payment(is_sandbox=False, sanbox_response=None): def convert_rupee_to_paisa(**kwargs): for addon in kwargs.get('addons'): addon['item']['amount'] *= 100 - - frappe.conf.converted_rupee_to_paisa = True \ No newline at end of file + + frappe.conf.converted_rupee_to_paisa = True + + +@frappe.whitelist(allow_guest=True) +def razorpay_subscription_callback(): + data = frappe.local.form_dict + data.update({ + "payment_gateway": "Razorpay" + }) + + doc = frappe.get_doc({ + "data": json.dumps(frappe.local.form_dict), + "doctype": "Integration Request", + "status": "Subscription Notification" + }).insert(ignore_permissions=True) + frappe.db.commit() + + frappe.enqueue(method='frappe.integrations.doctype.razorpay_settings.razorpay_settings.handle_subscription_notification', + queue='long', timeout=600, is_async=True, **{"doctype": "Integration Request", "docname": doc.name}) + +def handle_subscription_notification(doctype, docname): + call_hook_method("handle_subscription_notification", doctype=doctype, docname=docname) \ No newline at end of file From 3ce7ba4e5de2ab28b8348f00ff71cb30b02c956f Mon Sep 17 00:00:00 2001 From: Saurabh Date: Fri, 5 Oct 2018 17:16:28 +0530 Subject: [PATCH 2/8] [fix] add validations on payment notification callback --- .../paypal_settings/paypal_settings.py | 55 ++++++++++++++----- .../razorpay_settings/razorpay_settings.py | 53 +++++++++++++----- frappe/integrations/utils.py | 2 +- 3 files changed, 83 insertions(+), 27 deletions(-) diff --git a/frappe/integrations/doctype/paypal_settings/paypal_settings.py b/frappe/integrations/doctype/paypal_settings/paypal_settings.py index 80aadfd0bc..9fa3164f70 100644 --- a/frappe/integrations/doctype/paypal_settings/paypal_settings.py +++ b/frappe/integrations/doctype/paypal_settings/paypal_settings.py @@ -68,10 +68,10 @@ import frappe import json from frappe import _ from datetime import datetime -from frappe.utils import get_url, call_hook_method, cint, get_timestamp, cstr, now, date_diff, get_datetime from six.moves.urllib.parse import urlencode from frappe.model.document import Document from frappe.integrations.utils import create_request_log, make_post_request, create_payment_gateway +from frappe.utils import get_url, call_hook_method, cint, get_timestamp, cstr, now, date_diff, get_datetime api_path = '/api/method/frappe.integrations.doctype.paypal_settings.paypal_settings' @@ -363,27 +363,56 @@ def manage_recurring_payment_profile_status(profile_id, action, args, url): }) response = make_post_request(url, data=args) - if response.get("ACK")[0] != "Success": frappe.throw(_("Failed while amending subscription")) @frappe.whitelist(allow_guest=True) def ipn_handler(): - data = frappe.local.form_dict - data.update({ - "payment_gateway": "PayPal" + try: + data = frappe.local.form_dict + + validate_ipn_request(data) + + data.update({ + "payment_gateway": "PayPal" + }) + + doc = frappe.get_doc({ + "data": json.dumps(frappe.local.form_dict), + "doctype": "Integration Request", + "status": "Subscription Notification" + }).insert(ignore_permissions=True) + frappe.db.commit() + + frappe.enqueue(method='frappe.integrations.doctype.paypal_settings.paypal_settings.handle_subscription_notification', + queue='long', timeout=600, is_async=True, **{"doctype": "Integration Request", "docname": doc.name}) + + except frappe.InvalidStatusError: + pass + except Exception as e: + frappe.log(frappe.log_error(title=e)) + +def validate_ipn_request(data): + def _throw(): + frappe.throw(_("In Valid Request"), exc=frappe.InvalidStatusError) + + if not data.get("recurring_payment_id"): + _throw() + + doc = frappe.get_doc("PayPal Settings") + params, url = doc.get_paypal_params_and_url() + + params.update({ + "METHOD": "GetRecurringPaymentsProfileDetails", + "PROFILEID": data.get("recurring_payment_id") }) - doc = frappe.get_doc({ - "data": frappe.local.form_dict, - "doctype": "Integration Request", - "status": "Subscription Notification" - }).insert(ignore_permissions=True) - frappe.db.commit() + params = urlencode(params) + res = make_post_request(url=url, data=params.encode("utf-8")) - frappe.enqueue(method='frappe.integrations.doctype.paypal_settings.paypal_settings.handle_subscription_notification', - queue='long', timeout=600, is_async=True, **{"doctype": "Integration Request", "docname": doc.name}) + if res['ACK'][0] != 'Success': + _throw() def handle_subscription_notification(doctype, docname): call_hook_method("handle_subscription_notification", doctype=doctype, docname=docname) diff --git a/frappe/integrations/doctype/razorpay_settings/razorpay_settings.py b/frappe/integrations/doctype/razorpay_settings/razorpay_settings.py index a1029aa961..0eb41e5839 100644 --- a/frappe/integrations/doctype/razorpay_settings/razorpay_settings.py +++ b/frappe/integrations/doctype/razorpay_settings/razorpay_settings.py @@ -331,23 +331,50 @@ def convert_rupee_to_paisa(**kwargs): frappe.conf.converted_rupee_to_paisa = True - @frappe.whitelist(allow_guest=True) def razorpay_subscription_callback(): - data = frappe.local.form_dict - data.update({ - "payment_gateway": "Razorpay" - }) + try: + data = frappe.local.form_dict - doc = frappe.get_doc({ - "data": json.dumps(frappe.local.form_dict), - "doctype": "Integration Request", - "status": "Subscription Notification" - }).insert(ignore_permissions=True) - frappe.db.commit() + validate_payment_callback() - frappe.enqueue(method='frappe.integrations.doctype.razorpay_settings.razorpay_settings.handle_subscription_notification', - queue='long', timeout=600, is_async=True, **{"doctype": "Integration Request", "docname": doc.name}) + data.update({ + "payment_gateway": "Razorpay" + }) + + doc = frappe.get_doc({ + "data": json.dumps(frappe.local.form_dict), + "doctype": "Integration Request", + "status": "Subscription Notification" + }).insert(ignore_permissions=True) + frappe.db.commit() + + frappe.enqueue(method='frappe.integrations.doctype.razorpay_settings.razorpay_settings.handle_subscription_notification', + queue='long', timeout=600, is_async=True, **{"doctype": "Integration Request", "docname": doc.name}) + + except frappe.InvalidStatusError: + pass + except Exception as e: + frappe.log(frappe.log_error(title=e)) + +def validate_payment_callback(data): + def _throw(): + frappe.throw(_("Invalid Subscription"), exc=frappe.InvalidStatusError) + + subscription_id = data.get('payload').get("subscription").get("entity").get("id") + + if not(subscription_id): + _throw() + + controller = frappe.get_doc("Razorpay Settings") + + settings = controller.get_settings(data) + + resp = make_get_request("https://api.razorpay.com/v1/subscriptions/{0}".format(subscription_id), + auth=(settings.api_key, settings.api_secret)) + + if resp.get("status") != "active": + _throw() def handle_subscription_notification(doctype, docname): call_hook_method("handle_subscription_notification", doctype=doctype, docname=docname) \ No newline at end of file diff --git a/frappe/integrations/utils.py b/frappe/integrations/utils.py index 319f78e044..ca4c7bc3cb 100644 --- a/frappe/integrations/utils.py +++ b/frappe/integrations/utils.py @@ -112,4 +112,4 @@ def create_payment_gateway(gateway, settings=None, controller=None): def json_handler(obj): if isinstance(obj, (datetime.date, datetime.timedelta, datetime.datetime)): - return text_type(obj) \ No newline at end of file + return text_type(obj) From 0d260be0aa85032885b80eb3f4ed1960e8cb741f Mon Sep 17 00:00:00 2001 From: Saurabh Date: Mon, 8 Oct 2018 13:49:12 +0530 Subject: [PATCH 3/8] [fix] pass request type as Subscription Notification --- .../doctype/integration_request/integration_request.json | 4 ++-- .../integrations/doctype/paypal_settings/paypal_settings.py | 3 ++- .../doctype/razorpay_settings/razorpay_settings.py | 3 ++- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/frappe/integrations/doctype/integration_request/integration_request.json b/frappe/integrations/doctype/integration_request/integration_request.json index 7d82ff491d..b0e9290adc 100644 --- a/frappe/integrations/doctype/integration_request/integration_request.json +++ b/frappe/integrations/doctype/integration_request/integration_request.json @@ -27,7 +27,7 @@ "label": "Integration Type", "length": 0, "no_copy": 0, - "options": "\nHost\nRemote", + "options": "\nHost\nRemote\nSubscription Notification", "permlevel": 0, "precision": "", "print_hide": 0, @@ -86,7 +86,7 @@ "label": "Status", "length": 0, "no_copy": 0, - "options": "\nQueued\nAuthorized\nCompleted\nCancelled\nFailed\nSubscription Notification\n", + "options": "\nQueued\nAuthorized\nCompleted\nCancelled\nFailed\n", "permlevel": 0, "precision": "", "print_hide": 0, diff --git a/frappe/integrations/doctype/paypal_settings/paypal_settings.py b/frappe/integrations/doctype/paypal_settings/paypal_settings.py index 9fa3164f70..3d4be38467 100644 --- a/frappe/integrations/doctype/paypal_settings/paypal_settings.py +++ b/frappe/integrations/doctype/paypal_settings/paypal_settings.py @@ -381,7 +381,8 @@ def ipn_handler(): doc = frappe.get_doc({ "data": json.dumps(frappe.local.form_dict), "doctype": "Integration Request", - "status": "Subscription Notification" + "integration_type": "Subscription Notification", + "status": "Queued" }).insert(ignore_permissions=True) frappe.db.commit() diff --git a/frappe/integrations/doctype/razorpay_settings/razorpay_settings.py b/frappe/integrations/doctype/razorpay_settings/razorpay_settings.py index 0eb41e5839..3cca99ebe2 100644 --- a/frappe/integrations/doctype/razorpay_settings/razorpay_settings.py +++ b/frappe/integrations/doctype/razorpay_settings/razorpay_settings.py @@ -345,7 +345,8 @@ def razorpay_subscription_callback(): doc = frappe.get_doc({ "data": json.dumps(frappe.local.form_dict), "doctype": "Integration Request", - "status": "Subscription Notification" + "integration_type": "Subscription Notification", + "status": "Queued" }).insert(ignore_permissions=True) frappe.db.commit() From fbc101db2df84a45c95fe3f9611442b09e5034c1 Mon Sep 17 00:00:00 2001 From: Saurabh Date: Mon, 8 Oct 2018 13:51:37 +0530 Subject: [PATCH 4/8] [fix] change modified date --- .../doctype/integration_request/integration_request.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/integrations/doctype/integration_request/integration_request.json b/frappe/integrations/doctype/integration_request/integration_request.json index b0e9290adc..f69668973e 100644 --- a/frappe/integrations/doctype/integration_request/integration_request.json +++ b/frappe/integrations/doctype/integration_request/integration_request.json @@ -252,7 +252,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2019-10-02 14:40:00.783063", + "modified": "2019-10-09 14:40:00.783063", "modified_by": "Administrator", "module": "Integrations", "name": "Integration Request", From dbe1e41775d6c9f009fabf01dbb6dd2474507aa6 Mon Sep 17 00:00:00 2001 From: Saurabh Date: Mon, 8 Oct 2018 18:24:07 +0530 Subject: [PATCH 5/8] [fix] pass form dict to validate function --- .../integrations/doctype/razorpay_settings/razorpay_settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/integrations/doctype/razorpay_settings/razorpay_settings.py b/frappe/integrations/doctype/razorpay_settings/razorpay_settings.py index 3cca99ebe2..1848e4d205 100644 --- a/frappe/integrations/doctype/razorpay_settings/razorpay_settings.py +++ b/frappe/integrations/doctype/razorpay_settings/razorpay_settings.py @@ -336,7 +336,7 @@ def razorpay_subscription_callback(): try: data = frappe.local.form_dict - validate_payment_callback() + validate_payment_callback(data) data.update({ "payment_gateway": "Razorpay" From 4f4706ae12ae32cfcbbbb03eb6a007c77609a248 Mon Sep 17 00:00:00 2001 From: Saurabh Date: Wed, 10 Oct 2018 17:28:19 +0530 Subject: [PATCH 6/8] [fix] do not cache payment success page --- frappe/templates/pages/integrations/payment_success.py | 1 + 1 file changed, 1 insertion(+) diff --git a/frappe/templates/pages/integrations/payment_success.py b/frappe/templates/pages/integrations/payment_success.py index 655767e896..4ece359936 100644 --- a/frappe/templates/pages/integrations/payment_success.py +++ b/frappe/templates/pages/integrations/payment_success.py @@ -4,6 +4,7 @@ from __future__ import unicode_literals import frappe +no_cache = True def get_context(context): token = frappe.local.form_dict.token From 7201f33eee06c717604e89afedae85ecce0d9ac4 Mon Sep 17 00:00:00 2001 From: Saurabh Date: Wed, 17 Oct 2018 19:50:36 +0530 Subject: [PATCH 7/8] Add more keys to site config to support subscription --- frappe/commands/site.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/frappe/commands/site.py b/frappe/commands/site.py index 5ec3db3cb9..61576fcb25 100755 --- a/frappe/commands/site.py +++ b/frappe/commands/site.py @@ -458,8 +458,10 @@ def _set_limits(context, site, limits): frappe.connect() new_limits = {} for limit, value in limits: - if limit not in ('daily_emails', 'emails', 'space', 'users', 'email_group', - 'expiry', 'support_email', 'support_chat', 'upgrade_url'): + if limit not in ('daily_emails', 'emails', 'space', 'users', 'email_group', 'currency', + 'expiry', 'support_email', 'support_chat', 'upgrade_url', 'subscription_id', + 'subscription_type', 'current_plan', 'subscription_base_price', 'upgrade_plan', + 'upgrade_base_price'): frappe.throw(_('Invalid limit {0}').format(limit)) if limit=='expiry' and value: @@ -468,7 +470,7 @@ def _set_limits(context, site, limits): except ValueError: raise ValueError("Incorrect data format, should be YYYY-MM-DD") - elif limit=='space': + elif limit in ('space', 'subscription_base_price', 'upgrade_base_price'): value = float(value) elif limit in ('users', 'emails', 'email_group', 'daily_emails'): From 0757c927137c4fa94303bf39ceeec64dcbdd0ae8 Mon Sep 17 00:00:00 2001 From: Saurabh Date: Wed, 17 Oct 2018 19:50:54 +0530 Subject: [PATCH 8/8] [fix] razorpay subscription cancellation --- .../doctype/razorpay_settings/razorpay_settings.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/frappe/integrations/doctype/razorpay_settings/razorpay_settings.py b/frappe/integrations/doctype/razorpay_settings/razorpay_settings.py index 1848e4d205..bca9291f54 100644 --- a/frappe/integrations/doctype/razorpay_settings/razorpay_settings.py +++ b/frappe/integrations/doctype/razorpay_settings/razorpay_settings.py @@ -282,13 +282,12 @@ class RazorpaySettings(Document): }) return settings - - + def cancel_subscription(self, subscription_id): - settings = self.get_settings() - + settings = self.get_settings({}) + try: - resp = make_get_request("https://api.razorpay.com/v1/subscriptions/{0}/cancel" + resp = make_post_request("https://api.razorpay.com/v1/subscriptions/{0}/cancel" .format(subscription_id), auth=(settings.api_key, settings.api_secret)) except Exception: