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

wp-graphql / wp-graphql-woocommerce / 5604182421

pending completion
5604182421

push

github

web-flow
fix: Fixed shipping address getting skipped unnecessary (#752)

1 of 1 new or added line in 1 file covered. (100.0%)

10305 of 12439 relevant lines covered (82.84%)

53.98 hits per line

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

75.16
/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' ) );
4✔
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() ) ) {
5✔
48
                        return true;
1✔
49
                }
50

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

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

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

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

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

99
                if ( in_array( 'shipping', $skipped, true ) && ( \WC()->cart->needs_shipping_address() || \wc_ship_to_billing_address_only() ) ) {
5✔
100
                        foreach ( self::get_checkout_fields( 'shipping' ) as $field => $input_key ) {
×
101
                                $data[ "shipping_{$field}" ] = isset( $data[ "billing_{$field}" ] ) ? $data[ "billing_{$field}" ] : '';
×
102
                        }
103
                }
104

105
                // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
106
                return apply_filters( 'woocommerce_checkout_posted_data', $data, $input, $context, $info );
5✔
107
        }
108

109
        /**
110
         * Get an array of checkout fields.
111
         *
112
         * @param string  $fieldset Target fieldset.
113
         * @param boolean $prefixed Prefixed field keys with fieldset name.
114
         *
115
         * @return array
116
         */
117
        public static function get_checkout_fields( $fieldset = '', $prefixed = false ) {
118
                $fields = [
5✔
119
                        'billing'  => [
5✔
120
                                'first_name' => 'firstName',
5✔
121
                                'last_name'  => 'lastName',
5✔
122
                                'company'    => 'company',
5✔
123
                                'address_1'  => 'address1',
5✔
124
                                'address_2'  => 'address2',
5✔
125
                                'city'       => 'city',
5✔
126
                                'postcode'   => 'postcode',
5✔
127
                                'state'      => 'state',
5✔
128
                                'country'    => 'country',
5✔
129
                                'phone'      => 'phone',
5✔
130
                                'email'      => 'email',
5✔
131
                        ],
5✔
132
                        'shipping' => [
5✔
133
                                'first_name' => 'firstName',
5✔
134
                                'last_name'  => 'lastName',
5✔
135
                                'company'    => 'company',
5✔
136
                                'address_1'  => 'address1',
5✔
137
                                'address_2'  => 'address2',
5✔
138
                                'city'       => 'city',
5✔
139
                                'postcode'   => 'postcode',
5✔
140
                                'state'      => 'state',
5✔
141
                                'country'    => 'country',
5✔
142
                        ],
5✔
143
                        'account'  => [
5✔
144
                                'username' => 'username',
5✔
145
                                'password' => 'password',
5✔
146
                        ],
5✔
147
                        'order'    => [
5✔
148
                                'comments' => 'customerNote',
5✔
149
                        ],
5✔
150
                ];
5✔
151

152
                if ( $prefixed ) {
5✔
153
                        foreach ( $fields as $prefix => $values ) {
5✔
154
                                foreach ( $values as $index => $value ) {
5✔
155
                                        $fields[ $prefix ][ $index ] = "{$prefix}_{$value}";
5✔
156
                                }
157
                        }
158
                }
159

160
                if ( ! empty( $fieldset ) ) {
5✔
161
                        return ! empty( $fields[ $fieldset ] ) ? $fields[ $fieldset ] : [];
×
162
                }
163

164
                return $fields;
5✔
165
        }
166

167
        /**
168
         * Update customer and session data from the posted checkout data.
169
         *
170
         * @param array $data Order data.
171
         *
172
         * @return void
173
         */
