Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 40 additions & 13 deletions webshop/templates/pages/order.html
Original file line number Diff line number Diff line change
Expand Up @@ -34,23 +34,13 @@ <h3 class="m-0">{{ doc.name }}</h3>
</a>
</ul>
</div>
<div class="form-column col-sm-6">
<div class="page-header-actions-block" data-html-block="header-actions">
<p>
{% if enabled_checkout and (doc.doctype != "Sales Invoice" or doc.outstanding_amount > 0) %}
<a href="/api/method/erpnext.accounts.doctype.payment_request.payment_request.make_payment_request?dn={{ doc.name }}&dt={{ doc.doctype }}&submit_doc=1&order_type=Shopping Cart"
class="btn btn-primary btn-sm" id="pay-for-order">
{{ _("Pay") }} {{doc.get_formatted("grand_total") }}
</a>
{% endif %}
</p>
</div>
</div>
</div>
{% endblock %}

{% block page_content %}
<div>
<div class="row m-0">
<div class="col-md-9">
<div>
<div class="row transaction-subheading mt-1">
<div class="col-6 text-muted small mt-1">
{{ frappe.utils.format_date(doc.transaction_date, 'medium') }}
Expand Down Expand Up @@ -185,12 +175,49 @@ <h3 class="m-0">{{ doc.name }}</h3>
</div>
{% endif %}





{% if doc.terms %}
<div class="terms-and-condition text-muted small">
<hr>
<p>{{ doc.terms }}</p>
</div>
{% endif %}

</div>

<div class="col-md-3">
<div class="">
<div class="">
<div class="card-body">
<div class="form-group mb-6">
<label for="payment-method">{{ _("Payment Method") }}</label>
<select id="payment-method" class="form-control">
<option value="others" selected>{{ _("Others") }}</option>
<option value="mpesa">{{ _("Mpesa") }}</option>
</select>
</div>

<div class="form-group mb-4" id="mpesa-phone-field" style="display: none;">
<label for="phone-number">{{ _("Phone Number") }}</label>
<input type="text" class="form-control" id="phone-number" placeholder="">
</div>

{% if enabled_checkout and (doc.doctype != "Sales Invoice" or doc.outstanding_amount > 0) %}
<button type="button" class="btn btn-primary w-100" id="pay-for-order">
{{ _("Pay") }} {{ doc.get_formatted("grand_total") }}
</button>

{% endif %}
</div>
</div>
</div>
</div>


</div>
{% endblock %}

{% block script %}
Expand Down
241 changes: 176 additions & 65 deletions webshop/templates/pages/order.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
});