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

wp-graphql / wp-graphql-woocommerce / 23769685853

30 Mar 2026 09:56PM UTC coverage: 89.92% (+0.5%) from 89.424%
23769685853

Pull #1003

github

web-flow
Merge d180fa72b into 6fb7b226f
Pull Request #1003: devops: WC email template tests, COT cursor HPOS fix, checkout account auth

79 of 85 new or added lines in 6 files covered. (92.94%)

2 existing lines in 2 files now uncovered.

15959 of 17748 relevant lines covered (89.92%)

143.41 hits per line

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

78.86
/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' ) );
15✔
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() ) ) {
21✔
48
                        return true;
8✔
49
                }
50

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

55
                return false;
21✔
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 = [
21✔
69
                        'terms'                     => (int) isset( $input['terms'] ),
21✔
70
                        'createaccount'             => (int) ! empty( $input['account'] ),
21✔
71
                        'authenticate_account'      => ! empty( $input['account']['authenticate'] ),
21✔
72
                        'payment_method'            => isset( $input['paymentMethod'] ) ? $input['paymentMethod'] : '',
21✔
73
                        'shipping_method'           => isset( $input['shippingMethod'] ) ? $input['shippingMethod'] : '',
21✔
74
                        'ship_to_different_address' => ! empty( $input['shipToDifferentAddress'] ) && ! wc_ship_to_billing_address_only(),
21✔
75
                ];
21✔
76

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

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

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

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

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

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

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

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

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

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

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

189
                return $fields;
21✔
190
        }
191

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

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

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

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

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

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

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

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

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

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

275
                return true;
14✔
276
        }
277

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

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

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

308
                        if ( ! empty( $data['authenticate_account'] ) ) {
2✔
309
                                wc_set_customer_auth_cookie( $customer_id );
1✔
310

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

315
                        // Also, recalculate cart totals to reveal any role-based discounts that were unavailable before registering.
316
                        WC()->cart->calculate_totals();
2✔
317
                }//end if
318

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

324
                // Add customer info from other fields.
325
                // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
326
                if ( $customer_id && apply_filters( 'woocommerce_checkout_update_customer_data', true, WC()->checkout() ) ) {
16✔
327
                        $customer = new \WC_Customer( $customer_id );
8✔
328

329
                        if ( ! empty( $data['billing_first_name'] ) && '' === $customer->get_first_name() ) {
8✔
330
                                $customer->set_first_name( $data['billing_first_name'] );
5✔
331
                        }
332

333
                        if ( ! empty( $data['billing_last_name'] ) && '' === $customer->get_last_name() ) {
8✔
334
                                $customer->set_last_name( $data['billing_last_name'] );
5✔
335
                        }
336

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

342
                        foreach ( $data as $key => $value ) {
8✔
343
                                // Use setters where available.
344
                                if ( is_callable( [ $customer, "set_{$key}" ] ) ) {
8✔
345
                                        $customer->{"set_{$key}"}( $value );
8✔
346

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

353
                        // Action hook to adjust customer before save.
354
                        // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
355
                        do_action( 'woocommerce_checkout_update_customer', $customer, $data );
8✔
356

357
                        $customer->save();
8✔
358
                }//end if
359

360
                // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
361
                do_action( 'woocommerce_checkout_update_user_meta', $customer_id, $data );
16✔
362
        }
363

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

376
                if ( isset( $data[ "billing_{$field}" ] ) && is_callable( [ WC()->customer, "set_billing_{$field}" ] ) ) {
21✔
377
                        $billing_value  = $data[ "billing_{$field}" ];
21✔
378
                        $shipping_value = $data[ "billing_{$field}" ];
21✔
379
                }
380

381
                if ( isset( $data[ "shipping_{$field}" ] ) && is_callable( [ WC()->customer, "set_shipping_{$field}" ] ) ) {
21✔
382
                        $shipping_value = $data[ "shipping_{$field}" ];
13✔
383
                }
384

385
                if ( ! is_null( $billing_value ) && is_callable( [ WC()->customer, "set_billing_{$field}" ] ) ) {
21✔
386
                        WC()->customer->{"set_billing_{$field}"}( $billing_value );
21✔
387
                }
388

389
                if ( ! is_null( $shipping_value ) && is_callable( [ WC()->customer, "set_shipping_{$field}" ] ) ) {
21✔
390
                        WC()->customer->{"set_shipping_{$field}"}( $shipping_value );
21✔
391
                }
392
        }