174
        protected static function update_session( $data ) {
175
                // Update both shipping and billing to the passed billing address first if set.
176
                $address_fields = [
5✔
177
                        'first_name',
5✔
178
                        'last_name',
5✔
179
                        'company',
5✔
180
                        'email',
5✔
181
                        'phone',
5✔
182
                        'address_1',
5✔
183
                        'address_2',
5✔
184
                        'city',
5✔
185
                        'postcode',
5✔
186
                        'state',
5✔
187
                        'country',
5✔
188
                ];
5✔
189

190
                foreach ( $address_fields as $field ) {
5✔
191
                        self::set_customer_address_fields( $field, $data );
5✔
192
                }
193
                WC()->customer->save();
5✔
194

195
                // Update customer shipping and payment method to posted method.
196
                $chosen_shipping_methods = WC()->session->get( 'chosen_shipping_methods' );
5✔
197

198
                if ( is_array( $data['shipping_method'] ) ) {
5✔
199
                        foreach ( $data['shipping_method'] as $i => $value ) {
5✔
200
                                $chosen_shipping_methods[ $i ] = $value;
5✔
201
                        }
202
                }
203

204
                WC()->session->set( 'chosen_shipping_methods', $chosen_shipping_methods );
5✔
205
                WC()->session->set( 'chosen_payment_method', $data['payment_method'] );
5✔
206

207
                // Update cart totals now we have customer address.
208
                WC()->cart->calculate_totals();
5✔
209
        }
210

211
        /**
212
         * Clears customer address
213
         *
214
         * @param string $type  Address type.
215
         *
216
         * @return bool
217
         */
218
        protected static function clear_customer_address( $type = 'billing' ) {
219
                if ( 'billing' !== $type && 'shipping' !== $type ) {
5✔
220
                        return false;
×
221
                }
222

223
                $address = [
5✔
224
                        'first_name' => '',
5✔
225
                        'last_name'  => '',
5✔
226
                        'company'    => '',
5✔
227
                        'address_1'  => '',
5✔
228
                        'address_2'  => '',
5✔
229
                        'city'       => '',
5✔
230
                        'state'      => '',
5✔
231
                        'postcode'   => '',
5✔
232
                        'country'    => '',
5✔
233
                ];
5✔
234

235
                if ( 'billing' === $type ) {
5✔
236
                        $address = array_merge(
5✔
237
                                $address,
5✔
238
                                [
5✔
239
                                        'email' => '',
5✔
240
                                        'phone' => '',
5✔
241
                                ]
5✔
242
                        );
5✔
243
                }
244

245
                foreach ( $address as $prop => $value ) {
5✔
246
                        $setter = "set_{$type}_{$prop}";
5✔
247
                        WC()->customer->{$setter}( $value );
5✔
248
                }
249

250
                return true;
5✔
251
        }
252

253
        /**
254
         * Create a new customer account if needed.
255
         *
256
         * @param array $data Checkout data.
257
         *
258
         * @throws \GraphQL\Error\UserError When not able to create customer.
259
         *
260
         * @return void
261
         */
262
        protected static function process_customer( $data ) {
263
                // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
264
                $customer_id = apply_filters( 'woocommerce_checkout_customer_id', get_current_user_id() );
4✔
265

266
                if ( ! is_user_logged_in() && ( self::is_registration_required() || ! empty( $data['createaccount'] ) ) ) {
4✔
267
                        $username    = ! empty( $data['account_username'] ) ? $data['account_username'] : '';
1✔
268
                        $password    = ! empty( $data['account_password'] ) ? $data['account_password'] : '';
1✔
269
                        $customer_id = wc_create_new_customer(
1✔
270
                                $data['billing_email'],
1✔
271
                                $username,
1✔
272
                                $password,
1✔
273
                                [
1✔
274
                                        'first_name' => ! empty( $data['billing_first_name'] ) ? $data['billing_first_name'] : '',
1✔
275
                                        'last_name'  => ! empty( $data['billing_last_name'] ) ? $data['billing_last_name'] : '',
1✔
276
                                ]
1✔
277
                        );
1✔
278

279
                        if ( is_wp_error( $customer_id ) ) {
1✔
280
                                throw new UserError( $customer_id->get_error_message() );
×
281
                        }
282

283
                        wc_set_customer_auth_cookie( $customer_id );
1✔
284

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

288
                        // Also, recalculate cart totals to reveal any role-based discounts that were unavailable before registering.
289
                        WC()->cart->calculate_totals();
1✔
290
                }//end if
291

292
                // On multisite, ensure user exists on current site, if not add them before allowing login.
293
                if ( $customer_id && is_multisite() && is_user_logged_in() && ! is_user_member_of_blog() ) {
4✔
294
                        add_user_to_blog( get_current_blog_id(), $customer_id, 'customer' );
×
295
                }
296

297
                // Add customer info from other fields.
298
                // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
299
                if ( $customer_id && apply_filters( 'woocommerce_checkout_update_customer_data', true, WC()->checkout() ) ) {
4✔
300
                        $customer = new \WC_Customer( $customer_id );
2✔
301

302
                        if ( ! empty( $data['billing_first_name'] ) && '' === $customer->get_first_name() ) {
2✔
303
                                $customer->set_first_name( $data['billing_first_name'] );
×
304
                        }
305

306
                        if ( ! empty( $data['billing_last_name'] ) && '' === $customer->get_last_name() ) {
2✔
307
                                $customer->set_last_name( $data['billing_last_name'] );
×
308
                        }
309

310
                        // If the display name is an email, update to the user's full name.
311
                        if ( is_email( $customer->get_display_name() ) ) {
2✔
312
                                $customer->set_display_name( $customer->get_first_name() . ' ' . $customer->get_last_name() );
×
313
                        }
314

315
                        foreach ( $data as $key => $value ) {
2✔
316
                                // Use setters where available.
317
                                if ( is_callable( [ $customer, "set_{$key}" ] ) ) {
2✔
318
                                        $customer->{"set_{$key}"}( $value );
2✔
319

320
                                        // Store custom fields prefixed with wither shipping_ or billing_.
321
                                } elseif ( 0 === stripos( $key, 'billing_' ) || 0 === stripos( $key, 'shipping_' ) ) {
2✔
322
                                        $customer->update_meta_data( $key, $value );
2✔
323
                                }
324
                        }
325

326
                        // Action hook to adjust customer before save.
327
                        // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
328
                        do_action( 'woocommerce_checkout_update_customer', $customer, $data );
2✔
329

330
                        $customer->save();
2✔
331
                }//end if
332

333
                // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
334
                do_action( 'woocommerce_checkout_update_user_meta', $customer_id, $data );
4✔
335
        }
