Completed
Pull Request — master (#739)
by Roy
01:24
created

WC_Stripe_Order_Handler::capture_payment()   C

Complexity

Conditions 15
Paths 205

Size

Total Lines 53

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 15
nc 205
nop 1
dl 0
loc 53
rs 5.0208
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
if ( ! defined( 'ABSPATH' ) ) {
3
	exit;
4
}
5
6
/**
7
 * Handles and process orders from asyncronous flows.
8
 *
9
 * @since 4.0.0
10
 */
11
class WC_Stripe_Order_Handler extends WC_Stripe_Payment_Gateway {
12
	private static $_this;
13
	public $retry_interval;
14
15
	/**
16
	 * Constructor.
17
	 *
18
	 * @since 4.0.0
19
	 * @version 4.0.0
20
	 */
21
	public function __construct() {
22
		self::$_this = $this;
23
24
		$this->retry_interval = 1;
25
26
		add_action( 'wp', array( $this, 'maybe_process_redirect_order' ) );
27
		add_action( 'woocommerce_order_status_on-hold_to_processing', array( $this, 'capture_payment' ) );
28
		add_action( 'woocommerce_order_status_on-hold_to_completed', array( $this, 'capture_payment' ) );
29
		add_action( 'woocommerce_order_status_on-hold_to_cancelled', array( $this, 'cancel_payment' ) );
30
		add_action( 'woocommerce_order_status_on-hold_to_refunded', array( $this, 'cancel_payment' ) );
31
	}
32
33
	/**
34
	 * Public access to instance object.
35
	 *
36
	 * @since 4.0.0
37
	 * @version 4.0.0
38
	 */
39
	public static function get_instance() {
40
		return self::$_this;
41
	}
42
43
	/**
44
	 * Processes payments.
45
	 * Note at this time the original source has already been
46
	 * saved to a customer card (if applicable) from process_payment.
47
	 *
48
	 * @since 4.0.0
49
	 * @since 4.1.8 Add $previous_error parameter.
50
	 * @param int $order_id
51
	 * @param bool $retry
52
	 * @param mix $previous_error Any error message from previous request.
53
	 */
54
	public function process_redirect_payment( $order_id, $retry = true, $previous_error = false ) {
55
		try {
56
			$source = wc_clean( $_GET['source'] );
57
58
			if ( empty( $source ) ) {
59
				return;
60
			}
61
62
			if ( empty( $order_id ) ) {
63
				return;
64
			}
65
66
			$order = wc_get_order( $order_id );
67
68
			if ( ! is_object( $order ) ) {
69
				return;
70
			}
71
72
			if ( 'processing' === $order->get_status() || 'completed' === $order->get_status() || 'on-hold' === $order->get_status() ) {
73
				return;
74
			}
75
76
			// Result from Stripe API request.
77
			$response = null;
78
79
			// This will throw exception if not valid.
80
			$this->validate_minimum_order_amount( $order );
81
82
			WC_Stripe_Logger::log( "Info: (Redirect) Begin processing payment for order $order_id for the amount of {$order->get_total()}" );
83
84
			/**
85
			 * First check if the source is chargeable at this time. If not,
86
			 * webhook will take care of it later.
87
			 */
88
			$source_info = WC_Stripe_API::retrieve( 'sources/' . $source );
89
90
			if ( ! empty( $source_info->error ) ) {
91
				throw new WC_Stripe_Exception( print_r( $source_info, true ), $source_info->error->message );
92
			}
93
94
			if ( 'failed' === $source_info->status || 'canceled' === $source_info->status ) {
95
				throw new WC_Stripe_Exception( print_r( $source_info, true ), __( 'Unable to process this payment, please try again or use alternative method.', 'woocommerce-gateway-stripe' ) );
96
			}
97
98
			// If already consumed, then ignore request.
99
			if ( 'consumed' === $source_info->status ) {
100
				return;
101
			}
102
103
			// If not chargeable, then ignore request.
104
			if ( 'chargeable' !== $source_info->status ) {
105
				return;
106
			}
107
108
			// Prep source object.
109
			$source_object           = new stdClass();
110
			$source_object->token_id = '';
111
			$source_object->customer = $this->get_stripe_customer_id( $order );
112
			$source_object->source   = $source_info->id;
113
			$source_object->status   = 'chargeable';
114
115
			/* If we're doing a retry and source is chargeable, we need to pass
116
			 * a different idempotency key and retry for success.
117
			 */
118
			if ( $this->need_update_idempotency_key( $source_object, $previous_error ) ) {
119
				add_filter( 'wc_stripe_idempotency_key', array( $this, 'change_idempotency_key' ), 10, 2 );
120
			}
121
122
			// Make the request.
123
			$response = WC_Stripe_API::request( $this->generate_payment_request( $order, $source_object ), 'charges', 'POST', true );
124
			$headers  = $response['headers'];
125
			$response = $response['body'];
126
127 View Code Duplication
			if ( ! empty( $response->error ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
128
				// Customer param wrong? The user may have been deleted on stripe's end. Remove customer_id. Can be retried without.
129
				if ( $this->is_no_such_customer_error( $response->error ) ) {
130
					if ( WC_Stripe_Helper::is_wc_lt( '3.0' ) ) {
131
						delete_user_meta( $order->customer_user, '_stripe_customer_id' );
132
						delete_post_meta( $order_id, '_stripe_customer_id' );
133
					} else {
134
						delete_user_meta( $order->get_customer_id(), '_stripe_customer_id' );
135
						$order->delete_meta_data( '_stripe_customer_id' );
136
						$order->save();
137
					}
138
				}
139
140
				if ( $this->is_no_such_token_error( $response->error ) && $prepared_source->token_id ) {
0 ignored issues
show
Bug introduced by
The variable $prepared_source does not exist. Did you mean $source?

This check looks for variables that are accessed but have not been defined. It raises an issue if it finds another variable that has a similar name.

The variable may have been renamed without also renaming all references.

Loading history...
141
					// Source param wrong? The CARD may have been deleted on stripe's end. Remove token and show message.
142
					$wc_token = WC_Payment_Tokens::get( $prepared_source->token_id );
0 ignored issues
show
Bug introduced by
The variable $prepared_source does not exist. Did you mean $source?

This check looks for variables that are accessed but have not been defined. It raises an issue if it finds another variable that has a similar name.

The variable may have been renamed without also renaming all references.

Loading history...
143
					$wc_token->delete();
144
					$localized_message = __( 'This card is no longer available and has been removed.', 'woocommerce-gateway-stripe' );
145
					$order->add_order_note( $localized_message );
146
					throw new WC_Stripe_Exception( print_r( $response, true ), $localized_message );
147
				}
148
149
				// We want to retry.
150
				if ( $this->is_retryable_error( $response->error ) ) {
151
					if ( $retry ) {
152
						// Don't do anymore retries after this.
153
						if ( 5 <= $this->retry_interval ) {
154
							return $this->process_redirect_payment( $order_id, false, $response->error );
155
						}
156
157
						sleep( $this->retry_interval );
158
159
						$this->retry_interval++;
160
						return $this->process_redirect_payment( $order_id, true, $response->error );
161
					} else {
162
						$localized_message = __( 'Sorry, we are unable to process your payment at this time. Please retry later.', 'woocommerce-gateway-stripe' );
163
						$order->add_order_note( $localized_message );
164
						throw new WC_Stripe_Exception( print_r( $response, true ), $localized_message );
165
					}
166
				}
167
168
				$localized_messages = WC_Stripe_Helper::get_localized_messages();
169
170
				if ( 'card_error' === $response->error->type ) {
171
					$message = isset( $localized_messages[ $response->error->code ] ) ? $localized_messages[ $response->error->code ] : $response->error->message;
172
				} else {
173
					$message = isset( $localized_messages[ $response->error->type ] ) ? $localized_messages[ $response->error->type ] : $response->error->message;
174
				}
175
176
				throw new WC_Stripe_Exception( print_r( $response, true ), $message );
177
			}
178
179
			// To prevent double processing the order on WC side.
180
			if ( ! $this->is_original_request( $headers ) ) {
181
				return;
182
			}
183
184
			do_action( 'wc_gateway_stripe_process_redirect_payment', $response, $order );
185
186
			$this->process_response( $response, $order );
187
188
		} catch ( WC_Stripe_Exception $e ) {
189
			WC_Stripe_Logger::log( 'Error: ' . $e->getMessage() );
190
191
			do_action( 'wc_gateway_stripe_process_redirect_payment_error', $e, $order );
0 ignored issues
show
Bug introduced by
The variable $order does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
192
193
			/* translators: error message */
194
			$order->update_status( 'failed', sprintf( __( 'Stripe payment failed: %s', 'woocommerce-gateway-stripe' ), $e->getLocalizedMessage() ) );
195
196
			wc_add_notice( $e->getLocalizedMessage(), 'error' );
197
			wp_safe_redirect( wc_get_checkout_url() );
198
			exit;
199
		}
200
	}
201
202
	/**
203
	 * Processses the orders that are redirected.
204
	 *
205
	 * @since 4.0.0
206
	 * @version 4.0.0
207
	 */
208
	public function maybe_process_redirect_order() {
209
		if ( ! is_order_received_page() || empty( $_GET['client_secret'] ) || empty( $_GET['source'] ) ) {
210
			return;
211
		}
212
213
		$order_id = wc_clean( $_GET['order_id'] );
214
215
		$this->process_redirect_payment( $order_id );
216
	}
217
218
	/**
219
	 * Capture payment when the order is changed from on-hold to complete or processing.
220
	 *
221
	 * @since 3.1.0
222
	 * @version 4.0.0
223
	 * @param  int $order_id
224
	 */
225
	public function capture_payment( $order_id ) {
226
		$order = wc_get_order( $order_id );
227
228
		if ( 'stripe' === ( WC_Stripe_Helper::is_wc_lt( '3.0' ) ? $order->payment_method : $order->get_payment_method() ) ) {
229
			$charge             = WC_Stripe_Helper::is_wc_lt( '3.0' ) ? get_post_meta( $order_id, '_transaction_id', true ) : $order->get_transaction_id();
230
			$captured           = WC_Stripe_Helper::is_wc_lt( '3.0' ) ? get_post_meta( $order_id, '_stripe_charge_captured', true ) : $order->get_meta( '_stripe_charge_captured', true );
231
			$is_stripe_captured = false;
232
233
			if ( $charge && 'no' === $captured ) {
234
				$order_total = $order->get_total();
235
236
				if ( 0 < $order->get_total_refunded() ) {
237
					$order_total = $order_total - $order->get_total_refunded();
238
				}
239
240
				// First retrieve charge to see if it has been captured.
241
				$result = WC_Stripe_API::retrieve( 'charges/' . $charge );
242
243
				if ( ! empty( $result->error ) ) {
244
					/* translators: error message */
245
					$order->add_order_note( sprintf( __( 'Unable to capture charge! %s', 'woocommerce-gateway-stripe' ), $result->error->message ) );
246
				} elseif ( false === $result->captured ) {
247
					$result = WC_Stripe_API::request( array(
248
						'amount'   => WC_Stripe_Helper::get_stripe_amount( $order_total ),
249
						'expand[]' => 'balance_transaction',
250
					), 'charges/' . $charge . '/capture' );
251
252
					if ( ! empty( $result->error ) ) {
253
						/* translators: error message */
254
						$order->update_status( 'failed', sprintf( __( 'Unable to capture charge! %s', 'woocommerce-gateway-stripe' ), $result->error->message ) );
255
					} else {
256
						$is_stripe_captured = true;
257
					}
258
				} elseif ( true === $result->captured ) {
259
					$is_stripe_captured = true;
260
				}
261
262
				if ( $is_stripe_captured ) {
263
					/* translators: transaction id */
264
					$order->add_order_note( sprintf( __( 'Stripe charge complete (Charge ID: %s)', 'woocommerce-gateway-stripe' ), $result->id ) );
265
					WC_Stripe_Helper::is_wc_lt( '3.0' ) ? update_post_meta( $order_id, '_stripe_charge_captured', 'yes' ) : $order->update_meta_data( '_stripe_charge_captured', 'yes' );
266
267
					// Store other data such as fees
268
					WC_Stripe_Helper::is_wc_lt( '3.0' ) ? update_post_meta( $order_id, '_transaction_id', $result->id ) : $order->set_transaction_id( $result->id );
269
270
					$this->update_fees( $order, $result->balance_transaction );
271
				}
272
273
				// This hook fires when admin manually changes order status to processing or completed.
274
				do_action( 'woocommerce_stripe_process_manual_capture', $order, $result );
275
			}
276
		}
277
	}
278
279
	/**
280
	 * Cancel pre-auth on refund/cancellation.
281
	 *
282
	 * @since 3.1.0
283
	 * @version 4.0.0
284
	 * @param  int $order_id
285
	 */
286
	public function cancel_payment( $order_id ) {
287
		$order = wc_get_order( $order_id );
288
289
		if ( 'stripe' === ( WC_Stripe_Helper::is_wc_lt( '3.0' ) ? $order->payment_method : $order->get_payment_method() ) ) {
290
			$this->process_refund( $order_id );
291
292
			// This hook fires when admin manually changes order status to cancel.
293
			do_action( 'woocommerce_stripe_process_manual_cancel', $order );
294
		}
295
	}
296
}
297
298
new WC_Stripe_Order_Handler();
299