393

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

410
                        foreach ( $fieldset as $key => $field_label ) {
21✔
411
                                if ( ! isset( $data[ $key ] ) ) {
21✔
412
                                        continue;
21✔
413
                                }
414

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

419
                                        if ( $validate_fieldset && '' !== $data[ $key ] && ! \WC_Validation::is_postcode( $data[ $key ], $country ) ) {
×
420
                                                switch ( $country ) {
421
                                                        case 'IE':
×
422
                                                                /* translators: %1$s: field name, %2$s finder.eircode.ie URL */
423
                                                                $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' );
×
424
                                                                break;
×
425
                                                        default:
426
                                                                /* translators: %s: field name */
427
                                                                $postcode_validation_notice = sprintf( __( '%s is not a valid postcode / ZIP.', 'wp-graphql-woocommerce' ), $field_label );
×
428
                                                }
429
                                                // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
430
                                                throw new UserError( apply_filters( 'woocommerce_checkout_postcode_validation_notice', $postcode_validation_notice, $country, $data[ $key ] ) );
×
431
                                        }
432
                                }
433

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

441
                                if ( \str_ends_with( $key, 'email' ) && '' !== $data[ $key ] ) {
×
442
                                        $email_is_valid = is_email( $data[ $key ] );
×
443
                                        $data[ $key ]   = sanitize_email( $data[ $key ] );
×
444

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

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

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

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

464
                                                if ( $validate_fieldset && ! in_array( $data[ $key ], $valid_state_values, true ) ) {
×
465
                                                        /* translators: 1: state field 2: valid states */
466
                                                        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 ) ) );
×
467
                                                }
468
                                        }
469
                                }
470
                        }//end foreach
471
                }//end foreach
472
        }
473

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

488
                if ( empty( $data['woocommerce_checkout_update_totals'] ) && empty( $data['terms'] ) && ! empty( $data['terms-field'] ) ) {
21✔
489
                        $errors->add( 'terms', __( 'Please read and accept the terms and conditions to proceed with your order.', 'wp-graphql-woocommerce' ) );
×
490
                }
491

