• Resolved amsterdamquality

    (@amsterdamquality)


    Hi ! I’ve been facing an issue with order status transitions after successful payments.

    Normally, all our products are physical, so the expected flow is : Pending payment → Processing → Completed (after manual action)

    However, on some recent orders, the order status skips “Processing” entirely and jumps straight from Pending to Completed immediately after the payment is received. This doesn’t happen on every order, most behave correctly, so we cannot consistently reproduce on demand since it seems to happen randomly.

    To investigate, I installed a small trace that logs the plugin call stack at the exact moment WooCommerce changes the order status and also records the value returned. For the problematic orders , the trace shows the same Trust Payments handlers in the stack, but the final status returned is Completed, not Processing.

    I also contacted Trust Payments. They confirmed their plugin indeed triggers the change, but that the final status is determined by WooCommerce itself.

    What I need clarification on :

    1. Under what conditions can WooCommerce set an order to Completed automatically after payment, even for physical products ?
    2. Is applying a permanent filter like below considered safe and recommended to prevent this
    add_filter('woocommerce_payment_complete_order_status', function($status, $order_id, $order){
    return 'processing';
    }, 9999, 3);

    Are there any side effects to be aware of if we apply this filter globally (e.g., refunds, subscriptions, pre-orders) ?

    Thanks in advance for your help.

    Best regards

    The page I need help with: [log in to see the link]

