All deposit / split / partial payment plugins generate an additional order for paying the balance. Today, we change that.
Hosted by Rodolfo Melogli
Masterclass overview
It may happen you need to enable deposit payments, partial payments, multiple payment methods per order (e.g. half PayPal, half Bank), installments, split an order between more customers, or whatever requires the customer to pay a percentage of the order total and then come back to pay the difference, possibly with a different payment method.
Most, maybe all, plugins out there, complete the current order upon partial payment and create another order for the balance (a “sub-order” i.e. an order linked to the one that was partially paid), so that the customer can complete that one at a given date.
This is because WooCommerce requires a 1:1 relationship between orders and payments, and does not allow you to use multiple “transactions” for the same order.
Speaking of which, welcome to the world of “transactions“. By using this custom post type, we can save each payment on its own (partial, deposit, partial refund, installment), and then associate multiple transactions to a single order, so that the order status and total are dynamically set based on the outstanding balance.
A customer can then pay for the same orders multiple times, and each time the order amount will be defined by the total sum of its associated transactions.
In this class, we will take a look at some PHP code and demo it live, so that you can get the full picture of this – really! – revolutionary WooCommerce customization.
This is an amazing opportunity to see how a simple yet effective functionality can be coded, so that you can learn a new thing or two for your WooCommerce website or your WooCommerce clients. It’s also a great opportunity to hang out with like-minded professionals during the live class.
Recording & Materials
Video recording
If you are a member, please log in.
Otherwise, here is why you should join the Club.
Code used during the class
add_action( 'init', function() {
$supports_array = array( 'title' );
$args = array(
'labels' => array(
'name' => 'Payments',
'singular_name' => 'Payments'
),
'capability_type' => 'shop_order',
'public' => false,
'supports' => $supports_array,
'rewrite' => false,
'show_ui' => true,
'show_in_menu' => current_user_can( 'edit_others_shop_orders' ) ? 'woocommerce' : false,
'menu_position' => 3,
);
register_post_type( 'payment', $args );
});
add_action( 'woocommerce_order_after_calculate_totals', function( $and_taxes, $order ) {
$payments = get_posts( [ 'post_type' => 'payment', 'post_status' => 'publish', 'numberposts' => -1, 'title' => $order->get_id(), 'fields' => 'ids' ] );
if ( $payments ) {
$total = $order->get_total();
$paid = 0.00;
foreach ( $payments as $payment_id ) {
$amount = get_field( "amount", $payment_id );
$paid += $amount;
}
$new_total = $total - $paid;
$order->set_total( $new_total );
if ( $new_total > 0 ) $order->set_status( 'pending' );
$order->save();
}
}, 9999, 2 );
add_action( 'acf/save_post', function( $post_id ) {
if ( get_post_type( $post_id ) !== 'payment' && get_post_status( $post_id ) !== 'publish' ) return;
$order = wc_get_order( get_the_title( $post_id ) );
if ( ! $order ) return;
$order->calculate_totals();
});
add_action( 'woocommerce_thankyou', function( $order_id ) {
$order = wc_get_order( $order_id );
if ( $order->has_status( 'failed' ) ) return;
$my_post = array(
'post_title' => $order_id,
'post_status' => 'publish',
'post_type' => 'payment',
);
$new_post_id = wp_insert_post( $my_post );
update_field( 'amount', $order->get_total(), $new_post_id );
$order->calculate_totals();
});
add_action( 'woocommerce_review_order_before_payment', function() {
$chosen = WC()->session->get( 'deposit_chosen' );
$chosen = empty( $chosen ) ? WC()->checkout->get_value( 'deposit' ) : $chosen;
$chosen = empty( $chosen ) ? '100' : $chosen;
$args = array(
'type' => 'radio',
'class' => array( 'form-row-wide', 'update_totals_on_change' ),
'options' => array(
'100' => 'Pay 100%',
'30' => 'Pay 30% Deposit',
),
'default' => $chosen,
);
echo '<p><b>Payment Option</b></p>';
woocommerce_form_field( 'deposit', $args, $chosen );
}, 1 );
add_action( 'woocommerce_checkout_update_order_review', function( $posted_data ) {
parse_str( $posted_data, $output );
if ( isset( $output['deposit'] ) ) {
WC()->session->set( 'deposit_chosen', $output['deposit'] );
}
});
add_filter( 'woocommerce_calculated_total', function( $total, $cart ) {
if ( ! WC()->session->get( 'deposit_chosen' ) || WC()->session->get( 'deposit_chosen' ) == '100' ) return $total;
return $total * 0.3;
}, 9999, 2 );
add_action( 'woocommerce_admin_order_totals_after_total', function ( $order_id ) {
echo '<tr><td class="label">Payments:</td><td width="1%"></td><td class="total"><ul style="margin:0">';
$payments = get_posts( [ 'post_type' => 'payment', 'post_status' => 'publish', 'numberposts' => -1, 'title' => $order_id, 'fields' => 'ids' ] );
if ( $payments ) {
foreach ( $payments as $payment_id ) {
$amount = get_field( "amount", $payment_id );
echo '<li style="margin:0"><a href="' . get_edit_post_link( $payment_id ) . '">' . wc_price( $amount ) . '</a> (' . get_the_time( 'd-m-y', $payment_id ) . ')</li>';
}
}
echo '</ul></td></tr>';
});
What you’ll learn
Requirements
More WooCommerce Masterclasses
Here’s a list of free live webinars and member-only class recordings (we usually take a break for June-August, otherwise you should expect about 2 live classes per month). Make sure to attend live so you can interact with the teacher and the other attendees!
-
WooCommerce Plugin Marketing 101: Your First 1,000 Users
Most WooCommerce plugins never reach 1,000 active installs—but hitting that milestone is crucial for validating your product before going PRO. In this class, I’ll show…
-
WooCommerce Settings API: Build Custom Option Pages
Stop cluttering the WordPress admin menu with separate settings pages! Learn to create professional, native-feeling custom tabs and options right inside the WooCommerce Settings interface.…
-
WooCommerce Database Walkthrough: Tables Explained
Tired of relying on guesswork when querying crucial WooCommerce data? This is your essential tour. We will walk you table-by-table through the WooCommerce database schema,…
-
From Woo Plugins to Shopify Apps Dev: Is it Worth it?
You’ve mastered WooCommerce plugin development. But is the scalable income of the Shopify App Store worth the pivot? This session provides a clear-eyed look at…
-
Avoid Costly Mistakes: Spotting WooCommerce Client Red Flags
Are you tired of projects that go over budget, clients who ghost, or customers who drain support? Bad clients — whether for consulting, development, or…
-
Classic vs Block: Add, Remove & Edit WooCommerce Checkout Fields
Let’s dive into the ins and outs of customizing WooCommerce checkout fields, comparing the Classic Checkout with the Checkout Block. You’ll see exactly what’s possible…
-
Behind the Scenes: The Making of Checkout Summit 2026
What does it really take to build a WooCommerce site that can handle a major international conference? For Checkout Summit 2026, I started with nothing…
-
Supercharge WooCommerce With Custom Product Options
Custom product options (“add-ons”) in WooCommerce can do much more than just add text boxes or checkboxes to the product page. In this class, we’ll…
-
Send These 7 WooCommerce Emails & Watch Sales Grow
Think email marketing is too complicated? Think again… If you’re only sending WooCommerce order emails, you’re leaving money on the table. With the right premium…
-
Spotting WooCommerce Conversion Rate Killers: A Live Audit
In this class, I’ll be auditing several live WooCommerce stores to identify and analyze conversion rate optimization (CRO) issues. Whether it’s slow checkout, poor product…
– BACKED BY –
Is your WooCommerce store prepared for traffic spikes? Improve speeds up to 200% with our managed WooCommerce hosting. Enjoy scalable server resources, rock-solid security, and 24/7 support.





















