refactor: razorpay subscription for memberships
This commit is contained in:
parent
8d6cded07f
commit
65e11a9640
@ -2,6 +2,14 @@
|
|||||||
// For license information, please see license.txt
|
// For license information, please see license.txt
|
||||||
|
|
||||||
frappe.ui.form.on('Member', {
|
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) {
|
refresh: function(frm) {
|
||||||
|
|
||||||
frappe.dynamic_link = {doc: frm.doc, fieldname: 'name', doctype: 'Member'};
|
frappe.dynamic_link = {doc: frm.doc, fieldname: 'name', doctype: 'Member'};
|
||||||
|
|||||||
@ -23,7 +23,14 @@
|
|||||||
"address_contacts",
|
"address_contacts",
|
||||||
"address_html",
|
"address_html",
|
||||||
"column_break_9",
|
"column_break_9",
|
||||||
"contact_html"
|
"contact_html",
|
||||||
|
"razorpay_details_section",
|
||||||
|
"subscription_id",
|
||||||
|
"customer_id",
|
||||||
|
"subscription_activated",
|
||||||
|
"column_break_21",
|
||||||
|
"subscription_start",
|
||||||
|
"subscription_end"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
@ -127,11 +134,49 @@
|
|||||||
"fieldname": "email_id",
|
"fieldname": "email_id",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"label": "Email Address"
|
"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",
|
"image_field": "image",
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-03-30 13:42:06.488851",
|
"modified": "2020-04-07 14:20:33.215700",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Non Profit",
|
"module": "Non Profit",
|
||||||
"name": "Member",
|
"name": "Member",
|
||||||
|
|||||||
@ -3,9 +3,12 @@
|
|||||||
# For license information, please see license.txt
|
# For license information, please see license.txt
|
||||||
|
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
import frappe
|
||||||
|
from frappe import _
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from frappe.contacts.address_and_contact import load_address_and_contact
|
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):
|
class Member(Document):
|
||||||
def onload(self):
|
def onload(self):
|
||||||
@ -21,4 +24,107 @@ class Member(Document):
|
|||||||
|
|
||||||
def validate_email_type(self, email):
|
def validate_email_type(self, email):
|
||||||
from frappe.utils import validate_email_address
|
from frappe.utils import validate_email_address
|
||||||
validate_email_address(email.strip(), True)
|
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
|
||||||
@ -4,11 +4,12 @@
|
|||||||
|
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import json
|
import json
|
||||||
from datetime import datetime
|
|
||||||
import frappe
|
import frappe
|
||||||
|
import six
|
||||||
|
from datetime import datetime
|
||||||
from frappe.model.document import Document
|
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.utils import add_days, add_years, nowdate, getdate, add_months, cint
|
||||||
from frappe.integrations.utils import get_payment_gateway_controller
|
|
||||||
from frappe import _
|
from frappe import _
|
||||||
import erpnext
|
import erpnext
|
||||||
|
|
||||||
@ -56,139 +57,16 @@ class Membership(Document):
|
|||||||
self.load_from_db()
|
self.load_from_db()
|
||||||
self.db_set('paid', 1)
|
self.db_set('paid', 1)
|
||||||
|
|
||||||
def setup_subscription(self):
|
def get_member_based_on_subscription(subscription_id, email):
|
||||||
membership_settings = frappe.get_doc("Membership Settings")
|
members = frappe.get_all("Member", filters={
|
||||||
if not membership_settings.enable_razorpay:
|
'subscription_id': subscription_id,
|
||||||
frappe.throw("Please enable Razorpay to setup subscription")
|
'email_id': email
|
||||||
|
}, order_by="creation desc")
|
||||||
|
|
||||||
controller = get_payment_gateway_controller("Razorpay")
|
return frappe.get_doc("Member", members[0]['name'])
|
||||||
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_member_if_exists(email, plan):
|
@frappe.whitelist()
|
||||||
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)
|
|
||||||
def trigger_razorpay_subscription(data):
|
def trigger_razorpay_subscription(data):
|
||||||
if isinstance(data, six.string_types):
|
if isinstance(data, six.string_types):
|
||||||
data = json.loads(data)
|
data = json.loads(data)
|
||||||
@ -200,49 +78,52 @@ def trigger_razorpay_subscription(data):
|
|||||||
payment = data.payload.get("payment", {}).get('entity', {})
|
payment = data.payload.get("payment", {}).get('entity', {})
|
||||||
payment = frappe._dict(payment)
|
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":
|
if data.event == "subscription.activated":
|
||||||
membership = get_membership_based_on_subscription(subscription.id, {"membership_status": "New"})
|
member.customer_id = payment.customer_id
|
||||||
else
|
member.subscription_start = datetime.fromtimestamp(subscription.start_at)
|
||||||
prev_membership = get_membership_based_on_subscription(subscription.id, {"payment_id": payment.id, "paid": 1})
|
member.subscription_end = datetime.fromtimestamp(subscription.end_at)
|
||||||
if prev_membership:
|
member.subscription_activated = 1
|
||||||
print("payment already done")
|
member.save(ignore_permissions=True)
|
||||||
return
|
elif data.event == "subscription.charged":
|
||||||
prev_membership = get_membership_based_on_subscription(subscription.id)
|
|
||||||
membership = frappe.new_doc("Membership")
|
membership = frappe.new_doc("Membership")
|
||||||
membership.update({
|
membership.update({
|
||||||
"member": prev_membership.member,
|
"member": member.name,
|
||||||
"membership_status": "Current",
|
"membership_status": "Current",
|
||||||
"membership_type": prev_membership.membership_type,
|
"membership_type": member.membership_type,
|
||||||
"currency": "INR",
|
"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
|
||||||
})
|
})
|
||||||
|
membership.insert(ignore_permissions=True)
|
||||||
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()
|
|
||||||
|
|
||||||
return 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
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user