Viewing 10 replies - 1 through 10 (of 10 total)
  • Plugin Support LovingBro (woo-hc)

    (@lovingbro)

    Hi @amsterdamquality,

    I hear you; when physical goods orders jump straight to Completed, it breaks fulfillment workflows and is hard to reproduce. Thanks for digging in with a trace and confirming that Trust Payments is in the call stack.

    When can WooCommerce auto-complete an order (skip “Processing”)? WooCommerce itself will set Completed right after payment only when the order does not need processing, i.e., it contains only items that are both virtual and/or downloadable, so no shipping/handling is required. In practice, “physical” orders should land in Processing unless:

    • One or more affected products were accidentally marked Virtual or Downloadable, or
    • A gateway/extension (e.g., Trust Payments) changes the status via hooks/filters on payment success, or
    • A snippet/plugin that “auto-completes orders” is active, or
    • A race condition/webhook handler runs logic that decides the order needs no further action.

    About your proposed filter

    Your global override will work, but it’s safer to change the status only when the order truly needs processing (has shippable items). That avoids breaking legitimate digital orders, renewals, etc.

    add_filter( 'woocommerce_payment_complete_order_status', function ( $status, $order_id, $order ) {
    	if ( $order instanceof WC_Order ) {
    		// If the order contains anything that requires fulfillment,
    		// keep it in Processing after payment.
    		if ( $order->needs_processing() ) {
    			return 'processing';
    		}
    	}
    	return $status; // Leave digital-only orders as-is (Completed).
    }, 9999, 3 );

    Why this approach?

    • Safer for virtual/downloadable: Those can still auto-complete.
    • Fewer side effects: Reduces risk with Subscriptions/Pre-Orders/Bookings or gift-card/digital-license flows that expect “Completed.”
    • Keeps refunds/captures intact: Status choice doesn’t block refunds, but some automations (emails, downloads, LMS enrolments) key off Completed; you won’t silently break them.

    What to double-check right now

    1. In the specific “jumped” orders, open each line item and confirm the product isn’t flagged Virtual/Downloadable.
    2. In your codebase, search for woocommerce_payment_complete_order_status, order_status_completed, payment_complete, or any “auto-complete” snippets/plugins.
    3. In Trust Payments settings, look for options like “auto-complete on successful payment/capture.”
    4. In the order notes, confirm which actor changed the status (gateway callback/webhook vs. something else).
    5. Review Action Scheduler for jobs around that timestamp that may have flipped status.

    If you’d like, share an order notes screenshot of one affected order plus your System Status Report (WooCommerce → Status → “Copy for support” via Pastebin) and we can sanity-check for anything that might influence status transitions.

    Thread Starter amsterdamquality

    (@amsterdamquality)

    Hi @lovingbro ,

    Thanks a lot for the clear and detailed explanation.

    Can you just confirm that If I implement the conditional filter you suggested this will reliably prevent physical orders from skipping “Processing” and jumping straight to “Completed” ?

    If so, I’ll go ahead with this fix and mark as resolved.

    Hi @amsterdamquality,

    Thank you for getting back. I’ve reviewed the code, and it looks correct and valid. It ensures that orders requiring physical fulfillment remain in Processing status after payment instead of being automatically marked as Completed.

    You can safely implement it using the Code Snippets plugin so it’s easier to manage or disable later if needed. After applying the code, please monitor the behavior and share your observations with us if the issue is resolved or still persists.

    Let me know if you have any further questions.

    Plugin Support thelmachido a11n

    (@thelmachido)

    It’s been a while since we heard back from you for this reason we are closing this thread. 

    If WooCommerce has been useful for your store and you appreciate the support you’ve received, we’d truly appreciate it if you could leave us a quick review here: 

     https://wordpress.org/support/plugin/woocommerce/reviews/#new-post

    Feel free to open a new forum topic if you run into any other problem. 

    Thread Starter amsterdamquality

    (@amsterdamquality)

    Hi again,

    Quick follow-up on my topic (previously marked as resolved), as the issue is still occurring.

    Since your last reply :

    • I implemented the conditional guard you recommended on woocommerce_payment_complete_order_status.
    • No incidents for a while, then I added a trace snippet that logs:
      • when woocommerce_payment_complete_order_status runs,
      • when woocommerce_order_status_changed runs,
      • the execution context (REST/AJAX/FRONT + route),
      • and the plugin call stack.
        The trace does not modify the status.

    With this trace active, I’ve now captured two new cases where physical orders still skip “Processing” and go directly Pending → Completed.

    Key details:

    • Context is always REST on /wp-json/st/v2/response (Trust Payments webhook).
    • woocommerce_payment_complete_order_status is called with $status = 'completed' even though the order needs processing.
    • No auto-complete snippet or plugin, and all products are physical.
    • Trust Payments confirms they trigger payment completion, but insist the final status is chosen by WooCommerce.

    Could you please confirm :

    1. If core WooCommerce has any scenario where a physical order could be treated as “does not need processing” specifically in this REST/webhook context?
    2. Whether permanently applying the conditional guard below is still the recommended approach to force physical orders to stay in processing, even when triggered via a gateway webhook?
    add_filter( 'woocommerce_payment_complete_order_status', function ( $status, $order_id, $order ) {
        if ( $order instanceof WC_Order && $order->needs_processing() ) {
            return 'processing';
        }
        return $status;
    }, 9999, 3 );
    

    Best regards,

    Hi @amsterdamquality,

    Thank you for getting back and for the updated details.

    It looks like what you’re experiencing is caused by how your payment gateway is completing the order. WooCommerce itself doesn’t normally skip the Processing status for physical products. Instead, what usually happens is that the payment gateway (or another plugin tied into the payment flow) passes a status of “completed” into payment_complete() before WooCommerce checks whether the order actually needs processing.

    Your current safeguard using the woocommerce_payment_complete_order_status filter is the correct long-term fix. Because it runs at a very high priority, it ensures WooCommerce properly checks needs_processing() and switches the order back to Processing whenever an order contains physical items. This prevents the gateway from forcing an incorrect status.

    If you’d like to confirm exactly what the gateway is passing during webhook calls, you can add temporary logging to track when payment_complete() is triggered and what parameters it’s receiving. That will show whether it’s coming directly from the gateway or another plugin in the payment chain. You can use the code below:

    add_action( 'woocommerce_payment_complete', function( $order_id ) {
    $order = wc_get_order( $order_id );
    error_log( sprintf(
    'payment_complete() called | Order #%d | Needs Processing: %s | Current Status: %s | Context: %s',
    $order_id,
    $order->needs_processing() ? 'YES' : 'NO',
    $order->get_status(),
    defined('REST_REQUEST') && REST_REQUEST ? 'REST' : 'OTHER'
    ));
    }, 1 );

    Thread Starter amsterdamquality

    (@amsterdamquality)

    Hi Moses,

    Thanks for the clear breakdown, it matches what I’m seeing on the ground. I’ve already added the temporary logg you suggested, I can confirm two things with the latest affected order :

    1) The status decision happens inside the Trust Payments REST webhook context.
    On order 9569, the flip pending → completed, and the execution of woocommerce_payment_complete_order_status, all happen during the webhook POST to /wp-json/st/v2/response. Trust plugin files are in the call stack at the exact moment the status is applied.

    2) New clue: at that exact webhook time, WooCommerce still sees zero line items in standard line-item hooks.
    My trace on woocommerce_checkout_create_order_line_item produced no captured items for that order (log line: “no lines captured at creation hook — cart empty or bypass”), while the order is obviously a physical cart a few seconds later. So the gateway is calling payment_complete() while the order is still “not fully populated” from Woo’s perspective.

    Best regards,
    Pierre

    Plugin Support LovingBro (woo-hc)

    (@lovingbro)

    Hi @amsterdamquality, I can see how helpful your latest findings are, especially identifying that the webhook is firing while WooCommerce still sees an empty set of line items. That explains why needs_processing is returning a misleading result at that specific moment, and it also aligns with how some gateways finalize orders before WooCommerce completes population of core order data. You have done excellent groundwork here.

    To your question, WooCommerce core does not treat physical orders as complete in any REST or webhook context on its own. What you are seeing happens only when an external handler calls payment_complete too early in the flow, before WooCommerce has created the line items that would correctly signal fulfillment is required.

    Your conditional guard on woocommerce_payment_complete_order_status remains the recommended mitigation. It runs late enough in the process to override any premature completed status coming from the gateway, even inside a webhook callback. This ensures that once WooCommerce has line items and context, the order will stay in processing as expected.

    If the Trust Payments webhook continues to fire before WooCommerce finishes building the order, the next step would be checking whether their plugin provides a setting or hook to delay payment_complete until after the order is fully populated. You can also continue extending your logging to confirm whether payment_complete always occurs before the line item save actions. This will help the gateway dev team pinpoint their timing issue.

    Feel free to keep sharing any new findings, we here to help you get to the bottom of this.

    Thread Starter amsterdamquality

    (@amsterdamquality)

    Hi @lovingbro ,

    Thanks a lot for your answer.

    Your explanation matches what I though. The Trust payments webhook is calling payment_complete() too early, before line items exist, which may makes needs_processing() temporarily return false and leads to an incorrect “completed” statut.

    I’ll keep the guard in place as mitigation, and I’m now escalating to Trust Payments with this timing/race-condition evidence.

    I’ll share any new findings if their side reveals more about the webhook flow.

    Best regards

    Plugin Support LovingBro (woo-hc)

    (@lovingbro)

    Hi @amsterdamquality, I’m really glad we could point you in the right direction and that the deeper investigation on your end helped clarify what is happening in that webhook flow. If you uncover any new findings from Trust Payments or notice anything else unusual in how the order data is being populated, feel free to reach out, I’m here to help keep things moving smoothly for you.

    If WooCommerce has been helpful for your store and you’ve appreciated the support so far, a quick review would mean a lot to us https://wordpress.org/support/plugin/woocommerce/reviews/#new-post

    Wishing you continued smooth orders ahead.

Viewing 10 replies - 1 through 10 (of 10 total)

You must be logged in to reply to this topic.