diff --git a/lms/hooks.py b/lms/hooks.py index 1bb0f1f0..f8796e7e 100644 --- a/lms/hooks.py +++ b/lms/hooks.py @@ -188,6 +188,10 @@ website_route_rules = [ "from_route": "/quiz-submission//", "to_route": "quiz_submission/quiz_submission", }, + { + "from_route": "/billing/", + "to_route": "billing/billing", + }, ] website_redirects = [ diff --git a/lms/lms/doctype/lms_batch_membership/lms_batch_membership.json b/lms/lms/doctype/lms_batch_membership/lms_batch_membership.json index 903f66ce..6aba9d5b 100644 --- a/lms/lms/doctype/lms_batch_membership/lms_batch_membership.json +++ b/lms/lms/doctype/lms_batch_membership/lms_batch_membership.json @@ -12,6 +12,12 @@ "member", "member_name", "member_username", + "billing_information_section", + "address", + "payment_received", + "column_break_rvzn", + "order_id", + "payment_id", "section_break_8", "cohort", "subgroup", @@ -112,11 +118,42 @@ { "fieldname": "section_break_8", "fieldtype": "Section Break" + }, + { + "fieldname": "billing_information_section", + "fieldtype": "Section Break", + "label": "Billing Information" + }, + { + "fieldname": "address", + "fieldtype": "Link", + "label": "Address", + "options": "Address" + }, + { + "default": "0", + "fieldname": "payment_received", + "fieldtype": "Check", + "label": "Payment Received" + }, + { + "fieldname": "column_break_rvzn", + "fieldtype": "Column Break" + }, + { + "fieldname": "order_id", + "fieldtype": "Data", + "label": "Order ID" + }, + { + "fieldname": "payment_id", + "fieldtype": "Data", + "label": "Payment ID" } ], "index_web_pages_for_search": 1, "links": [], - "modified": "2022-10-10 12:38:17.839526", + "modified": "2023-08-11 15:39:50.194348", "modified_by": "Administrator", "module": "LMS", "name": "LMS Batch Membership", @@ -141,4 +178,4 @@ "sort_order": "DESC", "states": [], "title_field": "member_name" -} +} \ No newline at end of file diff --git a/lms/lms/doctype/lms_course/lms_course.py b/lms/lms/doctype/lms_course/lms_course.py index 967d8e93..7cc082a4 100644 --- a/lms/lms/doctype/lms_course/lms_course.py +++ b/lms/lms/doctype/lms_course/lms_course.py @@ -359,14 +359,12 @@ def reorder_chapter(chapter_array): @frappe.whitelist() -def get_payment_options(course): +def get_payment_options(course, phone): course_details = frappe.db.get_value( "LMS Course", course, ["name", "title", "currency", "course_price"], as_dict=True ) razorpay_key = frappe.db.get_single_value("LMS Settings", "razorpay_key") - razorpay_secret = frappe.db.get_single_value("LMS Settings", "razorpay_secret") - - client = get_client(razorpay_key, razorpay_secret) + client = get_client() order = create_order(client, course_details) options = { @@ -379,15 +377,32 @@ def get_payment_options(course): "prefill": { "name": frappe.db.get_value("User", frappe.session.user, "full_name"), "email": frappe.session.user, + "contact": phone, }, - "callback_url": frappe.utils.get_url( - "/api/method/lms.lms.doctype.lms_course.lms_course.verify_payment" - ), } return options -def get_client(razorpay_key, razorpay_secret): +def save_address(address): + address = json.loads(address) + address.update( + { + "address_title": frappe.db.get_value("User", frappe.session.user, "full_name"), + "address_type": "Billing", + "is_primary_address": 1, + "email_id": frappe.session.user, + } + ) + doc = frappe.new_doc("Address") + doc.update(address) + doc.save(ignore_permissions=True) + return doc.name + + +def get_client(): + razorpay_key = frappe.db.get_single_value("LMS Settings", "razorpay_key") + razorpay_secret = frappe.db.get_single_value("LMS Settings", "razorpay_secret") + if not razorpay_key and not razorpay_secret: frappe.throw( _( @@ -399,9 +414,48 @@ def get_client(razorpay_key, razorpay_secret): def create_order(client, course_details): - return client.order.create( + try: + return client.order.create( + { + "amount": course_details.course_price * 100, + "currency": course_details.currency, + } + ) + except Exception as e: + frappe.throw( + _("Error during payment: {0}. Please contact the Administrator.").format(e) + ) + + +@frappe.whitelist() +def verify_payment(response, course, address, order_id): + response = json.loads(response) + client = get_client() + client.utility.verify_payment_signature( { - "amount": course_details.course_price * 100, - "currency": course_details.currency, + "razorpay_order_id": order_id, + "razorpay_payment_id": response["razorpay_payment_id"], + "razorpay_signature": response["razorpay_signature"], } ) + + return create_membership(address, response, course) + + +def create_membership(address, response, course): + address_name = save_address(address) + membership = frappe.new_doc("LMS Batch Membership") + + membership.update( + { + "member": frappe.session.user, + "course": course, + "address": address_name, + "payment_received": 1, + "order_id": response["razorpay_order_id"], + "payment_id": response["razorpay_payment_id"], + } + ) + membership.save(ignore_permissions=True) + + return f"/courses/{course}/learn/1.1" diff --git a/lms/public/css/style.css b/lms/public/css/style.css index 6b885abe..c3768a6c 100644 --- a/lms/public/css/style.css +++ b/lms/public/css/style.css @@ -2285,4 +2285,15 @@ select { position: absolute; top: 0; left: 0; -} \ No newline at end of file +} + +.form-section .section-head { + margin-bottom: var(--margin-sm); + font-weight: 700; + color: var(--heading-color); +} + +.form-column:first-child { + padding-right: 1rem !important; +} + diff --git a/lms/public/js/common_functions.js b/lms/public/js/common_functions.js index 9605629f..124c401f 100644 --- a/lms/public/js/common_functions.js +++ b/lms/public/js/common_functions.js @@ -201,7 +201,7 @@ const expand_the_first_chapter = () => { const expand_the_active_chapter = () => { let selector = $(".course-home-headings.title"); - console.log(selector); + if (selector.length && $(".course-details-page").length) { expand_for_course_details(selector); } else if ($(".active-lesson").length) { diff --git a/lms/www/billing/billing.html b/lms/www/billing/billing.html new file mode 100644 index 00000000..373461d9 --- /dev/null +++ b/lms/www/billing/billing.html @@ -0,0 +1,75 @@ +{% extends "lms/templates/lms_base.html" %} +{% block title %} + {{ course.title if course.title else _("New Course") }} +{% endblock %} + + +{% block page_content %} +
+ +
+{% endblock %} + +{% macro Header() %} +
+
+ {{ _("Order Details") }} +
+
+ {{ _("Enter the billing information and complete the payment to purchase this course.") }} +
+
+{% endmacro %} + +{% macro CourseDetails() %} +
+
+
+ {{ _("Course:") }} +
+
+ {{ course.title }} +
+
+ +
+ +
+
+ {{ _("Total Price:") }} +
+
+ + {{ frappe.utils.fmt_money(course.course_price, 2, course.currency) }} +
+
+
+{% endmacro %} + +{% macro BillingDetails() %} +
+
+ {{ _("Billing Details") }} +
+
+ +
+{% endmacro %} + +{%- block script %} +{{ super() }} + + +{% endblock %} diff --git a/lms/www/billing/billing.js b/lms/www/billing/billing.js new file mode 100644 index 00000000..51f57cef --- /dev/null +++ b/lms/www/billing/billing.js @@ -0,0 +1,105 @@ +frappe.ready(() => { + if ($("#billing-form").length) { + setup_billing(); + } + + $(".btn-pay").click((e) => { + generate_payment_link(e); + }); +}); + +const setup_billing = () => { + this.billing = new frappe.ui.FieldGroup({ + fields: [ + { + fieldtype: "Data", + label: __("Address Line 1"), + fieldname: "address_line1", + reqd: 1, + }, + { + fieldtype: "Data", + label: __("Address Line 2"), + fieldname: "address_line2", + }, + { + fieldtype: "Data", + label: __("City/Town"), + fieldname: "city", + reqd: 1, + }, + { + fieldtype: "Data", + label: __("State/Province"), + fieldname: "state", + }, + { + fieldtype: "Column Break", + }, + { + fieldtype: "Link", + label: __("Country"), + fieldname: "country", + options: "Country", + reqd: 1, + }, + { + fieldtype: "Data", + label: __("Postal Code"), + fieldname: "pincode", + reqd: 1, + }, + { + fieldtype: "Data", + label: __("Phone Number"), + fieldname: "phone", + reqd: 1, + }, + ], + body: $("#billing-form").get(0), + }); + this.billing.make(); + $("#billing-form .form-section:last").removeClass("empty-section"); + $("#billing-form .frappe-control").removeClass("hide-control"); + $("#billing-form .form-column").addClass("p-0"); +}; + +const generate_payment_link = (e) => { + address = this.billing.get_values(); + let course = decodeURIComponent($(e.currentTarget).attr("data-course")); + + frappe.call({ + method: "lms.lms.doctype.lms_course.lms_course.get_payment_options", + args: { + course: course, + phone: address.phone, + }, + callback: (data) => { + data.message.handler = (response) => { + handle_success( + response, + course, + address, + data.message.order_id + ); + }; + let rzp1 = new Razorpay(data.message); + rzp1.open(); + }, + }); +}; + +const handle_success = (response, course, address, order_id) => { + frappe.call({ + method: "lms.lms.doctype.lms_course.lms_course.verify_payment", + args: { + response: response, + course: course, + address: address, + order_id: order_id, + }, + callback: (data) => { + window.location.href = data.message; + }, + }); +}; diff --git a/lms/www/billing/billing.py b/lms/www/billing/billing.py new file mode 100644 index 00000000..562e6969 --- /dev/null +++ b/lms/www/billing/billing.py @@ -0,0 +1,23 @@ +import frappe +from frappe import _ + + +def get_context(context): + course_name = frappe.form_dict.course + + if not course_name: + raise ValueError(_("Course is required.")) + + if frappe.session.user == "Guest": + raise frappe.PermissionError(_("You are not allowed to access this page.")) + + membership = frappe.db.exists( + "LMS Batch Membership", {"member": frappe.session.user, "course": course_name} + ) + + if membership: + raise frappe.PermissionError(_("You are already enrolled for this course")) + + context.course = frappe.db.get_value( + "LMS Course", course_name, ["title", "name", "course_price", "currency"], as_dict=True + ) diff --git a/lms/www/courses/course.html b/lms/www/courses/course.html index ad584ec0..450cf640 100644 --- a/lms/www/courses/course.html +++ b/lms/www/courses/course.html @@ -222,9 +222,9 @@ {% elif course.paid_course %} - + {% elif show_start_learing_cta(course, membership) %}
@@ -257,9 +257,6 @@ {% endif %}
- - - {% endmacro %} @@ -280,9 +277,4 @@ {{ _("You have exceeded the maximum number of attempts allowed to appear for evaluations of this course.") }}

{% endif %} -{% endmacro %} - -{%- block script %} -{{ super() }} - -{% endblock %} \ No newline at end of file +{% endmacro %} \ No newline at end of file diff --git a/lms/www/courses/course.js b/lms/www/courses/course.js index 4f26b877..a3f86b84 100644 --- a/lms/www/courses/course.js +++ b/lms/www/courses/course.js @@ -20,10 +20,6 @@ frappe.ready(() => { $("#submit-for-review").click((e) => { submit_for_review(e); }); - - $("#buy-course").click((e) => { - generate_checkout_link(e); - }); }); const hide_wrapped_mentor_cards = () => { @@ -146,24 +142,3 @@ const submit_for_review = (e) => { }, }); }; - -generate_checkout_link = (e) => { - e.preventDefault(); - let course = decodeURIComponent($(e.currentTarget).attr("data-course")); - - if (frappe.session.user == "Guest") { - window.location.href = `/login?redirect-to=/courses/${course}`; - return; - } - - frappe.call({ - method: "lms.lms.doctype.lms_course.lms_course.get_payment_options", - args: { - course: course, - }, - callback: (data) => { - let rzp1 = new Razorpay(data.message); - rzp1.open(); - }, - }); -};