diff --git a/erpnext/non_profit/doctype/member/member.js b/erpnext/non_profit/doctype/member/member.js index eb74dc1507e..3e9d0baba5f 100644 --- a/erpnext/non_profit/doctype/member/member.js +++ b/erpnext/non_profit/doctype/member/member.js @@ -2,6 +2,14 @@ // For license information, please see license.txt frappe.ui.form.on('Member', { + setup: function(frm) { + frappe.db.get_single_value("Membership Settings", "enable_razorpay").then(val => { + if (val && (frm.doc.subscription_id || frm.doc.customer_id)) { + frm.set_df_property('razorpay_details_section', 'hidden', false); + } + }) + }, + refresh: function(frm) { frappe.dynamic_link = {doc: frm.doc, fieldname: 'name', doctype: 'Member'}; diff --git a/erpnext/non_profit/doctype/member/member.json b/erpnext/non_profit/doctype/member/member.json index c6c6dcd366f..bb73a843eeb 100644 --- a/erpnext/non_profit/doctype/member/member.json +++ b/erpnext/non_profit/doctype/member/member.json @@ -23,7 +23,14 @@ "address_contacts", "address_html", "column_break_9", - "contact_html" + "contact_html", + "razorpay_details_section", + "subscription_id", + "customer_id", + "subscription_activated", + "column_break_21", + "subscription_start", + "subscription_end" ], "fields": [ { @@ -127,11 +134,49 @@ "fieldname": "email_id", "fieldtype": "Data", "label": "Email Address" + }, + { + "fieldname": "subscription_id", + "fieldtype": "Data", + "label": "Subscription ID", + "read_only": 1 + }, + { + "fieldname": "customer_id", + "fieldtype": "Data", + "label": "Customer ID", + "read_only": 1 + }, + { + "fieldname": "razorpay_details_section", + "fieldtype": "Section Break", + "hidden": 1, + "label": "Razorpay Details" + }, + { + "fieldname": "column_break_21", + "fieldtype": "Column Break" + }, + { + "default": "0", + "fieldname": "subscription_activated", + "fieldtype": "Check", + "label": "Subscription Activated" + }, + { + "fieldname": "subscription_start", + "fieldtype": "Date", + "label": "Subscription Start " + }, + { + "fieldname": "subscription_end", + "fieldtype": "Date", + "label": "Subscription End" } ], "image_field": "image", "links": [], - "modified": "2020-03-30 13:42:06.488851", + "modified": "2020-04-07 14:20:33.215700", "modified_by": "Administrator", "module": "Non Profit", "name": "Member", diff --git a/erpnext/non_profit/doctype/member/member.py b/erpnext/non_profit/doctype/member/member.py index cec1ea01124..1ae8699db16 100644 --- a/erpnext/non_profit/doctype/member/member.py +++ b/erpnext/non_profit/doctype/member/member.py @@ -3,9 +3,12 @@ # For license information, please see license.txt from __future__ import unicode_literals +import frappe +from frappe import _ from frappe.model.document import Document from frappe.contacts.address_and_contact import load_address_and_contact - +from frappe.utils import cint +from frappe.integrations.utils import get_payment_gateway_controller class Member(Document): def onload(self): @@ -21,4 +24,107 @@ class Member(Document): def validate_email_type(self, email): from frappe.utils import validate_email_address - validate_email_address(email.strip(), True) \ No newline at end of file + validate_email_address(email.strip(), True) + + def setup_subscription(self): + membership_settings = frappe.get_doc("Membership Settings") + if not membership_settings.enable_razorpay: + frappe.throw("Please enable Razorpay to setup subscription") + + controller = get_payment_gateway_controller("Razorpay") + settings = controller.get_settings({}) + + plan_id = frappe.get_value("Membership Type", self.membership_type, "razorpay_plan_id") + + if not plan_id: + frappe.throw(_("Please setup Razorpay Plan ID")) + + subscription_details = { + "plan_id": plan_id, + "billing_frequency": cint(membership_settings.billing_frequency), + "customer_notify": 1 + } + + args = { + 'subscription_details': subscription_details + } + + subscription = controller.setup_subscription(settings, **args) + + return subscription + +def get_or_create_member(user_details): + member_list = frappe.get_all("Member", filters={'email': user_details.email, 'membership_type': user_details.plan_id}) + if member_list and member_list[0]: + return member_list[0]['name'] + else: + return create_member(user_details) + +def create_member(user_details): + member = frappe.new_doc("Member") + member.update({ + "member_name": user_details.fullname, + "email_id": user_details.email, + "pan_number": user_details.pan, + "membership_type": user_details.plan_id, + "customer": create_customer(user_details) + }) + + member.insert(ignore_permissions=True) + return member + +def create_customer(user_details): + customer = frappe.new_doc("Customer") + customer.customer_name = user_details.fullname + customer.customer_type = "Individual" + customer.insert(ignore_permissions=True) + + try: + contact = frappe.new_doc("Contact") + contact.first_name = user_details.fullname + contact.add_phone(user_details.mobile, is_primary_phone=1, is_primary_mobile_no=1) + contact.add_email(user_details.email, is_primary=1) + contact.insert(ignore_permissions=True) + + contact.append("links", { + "link_doctype": "Customer", + "link_name": customer.name + }) + + contact.insert() + except Exception: + error_log = frappe.log_error(frappe.get_traceback(), _("Contact Creation Failed")) + + return customer.name + +@frappe.whitelist(allow_guest=True) +def create_member_subscription_order(user_details): + """Summary + + Args: + user_details (TYPE): Description + + Returns: + Dictionary: Dictionary with subscription details + { + 'subscription_details': { + 'plan_id': 'plan_EXwyxDYDCj3X4v', + 'billing_frequency': 24, + 'customer_notify': 1 + }, + 'subscription_id': 'sub_EZycCvXFvqnC6p' + } + """ + # {"plan_id":"IFF Starter","fullname":"Shivam Mishra","mobile":"7506056962","email":"shivam@shivam.dev","pan":"Testing123"} + user_details = frappe._dict(user_details) + plan = frappe.get_doc("Membership Type", user_details.plan_id) + member = get_or_create_member(user_details) + if not member: + member = create_member(user_details) + + subscription = member.setup_subscription() + + member.subscription_id = subscription.get('subscription_id') + member.save(ignore_permissions=True) + + return subscription \ No newline at end of file diff --git a/erpnext/non_profit/doctype/membership/membership.py b/erpnext/non_profit/doctype/membership/membership.py index 574ae7fb7bb..00ecd8fbdf6 100644 --- a/erpnext/non_profit/doctype/membership/membership.py +++ b/erpnext/non_profit/doctype/membership/membership.py @@ -4,11 +4,12 @@ from __future__ import unicode_literals import json -from datetime import datetime import frappe +import six +from datetime import datetime from frappe.model.document import Document +from frappe.email import sendmail_to_system_managers from frappe.utils import add_days, add_years, nowdate, getdate, add_months, cint -from frappe.integrations.utils import get_payment_gateway_controller from frappe import _ import erpnext @@ -56,139 +57,16 @@ class Membership(Document): self.load_from_db() self.db_set('paid', 1) - def setup_subscription(self): - membership_settings = frappe.get_doc("Membership Settings") - if not membership_settings.enable_razorpay: - frappe.throw("Please enable Razorpay to setup subscription") +def get_member_based_on_subscription(subscription_id, email): + members = frappe.get_all("Member", filters={ + 'subscription_id': subscription_id, + 'email_id': email + }, order_by="creation desc") - controller = get_payment_gateway_controller("Razorpay") - settings = controller.get_settings({}) - - plan_id = frappe.get_value("Membership Type", self.membership_type, "razorpay_plan_id") - - if not plan_id: - frappe.throw(_("Please setup Razorpay Plan ID")) - - subscription_details = { - "plan_id": plan_id, - "billing_frequency": cint(membership_settings.billing_frequency), - "customer_notify": 1 - } - - args = { - 'subscription_details': subscription_details - } - - subscription = controller.setup_subscription(settings, **args) - - return subscription + return frappe.get_doc("Member", members[0]['name']) -def get_member_if_exists(email, plan): - member_list = frappe.get_all("Member", filters={'email': email, 'membership_type': plan}) - if member_list and member_list[0]: - return member_list[0]['name'] - else: - return None - -def create_member(user_details): - member = frappe.new_doc("Member") - member.update({ - "member_name": user_details.fullname, - "email_id": user_details.email, - "pan_number": user_details.pan, - "membership_type": user_details.plan_id, - "customer": create_customer(user_details) - }) - - member.insert(ignore_permissions=True) - return member - -def create_customer(user_details): - customer = frappe.new_doc("Customer") - customer.customer_name = user_details.fullname - customer.customer_type = "Individual" - customer.insert(ignore_permissions=True) - - try: - contact = frappe.new_doc("Contact") - contact.first_name = user_details.fullname - contact.add_phone(user_details.mobile, is_primary_phone=1, is_primary_mobile_no=1) - contact.add_email(user_details.email, is_primary=1) - contact.insert(ignore_permissions=True) - - contact.append("links", { - "link_doctype": "Customer", - "link_name": customer.name - }) - - contact.insert() - except Exception: - error_log = frappe.log_error(frappe.get_traceback(), _("Contact Creation Failed")) - - return customer.name - -def create_membership(member, plan): - membership = frappe.new_doc("Membership") - membership.update({ - "member": member.name, - "membership_status": "New", - "membership_type": member.membership_type, - "currency": "INR", - "amount": plan.amount, - "from_date": getdate() - }) - - membership.insert(ignore_permissions=True) - - return membership - -@frappe.whitelist(allow_guest=True) -def create_membership_subscription(user_details): - """Summary - - Args: - user_details (TYPE): Description - - Returns: - Dictionary: Dictionary with subscription details - { - 'subscription_details': { - 'plan_id': 'plan_EXwyxDYDCj3X4v', - 'billing_frequency': 24, - 'customer_notify': 1 - }, - 'subscription_id': 'sub_EZycCvXFvqnC6p' - } - """ - # {"plan_id":"IFF Starter","fullname":"Shivam Mishra","mobile":"7506056962","email":"shivam@shivam.dev","pan":"Testing123"} - user_details = frappe._dict(user_details) - member = get_member_if_exists(user_details.email, user_details.plan_id) - plan = frappe.get_doc("Membership Type", user_details.plan_id) - if not member: - member = create_member(user_details) - - membership = create_membership(member, plan) - - subscription = membership.setup_subscription() - - membership.subscription_id = subscription.get('subscription_id') - membership.save(ignore_permissions=True) - - return subscription - -def get_membership_based_on_subscription(subscription_id, custom_filters={}): - filters = {'subscription_id': subscription.id} - filters.update(custom_filters) - - memberships = frappe.get_all("Membership", filters=filters, order_by="creation") - if not memberships: - return None - - return frappe.get_doc("Membership", memberships[0]['name']) - - -@frappe.whitelist(allow_guest=True) +@frappe.whitelist() def trigger_razorpay_subscription(data): if isinstance(data, six.string_types): data = json.loads(data) @@ -200,49 +78,52 @@ def trigger_razorpay_subscription(data): payment = data.payload.get("payment", {}).get('entity', {}) payment = frappe._dict(payment) + try: + data_json = json.dumps(data, indent=4, sort_keys=True) + member = get_member_based_on_subscription(subscription.id, payment.email) + except Exception as e: + error_log = frappe.log_error(frappe.get_traceback() + '\n' + data_json , _("Membership Webhook Failed")) + notify_failure(log) + raise e + if data.event == "subscription.activated": - membership = get_membership_based_on_subscription(subscription.id, {"membership_status": "New"}) - else - prev_membership = get_membership_based_on_subscription(subscription.id, {"payment_id": payment.id, "paid": 1}) - if prev_membership: - print("payment already done") - return - prev_membership = get_membership_based_on_subscription(subscription.id) + member.customer_id = payment.customer_id + member.subscription_start = datetime.fromtimestamp(subscription.start_at) + member.subscription_end = datetime.fromtimestamp(subscription.end_at) + member.subscription_activated = 1 + member.save(ignore_permissions=True) + elif data.event == "subscription.charged": membership = frappe.new_doc("Membership") membership.update({ - "member": prev_membership.member, + "member": member.name, "membership_status": "Current", - "membership_type": prev_membership.membership_type, + "membership_type": member.membership_type, "currency": "INR", + "paid": 1, + "payment_id": payment.id, + "webhook_payload": data_json, + "from_date": datetime.fromtimestamp(subscription.current_start), + "to_date": datetime.fromtimestamp(subscription.current_end), + "amount": payment.amount / 100 # Convert to rupees from paise }) - - subscription_charged(subscription, payment, membership) - -def subscription_charged(subscription, payment, membership=None): - data = { - "subscription": subscription, - "payment": payment, - } - membership.paid = 1 - membership.payment_id = payment.id - membership.webhook_payload = json.dumps(data, indent=4, sort_keys=True) - membership.from_date = datetime.fromtimestamp(subscription.current_start) - membership.to_date = datetime.fromtimestamp(subscription.current_end) - membership.amount = payment.amount / 100 # Convert to rupees from paise - - if membership.is_new(): - membership.insert() - else: - membership.save() + membership.insert(ignore_permissions=True) return True - - - - +def notify_failure(log): + try: + content = """Dear System Manager, +Razorpay webhook for creating renewing membership subscription failed due to some reason. Please check the following error log linked below + +Error Log: {0} + +Regards, +Administrator""".format(get_link_to_form("Error Log", log.name)) + sendmail_to_system_managers("[Important] [ERPNext] Razorpay membership webhook failed , please check.", content) + except: + pass