+
{{ frappe.utils.format_date(doc.transaction_date, 'medium') }}
@@ -185,12 +175,49 @@
{{ doc.name }}
{% endif %}
+
+
+
+
{% if 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);
+ }
+ }
+});