How to Hide Payment Methods from Specific Customers in WooCommerce
Every B2B store eventually runs into this: Net-30 customers shouldn't see the credit card option, COD should only show for local customers, and "Invoice on Account" shouldn't be visible to retail shoppers.
WooCommerce has no UI for per-customer payment rules. It does have a filter — woocommerce_available_payment_gateways — which is what we'll use.
The filter
add_filter( 'woocommerce_available_payment_gateways', function ( $gateways ) {
// $gateways is an array of gateway_id => WC_Payment_Gateway object
return $gateways;
});
The array is keyed by gateway ID. Common ones:
stripe— Stripe credit cardpaypal— PayPalcod— Cash on deliverycheque— Checkbacs— Bank transfer / wirewoocommerce_payments— WooPayments
Plugins add their own. Find yours with:
add_filter( 'woocommerce_available_payment_gateways', function ( $gateways ) {
error_log( print_r( array_keys( $gateways ), true ) );
return $gateways;
});
Pattern 1: Hide a gateway from a role
"Wholesale customers have Net 30 terms. They shouldn't see Stripe."
add_filter( 'woocommerce_available_payment_gateways', function ( $gateways ) {
if ( is_admin() ) return $gateways; // don't filter in admin order creation
$user = wp_get_current_user();
$is_wholesale = in_array( 'wholesale_customer', (array) $user->roles, true );
if ( $is_wholesale ) {
unset( $gateways['stripe'] );
unset( $gateways['paypal'] );
}
return $gateways;
});
Pattern 2: Show a gateway only to specific roles
"'Invoice on Account' (gateway id invoice_account) is only for approved wholesale customers."
add_filter( 'woocommerce_available_payment_gateways', function ( $gateways ) {
if ( is_admin() ) return $gateways;
$user = wp_get_current_user();
$is_wholesale = in_array( 'wholesale_customer', (array) $user->roles, true );
if ( ! $is_wholesale && isset( $gateways['invoice_account'] ) ) {
unset( $gateways['invoice_account'] );
}
return $gateways;
});
Pattern 3: Per-user overrides
"Normally wholesale customers can't use credit card, but this specific customer has been approved for it."
Set a user meta flag, then check it:
add_filter( 'woocommerce_available_payment_gateways', function ( $gateways ) {
if ( is_admin() ) return $gateways;
$user = wp_get_current_user();
$is_wholesale = in_array( 'wholesale_customer', (array) $user->roles, true );
$can_use_card = get_user_meta( $user->ID, '_allow_card_payment', true ) === 'yes';
if ( $is_wholesale && ! $can_use_card ) {
unset( $gateways['stripe'] );
}
return $gateways;
});
Now you have a per-user override that your admin can flip via a user edit screen custom field.
Pattern 4: Context-dependent (cart contents)
"Cash on delivery only if the cart total is under $500 and the destination is local."
add_filter( 'woocommerce_available_payment_gateways', function ( $gateways ) {
if ( is_admin() ) return $gateways;
if ( ! WC()->cart ) return $gateways;
$total = WC()->cart->get_total( 'raw' );
$country = WC()->customer->get_shipping_country();
if ( $total > 500 || $country !== 'US' ) {
unset( $gateways['cod'] );
}
return $gateways;
});
Context-dependent filtering is where you'll start hitting the Blocks caching issues. See the Blocks section below.
The is_admin() guard matters
If you're creating orders from the WordPress admin ("Add order" screen), the filter runs there too. Stripping gateways in admin means you can't process manual orders. Always guard:
if ( is_admin() ) return $gateways;
Unless you want to restrict admin-side too, which is rare.
Cart & Checkout Blocks caveats
The Blocks checkout aggressively caches the list of available payment methods on the client. Your filter still runs server-side — but the client might show a stale list until the cart updates.
In practice:
- First page load: your filter runs correctly, client renders the right list.
- User navigates away and back: client may render from cache.
- Cart contents change: triggers a fresh fetch, your filter re-runs.
If your logic depends on the user's role (static per-session), caching is fine. If it depends on cart contents (dynamic), the user might see a stale option briefly.
Worst case: they click the option, the server re-validates, and rejects. This isn't ideal UX but it's not unsafe — the server still enforces the rule.
Blocks also has a concept of "registered payment methods" vs. "available payment methods". Your server-side filter removes things from available. If you want a gateway to never be registered at all for a specific role, that's harder — you'd need a JS integration on the client. For most cases, server-side filtering is enough.
Users with multiple roles
Same rule as shipping: a user can have customer and wholesale_customer roles simultaneously. Define precedence:
$roles = (array) wp_get_current_user()->roles;
$is_wholesale = in_array( 'wholesale_customer', $roles, true );
$is_retail = in_array( 'customer', $roles, true ) && ! $is_wholesale;
In most B2B setups, the most-privileged role wins.
Guest users
wp_get_current_user()->ID === 0 for guests. They have no roles (well, technically they have the empty array). Handle them explicitly if you have guest-specific rules:
$user = wp_get_current_user();
if ( 0 === $user->ID ) {
// Guest checkout
// Usually: only allow Stripe and PayPal; hide invoice/account options
foreach ( [ 'invoice_account', 'cheque', 'bacs' ] as $gw ) {
unset( $gateways[ $gw ] );
}
}
Common mistakes
Removing a gateway your site only has one of. If you filter out Stripe and your store only has Stripe, the customer sees "no payment methods available" — and can't check out. Always make sure at least one gateway remains for every valid user state.
Using labels to match gateways. Labels are translated. If you write if ( $rate->get_title() === 'Credit Card' ), your rule breaks on localized sites. Match on gateway ID (stripe) instead.
Forgetting to re-run the filter on cart changes. woocommerce_available_payment_gateways re-runs naturally on cart updates. You don't need to trigger it manually. But if you're relying on user meta that changes mid-session, update the meta and also bust any custom caches.
Filtering in admin by mistake. See the is_admin() guard. Without it, admin order creation loses payment options.
When to use a plugin
If you're adding more than a handful of rules, or if the person maintaining them isn't a developer, the code approach stops scaling.
Role Based Methods is our plugin for this. It handles role-based payment gateway visibility (and shipping, see the shipping guide) from a single admin screen. Multi-role, guest handling, per-user overrides — all wired up without is_admin() foot-guns or label-matching fragility.
The short version
woocommerce_available_payment_gatewaysis the filter- Match on gateway ID, never on translated labels
- Always guard with
is_admin()unless you explicitly want to restrict backend order creation - Handle multiple roles, guests, and cart-context dependencies explicitly
- Test with every role and a guest before you ship