From e6c9261cfbc47456f554e66f7284f5fb7232806b Mon Sep 17 00:00:00 2001 From: muruthigitau Date: Tue, 15 Apr 2025 11:07:27 +0300 Subject: [PATCH] feat(order): enhance layout and integrate Mpesa payment processing --- webshop/templates/pages/order.html | 53 +++++-- webshop/templates/pages/order.js | 241 +++++++++++++++++++++-------- 2 files changed, 216 insertions(+), 78 deletions(-) diff --git a/webshop/templates/pages/order.html b/webshop/templates/pages/order.html index 2e11784b91..c3d949c7ae 100644 --- a/webshop/templates/pages/order.html +++ b/webshop/templates/pages/order.html @@ -34,23 +34,13 @@

{{ doc.name }}

-
-
-

- {% if enabled_checkout and (doc.doctype != "Sales Invoice" or doc.outstanding_amount > 0) %} - - {{ _("Pay") }} {{doc.get_formatted("grand_total") }} - - {% endif %} -

-
-
{% endblock %} {% block page_content %} -
+
+
+
{{ frappe.utils.format_date(doc.transaction_date, 'medium') }} @@ -185,12 +175,49 @@

{{ doc.name }}

{% endif %} + + + + {% if doc.terms %}

{{ doc.terms }}