That’s awesome. I was on clueless how to accept multiple payments.
Thank you mate
Great!
Great tutorial, Rodolfo!
Thank you!
This was helpful, but felt like it was missing the major piece to tie it all together, which would be to send a order request to a customer with a preset deposit amount. While this accomplished the deposit by first using the checkout page, it doesn’t let shop owners send a pay order request (invoice) to a customer with the down payment request.
I would have loved to see the deposit radio fields added to the order page on possibly the ‘before_woocommerce_pay_form’ hook, or even better allow the site owner to choose the deposit amount to send the order, instead of requiring a end user checkout to trigger this.
Any advice on how to achieve this? Overall great video.
Thank you Bryan!
Good point! My solution only applies to checkout orders and not orders created via admin. However, you can always display the radio buttons (“Pay deposit” / “Pay in full”) on the Order Pay page as well, and that will let the customer choose between a set amount and a 100% payment.
I’m sure there’s also a way to implement the preset deposit amount right from the admin edit order page – this can be custom coded: you enter an amount, click “request payment”, and the Order Pay page asks for the preset deposit instead of the full amount.
The smooth alternative may be a URL parameter! Let’s say you want to force a $5 deposit – simply add “&dep=5” to the Order Pay URL (e.g. example.com/checkout/order-pay/123456/?pay_for_order=true&key=wc_order_xyz&dep=5) and install this snippet:
add_filter( 'woocommerce_order_get_total', function( $total, $order ) { if ( is_checkout_pay_page() && isset( $_GET['dep'] ) ) { return (int) $_GET['dep']; } return $total; }, 9999, 2 );Let me know!