Hi everyone,
I am developing a custom "Pay Later" feature for Odoo POS (Odoo 19 )
My goal is:
- Add a "Pay Later" button in the POS Payment Screen
- Allow saving POS orders without immediate payment
- Store a Boolean field is_pay_later in pos.order
- Filter Pay Later orders from backend POS Orders
I have implemented:
- Python model inheritance for pos.order
- Frontend Order model patch
- PaymentScreen patch
- XML template inheritance
- Backend form and search view inheritance
# -*- coding: utf-8 -*-
{
'name': 'POS Pay Later',
'version': '19.0.1.0.0',
'category': 'Point of Sale',
'summary': 'Allows POS orders to be paid at a later time.',
'description': """
This module adds a "Pay Later" functionality to the Point of Sale.
- A "Pay Later" button is added to the payment screen.
- Orders can be submitted without immediate payment.
- "Pay Later" orders can be retrieved and paid for at a later time.
""",
'author': 'ashil',
'website': 'https://yourwebsite.com',
'depends': ['point_of_sale'],
'data': [
'views/pos_order_views.xml',
],
'assets': {
'point_of_sale.assets': [
'zl_pos_pay_later/static/src/app/**/*',
],
},
'installable': True,
'application': True,
}
# -*- coding: utf-8 -*-
from odoo import api, fields, models
class PosOrder(models.Model):
_inherit = 'pos.order'
is_pay_later = fields.Boolean(
string='Is Pay Later',
default=False,
help="Indicates if this order was marked to be paid later."
)
@api.model
def _order_fields(self, ui_order):
# Override to extract the 'is_pay_later' flag from the frontend order data
order_fields = super(PosOrder, self)._order_fields(ui_order)
order_fields['is_pay_later'] = ui_order.get('is_pay_later', False)
return order_fields
/** @odoo-module */
import { PaymentScreen } from "@point_of_sale/app/screens/payment_screen/payment_screen";
import { patch } from "@web/core/utils/patch";
import { ConfirmPopup } from "@point_of_sale/app/utils/confirm_popup/confirm_popup";
patch(PaymentScreen.prototype, {
async validateOrder(isForceValidate) {
// If the order is marked as "Pay Later", skip payment validation
if (this.currentOrder.is_pay_later) {
// Bypass payment checks and directly create the order
this.env.services.pos.push_orders(this.currentOrder, { show_error: true });
// Show a confirmation popup
await this.popup.add(ConfirmPopup, {
title: this.env._t("Order Saved"),
body: this.env._t("The order has been saved and will be paid later."),
});
// Go to the next screen (receipt or product screen)
this.pos.showScreen(this.pos.getNextScreen());
return;
}
// Otherwise, proceed with the standard validation
return super.validateOrder(...arguments);
},
async clickPayLater() {
// Set the 'is_pay_later' flag on the current order
this.currentOrder.is_pay_later = true;
// Trigger order validation
await this.validateOrder(false);
},
});
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="zl_pos_pay_later.PaymentScreen" t-inherit="point_of_sale.PaymentScreen" t-inherit-mode="extension" owl="1">
<xpath expr="//div[hasclass('payment-buttons')]" position="inside">
<button class="btn btn-lg btn-secondary" t-on-click="clickPayLater">
Pay Later
</button>
</xpath>
</t>
</templates>
/** @odoo-module */
import { Order } from "@point_of_sale/app/models/order";
import { patch } from "@web/core/utils/patch";
patch(Order.prototype, {
setup() {
super.setup(...arguments);
this.is_pay_later = this.is_pay_later || false;
},
export_as_JSON() {
const json = super.export_as_JSON(...arguments);
json.is_pay_later = this.is_pay_later;
return json;
},
init_from_JSON(json) {
super.init_from_JSON(...arguments);
this.is_pay_later = json.is_pay_later;
},
});
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<!-- Add 'is_pay_later' to POS Order Form View -->
<record id="view_pos_pos_form_inherit_pay_later" model="ir.ui.view">
<field name="name">pos.order.form.inherit.pay.later</field>
<field name="model">pos.order</field>
<field name="inherit_id" ref="point_of_sale.view_pos_pos_form"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='partner_id']" position="after">
<field name="is_pay_later" readonly="1"/>
</xpath>
</field>
</record>
<!-- Add filter for Pay Later orders -->
<record id="view_pos_order_filter_inherit_pay_later" model="ir.ui.view">
<field name="name">pos.order.list.select.inherit.pay.later</field>
<field name="model">pos.order</field>
<field name="inherit_id" ref="point_of_sale.view_pos_order_filter"/>
<field name="arch" type="xml">
<xpath expr="//filter[@name='invoiced']" position="after">
<filter string="Pay Later" name="pay_later" domain="[('is_pay_later', '=', True)]"/>
</xpath>
</field>
</record>
</data>
</odoo>

Could someone please review whether my implementation approach is correct.