Hey there! I'm having a tricky issue with my custom Cards payment module for Odoo 18 POS. I built this module to handle credit card payments with installments, and everything works great except for one annoying detail in the payment screen. The total amount in the payment summary doesn't update automatically when selecting installments. This is my custom implementation that extends the default POS payment screen, using a CardsPaymentHandler class to manage card brands and installment calculations. The weird part is that the amount only updates in two scenarios:
If I remove the payment method and add it again (then it shows the correct total from the order/status)
If I manually type numbers using the POS numeric keypad (then it updates the payment method total in the summary)
All the interest calculations work fine, and I can see the correct amounts in the state, but for some reason, the payment summary display won't refresh with the new total when choosing installments.
Has anyone encountered something similar when extending the POS payment screen or knows what might be missing? Maybe there's a specific method I need to call to force the payment summary update?
I published the main functions that manage the updates of the totals according to the installments.
Thanks in advance!
/**
* Handles card brand selection change
* Resets prices and loads available installments
* @param {Event} ev Change event from select element
*/
async onCardBrandChange(ev) {
const cardBrand = ev.target.value;
this._resetPrices();
if (!cardBrand) {
this._resetState();
return;
}
try {
const amount = this.currentOrder.get_total_with_tax();
const installments = await rpc('/pos_payment_method/get_installments', {
payment_method_id: CARDS_PAYMENT_METHOD_ID,
card_brand: cardBrand,
amount: amount
});
if (!installments) {
throw new Error('There isn´t installments plan.');
}
this.state.selectedCardBrand = cardBrand;
this.state.installmentOptions = this._formatInstallmentOptions(installments);
this.screen.render();
} catch (error) {
this._resetState();
this.showError('Error loading installments');
}
}
/**
* Handles installment selection change
* Updates order amounts and payment line with interest
* @param {Event} ev Change event from select element
*/
onInstallmentChange(ev) {
const installments = ev.target.value;
const option = this.state.installmentOptions.find(
opt => opt.installments.toString() === installments
);
if (!option) return;
try {
// Update state with selected installment data
Object.assign(this.state, {
selectedInstallments: option.installments,
installmentAmount: parseFloat(option.amount),
totalWithInterest: parseFloat(option.total),
interestRate: parseFloat(option.rate)
});
if (typeof option.coefficient === 'number' && !isNaN(option.coefficient)) {
this._updateOrderLinesWithInterest(option.coefficient);
const totalWithInterest = parseFloat(this.state.totalWithInterest);
if (!isNaN(totalWithInterest) && this.selectedPaymentLine) {
this.selectedPaymentLine.amount = totalWithInterest;
this.selectedPaymentLine.set_payment_status("done");
if (this.screen.numberBuffer) {
this.screen.numberBuffer.set(totalWithInterest.toString());
}
}
}
this._updatePaymentSummary();
this.screen.render();
} catch (error) {
this.showError('Error processing installment change');
}
}
/**
* Updates order lines with interest coefficient
* Recalculates totals and updates UI
* @param {number} coefficient Interest multiplier
*/
_updateOrderLinesWithInterest(coefficient) {
const order = this.currentOrder;
if (!order) return;
const coef = parseFloat(coefficient);
if (isNaN(coef)) return;
order.get_orderlines().forEach(line => {
try {
if (!line.original_price) {
line.original_price = line.get_unit_price();
}
line.set_unit_price(line.original_price * coef);
} catch (error) {
console.error('Error processing line:', error);
}
});
order.recomputeOrderData();
this.state.totalWithInterest = order.amount_total;
this._forcePaymentSummaryUpdate();
}
/**
* Forces payment summary update in UI
* Updates totals, remaining amount and payment lines
*/
_forcePaymentSummaryUpdate() {
if (!this.currentOrder) return;
try {
Object.assign(this.screen.state, {
totalAmount: this.currentOrder.amount_total,
remainingAmount: this.currentOrder.get_due(),
paymentLines: [...this.currentOrder.payment_ids]
});
if (this.screen.numberBuffer) {
this.screen.numberBuffer.set(this.state.totalWithInterest.toString());
}
this.screen.render();
} catch (error) {
this.showError('Error updating payment summary');
}
}
/**
* Updates payment summary with new totals
* Handles payment line amount updates
*/
_updatePaymentSummary() {
if (!this.currentOrder || !this.selectedPaymentLine) return;
const paid = this.currentOrder.get_total_paid();
const remaining = Math.max(this.state.totalWithInterest - paid, 0);
Object.assign(this.screen.state, {
totalAmount: this.state.totalWithInterest,
remainingAmount: remaining,
paymentLines: [...this.currentOrder.payment_ids]
});
this.selectedPaymentLine.set_amount(this.state.totalWithInterest);
this.currentOrder.set_total_with_tax(this.state.totalWithInterest);
this.screen.payment_interface.update_payment_summary({
total: this.state.totalWithInterest,
total_paid: paid,
remaining: remaining
});
this.screen.render();
}
/**
* Updates payment line amount
* Handles amount validation and UI updates
* @param {number|false} amount New amount or false to get from buffer
*/
updatePaymentLine(amount = false) {
this.updateReferences();
if (amount === false) {
amount = this.screen.numberBuffer.getFloat();
}
if (amount === null) {
this._resetPrices();
this.screen.deletePaymentLine(this.screen.selectedPaymentLine.uuid);
return;
}
const maxAmount = this.state.totalWithInterest || this.currentOrder.get_total_with_tax();
if (amount > maxAmount) {
amount = maxAmount;
this.screen.numberBuffer.set(maxAmount.toString());
this._showMaxAmountError();
}
this.screen.selectedPaymentLine.set_amount(amount);
this.screen.render();
}
/**
* Resets prices to original values
* Clears interest calculations
*/
_resetPrices() {
const order = this.currentOrder;
if (!order) return;
order.get_orderlines().forEach(line => {
try {
if (line.original_price) {
line.set_unit_price(line.original_price);
line.setLinePrice();
}
} catch (error) {
console.error('Error resetting price:', error);
}
});
order.recomputeOrderData();
}
Hi there!, after 4 months i managed to make an absolutely FREE module for use with credit cards through the Fiserv gateway service (at the moment it only works for Argentina but it could be improved for other countries).
It is published on github, you can find it as fiserv_gateway.
The ideal is that we can all improve it as a community so that it works in the countries that see it useful.
It still has several points to improve, but it is a good starting point for all those who need a form of payment online and at the POS with debit/credit cards.
Any queries can locate my contact details on my github profile.
Kind regards