492
                if ( WC()->cart->needs_shipping() ) {
21✔
493
                        $shipping_country = WC()->customer->get_shipping_country();
14✔
494

495
                        if ( empty( $shipping_country ) ) {
14✔
496
                                $errors->add( 'shipping', __( 'Please enter an address to continue.', 'wp-graphql-woocommerce' ) );
×
497
                        } elseif ( ! in_array( WC()->customer->get_shipping_country(), array_keys( WC()->countries->get_shipping_countries() ), true ) ) {
14✔
498
                                $errors->add(
×
499
                                        'shipping',
×
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' );
14✔
508

509
                                foreach ( WC()->shipping()->get_packages() as $i => $package ) {
14✔
510
                                        if ( ! isset( $chosen_shipping_methods[ $i ], $package['rates'][ $chosen_shipping_methods[ $i ] ] ) ) {
14✔
511
                                                $errors->add( 'shipping', __( '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() ) {
21✔
518
                        $available_gateways = WC()->payment_gateways->get_available_payment_gateways();
21✔
519
                        if ( ! isset( $available_gateways[ $data['payment_method'] ] ) ) {
21✔
UNCOV
520
                                $errors->add( 'payment', __( 'Invalid payment method.', 'wp-graphql-woocommerce' ) );
×
521
                        } else {
522
                                $available_gateways[ $data['payment_method'] ]->validate_fields();
21✔
523
                        }
524
                }
525

526
                // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
527
                do_action( 'woocommerce_after_checkout_validation', $data, $errors );
21✔
528
                do_action( 'graphql_woocommerce_after_checkout_validation', $data, $errors );
21✔
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();
9✔
543

544
                if ( ! isset( $available_gateways[ $payment_method ] ) ) {
9✔
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 );
9✔
550

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

557
                // Process Payment.
558
                return $available_gateways[ $payment_method ]->process_payment( ...$process_payment_args );
9✔
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 );
7✔
574
                if ( ! is_object( $order ) || ! is_a( $order, \WC_Order::class ) ) {
7✔
575
                        throw new \Exception( __( 'Failed to retrieve order.', 'wp-graphql-woocommerce' ) );
×
576
                }
577

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

580
                return [
7✔
581
                        'result'   => 'success',
7✔
582
                        // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
583
                        'redirect' => apply_filters( 'woocommerce_checkout_no_payment_needed_redirect', $order->get_checkout_order_received_url(), $order ),
7✔
584
                ];
7✔
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 );
21✔
602
                wc_set_time_limit( 0 );
21✔
603

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

606
                if ( WC()->cart->is_empty() ) {
21✔
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
21✔
611

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

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

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

624
                // Validate posted data and cart items before proceeding.
625
                $errors = new WP_Error();
21✔
626
                self::validate_checkout( $data, $errors );
21✔
627

628
                foreach ( $errors->errors as $code => $messages ) {
21✔
629
                        $data = $errors->get_error_data( $code );
×
630
                        foreach ( $messages as $message ) {
×
631
                                wc_add_notice( $message, 'error', $data );
×
632
                        }
633
                }
634

635
                if ( 0 < wc_notice_count( 'error' ) ) {
21✔
636
                        throw new UserError( __( 'Failed to validate checkout', 'wp-graphql-woocommerce' ) );
5✔
637
                }
638

639
                self::process_customer( $data );
16✔
640
                $order_id = WC()->checkout->create_order( $data );
16✔
641
                $order    = wc_get_order( $order_id );
16✔
642

643
                if ( is_wp_error( $order_id ) ) {
16✔
644
                        throw new UserError( $order_id->get_error_message() );
×
645
                }
646

647
                if ( ! is_object( $order ) || ! is_a( $order, \WC_Order::class ) ) {
16✔
648
                        throw new UserError( __( 'Unable to create order.', 'wp-graphql-woocommerce' ) );
×
649
                }
650

651
                // Add meta data.
652
                if ( ! empty( $input['metaData'] ) ) {
16✔
653
                        self::update_order_meta( $order_id, $input['metaData'], $input, $context, $info );
6✔
654

655
                        // Refresh the order object so the hook below receives the updated meta.
656
                        $order = wc_get_order( $order_id );
6✔
657

658
                        if ( ! is_object( $order ) || ! is_a( $order, \WC_Order::class ) ) {
6✔
659
                                throw new UserError( __( 'Failed to get order with updated meta.', 'wp-graphql-woocommerce' ) );
×
660
                        }
661
                }
662

663
                // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
664
                do_action( 'woocommerce_checkout_order_processed', $order_id, $data, $order );
16✔
665

666
                if ( WC()->cart->needs_payment() && ( empty( $input['isPaid'] ) ) ) {
16✔
667
                        $results = self::process_order_payment( $order_id, $data['payment_method'] );
9✔
668
                } else {
669
                        $transaction_id = ! empty( $input['transactionId'] ) ? $input['transactionId'] : '';
7✔
670

671
                        /**
672
                         * Use this to do some last minute transaction ID validation.
673
                         *
674
                         * @param bool        $is_valid        Is transaction ID valid.
675
                         * @param \WC_Order   $order           Order being processed.
676
                         * @param String|null $transaction_id  Order payment transaction ID.
677
                         * @param array       $data            Order data.
678
                         * @param array       $input           Order raw input data.
679
                         * @param \WPGraphQL\AppContext  $context         Request's AppContext instance.
680
                         * @param \GraphQL\Type\Definition\ResolveInfo $info            Request's ResolveInfo instance.
681
                         */
682
                        $valid = apply_filters(
7✔
683
                                'graphql_checkout_prepaid_order_validation',
7✔
684
                                true,
7✔
685
                                $order,
7✔
686
                                $transaction_id,
7✔
687
                                $data,
7✔
688
                                $input,
7✔
689
                                $context,
7✔
690
                                $info
7✔
691
                        );
7✔
692

693
                        if ( $valid ) {
7✔
694
                                $results = self::process_order_without_payment( $order_id, $transaction_id );
7✔
695
                        } else {
696
                                $results = [
×
697
                                        'result'   => 'failed',
×
698
                                        'redirect' => apply_filters(
×
699
                                                'graphql_woocommerce_checkout_payment_failed_redirect',
×
700
                                                $order->get_checkout_payment_url(),
×
701
                                                $order,
×
702
                                                $order_id,
×
703
                                                $transaction_id
×
704
                                        ),
×
705
                                ];
×
706
                        }
707
                }//end if
708

709
                if ( 'success' === $results['result'] ) {
16✔
710
                        wc_empty_cart();
16✔
711
                }
712

713
                return $order_id;
16✔
714
        }
715

716
        /**
717
         * Gets the value either from 3rd party logic or the customer object. Sets the default values in checkout fields.
718
         *
719
         * @param string $input Name of the input we want to grab data for. e.g. billing_country.
720
         * @return string The default value.
721
         */
722
        public static function get_value( $input ) {
723
                // Allow 3rd parties to short circuit the logic and return their own default value.
724
                // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
725
                $value = apply_filters( 'woocommerce_checkout_get_value', null, $input );
7✔
726
                if ( ! is_null( $value ) ) {
7✔
727
                        return $value;
×
728
                }
729

730
                /**
731
                 * For logged in customers, pull data from their account rather than the session which may contain incomplete data.
732
                 * Another reason is that WC sets shipping address to the billing address on the checkout updates unless the
733
                 * "shipToDifferentAddress" is set.
734
                 */
735
                $customer_object = false;
7✔
736
                if ( is_user_logged_in() ) {
7✔
737
                        // Load customer object, but keep it cached to avoid reloading it multiple times.
738
                        if ( is_null( self::$logged_in_customer ) ) {
×
739
                                self::$logged_in_customer = new \WC_Customer( get_current_user_id(), true );
×
740
                        }
741
                        $customer_object = new \WC_Customer( get_current_user_id(), true );
×
742
                }
743

744
                if ( ! $customer_object ) {
7✔
745
                        $customer_object = WC()->customer;
7✔
746
                }
747

748
                if ( is_callable( [ $customer_object, "get_$input" ] ) ) {
7✔
749
                        $value = $customer_object->{"get_$input"}();
7✔
750
                } elseif ( $customer_object->meta_exists( $input ) ) {
×
751
                        $value = $customer_object->get_meta( $input, true );
×
752
                }
753
                if ( '' === $value ) {
7✔
754
                        $value = null;
×
755
                }
756

757
                // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
758
                return apply_filters( 'default_checkout_' . $input, $value, $input );
7✔
759
        }
760

761
        /**
762
         * Add or update meta data not set in WC_Checkout::create_order().
763
         *
764
         * @param int                                  $order_id   Order ID.
765
         * @param array                                $meta_data  Order meta data.
766
         * @param array                                $input      Order properties.
767
         * @param \WPGraphQL\AppContext                $context    AppContext instance.
768
         * @param \GraphQL\Type\Definition\ResolveInfo $info       ResolveInfo instance.
769
         *
770
         * @throws \Exception Order cannot be retrieved.
771
         *
772
         * @return void
773
         */
774
        public static function update_order_meta( $order_id, $meta_data, $input, $context, $info ) {
775
                $order = \WC_Order_Factory::get_order( $order_id );
6✔
776
                if ( ! is_object( $order ) ) {
6✔
777
                        throw new \Exception( __( 'Failed to retrieve order.', 'wp-graphql-woocommerce' ) );
×
778
                }
779

780
                if ( $meta_data ) {
6✔
781
                        foreach ( $meta_data as $meta ) {
6✔
782
                                $order->update_meta_data( $meta['key'], $meta['value'] );
6✔
783
                        }
784
                }
785

786
                /**
787
                 * Action called before changes to order meta are saved.
788
                 *
789
                 * @param \WC_Order   $order      WC_Order instance.
790
                 * @param array       $meta_data  Order meta data.
791
                 * @param array       $props      Order props array.
792
                 * @param \WPGraphQL\AppContext  $context    Request AppContext instance.
793
                 * @param \GraphQL\Type\Definition\ResolveInfo $info       Request ResolveInfo instance.
794
                 */
795
                do_action( 'graphql_woocommerce_before_checkout_meta_save', $order, $meta_data, $input, $context, $info );
6✔
796

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

© 2026 Coveralls, Inc