Search code examples
phpwordpresswoocommercepayment-gatewayhook-woocommerce

woocommerce_before_pay_action Not Consistently Saving Order Meta Data


I have a very simple custom gateway plugin intended to store some passive data on Woocommerce orders, no actual processing is taking place. It uses the woocommerce_before_pay_action hook to capture the meta data from the custom woocommerce_form_field payment fields via $_POST and store them in the order meta like this:

function save_cash_custom_fields( $order ){
//If this order had been paid for with cash
if( $order->get_payment_method() === 'ms_cash' ){
    //If the cash_paid_by field is set
    if( isset( $_POST['cash_paid_by'] ) ){
        $order->update_meta_data( 'cash_paid_by', sanitize_text_field( $_POST['cash_paid_by'] ) );
    }
    //If the cash_received_by field is set
    if( isset( $_POST['cash_received_by'] ) ){
        $order->update_meta_data( 'cash_received_by', sanitize_text_field( $_POST['cash_received_by'] ) );
    }
}
add_action( 'woocommerce_before_pay_action', 'save_cash_custom_fields', 10, 1 );

And it works. Sometimes. It's being really inconsistent, sometimes it captures the data and other times it doesn't, there are no errors showing and the fields aren't even being created in the database the times it doesn't work.

I've stepped through the function and output every step of the way, and it's always there. No errors are being logged anywhere, and the transaction is completing correctly, just this meta data is not being stored.

Can anyone suggest why this is being intermittent? Is the hook not the best option for this?


Solution

  • To add/update order metadata consistently, use the save() method at the end inside your function.

    Also, you don't really need to check the payment method, but if those 2 custom form fields exists and are not empty.

    Try the following revised code:

    add_action( 'woocommerce_before_pay_action', 'save_cash_custom_fields', 10, 1 );
    function save_cash_custom_fields( $order ){
        $use_save = false; // Initializing
    
        // If the cash_paid_by field is set and not empty
        if( isset($_POST['cash_paid_by']) && !empty($_POST['cash_paid_by']) ){
            $order->update_meta_data('cash_paid_by', sanitize_text_field($_POST['cash_paid_by']));
            $use_save = true;
        }
        // If the cash_received_by field is set
        if( isset($_POST['cash_received_by']) && !empty($_POST['cash_received_by']) ){
            $order->update_meta_data('cash_received_by', sanitize_text_field($_POST['cash_received_by']));
            $use_save = true;
        }
        // If metadata has been added/updated, force saving order changes to the database.
        if( $use_save ){
            $order->save();
        }
    }
    

    Code goes in functions.php file of your child theme (or in a plugin). It should now work consistently.

    Note that the hook woocommerce_before_pay_action, located in WC_Form_Handler pay_action() method, works only on "order-pay" endpoint, so not on checkout.