336

337
        /**
338
         * Set address field for customer.
339
         *
340
         * @param string $field String to update.
341
         * @param array  $data  Array of data to get the value from.
342
         *
343
         * @return void
344
         */
345
        protected static function set_customer_address_fields( $field, $data ) {
346
                $billing_value  = null;
5✔
347
                $shipping_value = null;
5✔
348

349
                if ( isset( $data[ "billing_{$field}" ] ) && is_callable( [ WC()->customer, "set_billing_{$field}" ] ) ) {
5✔
350
                        $billing_value  = $data[ "billing_{$field}" ];
5✔
351
                        $shipping_value = $data[ "billing_{$field}" ];
5✔
352
                }
353

354
                if ( isset( $data[ "shipping_{$field}" ] ) && is_callable( [ WC()->customer, "set_shipping_{$field}" ] ) ) {
5✔
355
                        $shipping_value = $data[ "shipping_{$field}" ];
4✔
356
                }
357

358
                if ( ! is_null( $billing_value ) && is_callable( [ WC()->customer, "set_billing_{$field}" ] ) ) {
5✔
359
                        WC()->customer->{"set_billing_{$field}"}( $billing_value );
5✔
360
                }
361

362
                if ( ! is_null( $shipping_value ) && is_callable( [ WC()->customer, "set_shipping_{$field}" ] ) ) {
5✔
363
                        WC()->customer->{"set_shipping_{$field}"}( $shipping_value );
5✔
364
                }
365
        }
366

367
        /**
368
         * Validates the posted checkout data based on field properties.
369
         *
370
         * @param array $data  Checkout data.
371
         *
372
         * @throws \GraphQL\Error\UserError Invalid input.
373
         *
374
         * @return void
375
         */
