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

wp-graphql / wp-graphql-woocommerce / 27386231983

12 Jun 2026 12:25AM UTC coverage: 91.791%. Remained the same
27386231983

Pull #1019

github

web-flow
Merge 46a421a18 into 01876f534
Pull Request #1019: fix: address WordPress.org plugin review (rename + prefixing + headers)

1327 of 1584 new or added lines in 200 files covered. (83.78%)

1 existing line in 1 file now uncovered.

18505 of 20160 relevant lines covered (91.79%)

151.6 hits per line

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

78.92
/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() ) ) {
22✔
48
                        return true;
7✔
49
                }
50

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

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

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

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

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

100
                if ( ! empty( $input['fees'] ) ) {
22✔
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() ) ) {
22✔
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 );
22✔
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 = [
22✔
144
                        'billing'  => [
22✔
145
                                'first_name' => 'firstName',
22✔
146
                                'last_name'  => 'lastName',
22✔
147
                                'company'    => 'company',
22✔
148
                                'address_1'  => 'address1',
22✔
149
                                'address_2'  => 'address2',
22✔
150
                                'city'       => 'city',
22✔
151
                                'postcode'   => 'postcode',
22✔
152
                                'state'      => 'state',
22✔
153
                                'country'    => 'country',
22✔
154
                                'phone'      => 'phone',
22✔
155
                                'email'      => 'email',
22✔
156
                        ],
22✔
157
                        'shipping' => [
22✔
158
                                'first_name' => 'firstName',
22✔
159
                                'last_name'  => 'lastName',
22✔
160
                                'company'    => 'company',
22✔
161
                                'address_1'  => 'address1',
22✔
162
                                'address_2'  => 'address2',
22✔
163
                                'city'       => 'city',
22✔
164
                                'postcode'   => 'postcode',
22✔
165
                                'state'      => 'state',
22✔
166
                                'country'    => 'country',
22✔
167
                                'phone'      => 'phone',
22✔
168
                        ],
22✔
169
                        'account'  => [
22✔
170
                                'username' => 'username',
22✔
171
                                'password' => 'password',
22✔
172
                        ],
22✔
173
                        'order'    => [
22✔
174
                                'comments' => 'customerNote',
22✔
175
                        ],
22✔
176
                ];
22✔
177

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

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

190
                return $fields;
22✔
191
        }
192

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

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

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

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

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

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

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

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

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

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

276
                return true;
15✔
277
        }
278

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

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

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

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

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

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

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

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

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

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

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

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

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

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

358
                        $customer->save();
9✔
359
                }//end if
360

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

518
                if ( WC()->cart->needs_payment() ) {
22✔
519
                        $available_gateways = WC()->payment_gateways->get_available_payment_gateways();
22✔
520
                        if ( ! isset( $available_gateways[ $data['payment_method'] ] ) ) {
22✔
NEW
521
                                $errors->add( 'payment', __( 'Invalid payment method.', 'graphql-for-ecommerce' ) );
×
522
                        } else {
523
                                $available_gateways[ $data['payment_method'] ]->validate_fields();
22✔
524
                        }
525
                }
526

527
                // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
528
                do_action( 'woocommerce_after_checkout_validation', $data, $errors );
22✔
529
                do_action( 'graphql_woocommerce_after_checkout_validation', $data, $errors );
22✔
530
        }
531

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

545
                if ( ! isset( $available_gateways[ $payment_method ] ) ) {
10✔
NEW
546
                        throw new UserError( __( 'Cannot process invalid payment method.', 'graphql-for-ecommerce' ) );
×
547
                }
548

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

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

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

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

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

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

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

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

607
                if ( WC()->cart->is_empty() ) {
22✔
NEW
608
                        throw new UserError( __( 'Sorry, no session found.', 'graphql-for-ecommerce' ) );
×
609
                }
610

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

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

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

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

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

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

636
                if ( 0 < wc_notice_count( 'error' ) ) {
22✔
637
                        throw new UserError( __( 'Failed to validate checkout', 'graphql-for-ecommerce' ) );
5✔
638
                }
639

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

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

648
                if ( ! is_object( $order ) || ! is_a( $order, \WC_Order::class ) ) {
17✔
NEW
649
                        throw new UserError( __( 'Unable to create order.', 'graphql-for-ecommerce' ) );
×
650
                }
651

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

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

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

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

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

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

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

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

714
                return $order_id;
17✔
715
        }
716

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

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

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

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

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

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

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

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

798
                $order->save();
7✔
799
        }
800
}
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