Somewhere in the plumbing of every e-commerce transaction processed through Odoo sits a relationship between payment methods and payment providers. Credit card, bank transfer, wallet — each method connects to one or more providers that can actually process it. For years, that connection was modeled as a Many2Many relationship: one payment method could belong to multiple providers simultaneously, and providers shared methods between them.
That architectural decision is being reversed. Payment methods now belong to exactly one provider through a Many2One relationship, and the entire API surface that developers use to query, filter, and resolve payment methods has been rewritten to match.
Why the Old Model Broke Down
The Many2Many approach seemed logical on paper. A “Credit Card” payment method should be usable by Stripe, Adyen, Authorize.net, and any other card processor installed on the system. Why duplicate the method record when you can share it?
The problem showed up at scale. When multiple providers share the same method record, any provider-specific behavior — supported currencies, country restrictions, fee structures, display ordering — has to live somewhere else. Either the method record gets cluttered with provider-specific overrides, or the logic scatters across provider modules in ways that make debugging a nightmare. Worse, when a site admin disables one provider, the shared method’s behavior might change for all other providers using it.
The new model cuts through this by saying: each provider defines its own payment methods in its own data files. If Stripe and Adyen both support credit cards, they each get their own “Credit Card” method record with aprovider_id pointing back to the provider that owns it. No sharing, no ambiguity.
The API Changes in Detail
The old _get_compatible_payment_methods()method on the payment method model is gone. In its place, the provider model gains several new methods that shift the query logic from “which methods exist?” to “which methods does this provider offer?”
_find_available_payment_methods() now lives on the provider and returns only the methods that specific provider supports and has enabled. _find_available_tokens()moves from the token model to the provider as well, keeping the query context consistent.
On the method model itself, three new helpers appear._get_from_code() looks up a payment method by its code string. _deduplicate_by_code() handles the transition period where multiple providers might define methods with the same code, collapsing them into unique entries for display purposes. And _sort_by_display_order() lets modules control the visual ordering of methods in the checkout flow without relying on database record IDs.
For controller developers, a new_prepare_payment_form_values() method standardizes how payment form data is assembled before rendering, replacing scattered logic that each provider previously implemented independently.
Provider Modules Now Own Their Method Definitions
The most consequential part of this change isn’t the API — it’s the data architecture. Each payment provider module (Stripe, Adyen, PayPal, etc.) now includes its own data files defining the payment methods it supports. Previously, a central set of method records existed, and provider modules added themselves to the Many2Many relationship during installation.
With provider-owned definitions, installing a new payment provider automatically creates its specific method records. Uninstalling removes them cleanly. There’s no orphaned method record hanging around after a provider is removed, and no risk of one provider’s installation accidentally modifying another provider’s method configuration.
The Feature Support Computation
A new _compute_feature_support_fields() method on the provider model centralizes how providers declare their capabilities. Instead of checking individual boolean fields scattered across the provider record, modules now compute a set of feature flags that other parts of the system can query consistently. This covers things like tokenization support, manual capture, express checkout, and refund capabilities.
For developers building custom payment providers, this is the method to override. Declare what your provider supports, and the checkout flow adapts automatically.
What Developers Need to Do
Any custom module that calls_get_compatible_payment_methods()needs to be updated. The method no longer exists. The replacement depends on context: if you’re querying from a provider context, use_find_available_payment_methods(). If you need to look up a method by its code, use_get_from_code().
Modules that define payment methods in data files need to add the provider_id field pointing to the provider record. The old provider_ids Many2Many field will no longer work.
The migration is mechanical, but it’s not optional. Any module touching the payment layer that doesn’t adapt to the Many2One relationship will break when this change ships in the 19.4 release cycle. For businesses running custom payment integrations, this is the kind of change that justifies checking your custom code against the new API before upgrading.