This DIY example restricts selected WooCommerce coupon codes by checking the logged-in customer’s order history. It is intended only for stores that require customer accounts. It does not identify guest orders by billing email.
That limitation matters. A short snippet can be useful for a controlled account-only workflow, but it is not a complete new-customer identity system. If the store accepts guest checkout or needs a per-coupon setting, compare the example with First Order Coupon Guard before using it.
Use this only when: checkout requires an account, the chosen order statuses match the promotion policy, and a developer can test and maintain the code. Add it as a small plugin or through a snippet manager, never directly to a parent theme.
The logged-in first-order coupon snippet
<?php
/**
* Restrict selected coupon codes to logged-in customers
* without an earlier qualifying order on the same account.
*/
add_filter(
'woocommerce_coupon_is_valid',
static function ( $valid, $coupon, $discounts ) {
if ( ! $valid || ! $coupon instanceof WC_Coupon ) {
return (bool) $valid;
}
// Change these to the coupon codes you want to restrict.
$restricted_codes = array_map(
'wc_format_coupon_code',
array( 'welcome10' )
);
$coupon_code = wc_format_coupon_code( $coupon->get_code() );
if ( ! in_array( $coupon_code, $restricted_codes, true ) ) {
return true;
}
$customer_id = get_current_user_id();
if ( $customer_id < 1 ) {
throw new Exception(
'Please log in to use this first-order coupon.',
9101
);
}
$order_ids = wc_get_orders(
array(
'customer_id' => $customer_id,
'status' => array(
'wc-processing',
'wc-completed',
'wc-on-hold',
'wc-refunded',
),
'type' => 'shop_order',
'limit' => 1,
'return' => 'ids',
)
);
if ( ! is_array( $order_ids ) ) {
throw new Exception(
'We could not verify first-order eligibility. Please try again.',
9103
);
}
if ( ! empty( $order_ids ) ) {
throw new Exception(
'This coupon is available to first-time customers only.',
9102
);
}
return true;
},
10,
3
);
Set the coupon code and order policy
Replace welcome10 with the actual coupon code. The example counts processing, completed, on-hold and refunded orders. Pending, failed and cancelled orders do not make the account ineligible. If that policy does not match the promotion, change the list before testing rather than after customers begin using it.
The order lookup uses WooCommerce’s order API, not a direct query against legacy WordPress post metadata. That is the correct foundation for stores using High-Performance Order Storage, but the snippet still has deliberate business limitations.
Limits you must accept
- Guests are blocked rather than checked by billing email.
- A previous guest order is not automatically connected to a later customer account.
- A person can create another account and appear new to this account-only check.
- Refunded orders still count because the welcome discount was already used.
- The coupon list and qualifying statuses live in PHP, not in the WooCommerce coupon editor.
- Two simultaneous checkouts can both finish the history check before either new order exists. This example does not reserve eligibility atomically.
A practical test matrix
- Apply an unrelated coupon and confirm that its behavior does not change.
- Apply the restricted code while logged out and confirm that WooCommerce shows the login requirement.
- Use a new account with no orders and confirm that the coupon remains valid through totals calculation and order placement.
- Repeat with an account that owns one processing, completed, on-hold or refunded order and confirm rejection.
- Test the actual Cart and Checkout configuration. A server-side filter can run in both paths, but the surrounding messages and recalculation still need real browser verification.
Snippet versus First Order Coupon Guard
| DIY snippet | First Order Coupon Guard |
|---|---|
| Logged-in customer ID only | Customer ID, billing email and account email |
| Guests are blocked | Guests checked after a valid billing email is available |
| Coupon codes edited in PHP | Per-coupon checkbox under Usage restriction |
| Four fixed statuses | WooCommerce paid statuses plus on-hold and refunded |
| Merchant-maintained code | Classic and Blocks validation with HPOS and legacy order-storage support |
Neither approach claims that one person can always be recognized across unrelated identities, and neither promises an atomic reservation between simultaneous checkout requests. The difference is operational coverage: the plugin adds email-aware guest handling, a coupon-level control and a maintained integration without pretending to solve identity problems outside its scope.
Frequently asked questions
Why do refunded orders count?
A refund changes the commercial outcome but does not erase the fact that the account already placed an order and used the first-order opportunity. Change the status policy if the promotion has a different rule.
Can I use this with guest checkout?
Not as written. The snippet deliberately blocks logged-out customers because it has no billing-email identity available in every validation context. Use an implementation designed and tested for guest checkout instead of silently treating every guest as new.
Does a usage limit of one mean first order only?
No. A per-user coupon usage limit controls how often that coupon was used. A first-order rule asks whether the customer has any qualifying previous order, including an order that used a different coupon.
For guest and account-aware production validation, see First Order Coupon Guard and its documented order policy. More DIY examples are listed under WooCommerce snippets, while broader implementation guidance lives in WooCommerce guides.
