• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

wp-graphql / wp-graphql-woocommerce / 23266749290

18 Mar 2026 08:59PM UTC coverage: 83.127% (-0.06%) from 83.184%
23266749290

push

github

web-flow
feat: New fees input fields add to `cart` query and `checkout` mutation (#929)

* feat: New fees input fields add to `cart` query and `checkout` mutation

* chore: Linter compliance met

* devops: CartQueriesTest tests updated

105 of 108 new or added lines in 8 files covered. (97.22%)

18 existing lines in 2 files now uncovered.

12652 of 15220 relevant lines covered (83.13%)

76.97 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

76.18
/includes/data/mutation/class-checkout-mutation.php
1
<?php
2
/**
3
 * Defines helper functions for user checkout.
4
 *
5
 * @package WPGraphQL\WooCommerce\Data\Mutation
6
 * @since 0.2.0
7
 */
8

9
namespace WPGraphQL\WooCommerce\Data\Mutation;
10

11
use GraphQL\Error\UserError;
12
use WP_Error;
13

14
use function WC;
15

16
/**
17
 * Class - Checkout_Mutation
18
 */
19
class Checkout_Mutation {
20
        /**
21
         * Caches customer object. @see get_value.
22
         *
23
         * @var null|\WC_Customer
24
         */
25
        private static $logged_in_customer = null;
26

27
        /**
28
         * Is registration required to checkout?
29
         *
30
         * @since  3.0.0
31
         * @return boolean
32
         */
33
        public static function is_registration_required() {
34
                // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
35
                return apply_filters( 'woocommerce_checkout_registration_required', 'yes' !== get_option( 'woocommerce_enable_guest_checkout' ) );
5✔
36
        }
37

38
        /**
39
         * See if a fieldset should be skipped.
40
         *
41
         * @since 3.0.0
42
         * @param string $fieldset_key Fieldset key.
43
         * @param array  $data         Posted data.
44
         * @return bool
45
         */
46
        protected static function maybe_skip_fieldset( $fieldset_key, $data ) {
47
                if ( 'shipping' === $fieldset_key && ( ! $data['ship_to_different_address'] && ! \WC()->cart->needs_shipping_address() ) ) {
6✔
48
                        return true;
1✔
49
                }
50

51
                if ( 'account' === $fieldset_key && ( is_user_logged_in() || ( ! self::is_registration_required() && empty( $data['createaccount'] ) ) ) ) {
6✔
52
                        return true;
4✔
53
                }
54

55
                return false;
6✔
56
        }
57

58
        /**
59
         * Returns order data for use when user checking out.
60
         *
61
         * @param array                                $input    Input data describing order.
62
         * @param \WPGraphQL\AppContext                $context  AppContext instance.
63
         * @param \GraphQL\Type\Definition\ResolveInfo $info     ResolveInfo instance.
64
         *
65
         * @return array
66
         */
67
        public static function prepare_checkout_args( $input, $context, $info ) {
68
                $data = [
6✔
69
                        'terms'                     => (int) isset( $input['terms'] ),
6✔
70
                        'createaccount'             => (int) ! empty( $input['account'] ),
6✔
71
                        'payment_method'            => isset( $input['paymentMethod'] ) ? $input['paymentMethod'] : '',
6✔
72
                        'shipping_method'           => isset( $input['shippingMethod'] ) ? $input['shippingMethod'] : '',
6✔
73
                        'ship_to_different_address' => ! empty( $input['shipToDifferentAddress'] ) && ! wc_ship_to_billing_address_only(),
6✔
74
                ];
6✔
75

76
                $skipped = [ 'fees' ];
6✔
77
                foreach ( self::get_checkout_fields() as $fieldset_key => $fieldset ) {
6✔
78
                        if ( self::maybe_skip_fieldset( $fieldset_key, $data ) ) {
6✔
79
                                $skipped[] = $fieldset_key;
4✔
80
                                continue;
4✔
81
                        }
82

83
                        foreach ( $fieldset as $field => $input_key ) {
6✔
84
                                $key = "{$fieldset_key}_{$field}";
6✔
85
                                if ( 'order' === $fieldset_key ) {
6✔
86
                                        $value = ! empty( $input[ $input_key ] ) ? $input[ $input_key ] : null;
6✔
87
                                } else {
88
                                        $value = ! empty( $input[ $fieldset_key ][ $input_key ] ) ? $input[ $fieldset_key ][ $input_key ] : null;
6✔
89
                                }
90

91
                                if ( $value ) {
6✔
92
                                        $data[ $key ] = $value;
6✔
93
                                } elseif ( 'billing_country' === $key || 'shipping_country' === $key ) {
6✔
94
                                        $data[ $key ] = self::get_value( $key );
×
95
                                }
96
                        }
97
                }//end foreach
98

99
                if ( ! empty( $input['fees'] ) ) {
6✔
100
                        $fees = $input['fees'];
1✔
101
                        add_action(
1✔
102
                                'woocommerce_cart_calculate_fees',
1✔
103
                                static function () use ( $fees ) {
1✔
104
                                        foreach ( $fees as $fee_input ) {
1✔
105
                                                if ( empty( $fee_input['name'] ) || empty( $fee_input['amount'] ) ) {
1✔
106
                                                        // TODO: Log invalid fee input.
NEW
107
                                                        continue;
×
108
                                                }
109

110
                                                $fee_args = [
1✔
111
                                                        $fee_input['name'],
1✔
112
                                                        $fee_input['amount'],
1✔
113
                                                        isset( $fee_input['taxable'] ) ? $fee_input['taxable'] : false,
1✔
114
                                                        isset( $fee_input['taxClass'] ) ? $fee_input['taxClass'] : '',
1✔
115
                                                ];
1✔
116

117
                                                \WC()->cart->add_fee( ...$fee_args );
1✔
118
                                        }
119
                                }
1✔
120
                        );
1✔
121
                }
122

123
                if ( in_array( 'shipping', $skipped, true ) && ( \WC()->cart->needs_shipping_address() || \wc_ship_to_billing_address_only() ) ) {
6✔
124
                        foreach ( self::get_checkout_fields( 'shipping' ) as $field => $input_key ) {
×
125
                                $data[ "shipping_{$field}" ] = isset( $data[ "billing_{$field}" ] ) ? $data[ "billing_{$field}" ] : '';
×
126
                        }
127
                }
128

129
                // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
130
                return apply_filters( 'woocommerce_checkout_posted_data', $data, $input, $context, $info );
6✔
131
        }
132

133
        /**
134
         * Get an array of checkout fields.
135
         *
136
         * @param string  $fieldset Target fieldset.
137
         * @param boolean $prefixed Prefixed field keys with fieldset name.
138
         *
139
         * @return array
140
         */
141
        public static function get_checkout_fields( $fieldset = '', $prefixed = false ) {
142
                $fields = [
6✔
143
                        'billing'  => [
6✔
144
                                'first_name' => 'firstName',
6✔
145
                                'last_name'  => 'lastName',
6✔
146
                                'company'    => 'company',
6✔
147
                                'address_1'  => 'address1',
6✔
148
                                'address_2'  => 'address2',
6✔
149
                                'city'       => 'city',
6✔
150
                                'postcode'   => 'postcode',
6✔
151
                                'state'      => 'state',
6✔
152
                                'country'    => 'country',
6✔
153
                                'phone'      => 'phone',
6✔
154
                                'email'      => 'email',
6✔
155
                        ],
6✔
156
                        'shipping' => [
6✔
157
                                'first_name' => 'firstName',
6✔
158
                                'last_name'  => 'lastName',
6✔
159
                                'company'    => 'company',
6✔
160
                                'address_1'  => 'address1',
6✔
161
                                'address_2'  => 'address2',
6✔
162
                                'city'       => 'city',
6✔
163
                                'postcode'   => 'postcode',
6✔
164
                                'state'      => 'state',
6✔
165
                                'country'    => 'country',
6✔
166
                        ],
6✔
167
                        'account'  => [
6✔
168
                                'username' => 'username',
6✔
169
                                'password' => 'password',
6✔
170
                        ],
6✔
171
                        'order'    => [
6✔
172
                                'comments' => 'customerNote',
6✔
173
                        ],
6✔
174
                ];
6✔
175

176
                if ( $prefixed ) {
6✔
177
                        foreach ( $fields as $prefix => $values ) {
6✔
178
                                foreach ( $values as $index => $value ) {
6✔
179
                                        $fields[ $prefix ][ $index ] = "{$prefix}_{$value}";
6✔
180
                                }
181
                        }
182
                }
183

184
                if ( ! empty( $fieldset ) ) {
6✔
185
                        return ! empty( $fields[ $fieldset ] ) ? $fields[ $fieldset ] : [];
×
186
                }
187

188
                return $fields;
6✔
189
        }
190

191
        /**
192
         * Update customer and session data from the posted checkout data.
193
         *
194
         * @param array $data Order data.
195
         *
196
         * @return void
197
         */
198
        protected static function update_session( $data ) {
199
                // Update both shipping and billing to the passed billing address first if set.
200
                $address_fields = [
6✔
201
                        'first_name',
6✔
202
                        'last_name',
6✔
203
                        'company',
6✔
204
                        'email',
6✔
205
                        'phone',
6✔
206
                        'address_1',
6✔
207
                        'address_2',
6✔
208
                        'city',
6✔
209
                        'postcode',
6✔
210
                        'state',
6✔
211
                        'country',
6✔
212
                ];
6✔
213

214
                foreach ( $address_fields as $field ) {
6✔
215
                        self::set_customer_address_fields( $field, $data );
6✔
216
                }
217
                WC()->customer->save();
6✔
218

219
                // Update customer shipping and payment method to posted method.
220
                $chosen_shipping_methods = WC()->session->get( 'chosen_shipping_methods' );
6✔
221

222
                if ( is_array( $data['shipping_method'] ) ) {
6✔
223
                        foreach ( $data['shipping_method'] as $i => $value ) {
6✔
224
                                $chosen_shipping_methods[ $i ] = $value;
6✔
225
                        }
226
                }
227

228
                WC()->session->set( 'chosen_shipping_methods', $chosen_shipping_methods );
6✔
229
                WC()->session->set( 'chosen_payment_method', $data['payment_method'] );
6✔
230

231
                // Update cart totals now we have customer address.
232
                WC()->cart->calculate_totals();
6✔
233
        }
234

235
        /**
236
         * Clears customer address
237
         *
238
         * @param string $type  Address type.
239
         *
240
         * @return bool
241
         */
242
        protected static function clear_customer_address( $type = 'billing' ) {
243
                if ( 'billing' !== $type && 'shipping' !== $type ) {
6✔
244
                        return false;
×
245
                }
246

247
                $address = [
6✔
248
                        'first_name' => '',
6✔
249
                        'last_name'  => '',
6✔
250
                        'company'    => '',
6✔
251
                        'address_1'  => '',
6✔
252
                        'address_2'  => '',
6✔
253
                        'city'       => '',
6✔
254
                        'state'      => '',
6✔
255
                        'postcode'   => '',
6✔
256
                        'country'    => '',
6✔
257
                ];
6✔
258

259
                if ( 'billing' === $type ) {
6✔
260
                        $address = array_merge(
6✔
261
                                $address,
6✔
262
                                [
6✔
263
                                        'email' => '',
6✔
264
                                        'phone' => '',
6✔
265
                                ]
6✔
266
                        );
6✔
267
                }
268

269
                foreach ( $address as $prop => $value ) {
6✔
270
                        $setter = "set_{$type}_{$prop}";
6✔
271
                        WC()->customer->{$setter}( $value );
6✔
272
                }
273

274
                return true;
6✔
275
        }
276

277
        /**
278
         * Create a new customer account if needed.
279
         *
280
         * @param array $data Checkout data.
281
         *
282
         * @throws \GraphQL\Error\UserError When not able to create customer.
283
         *
284
         * @return void
285
         */
286
        protected static function process_customer( $data ) {
287
                // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
288
                $customer_id = apply_filters( 'woocommerce_checkout_customer_id', get_current_user_id() );
5✔
289

290
                if ( ! is_user_logged_in() && ( self::is_registration_required() || ! empty( $data['createaccount'] ) ) ) {
5✔
291
                        $username    = ! empty( $data['account_username'] ) ? $data['account_username'] : '';
1✔
292
                        $password    = ! empty( $data['account_password'] ) ? $data['account_password'] : '';
1✔
293
                        $customer_id = wc_create_new_customer(
1✔
294
                                $data['billing_email'],
1✔
295
                                $username,
1✔
296
                                $password,
1✔
297
                                [
1✔
298
                                        'first_name' => ! empty( $data['billing_first_name'] ) ? $data['billing_first_name'] : '',
1✔
299
                                        'last_name'  => ! empty( $data['billing_last_name'] ) ? $data['billing_last_name'] : '',
1✔
300
                                ]
1✔
301
                        );
1✔
302

303
                        if ( is_wp_error( $customer_id ) ) {
1✔
304
                                throw new UserError( $customer_id->get_error_message() );
×
305
                        }
306

307
                        wc_set_customer_auth_cookie( $customer_id );
1✔
308

309
                        // As we are now logged in, checkout will need to refresh to show logged in data.
310
                        WC()->session->set( 'reload_checkout', true );
1✔
311

312
                        // Also, recalculate cart totals to reveal any role-based discounts that were unavailable before registering.
313
                        WC()->cart->calculate_totals();
1✔
314
                }//end if
315

316
                // On multisite, ensure user exists on current site, if not add them before allowing login.
317
                if ( $customer_id && is_multisite() && is_user_logged_in() && ! is_user_member_of_blog() ) {
5✔
318
                        add_user_to_blog( get_current_blog_id(), $customer_id, 'customer' );
×
319
                }
320

321
                // Add customer info from other fields.
322
                // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
323
                if ( $customer_id && apply_filters( 'woocommerce_checkout_update_customer_data', true, WC()->checkout() ) ) {
5✔
324
                        $customer = new \WC_Customer( $customer_id );
2✔
325

326
                        if ( ! empty( $data['billing_first_name'] ) && '' === $customer->get_first_name() ) {
2✔
327
                                $customer->set_first_name( $data['billing_first_name'] );
×
328
                        }
329

330
                        if ( ! empty( $data['billing_last_name'] ) && '' === $customer->get_last_name() ) {
2✔
331
                                $customer->set_last_name( $data['billing_last_name'] );
×
332
                        }
333

334
                        // If the display name is an email, update to the user's full name.
335
                        if ( is_email( $customer->get_display_name() ) ) {
2✔
336
                                $customer->set_display_name( $customer->get_first_name() . ' ' . $customer->get_last_name() );
×
337
                        }
338

339
                        foreach ( $data as $key => $value ) {
2✔
340
                                // Use setters where available.
341
                                if ( is_callable( [ $customer, "set_{$key}" ] ) ) {
2✔
342
                                        $customer->{"set_{$key}"}( $value );
2✔
343

344
                                        // Store custom fields prefixed with wither shipping_ or billing_.
345
                                } elseif ( 0 === stripos( $key, 'billing_' ) || 0 === stripos( $key, 'shipping_' ) ) {
2✔
346
                                        $customer->update_meta_data( $key, $value );
2✔
347
                                }
348
                        }
349

350
                        // Action hook to adjust customer before save.
351
                        // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
352
                        do_action( 'woocommerce_checkout_update_customer', $customer, $data );
2✔
353

354
                        $customer->save();
2✔
355
                }//end if
356

357
                // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
358
                do_action( 'woocommerce_checkout_update_user_meta', $customer_id, $data );
5✔
359
        }
360

361
        /**
362
         * Set address field for customer.
363
         *
364
         * @param string $field String to update.
365
         * @param array  $data  Array of data to get the value from.
366
         *
367
         * @return void
368
         */
369
        protected static function set_customer_address_fields( $field, $data ) {
370
                $billing_value  = null;
6✔
371
                $shipping_value = null;
6✔
372

373
                if ( isset( $data[ "billing_{$field}" ] ) && is_callable( [ WC()->customer, "set_billing_{$field}" ] ) ) {
6✔
374
                        $billing_value  = $data[ "billing_{$field}" ];
6✔
375
                        $shipping_value = $data[ "billing_{$field}" ];
6✔
376
                }
377

378
                if ( isset( $data[ "shipping_{$field}" ] ) && is_callable( [ WC()->customer, "set_shipping_{$field}" ] ) ) {
6✔
379
                        $shipping_value = $data[ "shipping_{$field}" ];
5✔
380
                }
381

382
                if ( ! is_null( $billing_value ) && is_callable( [ WC()->customer, "set_billing_{$field}" ] ) ) {
6✔
383
                        WC()->customer->{"set_billing_{$field}"}( $billing_value );
6✔
384
                }
385

386
                if ( ! is_null( $shipping_value ) && is_callable( [ WC()->customer, "set_shipping_{$field}" ] ) ) {
6✔
387
                        WC()->customer->{"set_shipping_{$field}"}( $shipping_value );
6✔
388
                }
389
        }
390

391
        /**
392
         * Validates the posted checkout data based on field properties.
393
         *
394
         * @param array $data  Checkout data.
395
         *
396
         * @throws \GraphQL\Error\UserError Invalid input.
397
         *
398
         * @return void
399
         */
400
        protected static function validate_data( &$data ) {
401
                foreach ( self::get_checkout_fields( '', true ) as $fieldset_key => $fieldset ) {
6✔
402
                        $validate_fieldset = true;
6✔
403
                        if ( self::maybe_skip_fieldset( $fieldset_key, $data ) ) {
6✔
404
                                $validate_fieldset = false;
4✔
405
                        }
406

407
                        foreach ( $fieldset as $key => $field_label ) {
6✔
408
                                if ( ! isset( $data[ $key ] ) ) {
6✔
409
                                        continue;
6✔
410
                                }
411

412
                                if ( \str_ends_with( $key, 'postcode' ) ) {
×
413
                                        $country      = isset( $data[ $fieldset_key . '_country' ] ) ? $data[ $fieldset_key . '_country' ] : WC()->customer->{"get_{$fieldset_key}_country"}();
×
414
                                        $data[ $key ] = \wc_format_postcode( $data[ $key ], $country );
×
415

416
                                        if ( $validate_fieldset && '' !== $data[ $key ] && ! \WC_Validation::is_postcode( $data[ $key ], $country ) ) {
×
417
                                                switch ( $country ) {
418
                                                        case 'IE':
×
419
                                                                /* translators: %1$s: field name, %2$s finder.eircode.ie URL */
420
                                                                $postcode_validation_notice = sprintf( __( '%1$s is not valid. You can look up the correct Eircode. %2$s', 'wp-graphql-woocommerce' ), $field_label, 'https://finder.eircode.ie' );
×
421
                                                                break;
×
422
                                                        default:
423
                                                                /* translators: %s: field name */
424
                                                                $postcode_validation_notice = sprintf( __( '%s is not a valid postcode / ZIP.', 'wp-graphql-woocommerce' ), $field_label );
×
425
                                                }
426
                                                // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
427
                                                throw new UserError( apply_filters( 'woocommerce_checkout_postcode_validation_notice', $postcode_validation_notice, $country, $data[ $key ] ) );
×
428
                                        }
429
                                }
430

431
                                if ( \str_ends_with( $key, 'phone' ) ) {
×
432
                                        if ( $validate_fieldset && '' !== $data[ $key ] && ! \WC_Validation::is_phone( $data[ $key ] ) ) {
×
433
                                                /* translators: %s: phone number */
434
                                                throw new UserError( sprintf( __( '%s is not a valid phone number.', 'wp-graphql-woocommerce' ), $field_label ) );
×
435
                                        }
436
                                }
437

438
                                if ( \str_ends_with( $key, 'email' ) && '' !== $data[ $key ] ) {
×
439
                                        $email_is_valid = is_email( $data[ $key ] );
×
440
                                        $data[ $key ]   = sanitize_email( $data[ $key ] );
×
441

442
                                        if ( $validate_fieldset && ! $email_is_valid ) {
×
443
                                                /* translators: %s: email address */
444
                                                throw new UserError( sprintf( __( '%s is not a valid email address.', 'wp-graphql-woocommerce' ), $field_label ) );
×
445
                                        }
446
                                }
447

448
                                if ( \str_ends_with( $key, 'state' ) && '' !== $data[ $key ] ) {
×
449
                                        $country      = isset( $data[ $fieldset_key . '_country' ] ) ? $data[ $fieldset_key . '_country' ] : WC()->customer->{"get_{$fieldset_key}_country"}();
×
450
                                        $valid_states = WC()->countries->get_states( $country );
×
451

452
                                        if ( ! empty( $valid_states ) && is_array( $valid_states ) ) {
×
453
                                                $valid_state_values = array_map( 'wc_strtoupper', array_flip( array_map( 'wc_strtoupper', $valid_states ) ) );
×
454
                                                $data[ $key ]       = wc_strtoupper( $data[ $key ] );
×
455

456
                                                if ( isset( $valid_state_values[ $data[ $key ] ] ) ) {
×
457
                                                        // With this part we consider state value to be valid as well, convert it to the state key for the valid_states check below.
458
                                                        $data[ $key ] = $valid_state_values[ $data[ $key ] ];
×
459
                                                }
460

461
                                                if ( $validate_fieldset && ! in_array( $data[ $key ], $valid_state_values, true ) ) {
×
462
                                                        /* translators: 1: state field 2: valid states */
463
                                                        throw new UserError( sprintf( __( '%1$s is not valid. Please enter one of the following: %2$s', 'wp-graphql-woocommerce' ), $field_label, implode( ', ', $valid_states ) ) );
×
464
                                                }
465
                                        }
466
                                }
467
                        }//end foreach
468
                }//end foreach
469
        }
470

471
        /**
472
         * Validates that the checkout has enough info to proceed.
473
         *
474
         * @param array $data  An array of posted data.
475
         *
476
         * @throws \GraphQL\Error\UserError Invalid input.
477
         *
478
         * @return void
479
         */
480
        protected static function validate_checkout( &$data ) {
481
                self::validate_data( $data );
6✔
482
                WC()->checkout()->check_cart_items();
6✔
483

484
                // Throw cart validation errors stored in the session.
485
                $cart_item_errors = wc_get_notices( 'error' );
6✔
486

487
                if ( ! empty( $cart_item_errors ) ) {
6✔
488
                        $cart_item_error_msgs = implode( ' ', array_column( $cart_item_errors, 'notice' ) );
1✔
489
                        \wc_clear_notices();
1✔
490
                        throw new UserError( $cart_item_error_msgs );
1✔
491
                }
492

493
                if ( WC()->cart->needs_shipping() ) {
5✔
494
                        $shipping_country = WC()->customer->get_shipping_country();
4✔
495

496
                        if ( empty( $shipping_country ) ) {
4✔
497
                                throw new UserError( __( 'Please enter an address to continue.', 'wp-graphql-woocommerce' ) );
×
498
                        } elseif ( ! in_array( WC()->customer->get_shipping_country(), array_keys( WC()->countries->get_shipping_countries() ), true ) ) {
4✔
499
                                throw new UserError(
×
500
                                        sprintf(
×
501
                                                /* translators: %s: shipping location */
502
                                                __( 'Unfortunately, we do not ship %s. Please enter an alternative shipping address.', 'wp-graphql-woocommerce' ),
×
503
                                                WC()->countries->shipping_to_prefix() . ' ' . WC()->customer->get_shipping_country()
×
504
                                        )
×
505
                                );
×
506
                        } else {
507
                                $chosen_shipping_methods = WC()->session->get( 'chosen_shipping_methods' );
4✔
508

509
                                foreach ( WC()->shipping()->get_packages() as $i => $package ) {
4✔
510
                                        if ( ! isset( $chosen_shipping_methods[ $i ], $package['rates'][ $chosen_shipping_methods[ $i ] ] ) ) {
4✔
511
                                                throw new UserError( __( 'No shipping method has been selected. Please double check your address, or contact us if you need any help.', 'wp-graphql-woocommerce' ) );
×
512
                                        }
513
                                }
514
                        }
515
                }//end if
516

517
                if ( WC()->cart->needs_payment() ) {
5✔
518
                        $available_gateways = WC()->payment_gateways->get_available_payment_gateways();
5✔
519
                        \codecept_debug( $available_gateways );
5✔
520
                        if ( ! isset( $available_gateways[ $data['payment_method'] ] ) ) {
5✔
521
                                throw new UserError( __( 'Invalid payment method.', 'wp-graphql-woocommerce' ) );
×
522
                        } else {
523
                                $available_gateways[ $data['payment_method'] ]->validate_fields();
5✔
524
                        }
525
                }
526

527
                // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
528
                do_action( 'woocommerce_after_checkout_validation', $data, new WP_Error() );
5✔
529
        }
530

531
        /**
532
         * Process an order that does require payment.
533
         *
534
         * @param int    $order_id       Order ID.
535
         * @param string $payment_method Payment method.
536
         *
537
         * @throws \GraphQL\Error\UserError When payment method is invalid.
538
         *
539
         * @return array Processed payment results.
540
         */
541
        protected static function process_order_payment( $order_id, $payment_method ) {
542
                $available_gateways = WC()->payment_gateways->get_available_payment_gateways();
4✔
543

544
                if ( ! isset( $available_gateways[ $payment_method ] ) ) {
4✔
545
                        throw new UserError( __( 'Cannot process invalid payment method.', 'wp-graphql-woocommerce' ) );
×
546
                }
547

548
                // Store Order ID in session so it can be re-used after payment failure.
549
                WC()->session->set( 'order_awaiting_payment', $order_id );
4✔
550

551
                $process_payment_args = apply_filters(
4✔
552
                        "graphql_{$payment_method}_process_payment_args",
4✔
553
                        [ $order_id ],
4✔
554
                        $payment_method
4✔
555
                );
4✔
556

557
                // Process Payment.
558
                return $available_gateways[ $payment_method ]->process_payment( ...$process_payment_args );
4✔
559
        }
560

561
        /**
562
         * Process an order that doesn't require payment.
563
         *
564
         * @since 3.0.0
565
         * @param int    $order_id        Order ID.
566
         * @param string $transaction_id  Payment transaction ID.
567
         *
568
         * @throws \Exception Order cannot be retrieved.
569
         *
570
         * @return array
571
         */
572
        protected static function process_order_without_payment( $order_id, $transaction_id = '' ) {
573
                $order = wc_get_order( $order_id );
1✔
574
                if ( ! is_object( $order ) || ! is_a( $order, \WC_Order::class ) ) {
1✔
575
                        throw new \Exception( __( 'Failed to retrieve order.', 'wp-graphql-woocommerce' ) );
×
576
                }
577

578
                $order->payment_complete( $transaction_id );
1✔
579

580
                return [
1✔
581
                        'result'   => 'success',
1✔
582
                        // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
583
                        'redirect' => apply_filters( 'woocommerce_checkout_no_payment_needed_redirect', $order->get_checkout_order_received_url(), $order ),
1✔
584
                ];
1✔
585
        }
586

587
        /**
588
         * Process the checkout.
589
         *
590
         * @param array                                $data     Order data.
591
         * @param array                                $input    Input data describing order.
592
         * @param \WPGraphQL\AppContext                $context  AppContext instance.
593
         * @param \GraphQL\Type\Definition\ResolveInfo $info     ResolveInfo instance.
594
         * @param array                                $results  Order status.
595
         *
596
         * @throws \GraphQL\Error\UserError When validation fails.
597
         *
598
         * @return int Order ID.
599
         */
600
        public static function process_checkout( $data, $input, $context, $info, &$results = null ) {
601
                wc_maybe_define_constant( 'WOOCOMMERCE_CHECKOUT', true );
6✔
602
                wc_set_time_limit( 0 );
6✔
603

604
                do_action( 'woocommerce_before_checkout_process' ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
6✔
605

606
                if ( WC()->cart->is_empty() ) {
6✔
607
                        throw new UserError( __( 'Sorry, no session found.', 'wp-graphql-woocommerce' ) );
×
608
                }
609

610
                do_action( 'woocommerce_checkout_process', $data, $context, $info ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
6✔
611

612
                if ( ! empty( $input['billing']['overwrite'] ) && true === $input['billing']['overwrite'] ) {
6✔
613
                        self::clear_customer_address( 'billing' );
6✔
614
                }
615

616
                if ( ! empty( $input['shipping'] ) && ! empty( $input['shipping']['overwrite'] )
6✔
617
                        && true === $input['shipping']['overwrite'] ) {
6✔
618
                        self::clear_customer_address( 'shipping' );
×
619
                }
620

621
                // Update session for customer and totals.
622
                self::update_session( $data );
6✔
623

624
                // Validate posted data and cart items before proceeding.
625
                self::validate_checkout( $data );
6✔
626

627
                self::process_customer( $data );
5✔
628
                $order_id = WC()->checkout->create_order( $data );
5✔
629
                $order    = wc_get_order( $order_id );
5✔
630

631
                if ( is_wp_error( $order_id ) ) {
5✔
632
                        throw new UserError( $order_id->get_error_message() );
×
633
                }
634

635
                if ( ! is_object( $order ) || ! is_a( $order, \WC_Order::class ) ) {
5✔
636
                        throw new UserError( __( 'Unable to create order.', 'wp-graphql-woocommerce' ) );
×
637
                }
638

639
                // Add meta data.
640
                if ( ! empty( $input['metaData'] ) ) {
5✔
641
                        self::update_order_meta( $order_id, $input['metaData'], $input, $context, $info );
5✔
642
                }
643

644
                // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
645
                do_action( 'woocommerce_checkout_order_processed', $order_id, $data, $order );
5✔
646

647
                if ( WC()->cart->needs_payment() && ( empty( $input['isPaid'] ) ) ) {
5✔
648
                        $results = self::process_order_payment( $order_id, $data['payment_method'] );
4✔
649
                } else {
650
                        $transaction_id = ! empty( $input['transactionId'] ) ? $input['transactionId'] : '';
1✔
651

652
                        /**
653
                         * Use this to do some last minute transaction ID validation.
654
                         *
655
                         * @param bool        $is_valid        Is transaction ID valid.
656
                         * @param \WC_Order   $order           Order being processed.
657
                         * @param String|null $transaction_id  Order payment transaction ID.
658
                         * @param array       $data            Order data.
659
                         * @param array       $input           Order raw input data.
660
                         * @param \WPGraphQL\AppContext  $context         Request's AppContext instance.
661
                         * @param \GraphQL\Type\Definition\ResolveInfo $info            Request's ResolveInfo instance.
662
                         */
663
                        $valid = apply_filters(
1✔
664
                                'graphql_checkout_prepaid_order_validation',
1✔
665
                                true,
1✔
666
                                $order,
1✔
667
                                $transaction_id,
1✔
668
                                $data,
1✔
669
                                $input,
1✔
670
                                $context,
1✔
671
                                $info
1✔
672
                        );
1✔
673

674
                        if ( $valid ) {
1✔
675
                                $results = self::process_order_without_payment( $order_id, $transaction_id );
1✔
676
                        } else {
677
                                $results = [
×
678
                                        'result'   => 'failed',
×
679
                                        'redirect' => apply_filters(
×
680
                                                'graphql_woocommerce_checkout_payment_failed_redirect',
×
681
                                                $order->get_checkout_payment_url(),
×
682
                                                $order,
×
683
                                                $order_id,
×
684
                                                $transaction_id
×
685
                                        ),
×
686
                                ];
×
687
                        }
688
                }//end if
689

690
                if ( 'success' === $results['result'] ) {
5✔
691
                        wc_empty_cart();
5✔
692
                }
693

694
                return $order_id;
5✔
695
        }
696

697
        /**
698
         * Gets the value either from 3rd party logic or the customer object. Sets the default values in checkout fields.
699
         *
700
         * @param string $input Name of the input we want to grab data for. e.g. billing_country.
701
         * @return string The default value.
702
         */
703
        public static function get_value( $input ) {
704
                // Allow 3rd parties to short circuit the logic and return their own default value.
705
                // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
706
                $value = apply_filters( 'woocommerce_checkout_get_value', null, $input );
×
707
                if ( ! is_null( $value ) ) {
×
708
                        return $value;
×
709
                }
710

711
                /**
712
                 * For logged in customers, pull data from their account rather than the session which may contain incomplete data.
713
                 * Another reason is that WC sets shipping address to the billing address on the checkout updates unless the
714
                 * "shipToDifferentAddress" is set.
715
                 */
716
                $customer_object = false;
×
717
                if ( is_user_logged_in() ) {
×
718
                        // Load customer object, but keep it cached to avoid reloading it multiple times.
719
                        if ( is_null( self::$logged_in_customer ) ) {
×
720
                                self::$logged_in_customer = new \WC_Customer( get_current_user_id(), true );
×
721
                        }
722
                        $customer_object = new \WC_Customer( get_current_user_id(), true );
×
723
                }
724

725
                if ( ! $customer_object ) {
×
726
                        $customer_object = WC()->customer;
×
727
                }
728

729
                if ( is_callable( [ $customer_object, "get_$input" ] ) ) {
×
730
                        $value = $customer_object->{"get_$input"}();
×
731
                } elseif ( $customer_object->meta_exists( $input ) ) {
×
732
                        $value = $customer_object->get_meta( $input, true );
×
733
                }
734
                if ( '' === $value ) {
×
735
                        $value = null;
×
736
                }
737

738
                // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
739
                return apply_filters( 'default_checkout_' . $input, $value, $input );
×
740
        }
741

742
        /**
743
         * Add or update meta data not set in WC_Checkout::create_order().
744
         *
745
         * @param int                                  $order_id   Order ID.
746
         * @param array                                $meta_data  Order meta data.
747
         * @param array                                $input      Order properties.
748
         * @param \WPGraphQL\AppContext                $context    AppContext instance.
749
         * @param \GraphQL\Type\Definition\ResolveInfo $info       ResolveInfo instance.
750
         *
751
         * @throws \Exception Order cannot be retrieved.
752
         *
753
         * @return void
754
         */
755
        public static function update_order_meta( $order_id, $meta_data, $input, $context, $info ) {
756
                $order = \WC_Order_Factory::get_order( $order_id );
5✔
757
                if ( ! is_object( $order ) ) {
5✔
758
                        throw new \Exception( __( 'Failed to retrieve order.', 'wp-graphql-woocommerce' ) );
×
759
                }
760

761
                if ( $meta_data ) {
5✔
762
                        foreach ( $meta_data as $meta ) {
5✔
763
                                $order->update_meta_data( $meta['key'], $meta['value'] );
5✔
764
                        }
765
                }
766

767
                /**
768
                 * Action called before changes to order meta are saved.
769
                 *
770
                 * @param \WC_Order   $order      WC_Order instance.
771
                 * @param array       $meta_data  Order meta data.
772
                 * @param array       $props      Order props array.
773
                 * @param \WPGraphQL\AppContext  $context    Request AppContext instance.
774
                 * @param \GraphQL\Type\Definition\ResolveInfo $info       Request ResolveInfo instance.
775
                 */
776
                do_action( 'graphql_woocommerce_before_checkout_meta_save', $order, $meta_data, $input, $context, $info );
5✔
777

778
                $order->save();
5✔
779
        }
780
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc