Hiding Payment Methods with woocommerce_available_payment_gateways
The filter decides which gateways a shopper sees and which ones the checkout accepts on submit. A condition that reads differently at those two moments produces an Invalid payment method error instead of a hidden option.
woocommerce_available_payment_gateways receives the array of gateways that passed their own availability checks, and whatever you remove from it disappears from checkout — and is refused if someone submits it anyway. It is applied in WC_Payment_Gateways::get_available_payment_gateways(), with one argument: the array of gateway objects keyed by gateway ID, such as cod, bacs or stripe. Anything you return that is not a WC_Payment_Gateway object is filtered out afterwards, so returning junk fails quietly rather than loudly.
The working shape
add_filter( 'woocommerce_available_payment_gateways', 'ti_limit_cod' );
function ti_limit_cod( $gateways ) {
if ( is_admin() && ! wp_doing_ajax() ) {
return $gateways;
}
if ( ! isset( $gateways['cod'] ) || ! WC()->cart ) {
return $gateways;
}
$total = (float) WC()->cart->get_total( 'edit' );
$country = WC()->customer ? WC()->customer->get_shipping_country() : '';
if ( $total > 500 || 'GB' !== $country ) {
unset( $gateways['cod'] );
}
return $gateways;
}
Find a gateway's ID in the URL of its settings screen (section=cod) or by dumping array_keys( $gateways ) once. Plugins choose their own IDs, and a payment plugin that offers cards and wallets often registers several.
The filter runs in more places than checkout
The same method — and therefore your callback — is called from:
- the classic and block checkout, to render the list;
- checkout validation, when the order is submitted;
- the pay-for-order page for orders created in the admin or left pending;
- the add payment method page in My Account;
- the payment method icons block and some admin screens, including the payments settings, which uses the list to decide whether the store has any enabled gateways.
That list is why the guard at the top exists. A callback that removes everything unless a cart condition is met will tell the payments settings screen the store has no working gateways. Returning the array untouched outside the storefront avoids that, while wp_doing_ajax() keeps storefront requests sent through admin-ajax.php inside the condition. The classic checkout's own ?wc-ajax= refresh is not an admin request, so it is never skipped.
The trap: render and submit must agree
When the order is placed, the classic checkout's validate_checkout() calls get_available_payment_gateways() again, and if the method the shopper chose is not in the result it adds the error "Invalid payment method." The Store API does the same check in the block checkout and returns woocommerce_rest_checkout_payment_method_disabled.
So a condition that depends on something which changes between rendering and submitting turns a hidden option into a refused order. Typical culprits:
- Session values read before they are updated. The chosen shipping method lives in
WC()->session->get( 'chosen_shipping_methods' ). It is updated when the checkout refreshes, so a rule like "no COD for express delivery" — where the rates themselves come from woocommerce_package_rates — works if the checkout refreshes on shipping change — and fails on themes or optimisation plugins that suppress the refresh. - Request data. Reading
$_POST['billing_country']works on the submit request and is empty on the page load, so the gateway is shown and then refused, or the reverse. - Time or stock. "Hide bank transfer after 5 pm" is hidden on one request and valid on the next.
Prefer conditions on the customer object and the cart, which WooCommerce keeps in step with the form, and check that the checkout actually refreshes when the inputs your rule depends on change.
The pay-for-order page has an empty cart
On /checkout/order-pay/123/?pay_for_order=true the shopper is paying an existing order, and WC()->cart is their current cart — usually empty, or containing something unrelated. A rule based on the cart total hides gateways for the wrong reason there. WooCommerce sets the customer's billing country, state and postcode from the order before building the gateway list, but nothing copies the order's total into the cart.
if ( is_wc_endpoint_url( 'order-pay' ) ) {
$order = wc_get_order( absint( get_query_var( 'order-pay' ) ) );
$total = $order ? (float) $order->get_total() : 0;
} else {
$total = (float) WC()->cart->get_total( 'edit' );
}
Admin-created orders are the usual reason this page matters: the merchant sends a payment link, and the customer finds no way to pay.
Hiding is not disabling
Removing a gateway here only affects which methods are offered. Webhooks and return URLs from that gateway still work for orders already placed with it, which is what you want — an order paid by card yesterday must still receive its refund notification today. If a method should stop entirely, disable it in WooCommerce › Settings › Payments instead.
A gateway that is missing for everyone, before any of your code runs, is failing its own is_available() check — usually a minimum amount, an unsupported currency, or test mode settings. Debug that in the gateway's settings rather than with this filter; the payments overview in POS and payments covers what each type of gateway requires.
Block checkout
The Store API includes the filtered gateway IDs in its cart response, and the block checkout renders methods from that list. It is recalculated whenever the block checkout re-fetches the cart, which it does after address and shipping changes, so rules based on the customer's address update without custom JavaScript. A rule based on a classic checkout field that the block checkout does not send will never see a value.
Common mistakes
- Returning nothing in an early branch, which removes every gateway.
- Unsetting gateways in the admin, which hides them from screens that only wanted a count.
- Using cart totals on the pay-for-order page.
- Hiding a method with CSS instead, which leaves it in the form and still submittable.
If the checkout fails for reasons unrelated to payment methods, checkout not working covers the wider set of causes.
Verify
- Load checkout with a cart under and over the threshold; the gateway appears and disappears.
- Change the shipping country, wait for the refresh, and place an order with the method still shown.
- Create an order in the admin, open its payment link while logged out, and confirm the right methods are offered.
- Open WooCommerce › Settings › Payments and confirm no warning about missing gateways.
Frequently asked
- Checkout validation calls the same filter and rejects any method that is not in the list it returns. If your condition evaluates differently on submit than when the form was rendered, the method the shopper chose is no longer in the list and the order is refused.
- The array is keyed by gateway ID and the checkout renders it in order, so reordering the array changes the display order for that request. For a permanent order, the drag-and-drop ordering in the payments settings is simpler and is respected by every screen.
- Yes. The Store API includes the filtered list in the cart response and validates the chosen method against it when the order is placed, so the same callback controls both checkouts.