{% endif %} + +
+ +
+
+
+
+
+ + +
+ + + + {% if enabled_checkout and (doc.doctype != "Sales Invoice" or doc.outstanding_amount > 0) %} + + + {% endif %} +
+
+
+
+ + +
{% endblock %} {% block script %} diff --git a/webshop/templates/pages/order.js b/webshop/templates/pages/order.js index c1215a82bc..83ae7529eb 100644 --- a/webshop/templates/pages/order.js +++ b/webshop/templates/pages/order.js @@ -2,68 +2,179 @@ // For license information, please see license.txt frappe.ready(() => { - var loyalty_points_input = document.getElementById("loyalty-point-to-redeem"); - var loyalty_points_status = document.getElementById("loyalty-points-status"); - - if (loyalty_points_input) { - loyalty_points_input.onblur = apply_loyalty_points; - } - - function apply_loyalty_points() { - var loyalty_points = parseInt(loyalty_points_input.value); - - if (!loyalty_points) return; - - const callback = async (r) => { - if (!r) return; - - var message = "" - let loyalty_amount = flt(r.message * loyalty_points); - - if (doc_info.grand_total && doc_info.grand_total < loyalty_amount) { - let redeemable_amount = parseInt(doc_info.grand_total/r.message); - message = "You can only redeem max " + redeemable_amount + " points in this order."; - frappe.msgprint(__(message)); - return; - } - - message = loyalty_points + " Loyalty Points of amount "+ loyalty_amount + " is applied." - loyalty_points_status.innerHTML = message; - frappe.msgprint(__(message)); - - const args_obj = { - dn: doc_info.doctype_name, - dt: doc_info.doctype, - submit_doc: 1, - order_type: "Shopping Cart", - loyalty_points, - } - - const payment_gateway_account = await frappe.db.get_single_value('Webshop Settings', 'payment_gateway_account') - - if (payment_gateway_account) { - args_obj.payment_gateway_account = payment_gateway_account; - } - - const args_str = Object - .entries(args_obj) - .map((e) => e[0] + "=" + e[1]) - .join("&"); - - const href_base_url = "/api/method/erpnext.accounts.doctype.payment_request.payment_request.make_payment_request" - const href = href_base_url + "?" + args_str; - - var payment_button = document.getElementById("pay-for-order"); - payment_button.innerHTML = __("Pay Remaining"); - payment_button.href = href; - } - - frappe.call({ - method: "erpnext.accounts.doctype.loyalty_program.loyalty_program.get_redeemption_factor", - args: { - "customer": doc_info.customer - }, - callback, - }); - } -}) + // Initialize elements + const elements = { + loyalty: { + input: document.getElementById("loyalty-point-to-redeem"), + status: document.getElementById("loyalty-points-status"), + handler: applyLoyaltyPoints, + }, + payment: { + methodSelect: document.getElementById("payment-method"), + phoneField: document.getElementById("mpesa-phone-field"), + phoneInput: document.getElementById("phone-number"), + handler: toggleMpesaField, + }, + }; + + // Set up event listeners + if (elements.loyalty.input) { + elements.loyalty.input.onblur = elements.loyalty.handler; + } + + if (elements.payment.methodSelect) { + elements.payment.methodSelect.addEventListener( + "change", + elements.payment.handler + ); + // Initialize on load + elements.payment.handler(); + } + + // Payment button handler + const payButton = document.getElementById("pay-for-order"); + if (payButton) { + payButton.addEventListener("click", processPayment); + } + + // Common function to get payment gateway account + function getPaymentGatewayAccount() { + return new Promise((resolve) => { + frappe.call({ + method: "frappe.client.get_value", + args: { + doctype: "Webshop Settings", + fieldname: "payment_gateway_account", + }, + callback: (r) => resolve(r.message?.payment_gateway_account), + }); + }); + } + + // Common function to build request args + async function buildRequestArgs(additionalArgs = {}) { + const args = { + dn: doc_info.doctype_name, + dt: doc_info.doctype, + submit_doc: 1, + order_type: "Shopping Cart", + ...additionalArgs, + }; + + // Include phone number if it exists (for both payment and loyalty) + const phoneNumber = elements.payment.phoneInput?.value; + if (phoneNumber) { + args.phone_number = phoneNumber; + } + + // Get payment gateway account if needed + if (!additionalArgs.loyalty_points) { + const paymentGatewayAccount = await getPaymentGatewayAccount(); + if (paymentGatewayAccount) { + args.payment_gateway_account = paymentGatewayAccount; + } + } + + return args; + } + + // Common function to create payment URL + function createPaymentUrl(args) { + const argsStr = Object.entries(args) + .map(([key, val]) => `${key}=${encodeURIComponent(val)}`) + .join("&"); + return `/api/method/erpnext.accounts.doctype.payment_request.payment_request.make_payment_request?${argsStr}`; + } + + // Toggle MPesa field visibility + function toggleMpesaField() { + const method = elements.payment.methodSelect.value; + if (elements.payment.phoneField) { + elements.payment.phoneField.style.display = + method === "mpesa" ? "block" : "none"; + } + } + + // Process payment + async function processPayment() { + try { + const paymentMethod = elements.payment.methodSelect?.value || "others"; + const args = await buildRequestArgs(); + window.location.href = createPaymentUrl(args); + } catch (error) { + console.error("Payment processing error:", error); + showError(__("Error processing payment. Please try again."), error); + } + } + + // Apply loyalty points + async function applyLoyaltyPoints() { + const loyaltyPoints = parseInt(elements.loyalty.input.value); + if (!loyaltyPoints) return; + + try { + const redemptionFactor = await getRedemptionFactor(); + if (!redemptionFactor) return; + + const loyaltyAmount = flt(redemptionFactor * loyaltyPoints); + + if (doc_info.grand_total && doc_info.grand_total < loyaltyAmount) { + const redeemableAmount = parseInt( + doc_info.grand_total / redemptionFactor + ); + showMessage( + `You can only redeem max ${redeemableAmount} points in this order.` + ); + return; + } + + showMessage( + `${loyaltyPoints} Loyalty Points of amount ${loyaltyAmount} is applied.`, + true + ); + + const args = await buildRequestArgs({ loyalty_points: loyaltyPoints }); + updatePaymentButton(createPaymentUrl(args)); + } catch (error) { + showError(__("Error applying loyalty points."), error); + } + } + + // Helper function to get redemption factor + function getRedemptionFactor() { + return new Promise((resolve) => { + frappe.call({ + method: + "erpnext.accounts.doctype.loyalty_program.loyalty_program.get_redeemption_factor", + args: { customer: doc_info.customer }, + callback: (r) => resolve(r.message), + }); + }); + } + + // Helper function to update payment button + function updatePaymentButton(url) { + const paymentButton = document.getElementById("pay-for-order"); + if (paymentButton) { + paymentButton.innerHTML = __("Pay Remaining"); + paymentButton.href = url; + } + } + + // Helper function to show messages + function showMessage(message, updateStatus = false) { + frappe.msgprint(__(message)); + if (updateStatus && elements.loyalty.status) { + elements.loyalty.status.innerHTML = message; + } + } + + // Helper function for error handling + function showError(defaultMessage, error) { + frappe.msgprint(__(defaultMessage)); + if (error?.message) { + console.error(error); + frappe.msgprint(__("Details: ") + error.message); + } + } +});