Back to Blog

November 1, 2026

Odoo 20 Fixes a Stripe Tipping Bug That Was Silently Underbilling Restaurant Customers

A refactoring oversight in Odoo’s Point of Sale module caused Stripe to capture only the pre-tip authorization amount, meaning every tip added after payment was never actually charged. The fix restores kwargs forwarding and closes the gap for both POS terminals and self-order kiosks.

Diagram showing the Odoo POS Stripe payment flow from authorization through tip entry to final capture, highlighting the bug where tip amounts were dropped during the capture step

Here’s a scenario that would make any restaurant owner’s stomach drop: a customer finishes dinner, pays $48.50 on their card, adds a $7.50 tip on the screen, taps confirm, and walks out. The receipt says $56.00. The POS says $56.00. But Stripe? Stripe only charged $48.50. The tip — every single tip, on every single tipped transaction — vanished into thin air.

That’s exactly what was happening in Odoo’s Point of Sale module for an undetermined number of businesses running Stripe as their payment terminal. The bug wasn’t dramatic. There were no error messages, no failed transactions, no red warnings in the dashboard. Everything looked fine on the surface. But underneath, a quiet refactoring change had broken the plumbing that tells Stripe how much money to actually collect.

The Refactor That Ate Your Tips

To understand how this happened, you need to know how Stripe processes payments in a tipping-enabled environment. When a customer taps their card at a POS terminal, Stripe doesn’t immediately charge the full amount. Instead, it places a hold — an authorization — for the pre-tip total. The actual charge happens later, in a separate step called “capture,” which should include the tip. This two-step dance is standard in the payments industry. It’s how every sit-down restaurant with card payments works.

Odoo’s POS module handled this through a function calledcallPaymentMethod, which acted as the bridge between the POS interface and the Stripe backend. When it was time to capture a payment, the system would pass along the final amount (including the tip) and the currency rounding rules as keyword arguments. Stripe would then capture that exact amount from the cardholder.

Then someone refactored callPaymentMethod. The change was well-intentioned — it cleaned up how the function routed calls to different payment providers. But in the process, the function was rewritten to only pass positional arguments. The keyword arguments — the ones carrying the final capture amount and the currency rounding precision — were silently dropped.

The result: Stripe received a capture request with no amount override. When Stripe gets a capture request without a specified amount, it captures the originally authorized amount. Which is the pre-tip total. Every time.

Why Nobody Noticed

This is the kind of bug that survives because all the visible indicators say everything is working. The POS terminal shows the correct total. The receipt prints the right number. The order record in Odoo reflects the tip. The Stripe dashboard shows a successful payment. The only place the discrepancy shows up is in the actual bank settlement — and most restaurant operators aren’t reconciling individual Stripe captures against individual POS transactions on a nightly basis.

If you noticed your Stripe deposits were consistently lower than your POS reports suggested they should be, you might have chalked it up to processing fees, timing differences, or refunds. The gap per transaction was small — the size of a tip — but aggregated across hundreds of transactions per week, it adds up to real money.

The Fix: Four Files, One Missing Keyword

The correction is almost comically small relative to the damage it prevents. The core change adds a kwargs parameter tocallPaymentMethodand forwards it through to the underlying payment call. That’s it. The capture request now carries the final amount again, and Stripe charges what it’s supposed to charge.

But the fix goes further than just patching the immediate problem. The self-order kiosk — the tablet or phone interface where customers place orders directly at their table — had the same issue. Its override of callPaymentMethodwas similarly missing kwargs forwarding, which means businesses using Odoo’s self-service ordering with tipping enabled were also affected. Both paths are now corrected.

Overcapture and the Edge Cases

There’s a nuance here that matters for certain Stripe configurations. “Overcapture” is a Stripe feature that allows you to capture more than the originally authorized amount — which is exactly what happens when a customer adds a tip after authorization. Not all Stripe accounts support overcapture. Some payment methods, some regions, and some account configurations simply don’t allow it.

A related fix addresses this edge case. When overcapture isn’t supported, the system now handles the situation gracefully instead of failing. For payment methods like Interac — Canada’s dominant debit network — the system correctly skips the capture step entirely, since Interac transactions settle immediately at the point of authorization and don’t support deferred capture at all.

The backend also received attention on currency handling. Stripe expects amounts in minor units — cents, not dollars. A $12.50 charge needs to be sent as 1250. The fix ensures that this conversion, governed bystripe_currency_rounding, is correctly applied to the tip-inclusive amount before it reaches Stripe’s API. Getting this wrong could mean rounding errors on every transaction, which is another class of silent financial discrepancy that’s maddening to debug.

The Testing Gap That Let It Through

Perhaps the most telling part of this fix is what came alongside it: comprehensive test coverage that didn’t exist before. The Stripe payment integration in Odoo’s POS module now has dedicated JavaScript tests covering the payment flow, tip addition, and capture sequence, plus Python tests for amount conversion, access control, refunds, and merchant category code lookups. The original code had minimal test coverage for the capture path, which is how a refactor could break it without any automated check raising a flag.

This is a pattern worth noting for any team running business-critical payment integrations: if your payment capture path doesn’t have explicit end-to-end tests that verify the captured amount matches the expected amount including tips, discounts, and adjustments, you’re trusting that future code changes won’t break the money pipeline. That’s a bet most businesses would rather not make.

What to Do If You Were Affected

If you’re running Odoo’s Point of Sale with Stripe and you have tipping enabled, update to the latest version as soon as it’s available. Then take a look at your Stripe dashboard and compare captured amounts against your POS tip reports for the period since you last updated your Odoo instance. If you see a consistent pattern of captured amounts matching pre-tip totals while your POS records show higher amounts, you were affected.

The lost tip amounts are not recoverable through Stripe after the capture window closes — you can’t retroactively capture more than you already did. But knowing the scope of the impact lets you have an honest conversation with your staff about whether tip-sharing calculations need adjustment, and it gives you a concrete reason to expedite the update across all your terminals.

For a bug with no error messages and no obvious symptoms, this one had real teeth. The code change itself is small. The financial implications, for any business that was running the affected version with tipping and Stripe, were not.

Ready to experience Odoo AI?

Join hundreds of teams using DearERP to customize Odoo in minutes, not weeks. Plans start at $29/month.