Tutorials··7 min read

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 card
  • paypal — PayPal
  • cod — Cash on delivery
  • cheque — Check
  • bacs — Bank transfer / wire
  • woocommerce_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_gateways is 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