376
        protected static function validate_data( &$data ) {
377
                foreach ( self::get_checkout_fields( '', true ) as $fieldset_key => $fieldset ) {
5✔
378
                        $validate_fieldset = true;
5✔
379
                        if ( self::maybe_skip_fieldset( $fieldset_key, $data ) ) {
5✔
380
                                $validate_fieldset = false;
3✔
381
                        }
382

383
                        foreach ( $fieldset as $key => $field_label ) {
5✔
384
                                if ( ! isset( $data[ $key ] ) ) {
5✔
385
                                        continue;
5✔
386
                                }
387

388
                                if ( \wc_graphql_ends_with( $key, 'postcode' ) ) {
×
389
                                        $country      = isset( $data[ $fieldset_key . '_country' ] ) ? $data[ $fieldset_key . '_country' ] : WC()->customer->{"get_{$fieldset_key}_country"}();
×
390
                                        $data[ $key ] = \wc_format_postcode( $data[ $key ], $country );
×
391

392
                                        if ( $validate_fieldset && '' !== $data[ $key ] && ! \WC_Validation::is_postcode( $data[ $key ], $country ) ) {
×
393
                                                switch ( $country ) {
394
                                                        case 'IE':
×
395
                                                                /* translators: %1$s: field name, %2$s finder.eircode.ie URL */
396
                                                                $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' );
×
397
                                                                break;
×
398
                                                        default:
399
                                                                /* translators: %s: field name */
400
                                                                $postcode_validation_notice = sprintf( __( '%s is not a valid postcode / ZIP.', 'wp-graphql-woocommerce' ), $field_label );
×
401
                                                }
402
                                                // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
403
                                                throw new UserError( apply_filters( 'woocommerce_checkout_postcode_validation_notice', $postcode_validation_notice, $country, $data[ $key ] ) );
×
404
                                        }
405
                                }
406

407
                                if ( \wc_graphql_ends_with( $key, 'phone' ) ) {
×
408
                                        if ( $validate_fieldset && '' !== $data[ $key ] && ! \WC_Validation::is_phone( $data[ $key ] ) ) {
×
409
                                                /* translators: %s: phone number */
410
                                                throw new UserError( sprintf( __( '%s is not a valid phone number.', 'wp-graphql-woocommerce' ), $field_label ) );
×
411
                                        }
412
                                }
413

414
                                if ( \wc_graphql_ends_with( $key, 'email' ) && '' !== $data[ $key ] ) {
×
415
                                        $email_is_valid = is_email( $data[ $key ] );
×
416
                                        $data[ $key ]   = sanitize_email( $data[ $key ] );
×
417

418
                                        if ( $validate_fieldset && ! $email_is_valid ) {
×
419
                                                /* translators: %s: email address */
420
                                                throw new UserError( sprintf( __( '%s is not a valid email address.', 'wp-graphql-woocommerce' ), $field_label ) );
×
421
                                        }
422
                                }
423

424
                                if ( \wc_graphql_ends_with( $key, 'state' ) && '' !== $data[ $key ] ) {
×
425
                                        $country      = isset( $data[ $fieldset_key . '_country' ] ) ? $data[ $fieldset_key . '_country' ] : WC()->customer->{"get_{$fieldset_key}_country"}();
×
426
                                        $valid_states = WC()->countries->get_states( $country );
×
427

428
                                        if ( ! empty( $valid_states ) && is_array( $valid_states ) ) {
×
429
                                                $valid_state_values = array_map( 'wc_strtoupper', array_flip( array_map( 'wc_strtoupper', $valid_states ) ) );
×
430
                                                $data[ $key ]       = wc_strtoupper( $data[ $key ] );
×
431

432
                                                if ( isset( $valid_state_values[ $data[ $key ] ] ) ) {
×
433
                                                        // With this part we consider state value to be valid as well, convert it to the state key for the valid_states check below.
434
                                                        $data[ $key ] = $valid_state_values[ $data[ $key ] ];
×
435
                                                }
436

437
                                                if ( $validate_fieldset && ! in_array( $data[ $key ], $valid_state_values, true ) ) {
×
438
                                                        /* translators: 1: state field 2: valid states */
439
                                                        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 ) ) );
×
440
                                                }
441
                                        }
442
                                }
443
                        }//end foreach
444
                }//end foreach
445
        }
446

447
        /**
448
         * Validates that the checkout has enough info to proceed.
449
         *
450
         * @param array $data  An array of posted data.
451
         *
452
         * @throws \GraphQL\Error\UserError Invalid input.
453
         *
454
         * @return void
455
         */
