From e0f569c382c90f362e58b8f5aaa2a41fbc373634 Mon Sep 17 00:00:00 2001 From: Jannat Patel Date: Tue, 24 Sep 2024 18:14:34 +0530 Subject: [PATCH] feat: payment flow with payments app --- frontend/src/pages/Billing.vue | 84 ++++++------------ lms/lms/doctype/lms_batch/lms_batch.py | 71 +++++++++++---- .../doctype/lms_settings/lms_settings.json | 9 +- lms/lms/payments.py | 64 ++++++++++++-- lms/lms/utils.py | 86 +------------------ 5 files changed, 150 insertions(+), 164 deletions(-) diff --git a/frontend/src/pages/Billing.vue b/frontend/src/pages/Billing.vue index a98af8cb..a87aaa22 100644 --- a/frontend/src/pages/Billing.vue +++ b/frontend/src/pages/Billing.vue @@ -38,7 +38,7 @@ v-if="orderSummary.data.gst_applied" class="flex items-center justify-between mt-2" > -
+
{{ __('GST Amount') }}
@@ -171,7 +171,7 @@ import { Input, Button, createResource, call } from 'frappe-ui' import { reactive, inject, onMounted, ref } from 'vue' import Link from '@/components/Controls/Link.vue' import NotPermitted from '@/components/NotPermitted.vue' -import { createToast } from '@/utils/' +import { showToast } from '@/utils/' const user = inject('$user') @@ -224,73 +224,45 @@ const orderSummary = createResource({ const billingDetails = reactive({}) const setBillingDetails = (data) => { - billingDetails.billing_name = data.billing_name || '' - billingDetails.address_line1 = data.address_line1 || '' - billingDetails.address_line2 = data.address_line2 || '' - billingDetails.city = data.city || '' - billingDetails.state = data.state || '' - billingDetails.country = data.country || '' - billingDetails.pincode = data.pincode || '' - billingDetails.phone = data.phone || '' - billingDetails.source = data.source || '' - billingDetails.gstin = data.gstin || '' - billingDetails.pan = data.pan || '' + billingDetails.billing_name = data?.billing_name || '' + billingDetails.address_line1 = data?.address_line1 || '' + billingDetails.address_line2 = data?.address_line2 || '' + billingDetails.city = data?.city || '' + billingDetails.state = data?.state || '' + billingDetails.country = data?.country || '' + billingDetails.pincode = data?.pincode || '' + billingDetails.phone = data?.phone || '' + billingDetails.source = data?.source || '' + billingDetails.gstin = data?.gstin || '' + billingDetails.pan = data?.pan || '' } -const paymentOptions = createResource({ - url: 'lms.lms.utils.get_payment_options', +const paymentLink = createResource({ + url: 'lms.lms.payments.get_payment_link', makeParams(values) { return { doctype: props.type == 'course' ? 'LMS Course' : 'LMS Batch', docname: props.name, - phone: billingDetails.phone, - country: billingDetails.country, + amount: orderSummary.data.original_amount, + total_amount: orderSummary.data.amount, + currency: orderSummary.data.currency, + address: billingDetails, } }, }) const generatePaymentLink = () => { - call('lms.lms.payments.get_payment_link', { - doctype: props.type == 'course' ? 'LMS Course' : 'LMS Batch', - docname: props.name, - amount: orderSummary.data.amount, - currency: orderSummary.data.currency, - billing_name: billingDetails.billing_name, - }).then((data) => { - window.location.href = data - }) -} - -const paymentResource = createResource({ - url: 'lms.lms.utils.verify_payment', - makeParams(values) { - return { - response: values.response, - doctype: props.type == 'course' ? 'LMS Course' : 'LMS Batch', - docname: props.name, - address: billingDetails, - order_id: values.orderId, - } - }, -}) - -const handleSuccess = (response, doctype, docname, orderId) => { - paymentResource.submit( - { - response: response, - orderId: orderId, - }, + paymentLink.submit( + {}, { + validate() { + return validateAddress() + }, onSuccess(data) { - createToast({ - title: 'Success', - text: 'Payment Successful', - icon: 'check', - iconClasses: 'bg-green-600 text-white rounded-md p-px', - }) - setTimeout(() => { - window.location.href = data - }, 3000) + window.location.href = data + }, + onError(err) { + showToast(__('Error'), err.messages?.[0] || err, 'x') }, } ) diff --git a/lms/lms/doctype/lms_batch/lms_batch.py b/lms/lms/doctype/lms_batch/lms_batch.py index 306a1cee..82006ca1 100644 --- a/lms/lms/doctype/lms_batch/lms_batch.py +++ b/lms/lms/doctype/lms_batch/lms_batch.py @@ -165,25 +165,64 @@ class LMSBatch(Document): ) def on_payment_authorized(self, payment_status): - print(payment_status) + if payment_status == "Authorized": + self.update_payment_record() + + def update_payment_record(self): + request = frappe.get_all( + "Integration Request", + { + "reference_doctype": self.doctype, + "reference_docname": self.name, + "owner": frappe.session.user, + }, + order_by="creation desc", + limit=1, + ) + + if len(request): + data = frappe.db.get_value("Integration Request", request[0].name, "data") + data = frappe._dict(json.loads(data)) + + payment_gateway = data.get("payment_gateway") + frappe.db.set_value( + "LMS Payment", + data.payment, + { + "payment_received": 1, + "payment_id": data[f"{payment_gateway.lower()}_payment_id"], + "order_id": data["order_id"], + }, + ) + + try: + enroll_in_batch(data.payment, self) + except Exception as e: + frappe.log_error(frappe.get_traceback(), _("Enrollment Failed")) -@frappe.whitelist() -def remove_student(student, batch_name): - frappe.only_for("Moderator") - frappe.db.delete("Batch Student", {"student": student, "parent": batch_name}) +def enroll_in_batch(payment_name, batch): + if not frappe.db.exists( + "Batch Student", {"parent": batch.name, "student": frappe.session.user} + ): + student = frappe.new_doc("Batch Student") + current_count = frappe.db.count("Batch Student", {"parent": batch.name}) + payment = frappe.db.get_value( + "LMS Payment", payment_name, ["name", "source"], as_dict=True + ) - -@frappe.whitelist() -def remove_course(course, parent): - frappe.only_for("Moderator") - frappe.db.delete("Batch Course", {"course": course, "parent": parent}) - - -@frappe.whitelist() -def remove_assessment(assessment, parent): - frappe.only_for("Moderator") - frappe.db.delete("LMS Assessment", {"assessment_name": assessment, "parent": parent}) + student.update( + { + "student": frappe.session.user, + "payment": payment.name, + "source": payment.source, + "parent": batch.name, + "parenttype": "LMS Batch", + "parentfield": "students", + "idx": current_count + 1, + } + ) + student.save(ignore_permissions=True) @frappe.whitelist() diff --git a/lms/lms/doctype/lms_settings/lms_settings.json b/lms/lms/doctype/lms_settings/lms_settings.json index 03bcaf42..9b8dd0fd 100644 --- a/lms/lms/doctype/lms_settings/lms_settings.json +++ b/lms/lms/doctype/lms_settings/lms_settings.json @@ -42,6 +42,7 @@ "mentor_request_status_update", "payment_settings_tab", "payment_section", + "payment_gateway", "razorpay_key", "razorpay_secret", "apply_gst", @@ -331,12 +332,18 @@ "fieldname": "custom_signup_content", "fieldtype": "HTML Editor", "label": "Custom Signup Content" + }, + { + "fieldname": "payment_gateway", + "fieldtype": "Select", + "label": "Payment Gateway", + "options": "Razorpay\nMpesa\nPaytm\nBraintree\nStripe\nPaypal\nGoCardless" } ], "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2024-09-23 17:57:01.350020", + "modified": "2024-09-24 18:09:25.366651", "modified_by": "Administrator", "module": "LMS", "name": "LMS Settings", diff --git a/lms/lms/payments.py b/lms/lms/payments.py index d89508b9..cc8ccd4e 100644 --- a/lms/lms/payments.py +++ b/lms/lms/payments.py @@ -3,7 +3,7 @@ from payments.utils import get_payment_gateway_controller def get_payment_gateway(): - return "Razorpay" + return frappe.db.get_single_value("LMS Settings", "payment_gateway") def get_controller(payment_gateway): @@ -16,21 +16,73 @@ def validate_currency(payment_gateway, currency): @frappe.whitelist() -def get_payment_link(doctype, docname, amount, currency, billing_name): +def get_payment_link(doctype, docname, amount, total_amount, currency, address): payment_gateway = get_payment_gateway() + address = frappe._dict(address) + amount_with_gst = total_amount if total_amount != amount else 0 + + payment = record_payment(address, doctype, docname, amount, currency, amount_with_gst) payment_details = { - "amount": amount, + "amount": total_amount, "title": f"Payment for {doctype} {docname}", - "description": f"{billing_name}'s payment for {doctype} {docname}", + "description": f"{address.billing_name}'s payment for {doctype} {docname}", "reference_doctype": doctype, "reference_docname": docname, "payer_email": frappe.session.user, - "payer_name": billing_name, + "payer_name": address.billing_name, "order_id": docname, "currency": currency, "payment_gateway": payment_gateway, + "redirect_to": f"/lms/batches/{docname}", + "payment": payment.name, } controller = get_controller(payment_gateway) - url = controller().get_payment_url(**payment_details) + url = controller.get_payment_url(**payment_details) + return url + + +def record_payment(address, doctype, docname, amount, currency, amount_with_gst=0): + address = frappe._dict(address) + address_name = save_address(address) + + payment_doc = frappe.new_doc("LMS Payment") + payment_doc.update( + { + "member": frappe.session.user, + "billing_name": address.billing_name, + "address": address_name, + "amount": amount, + "currency": currency, + "amount_with_gst": amount_with_gst, + "gstin": address.gstin, + "pan": address.pan, + "source": address.source, + "payment_for_document_type": doctype, + "payment_for_document": docname, + } + ) + payment_doc.save(ignore_permissions=True) + return payment_doc + + +def save_address(address): + filters = {"email_id": frappe.session.user} + exists = frappe.db.exists("Address", filters) + if exists: + address_doc = frappe.get_last_doc("Address", filters=filters) + else: + address_doc = frappe.new_doc("Address") + + address_doc.update(address) + address_doc.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, + } + ) + address_doc.save(ignore_permissions=True) + return address_doc.name diff --git a/lms/lms/utils.py b/lms/lms/utils.py index b2692b23..8e2945c7 100644 --- a/lms/lms/utils.py +++ b/lms/lms/utils.py @@ -1021,27 +1021,6 @@ def get_details(doctype, docname): return details -def save_address(address): - filters = {"email_id": frappe.session.user} - exists = frappe.db.exists("Address", filters) - if exists: - address_doc = frappe.get_last_doc("Address", filters=filters) - else: - address_doc = frappe.new_doc("Address") - - address_doc.update(address) - address_doc.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, - } - ) - address_doc.save(ignore_permissions=True) - return address_doc.name - - def get_client(): settings = frappe.get_single("LMS Settings") razorpay_key = settings.razorpay_key @@ -1073,52 +1052,6 @@ def create_order(client, amount, currency): ) -@frappe.whitelist() -def verify_payment(response, doctype, docname, address, order_id): - client = get_client() - client.utility.verify_payment_signature( - { - "razorpay_order_id": order_id, - "razorpay_payment_id": response["razorpay_payment_id"], - "razorpay_signature": response["razorpay_signature"], - } - ) - - payment = record_payment(address, response, client, doctype, docname) - if doctype == "LMS Course": - return create_membership(docname, payment) - else: - return add_student_to_batch(docname, payment) - - -def record_payment(address, response, client, doctype, docname): - address = frappe._dict(address) - address_name = save_address(address) - - payment_details = get_payment_details(doctype, docname, address) - payment_doc = frappe.new_doc("LMS Payment") - payment_doc.update( - { - "member": frappe.session.user, - "billing_name": address.billing_name, - "address": address_name, - "payment_received": 1, - "order_id": response["razorpay_order_id"], - "payment_id": response["razorpay_payment_id"], - "amount": payment_details["amount"], - "currency": payment_details["currency"], - "amount_with_gst": payment_details["amount_with_gst"], - "gstin": address.gstin, - "pan": address.pan, - "source": address.source, - "payment_for_document_type": doctype, - "payment_for_document": docname, - } - ) - payment_doc.save(ignore_permissions=True) - return payment_doc - - def get_payment_details(doctype, docname, address): amount_field = "course_price" if doctype == "LMS Course" else "amount" amount = frappe.db.get_value(doctype, docname, amount_field) @@ -1146,24 +1079,6 @@ def create_membership(course, payment): return f"/lms/courses/{course}/learn/1-1" -def add_student_to_batch(batchname, payment): - student = frappe.new_doc("Batch Student") - current_count = frappe.db.count("Batch Student", {"parent": batchname}) - student.update( - { - "student": frappe.session.user, - "payment": payment.name, - "source": payment.source, - "parent": batchname, - "parenttype": "LMS Batch", - "parentfield": "students", - "idx": current_count + 1, - } - ) - student.save(ignore_permissions=True) - return f"/batches/{batchname}" - - def get_current_exchange_rate(source, target="USD"): url = f"https://api.frankfurter.app/latest?from={source}&to={target}" @@ -1765,6 +1680,7 @@ def get_order_summary(doctype, docname, country=None): details.amount, details.currency = check_multicurrency( details.amount, details.currency, country, details.amount_usd ) + details.original_amount = details.amount details.original_amount_formatted = fmt_money(details.amount, 0, details.currency) if details.currency == "INR":