public function create_order( $data ) {
/**
* Gives plugins an opportunity to create a new order themselves.
*
* @since 3.0.0 or earlier
*
* @param int|null $order_id Can be set to an order ID to short-circuit the default order creation process.
* @param WC_Checkout $checkout Reference to the current WC_Checkout instance.
*/
$order_id = apply_filters( 'woocommerce_create_order', null, $this );
if ( $order_id ) {
return $order_id;
}
try {
$order_id = absint( WC()->session->get( 'order_awaiting_payment' ) );
$cart_hash = WC()->cart->get_cart_hash();
$available_gateways = WC()->payment_gateways->get_available_payment_gateways();
$order = $order_id ? wc_get_order( $order_id ) : null;
/**
* If there is an order pending payment, we can resume it here so
* long as it has not changed. If the order has changed, i.e.
* different items or cost, create a new order. We use a hash to
* detect changes which is based on cart items + order total.
*/
if ( $order && $order->has_cart_hash( $cart_hash ) && $order->has_status( array( OrderStatus::PENDING, OrderStatus::FAILED ) ) ) {
/**
* Indicates that we are resuming checkout for an existing order (which is pending payment, and which
* has not changed since it was added to the current shopping session).
*
* @since 3.0.0 or earlier
*
* @param int $order_id The ID of the order being resumed.
*/
do_action( 'woocommerce_resume_order', $order_id );
// Remove all items - we will re-add them later.
$order->remove_order_items();
} else {
$order = new WC_Order();
}
$fields_prefix = array(
'shipping' => true,
'billing' => true,
);
foreach ( $data as $key => $value ) {
if ( is_callable( array( $order, "set_{$key}" ) ) ) {
$order->{"set_{$key}"}( $value );
// Store custom fields prefixed with either shipping_ or billing_. This is for backwards compatibility with 2.6.x.
} elseif (
isset( $fields_prefix[ current( explode( '_', $key ) ) ] )
&& ! in_array( $key, self::SHIPPING_FIELDS_EXCLUDED_FROM_META, true )
) {
$order->update_meta_data( '_' . $key, $value );
}
}
if ( isset( $data['billing_email'] ) ) {
$order->hold_applied_coupons( $data['billing_email'] );
}
$order->set_created_via( 'checkout' );
$order->set_cart_hash( $cart_hash );
/**
* This action is documented in woocommerce/includes/class-wc-checkout.php
*
* @since 3.0.0 or earlier
*/
$order->set_customer_id( apply_filters( 'woocommerce_checkout_customer_id', get_current_user_id() ) );
$order->set_currency( get_woocommerce_currency() );
$order->set_prices_include_tax( 'yes' === get_option( 'woocommerce_prices_include_tax' ) );
$order->set_customer_ip_address( WC_Geolocation::get_ip_address() );
$order->set_customer_user_agent( wc_get_user_agent() );
$order->set_customer_note( isset( $data['order_comments'] ) ? $data['order_comments'] : '' );
$order->set_payment_method( isset( $available_gateways[ $data['payment_method'] ] ) ? $available_gateways[ $data['payment_method'] ] : $data['payment_method'] );
$this->set_data_from_cart( $order );
if ( $order->has_cogs() && $this->cogs_is_enabled() ) {
$order->calculate_cogs_total_value();
}
/**
* Action hook to adjust order before save.
*
* @since 3.0.0
*/
do_action( 'woocommerce_checkout_create_order', $order, $data );
// Save the order.
$order_id = $order->save();
// Defense-in-depth: if the cart still has items but the persisted order has none,
// the save silently dropped every line item (e.g. a $wpdb->insert() returning false
// in save_items()). Re-read the order from the data store so we check DB state, not
// the in-memory copy that save_items() populated. Bail out so the caller can retry
// rather than completing a paid-but-empty order.
//
// This is intentionally narrow: it catches the catastrophic "no items at all" case,
// not partial item loss where some inserts succeed and some fail. Wider parity checks
// would need to reconcile quantities across products, bundles, fees, shipping, etc.,
// which is out of scope for the silent-failure guard.
if ( WC()->cart->get_cart_contents_count() > 0 ) {
$persisted_order = wc_get_order( $order_id );
if ( ! $persisted_order || 0 === count( $persisted_order->get_items() ) ) {
throw new Exception( __( 'Order items could not be saved. Please try again.', 'woocommerce' ) );
}
}
/**
* Action hook fired after an order is created used to add custom meta to the order.
*
* @since 3.0.0
*/
do_action( 'woocommerce_checkout_update_order_meta', $order_id, $data );
/**
* Action hook fired after an order is created.
*
* @since 4.3.0
*/
do_action( 'woocommerce_checkout_order_created', $order );
return $order_id;
} catch ( Exception $e ) {
if ( $order && $order instanceof WC_Order ) {
wc_release_coupons_for_order( $order );
/**
* Action hook fired when an order is discarded due to Exception.
*
* @since 4.3.0
*/
do_action( 'woocommerce_checkout_order_exception', $order );
}
return new WP_Error( 'checkout-error', $e->getMessage() );
}
}