Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: General "createOrder" cleanup #899

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 8 additions & 1 deletion includes/class-woocommerce-filters.php
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,14 @@ public static function add_session_header_to_allow_headers( array $allowed_heade
* @return array
*/
public static function woographql_stripe_gateway_args( $gateway_args, $payment_method ) {
if ( 'stripe' === $payment_method ) {
/** @var false|\WC_Order|\WC_Order_Refund $order */
$order = wc_get_order( $gateway_args[0] );
if ( false === $order ) {
return $gateway_args;
}

$stripe_source_id = $order->get_meta( '_stripe_source_id' );
if ( 'stripe' === $payment_method && ! empty( $stripe_source_id ) ) {
$gateway_args = [
$gateway_args[0],
true,
Expand Down
1 change: 1 addition & 0 deletions includes/data/mutation/class-checkout-mutation.php
Original file line number Diff line number Diff line change
Expand Up @@ -493,6 +493,7 @@ protected static function validate_checkout( &$data ) {
if ( WC()->cart->needs_payment() ) {
$available_gateways = WC()->payment_gateways->get_available_payment_gateways();

\codecept_debug( $available_gateways );
if ( ! isset( $available_gateways[ $data['payment_method'] ] ) ) {
throw new UserError( __( 'Invalid payment method.', 'wp-graphql-woocommerce' ) );
} else {
Expand Down
216 changes: 165 additions & 51 deletions includes/data/mutation/class-order-mutation.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

use GraphQL\Error\UserError;


/**
* Class - Order_Mutation
*/
Expand Down Expand Up @@ -86,8 +87,8 @@ public static function create_order( $input, $context, $info ) {
/**
* Action called before order is created.
*
* @param array $input Input data describing order.
* @param \WPGraphQL\AppContext $context Request AppContext instance.
* @param array $input Input data describing order.
* @param \WPGraphQL\AppContext $context Request AppContext instance.
* @param \GraphQL\Type\Definition\ResolveInfo $info Request ResolveInfo instance.
*/
do_action( 'graphql_woocommerce_before_order_create', $input, $context, $info );
Expand Down Expand Up @@ -118,78 +119,186 @@ public static function create_order( $input, $context, $info ) {
* @param \WPGraphQL\AppContext $context AppContext instance.
* @param \GraphQL\Type\Definition\ResolveInfo $info ResolveInfo instance.
*
* @throws \Exception Failed to retrieve order.
*
* @return void
*/
public static function add_items( $input, $order_id, $context, $info ) {
/** @var \WC_Order|false $order */
$order = \WC_Order_Factory::get_order( $order_id );
if ( false === $order ) {
throw new \Exception( __( 'Failed to retrieve order.', 'wp-graphql-woocommerce' ) );
}

$item_group_keys = [
'lineItems' => 'line_item',
'shippingLines' => 'shipping',
'feeLines' => 'fee',
];

$item_groups = [];
foreach ( $input as $key => $items ) {
$order_items = [];
foreach ( $input as $key => $group_items ) {
if ( array_key_exists( $key, $item_group_keys ) ) {
$type = $item_group_keys[ $key ];
$type = $item_group_keys[ $key ];
$order_items[ $type ] = [];

/**
* Action called before an item group is added to an order.
*
* @param array $items Item data being added.
* @param integer $order_id ID of target order.
* @param \WPGraphQL\AppContext $context Request AppContext instance.
* @param \GraphQL\Type\Definition\ResolveInfo $info Request ResolveInfo instance.
* @param array $group_items Items data being added.
* @param \WC_Order $order Order object.
* @param \WPGraphQL\AppContext $context Request AppContext instance.
* @param \GraphQL\Type\Definition\ResolveInfo $info Request ResolveInfo instance.
*/
do_action( "graphql_woocommerce_before_{$type}s_added_to_order", $items, $order_id, $context, $info );

foreach ( $items as $item_data ) {
// Create Order item.
$item_id = ( ! empty( $item_data['id'] ) && \WC_Order_Factory::get_order_item( $item_data['id'] ) )
? $item_data['id']
: \wc_add_order_item( $order_id, [ 'order_item_type' => $type ] );

// Continue if order item creation failed.
if ( ! $item_id ) {
continue;
do_action( "graphql_woocommerce_before_{$type}s_added_to_order", $group_items, $order, $context, $info );

foreach ( $group_items as $item_data ) {
$item = self::set_item(
$item_data,
$type,
$order,
$context,
$info
);

/**
* Action called before an item group is added to an order.
*
* @param \WC_Order_Item $item Order item object.
* @param array $item_data Item data being added.
* @param \WC_Order $order Order object.
* @param \WPGraphQL\AppContext $context Request AppContext instance.
* @param \GraphQL\Type\Definition\ResolveInfo $info Request ResolveInfo instance.
*/
do_action( "graphql_woocommerce_before_{$type}_added_to_order", $item, $item_data, $order, $context, $info );

if ( 0 === $item->get_id() ) {
$order->add_item( $item );
$order_items[ $type ][] = $item;
} else {
$item->save();
$order_items[ $type ][] = $item;
}

// Add input item data to order item.
$item_keys = self::get_order_item_keys( $type );
self::map_input_to_item( $item_id, $item_data, $item_keys, $context, $info );
}

/**
* Action called after an item group is added to an order.
* Action called after an item group is added to an order, and before the order has been saved with the new items.
*
* @param array $items Item data being added.
* @param integer $order_id ID of target order.
* @param \WPGraphQL\AppContext $context Request AppContext instance.
* @param \GraphQL\Type\Definition\ResolveInfo $info Request ResolveInfo instance.
* @param array $group_items Item data being added.
* @param \WC_Order $order Order object.
* @param \WPGraphQL\AppContext $context Request AppContext instance.
* @param \GraphQL\Type\Definition\ResolveInfo $info Request ResolveInfo instance.
*/
do_action( "graphql_woocommerce_after_{$type}s_added_to_order", $items, $order_id, $context, $info );
do_action( "graphql_woocommerce_after_{$type}s_added_to_order", $group_items, $order, $context, $info );
}//end if
}//end foreach

/**
* Action called after all items have been added and right before the new items have been saved.
*
* @param array<string, array<\WC_Order_Item>> $order_items Order items.
* @param \WC_Order $order WC_Order instance.
* @param array $input Input data describing order.
* @param \WPGraphQL\AppContext $context Request AppContext instance.
* @param \GraphQL\Type\Definition\ResolveInfo $info Request ResolveInfo instance.
*/
do_action( 'graphql_woocommerce_before_new_order_items_save', $order_items, $order, $input, $context, $info );

$order->save();
}

/**
* Return array of item mapped with the provided $item_keys and extracts $meta_data
*
* @param integer $item_id Order item ID.
* @param array $input Item input data.
* @param array $item_keys Item key map.
* @param array<string, mixed> $item_data Item data.
* @param string $type Item type.
* @param \WC_Order $order Order object.
* @param \WPGraphQL\AppContext $context AppContext instance.
* @param \GraphQL\Type\Definition\ResolveInfo $info ResolveInfo instance.
*
* @throws \Exception Failed to retrieve order item | Failed to retrieve connected product.
* @return \WC_Order_Item
*/
public static function set_item( $item_data, $type, $order, $context, $info ) {
$item_id = ! empty( $item_data['id'] ) ? $item_data['id'] : 0;
$item_class = self::get_order_item_classname( $type, $item_id );

/** @var \WC_Order_Item $item */
$item = new $item_class( $item_id );

/**
* Filter the order item object before it is created.
*
* @param \WC_Order_Item $item Order item object.
* @param array $item_data Item data.
* @param \WC_Order $order Order object.
* @param \WPGraphQL\AppContext $context AppContext instance.
* @param \GraphQL\Type\Definition\ResolveInfo $info ResolveInfo instance.
*/
$item = apply_filters( "graphql_create_order_{$type}_object", $item, $item_data, $order, $context, $info );

self::map_input_to_item( $item, $item_data, $type );

/**
* Action called after an order item is created.
*
* @param \WC_Order_Item $item Order item object.
* @param array $item_data Item data.
* @param \WC_Order $order Order object.
* @param \WPGraphQL\AppContext $context AppContext instance.
* @param \GraphQL\Type\Definition\ResolveInfo $info ResolveInfo instance.
*/
do_action( "graphql_create_order_{$type}", $item, $item_data, $order, $context, $info );

return $item;
}

/**
* Get order item class name.
*
* @return int
* @param string $type Order item type.
* @param int $id Order item ID.
*
* @return string
*/
protected static function map_input_to_item( $item_id, $input, $item_keys, $context, $info ) {
$order_item = \WC_Order_Factory::get_order_item( $item_id );
if ( ! is_object( $order_item ) ) {
throw new \Exception( __( 'Failed to retrieve order item.', 'wp-graphql-woocommerce' ) );
public static function get_order_item_classname( $type, $id = 0 ) {
$classname = false;
switch ( $type ) {
case 'line_item':
case 'product':
$classname = 'WC_Order_Item_Product';
break;
case 'coupon':
$classname = 'WC_Order_Item_Coupon';
break;
case 'fee':
$classname = 'WC_Order_Item_Fee';
break;
case 'shipping':
$classname = 'WC_Order_Item_Shipping';
break;
case 'tax':
$classname = 'WC_Order_Item_Tax';
break;
}

$classname = apply_filters( 'woocommerce_get_order_item_classname', $classname, $type, $id ); // phpcs:ignore WordPress.NamingConventions

return $classname;
}

/**
* Return array of item mapped with the provided $item_keys and extracts $meta_data
*
* @param \WC_Order_Item &$item Order item.
* @param array $input Item input data.
* @param string $type Item type.
*
* @throws \Exception Failed to retrieve connected product.
*
* @return void
*/
protected static function map_input_to_item( &$item, $input, $type ) {
$item_keys = self::get_order_item_keys( $type );

$args = [];
$meta_data = null;
foreach ( $input as $key => $value ) {
Expand All @@ -203,10 +312,9 @@ protected static function map_input_to_item( $item_id, $input, $item_keys, $cont
}

// Calculate to subtotal/total for line items.

if ( isset( $args['quantity'] ) ) {
$product = ( ! empty( $order_item['product_id'] ) )
? wc_get_product( $order_item['product_id'] )
$product = ( ! empty( $item['product_id'] ) )
? wc_get_product( $item['product_id'] )
: wc_get_product( self::get_product_id( $args ) );
if ( ! is_object( $product ) ) {
throw new \Exception( __( 'Failed to retrieve product connected to order item.', 'wp-graphql-woocommerce' ) );
Expand All @@ -219,18 +327,24 @@ protected static function map_input_to_item( $item_id, $input, $item_keys, $cont

// Set item props.
foreach ( $args as $key => $value ) {
if ( is_callable( [ $order_item, "set_{$key}" ] ) ) {
$order_item->{"set_{$key}"}( $value );
if ( is_callable( [ $item, "set_{$key}" ] ) ) {
$item->{"set_{$key}"}( $value );
}
}

// Update item meta data if any is found.
if ( 0 !== $item_id && ! empty( $meta_data ) ) {
// Update item meta data.
self::update_item_meta_data( $item_id, $meta_data, $context, $info );
if ( empty( $meta_data ) ) {
return;
}

return $order_item->save();
foreach ( $meta_data as $entry ) {
$exists = $item->get_meta( $entry['key'], true, 'edit' );
if ( '' !== $exists && $exists !== $entry['value'] ) {
$item->update_meta_data( $entry['key'], $entry['value'] );
} else {
$item->add_meta_data( $entry['key'], $entry['value'] );
}
}
}

/**
Expand Down Expand Up @@ -285,10 +399,10 @@ protected static function get_order_item_keys( $type ) {
protected static function get_product_id( $data ) {
if ( ! empty( $data['sku'] ) ) {
$product_id = (int) wc_get_product_id_by_sku( $data['sku'] );
} elseif ( ! empty( $data['product_id'] ) && empty( $data['variation_id'] ) ) {
$product_id = (int) $data['product_id'];
} elseif ( ! empty( $data['variation_id'] ) ) {
$product_id = (int) $data['variation_id'];
} elseif ( ! empty( $data['product_id'] ) ) {
$product_id = (int) $data['product_id'];
} else {
throw new UserError( __( 'Product ID or SKU is required.', 'wp-graphql-woocommerce' ) );
}
Expand Down
2 changes: 1 addition & 1 deletion includes/model/class-order.php
Original file line number Diff line number Diff line change
Expand Up @@ -497,7 +497,7 @@ protected function order_fields() {
return ! empty( $this->data->get_date_paid() ) ? $this->data->get_date_paid() : null;
},
'subtotal' => function () {
return ! empty( $this->data->get_subtotal() )
return ! is_null( $this->data->get_subtotal() )
? wc_graphql_price( $this->data->get_subtotal(), [ 'currency' => $this->data->get_currency() ] )
: null;
},
Expand Down
10 changes: 7 additions & 3 deletions tests/_support/Factory/OrderFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -143,10 +143,14 @@ public function createNew( $args = [], $items = [] ) {
public function add_line_item( $order, $args = [], $save = true ) {
$order = $save ? \wc_get_order( $order ) : $order;

if ( empty( $args['product'] ) ) {
$product = \wc_get_product( $this->factory->product->createSimple() );
} else {
if ( ! empty( $args['variation_id'] ) ) {
$product = \wc_get_product( $args['variation_id'] );
} elseif ( ! empty( $args['product_id'] ) ) {
$product = \wc_get_product( $args['product_id'] );
} elseif ( ! empty( $args['product'] ) ) {
$product = \wc_get_product( $args['product'] );
} else {
$product = \wc_get_product( $this->factory->product->createSimple() );
}

if ( empty( $args['qty'] ) ) {
Expand Down
Loading
Loading