456
        protected static function validate_checkout( &$data ) {
457
                self::validate_data( $data );
5✔
458
                WC()->checkout()->check_cart_items();
5✔
459

460
                // Throw cart validation errors stored in the session.
461
                $cart_item_errors = wc_get_notices( 'error' );
5✔
462

463
                if ( ! empty( $cart_item_errors ) ) {
5✔
464
                        $cart_item_error_msgs = implode( ' ', array_column( $cart_item_errors, 'notice' ) );
1✔
465
                        \wc_clear_notices();
1✔
466
                        throw new UserError( $cart_item_error_msgs );
1✔
467
                }
468

469
                if ( WC()->cart->needs_shipping() ) {
4✔
470
                        $shipping_country = WC()->customer->get_shipping_country();
3✔
471

472
                        if ( empty( $shipping_country ) ) {
3✔
473
                                throw new UserError( __( 'Please enter an address to continue.', 'wp-graphql-woocommerce' ) );
×
474
                        } elseif ( ! in_array( WC()->customer->get_shipping_country(), array_keys( WC()->countries->get_shipping_countries() ), true ) ) {
3✔
475
                                throw new UserError(
×
476
                                        sprintf(
×
477
                                                /* translators: %s: shipping location */
478
                                                __( 'Unfortunately, we do not ship %s. Please enter an alternative shipping address.', 'wp-graphql-woocommerce' ),
×
479
                                                WC()->countries->shipping_to_prefix() . ' ' . WC()->customer->get_shipping_country()
×
480
                                        )
×
481
                                );
×
482
                        } else {
483
                                $chosen_shipping_methods = WC()->session->get( 'chosen_shipping_methods' );
3✔
484

485
                                foreach ( WC()->shipping()->get_packages() as $i => $package ) {
3✔
486
                                        if ( ! isset( $chosen_shipping_methods[ $i ], $package['rates'][ $chosen_shipping_methods[ $i ] ] ) ) {
3✔
487
                                                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' ) );
×
488
                                        }
489
                                }
490
                        }
491
                }//end if
492

493
                if ( WC()->cart->needs_payment() ) {
4✔
494
                        $available_gateways = WC()->payment_gateways->get_available_payment_gateways();
4✔
495

496
                        if ( ! isset( $available_gateways[ $data['payment_method'] ] ) ) {
4✔
497
                                throw new UserError( __( 'Invalid payment method.', 'wp-graphql-woocommerce' ) );
×
498
                        } else {
499
                                $available_gateways[ $data['payment_method'] ]->validate_fields();
4✔
500
                        }
501
                }
502

503
                // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
504
                do_action( 'woocommerce_after_checkout_validation', $data, new WP_Error() );
4✔
505
        }
506

507
        /**
508
         * Process an order that does require payment.
509
         *
510
         * @param int    $order_id       Order ID.
511
         * @param string $payment_method Payment method.
512
         *
513
         * @throws \GraphQL\Error\UserError When payment method is invalid.
514
         *
515
         * @return array Processed payment results.
516
         */
517
        protected static function process_order_payment( $order_id, $payment_method ) {
518
                $available_gateways = WC()->payment_gateways->get_available_payment_gateways();
3✔
519

520
                if ( ! isset( $available_gateways[ $payment_method ] ) ) {
3✔
521
                        throw new UserError( __( 'Cannot process invalid payment method.', 'wp-graphql-woocommerce' ) );
×
522
                }
523

524
                // Store Order ID in session so it can be re-used after payment failure.
525
                WC()->session->set( 'order_awaiting_payment', $order_id );
3✔
526

527
                $process_payment_args = apply_filters(
3✔
528
                        "graphql_{$payment_method}_process_payment_args",
3✔
529
                        [ $order_id ],
3✔
530
                        $payment_method
3✔
531
                );
3✔
532

533
                // Process Payment.
534
                return $available_gateways[ $payment_method ]->process_payment( ...$process_payment_args );
3✔
535
        }
536

537
        /**
538
         * Process an order that doesn't require payment.
539
         *
540
         * @since 3.0.0
541
         * @param int    $order_id        Order ID.
542
         * @param string $transaction_id  Payment transaction ID.
543
         *
544
         * @throws \Exception Order cannot be retrieved.
545
         *
546
         * @return array
547
         */
548
        protected static function process_order_without_payment( $order_id, $transaction_id = '' ) {
549
                $order = wc_get_order( $order_id );
1✔
550
                if ( ! is_object( $order ) || ! is_a( $order, \WC_Order::class ) ) {
1✔
551
                        throw new \Exception( __( 'Failed to retrieve order.', 'wp-graphql-woocommerce' ) );
×
552
                }
553

554
                $order->payment_complete( $transaction_id );
1✔
555

556
                return [
1✔
557
                        'result'   => 'success',
1✔
558
                        // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
559
                        'redirect' => apply_filters( 'woocommerce_checkout_no_payment_needed_redirect', $order->get_checkout_order_received_url(), $order ),
1✔
560
                ];
1✔
561
        }
