Skip to content

Commit 594bc29

Browse files
authored
fix: General improvements and bugfixes (wp-graphql#899)
1 parent 5f924bb commit 594bc29

File tree

7 files changed

+197
-69
lines changed

7 files changed

+197
-69
lines changed

composer.lock

Lines changed: 7 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

includes/class-woocommerce-filters.php

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,14 @@ public static function add_session_header_to_allow_headers( array $allowed_heade
157157
* @return array
158158
*/
159159
public static function woographql_stripe_gateway_args( $gateway_args, $payment_method ) {
160-
if ( 'stripe' === $payment_method ) {
160+
/** @var false|\WC_Order|\WC_Order_Refund $order */
161+
$order = wc_get_order( $gateway_args[0] );
162+
if ( false === $order ) {
163+
return $gateway_args;
164+
}
165+
166+
$stripe_source_id = $order->get_meta( '_stripe_source_id' );
167+
if ( 'stripe' === $payment_method && ! empty( $stripe_source_id ) ) {
161168
$gateway_args = [
162169
$gateway_args[0],
163170
true,

includes/data/mutation/class-checkout-mutation.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -493,6 +493,7 @@ protected static function validate_checkout( &$data ) {
493493
if ( WC()->cart->needs_payment() ) {
494494
$available_gateways = WC()->payment_gateways->get_available_payment_gateways();
495495

496+
\codecept_debug( $available_gateways );
496497
if ( ! isset( $available_gateways[ $data['payment_method'] ] ) ) {
497498
throw new UserError( __( 'Invalid payment method.', 'wp-graphql-woocommerce' ) );
498499
} else {

includes/data/mutation/class-order-mutation.php

Lines changed: 165 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
use GraphQL\Error\UserError;
1212

13+
1314
/**
1415
* Class - Order_Mutation
1516
*/
@@ -86,8 +87,8 @@ public static function create_order( $input, $context, $info ) {
8687
/**
8788
* Action called before order is created.
8889
*
89-
* @param array $input Input data describing order.
90-
* @param \WPGraphQL\AppContext $context Request AppContext instance.
90+
* @param array $input Input data describing order.
91+
* @param \WPGraphQL\AppContext $context Request AppContext instance.
9192
* @param \GraphQL\Type\Definition\ResolveInfo $info Request ResolveInfo instance.
9293
*/
9394
do_action( 'graphql_woocommerce_before_order_create', $input, $context, $info );
@@ -118,78 +119,186 @@ public static function create_order( $input, $context, $info ) {
118119
* @param \WPGraphQL\AppContext $context AppContext instance.
119120
* @param \GraphQL\Type\Definition\ResolveInfo $info ResolveInfo instance.
120121
*
122+
* @throws \Exception Failed to retrieve order.
123+
*
121124
* @return void
122125
*/
123126
public static function add_items( $input, $order_id, $context, $info ) {
127+
/** @var \WC_Order|false $order */
128+
$order = \WC_Order_Factory::get_order( $order_id );
129+
if ( false === $order ) {
130+
throw new \Exception( __( 'Failed to retrieve order.', 'wp-graphql-woocommerce' ) );
131+
}
132+
124133
$item_group_keys = [
125134
'lineItems' => 'line_item',
126135
'shippingLines' => 'shipping',
127136
'feeLines' => 'fee',
128137
];
129138

130-
$item_groups = [];
131-
foreach ( $input as $key => $items ) {
139+
$order_items = [];
140+
foreach ( $input as $key => $group_items ) {
132141
if ( array_key_exists( $key, $item_group_keys ) ) {
133-
$type = $item_group_keys[ $key ];
142+
$type = $item_group_keys[ $key ];
143+
$order_items[ $type ] = [];
134144

135145
/**
136146
* Action called before an item group is added to an order.
137147
*
138-
* @param array $items Item data being added.
139-
* @param integer $order_id ID of target order.
140-
* @param \WPGraphQL\AppContext $context Request AppContext instance.
141-
* @param \GraphQL\Type\Definition\ResolveInfo $info Request ResolveInfo instance.
148+
* @param array $group_items Items data being added.
149+
* @param \WC_Order $order Order object.
150+
* @param \WPGraphQL\AppContext $context Request AppContext instance.
151+
* @param \GraphQL\Type\Definition\ResolveInfo $info Request ResolveInfo instance.
142152
*/
143-
do_action( "graphql_woocommerce_before_{$type}s_added_to_order", $items, $order_id, $context, $info );
144-
145-
foreach ( $items as $item_data ) {
146-
// Create Order item.
147-
$item_id = ( ! empty( $item_data['id'] ) && \WC_Order_Factory::get_order_item( $item_data['id'] ) )
148-
? $item_data['id']
149-
: \wc_add_order_item( $order_id, [ 'order_item_type' => $type ] );
150-
151-
// Continue if order item creation failed.
152-
if ( ! $item_id ) {
153-
continue;
153+
do_action( "graphql_woocommerce_before_{$type}s_added_to_order", $group_items, $order, $context, $info );
154+
155+
foreach ( $group_items as $item_data ) {
156+
$item = self::set_item(
157+
$item_data,
158+
$type,
159+
$order,
160+
$context,
161+
$info
162+
);
163+
164+
/**
165+
* Action called before an item group is added to an order.
166+
*
167+
* @param \WC_Order_Item $item Order item object.
168+
* @param array $item_data Item data being added.
169+
* @param \WC_Order $order Order object.
170+
* @param \WPGraphQL\AppContext $context Request AppContext instance.
171+
* @param \GraphQL\Type\Definition\ResolveInfo $info Request ResolveInfo instance.
172+
*/
173+
do_action( "graphql_woocommerce_before_{$type}_added_to_order", $item, $item_data, $order, $context, $info );
174+
175+
if ( 0 === $item->get_id() ) {
176+
$order->add_item( $item );
177+
$order_items[ $type ][] = $item;
178+
} else {
179+
$item->save();
180+
$order_items[ $type ][] = $item;
154181
}
155-
156-
// Add input item data to order item.
157-
$item_keys = self::get_order_item_keys( $type );
158-
self::map_input_to_item( $item_id, $item_data, $item_keys, $context, $info );
159182
}
160183

161184
/**
162-
* Action called after an item group is added to an order.
185+
* Action called after an item group is added to an order, and before the order has been saved with the new items.
163186
*
164-
* @param array $items Item data being added.
165-
* @param integer $order_id ID of target order.
166-
* @param \WPGraphQL\AppContext $context Request AppContext instance.
167-
* @param \GraphQL\Type\Definition\ResolveInfo $info Request ResolveInfo instance.
187+
* @param array $group_items Item data being added.
188+
* @param \WC_Order $order Order object.
189+
* @param \WPGraphQL\AppContext $context Request AppContext instance.
190+
* @param \GraphQL\Type\Definition\ResolveInfo $info Request ResolveInfo instance.
168191
*/
169-
do_action( "graphql_woocommerce_after_{$type}s_added_to_order", $items, $order_id, $context, $info );
192+
do_action( "graphql_woocommerce_after_{$type}s_added_to_order", $group_items, $order, $context, $info );
170193
}//end if
171194
}//end foreach
195+
196+
/**
197+
* Action called after all items have been added and right before the new items have been saved.
198+
*
199+
* @param array<string, array<\WC_Order_Item>> $order_items Order items.
200+
* @param \WC_Order $order WC_Order instance.
201+
* @param array $input Input data describing order.
202+
* @param \WPGraphQL\AppContext $context Request AppContext instance.
203+
* @param \GraphQL\Type\Definition\ResolveInfo $info Request ResolveInfo instance.
204+
*/
205+
do_action( 'graphql_woocommerce_before_new_order_items_save', $order_items, $order, $input, $context, $info );
206+
207+
$order->save();
172208
}
173209

174210
/**
175-
* Return array of item mapped with the provided $item_keys and extracts $meta_data
176211
*
177-
* @param integer $item_id Order item ID.
178-
* @param array $input Item input data.
179-
* @param array $item_keys Item key map.
212+
* @param array<string, mixed> $item_data Item data.
213+
* @param string $type Item type.
214+
* @param \WC_Order $order Order object.
180215
* @param \WPGraphQL\AppContext $context AppContext instance.
181216
* @param \GraphQL\Type\Definition\ResolveInfo $info ResolveInfo instance.
182217
*
183-
* @throws \Exception Failed to retrieve order item | Failed to retrieve connected product.
218+
* @return \WC_Order_Item
219+
*/
220+
public static function set_item( $item_data, $type, $order, $context, $info ) {
221+
$item_id = ! empty( $item_data['id'] ) ? $item_data['id'] : 0;
222+
$item_class = self::get_order_item_classname( $type, $item_id );
223+
224+
/** @var \WC_Order_Item $item */
225+
$item = new $item_class( $item_id );
226+
227+
/**
228+
* Filter the order item object before it is created.
229+
*
230+
* @param \WC_Order_Item $item Order item object.
231+
* @param array $item_data Item data.
232+
* @param \WC_Order $order Order object.
233+
* @param \WPGraphQL\AppContext $context AppContext instance.
234+
* @param \GraphQL\Type\Definition\ResolveInfo $info ResolveInfo instance.
235+
*/
236+
$item = apply_filters( "graphql_create_order_{$type}_object", $item, $item_data, $order, $context, $info );
237+
238+
self::map_input_to_item( $item, $item_data, $type );
239+
240+
/**
241+
* Action called after an order item is created.
242+
*
243+
* @param \WC_Order_Item $item Order item object.
244+
* @param array $item_data Item data.
245+
* @param \WC_Order $order Order object.
246+
* @param \WPGraphQL\AppContext $context AppContext instance.
247+
* @param \GraphQL\Type\Definition\ResolveInfo $info ResolveInfo instance.
248+
*/
249+
do_action( "graphql_create_order_{$type}", $item, $item_data, $order, $context, $info );
250+
251+
return $item;
252+
}
253+
254+
/**
255+
* Get order item class name.
184256
*
185-
* @return int
257+
* @param string $type Order item type.
258+
* @param int $id Order item ID.
259+
*
260+
* @return string
186261
*/
187-
protected static function map_input_to_item( $item_id, $input, $item_keys, $context, $info ) {
188-
$order_item = \WC_Order_Factory::get_order_item( $item_id );
189-
if ( ! is_object( $order_item ) ) {
190-
throw new \Exception( __( 'Failed to retrieve order item.', 'wp-graphql-woocommerce' ) );
262+
public static function get_order_item_classname( $type, $id = 0 ) {
263+
$classname = false;
264+
switch ( $type ) {
265+
case 'line_item':
266+
case 'product':
267+
$classname = 'WC_Order_Item_Product';
268+
break;
269+
case 'coupon':
270+
$classname = 'WC_Order_Item_Coupon';
271+
break;
272+
case 'fee':
273+
$classname = 'WC_Order_Item_Fee';
274+
break;
275+
case 'shipping':
276+
$classname = 'WC_Order_Item_Shipping';
277+
break;
278+
case 'tax':
279+
$classname = 'WC_Order_Item_Tax';
280+
break;
191281
}
192282

283+
$classname = apply_filters( 'woocommerce_get_order_item_classname', $classname, $type, $id ); // phpcs:ignore WordPress.NamingConventions
284+
285+
return $classname;
286+
}
287+
288+
/**
289+
* Return array of item mapped with the provided $item_keys and extracts $meta_data
290+
*
291+
* @param \WC_Order_Item &$item Order item.
292+
* @param array $input Item input data.
293+
* @param string $type Item type.
294+
*
295+
* @throws \Exception Failed to retrieve connected product.
296+
*
297+
* @return void
298+
*/
299+
protected static function map_input_to_item( &$item, $input, $type ) {
300+
$item_keys = self::get_order_item_keys( $type );
301+
193302
$args = [];
194303
$meta_data = null;
195304
foreach ( $input as $key => $value ) {
@@ -203,10 +312,9 @@ protected static function map_input_to_item( $item_id, $input, $item_keys, $cont
203312
}
204313

205314
// Calculate to subtotal/total for line items.
206-
207315
if ( isset( $args['quantity'] ) ) {
208-
$product = ( ! empty( $order_item['product_id'] ) )
209-
? wc_get_product( $order_item['product_id'] )
316+
$product = ( ! empty( $item['product_id'] ) )
317+
? wc_get_product( $item['product_id'] )
210318
: wc_get_product( self::get_product_id( $args ) );
211319
if ( ! is_object( $product ) ) {
212320
throw new \Exception( __( 'Failed to retrieve product connected to order item.', 'wp-graphql-woocommerce' ) );
@@ -219,18 +327,24 @@ protected static function map_input_to_item( $item_id, $input, $item_keys, $cont
219327

220328
// Set item props.
221329
foreach ( $args as $key => $value ) {
222-
if ( is_callable( [ $order_item, "set_{$key}" ] ) ) {
223-
$order_item->{"set_{$key}"}( $value );
330+
if ( is_callable( [ $item, "set_{$key}" ] ) ) {
331+
$item->{"set_{$key}"}( $value );
224332
}
225333
}
226334

227335
// Update item meta data if any is found.
228-
if ( 0 !== $item_id && ! empty( $meta_data ) ) {
229-
// Update item meta data.
230-
self::update_item_meta_data( $item_id, $meta_data, $context, $info );
336+
if ( empty( $meta_data ) ) {
337+
return;
231338
}
232339

233-
return $order_item->save();
340+
foreach ( $meta_data as $entry ) {
341+
$exists = $item->get_meta( $entry['key'], true, 'edit' );
342+
if ( '' !== $exists && $exists !== $entry['value'] ) {
343+
$item->update_meta_data( $entry['key'], $entry['value'] );
344+
} else {
345+
$item->add_meta_data( $entry['key'], $entry['value'] );
346+
}
347+
}
234348
}
235349

236350
/**
@@ -285,10 +399,10 @@ protected static function get_order_item_keys( $type ) {
285399
protected static function get_product_id( $data ) {
286400
if ( ! empty( $data['sku'] ) ) {
287401
$product_id = (int) wc_get_product_id_by_sku( $data['sku'] );
288-
} elseif ( ! empty( $data['product_id'] ) && empty( $data['variation_id'] ) ) {
289-
$product_id = (int) $data['product_id'];
290402
} elseif ( ! empty( $data['variation_id'] ) ) {
291403
$product_id = (int) $data['variation_id'];
404+
} elseif ( ! empty( $data['product_id'] ) ) {
405+
$product_id = (int) $data['product_id'];
292406
} else {
293407
throw new UserError( __( 'Product ID or SKU is required.', 'wp-graphql-woocommerce' ) );
294408
}

includes/model/class-order.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -497,7 +497,7 @@ protected function order_fields() {
497497
return ! empty( $this->data->get_date_paid() ) ? $this->data->get_date_paid() : null;
498498
},
499499
'subtotal' => function () {
500-
return ! empty( $this->data->get_subtotal() )
500+
return ! is_null( $this->data->get_subtotal() )
501501
? wc_graphql_price( $this->data->get_subtotal(), [ 'currency' => $this->data->get_currency() ] )
502502
: null;
503503
},

tests/_support/Factory/OrderFactory.php

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -143,10 +143,14 @@ public function createNew( $args = [], $items = [] ) {
143143
public function add_line_item( $order, $args = [], $save = true ) {
144144
$order = $save ? \wc_get_order( $order ) : $order;
145145

146-
if ( empty( $args['product'] ) ) {
147-
$product = \wc_get_product( $this->factory->product->createSimple() );
148-
} else {
146+
if ( ! empty( $args['variation_id'] ) ) {
147+
$product = \wc_get_product( $args['variation_id'] );
148+
} elseif ( ! empty( $args['product_id'] ) ) {
149+
$product = \wc_get_product( $args['product_id'] );
150+
} elseif ( ! empty( $args['product'] ) ) {
149151
$product = \wc_get_product( $args['product'] );
152+
} else {
153+
$product = \wc_get_product( $this->factory->product->createSimple() );
150154
}
151155

152156
if ( empty( $args['qty'] ) ) {

0 commit comments

Comments
 (0)