562

563
        /**
564
         * Process the checkout.
565
         *
566
         * @param array                                $data     Order data.
567
         * @param array                                $input    Input data describing order.
568
         * @param \WPGraphQL\AppContext                $context  AppContext instance.
569
         * @param \GraphQL\Type\Definition\ResolveInfo $info     ResolveInfo instance.
570
         * @param array                                $results  Order status.
571
         *
572
         * @throws \GraphQL\Error\UserError When validation fails.
573
         *
574
         * @return int Order ID.
575
         */
576
        public static function process_checkout( $data, $input, $context, $info, &$results = null ) {
577
                wc_maybe_define_constant( 'WOOCOMMERCE_CHECKOUT', true );
5✔
578
                wc_set_time_limit( 0 );
5✔
579

580
                do_action( 'woocommerce_before_checkout_process' ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
5✔
581

582
                if ( WC()->cart->is_empty() ) {
5✔
583
                        throw new UserError( __( 'Sorry, no session found.', 'wp-graphql-woocommerce' ) );
×
584
                }
585

586
                do_action( 'woocommerce_checkout_process', $data, $context, $info ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
5✔
587

588
                if ( ! empty( $input['billing']['overwrite'] ) && true === $input['billing']['overwrite'] ) {
5✔
589
                        self::clear_customer_address( 'billing' );
5✔
590
                }
591

592
                if ( ! empty( $input['shipping'] ) && ! empty( $input['shipping']['overwrite'] )
5✔
593
                        && true === $input['shipping']['overwrite'] ) {
5✔
594
                        self::clear_customer_address( 'shipping' );
×
595
                }
596

597
                // Update session for customer and totals.
598
                self::update_session( $data );
5✔
599

600
                // Validate posted data and cart items before proceeding.
601
                self::validate_checkout( $data );
5✔
602

603
                self::process_customer( $data );
4✔
604
                $order_id = WC()->checkout->create_order( $data );
4✔
605
                $order    = wc_get_order( $order_id );
4✔
606

607
                if ( is_wp_error( $order_id ) ) {
4✔
608
                        throw new UserError( $order_id->get_error_message() );
×
609
                }
610

611
                if ( ! is_object( $order ) || ! is_a( $order, \WC_Order::class ) ) {
4✔
612
                        throw new UserError( __( 'Unable to create order.', 'wp-graphql-woocommerce' ) );
×
613
                }
614

615
                // Add meta data.
616
                if ( ! empty( $input['metaData'] ) ) {
4✔
617
                        self::update_order_meta( $order_id, $input['metaData'], $input, $context, $info );
4✔
618
                }
619

620
                // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
621
                do_action( 'woocommerce_checkout_order_processed', $order_id, $data, $order );
4✔
622

623
                if ( WC()->cart->needs_payment() && ( empty( $input['isPaid'] ) ) ) {
4✔
624
                        $results = self::process_order_payment( $order_id, $data['payment_method'] );
3✔
625
                } else {
626
                        $transaction_id = ! empty( $input['transactionId'] ) ? $input['transactionId'] : '';
1✔
627

628
                        /**
629
                         * Use this to do some last minute transaction ID validation.
630
                         *
631
                         * @param bool        $is_valid        Is transaction ID valid.
632
                         * @param \WC_Order   $order           Order being processed.
633
                         * @param String|null $transaction_id  Order payment transaction ID.
634
                         * @param array       $data            Order data.
635
                         * @param array       $input           Order raw input data.
636
                         * @param \WPGraphQL\AppContext  $context         Request's AppContext instance.
637
                         * @param \GraphQL\Type\Definition\ResolveInfo $info            Request's ResolveInfo instance.
638
                         */
639
                        $valid = apply_filters(
1✔
640
                                'graphql_checkout_prepaid_order_validation',
1✔
641
                                true,
1✔
642
                                $order,
1✔
643
                                $transaction_id,
1✔
644
                                $data,
1✔
645
                                $input,
1✔
646
                                $context,
1✔
647
                                $info
1✔
648
                        );
1✔
649

650
                        if ( $valid ) {
1✔
651
                                $results = self::process_order_without_payment( $order_id, $transaction_id );
1✔
652
                        } else {
653
                                $results = [
×
654
                                        'result'   => 'failed',
×
655
                                        'redirect' => apply_filters(
×
656
                                                'graphql_woocommerce_checkout_payment_failed_redirect',
×
657
                                                $order->get_checkout_payment_url(),
×
658
                                                $order,
×
659
                                                $order_id,
×
660
                                                $transaction_id
×
661
                                        ),
×
662
                                ];
×
663
                        }
664
                }//end if
665

666
                if ( 'success' === $results['result'] ) {
4✔
667
                        wc_empty_cart();
4✔
668
                }
669

670
                return $order_id;
4✔
671
        }
672

673
        /**
674
         * Gets the value either from 3rd party logic or the customer object. Sets the default values in checkout fields.
675
         *
676
         * @param string $input Name of the input we want to grab data for. e.g. billing_country.
677
         * @return string The default value.
678
         */
679
        public static function get_value( $input ) {
680
                // Allow 3rd parties to short circuit the logic and return their own default value.
681
                // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
682
                $value = apply_filters( 'woocommerce_checkout_get_value', null, $input );
×
683
                if ( ! is_null( $value ) ) {
×
684
                        return $value;
×
685
                }
686

687
                /**
688
                 * For logged in customers, pull data from their account rather than the session which may contain incomplete data.
689
                 * Another reason is that WC sets shipping address to the billing address on the checkout updates unless the
690
                 * "shipToDifferentAddress" is set.
691
                 */
692
                $customer_object = false;
×
693
                if ( is_user_logged_in() ) {
×
694
                        // Load customer object, but keep it cached to avoid reloading it multiple times.
695
                        if ( is_null( self::$logged_in_customer ) ) {
×
696
                                self::$logged_in_customer = new \WC_Customer( get_current_user_id(), true );
×
697
                        }
698
                        $customer_object = new \WC_Customer( get_current_user_id(), true );
×
699
                }
700

701
                if ( ! $customer_object ) {
×
702
                        $customer_object = WC()->customer;
×
703
                }
704

705
                if ( is_callable( [ $customer_object, "get_$input" ] ) ) {
×
706
                        $value = $customer_object->{"get_$input"}();
×
707
                } elseif ( $customer_object->meta_exists( $input ) ) {
×
708
                        $value = $customer_object->get_meta( $input, true );
×
709
                }
710
                if ( '' === $value ) {
×
711
                        $value = null;
×
712
                }
713

714
                // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
715
                return apply_filters( 'default_checkout_' . $input, $value, $input );
×
716
        }
717

718
        /**
719
         * Add or update meta data not set in WC_Checkout::create_order().
720
         *
721
         * @param int                                  $order_id   Order ID.
722
         * @param array                                $meta_data  Order meta data.
723
         * @param array                                $input      Order properties.
724
         * @param \WPGraphQL\AppContext                $context    AppContext instance.
725
         * @param \GraphQL\Type\Definition\ResolveInfo $info       ResolveInfo instance.
726
         *
727
         * @throws \Exception Order cannot be retrieved.
728
         *
729
         * @return void
730
         */
731
        public static function update_order_meta( $order_id, $meta_data, $input, $context, $info ) {
732
                $order = \WC_Order_Factory::get_order( $order_id );
4✔
733
                if ( ! is_object( $order ) ) {
4✔
734
                        throw new \Exception( __( 'Failed to retrieve order.', 'wp-graphql-woocommerce' ) );
×
735
                }
736

737
                if ( $meta_data ) {
4✔
738
                        foreach ( $meta_data as $meta ) {
4✔
739
                                $order->update_meta_data( $meta['key'], $meta['value'] );
4✔
740
                        }
741
                }
742

743
                /**
744
                 * Action called before changes to order meta are saved.
745
                 *
746
                 * @param \WC_Order   $order      WC_Order instance.
747
                 * @param array       $meta_data  Order meta data.
748
                 * @param array       $props      Order props array.
749
                 * @param \WPGraphQL\AppContext  $context    Request AppContext instance.
750
                 * @param \GraphQL\Type\Definition\ResolveInfo $info       Request ResolveInfo instance.
751
                 */
752
                do_action( 'graphql_woocommerce_before_checkout_meta_save', $order, $meta_data, $input, $context, $info );
4✔
753

754
                $order->save();
4✔
755
        }
756
}
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