Completed
Push — master ( 1968f4...2723f5 )
by Roy
02:26
created

WC_Gateway_Stripe_Addons::log()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 7
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 4
nc 2
nop 1
1
<?php
2
if ( ! defined( 'ABSPATH' ) ) {
3
	exit;
4
}
5
6
/**
7
 * WC_Gateway_Stripe_Addons class.
8
 *
9
 * @extends WC_Gateway_Stripe
10
 */
11
class WC_Gateway_Stripe_Addons extends WC_Gateway_Stripe {
12
13
	/**
14
	 * Constructor
15
	 */
16
	public function __construct() {
17
		parent::__construct();
18
19
		if ( class_exists( 'WC_Subscriptions_Order' ) ) {
20
			add_action( 'woocommerce_scheduled_subscription_payment_' . $this->id, array( $this, 'scheduled_subscription_payment' ), 10, 2 );
21
			add_action( 'wcs_resubscribe_order_created', array( $this, 'delete_resubscribe_meta' ), 10 );
22
			add_action( 'wcs_renewal_order_created', array( $this, 'delete_renewal_meta' ), 10 );
23
			add_action( 'woocommerce_subscription_failing_payment_method_updated_stripe', array( $this, 'update_failing_payment_method' ), 10, 2 );
24
25
			// display the credit card used for a subscription in the "My Subscriptions" table
26
			add_filter( 'woocommerce_my_subscriptions_payment_method', array( $this, 'maybe_render_subscription_payment_method' ), 10, 2 );
27
28
			// allow store managers to manually set Stripe as the payment method on a subscription
29
			add_filter( 'woocommerce_subscription_payment_meta', array( $this, 'add_subscription_payment_meta' ), 10, 2 );
30
			add_filter( 'woocommerce_subscription_validate_payment_meta', array( $this, 'validate_subscription_payment_meta' ), 10, 2 );
31
		}
32
33
		if ( class_exists( 'WC_Pre_Orders_Order' ) ) {
34
			add_action( 'wc_pre_orders_process_pre_order_completion_payment_' . $this->id, array( $this, 'process_pre_order_release_payment' ) );
35
		}
36
	}
37
38
	/**
39
	 * Is $order_id a subscription?
40
	 * @param  int  $order_id
41
	 * @return boolean
42
	 */
43
	protected function is_subscription( $order_id ) {
44
		return ( function_exists( 'wcs_order_contains_subscription' ) && ( wcs_order_contains_subscription( $order_id ) || wcs_is_subscription( $order_id ) || wcs_order_contains_renewal( $order_id ) ) );
45
	}
46
47
	/**
48
	 * Is $order_id a pre-order?
49
	 * @param  int  $order_id
50
	 * @return boolean
51
	 */
52
	protected function is_pre_order( $order_id ) {
53
		return ( class_exists( 'WC_Pre_Orders_Order' ) && WC_Pre_Orders_Order::order_contains_pre_order( $order_id ) );
54
	}
55
56
	/**
57
	 * Process the payment based on type.
58
	 * @param  int $order_id
59
	 * @return array
60
	 */
61
	public function process_payment( $order_id, $retry = true, $force_customer = false ) {
62
		if ( $this->is_subscription( $order_id ) ) {
63
			// Regular payment with force customer enabled
64
			return parent::process_payment( $order_id, true, true );
65
66
		} elseif ( $this->is_pre_order( $order_id ) ) {
67
			return $this->process_pre_order( $order_id, $retry, $force_customer );
68
69
		} else {
70
			return parent::process_payment( $order_id, $retry, $force_customer );
71
		}
72
	}
73
74
	/**
75
	 * Updates other subscription sources.
76
	 */
77
	protected function save_source( $order, $source ) {
78
		parent::save_source( $order, $source );
79
80
		// Also store it on the subscriptions being purchased or paid for in the order
81
		if ( function_exists( 'wcs_order_contains_subscription' ) && wcs_order_contains_subscription( $order->id ) ) {
82
			$subscriptions = wcs_get_subscriptions_for_order( $order->id );
83
		} elseif ( function_exists( 'wcs_order_contains_renewal' ) && wcs_order_contains_renewal( $order->id ) ) {
84
			$subscriptions = wcs_get_subscriptions_for_renewal_order( $order->id );
85
		} else {
86
			$subscriptions = array();
87
		}
88
89
		foreach ( $subscriptions as $subscription ) {
90
			update_post_meta( $subscription->id, '_stripe_customer_id', $source->customer );
91
			update_post_meta( $subscription->id, '_stripe_card_id', $source->source );
92
		}
93
	}
94
95
	/**
96
	 * process_subscription_payment function.
97
	 * @param mixed $order
98
	 * @param int $amount (default: 0)
99
	 * @param string $stripe_token (default: '')
0 ignored issues
show
Bug introduced by
There is no parameter named $stripe_token. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
100
	 * @param  bool initial_payment
101
	 */
102
	public function process_subscription_payment( $order = '', $amount = 0 ) {
103 View Code Duplication
		if ( $amount * 100 < WC_Stripe::get_minimum_amount() ) {
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...
104
			return new WP_Error( 'stripe_error', sprintf( __( 'Sorry, the minimum allowed order total is %1$s to use this payment method.', 'woocommerce-gateway-stripe' ), wc_price( WC_Stripe::get_minimum_amount() / 100 ) ) );
105
		}
106
107
		// Get source from order
108
		$source = $this->get_order_source( $order );
109
110
		// If no order source was defined, use user source instead.
111
		if ( ! $source->customer ) {
112
			$source = $this->get_source( $order->customer_user );
113
		}
114
115
		// Or fail :(
0 ignored issues
show
Unused Code Comprehensibility introduced by
43% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
116
		if ( ! $source->customer ) {
117
			return new WP_Error( 'stripe_error', __( 'Customer not found', 'woocommerce-gateway-stripe' ) );
118
		}
119
120
		$this->log( "Info: Begin processing subscription payment for order {$order->id} for the amount of {$amount}" );
121
122
		// Make the request
123
		$request             = $this->generate_payment_request( $order, $source );
124
		$request['capture']  = 'true';
125
		$request['amount']   = $this->get_stripe_amount( $amount, $request['currency'] );
126
		$request['metadata'] = array(
127
			'payment_type'   => 'recurring',
128
		);
129
		$response            = WC_Stripe_API::request( $request );
130
131
		// Process valid response
132
		if ( ! is_wp_error( $response ) ) {
133
			$this->process_response( $response, $order );
134
		}
135
136
		return $response;
137
	}
138
139
	/**
140
	 * Process the pre-order
141
	 * @param int $order_id
142
	 * @return array
143
	 */
144
	public function process_pre_order( $order_id, $retry, $force_customer ) {
145
		if ( WC_Pre_Orders_Order::order_requires_payment_tokenization( $order_id ) ) {
146
			try {
147
				$order = wc_get_order( $order_id );
148
149 View Code Duplication
				if ( $order->get_total() * 100 < WC_Stripe::get_minimum_amount() ) {
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...
150
					throw new Exception( sprintf( __( 'Sorry, the minimum allowed order total is %1$s to use this payment method.', 'woocommerce-gateway-stripe' ), wc_price( WC_Stripe::get_minimum_amount() / 100 ) ) );
151
				}
152
153
				$source = $this->get_source( get_current_user_id(), true );
154
155
				// We need a source on file to continue.
156
				if ( empty( $source->customer ) || empty( $source->source ) ) {
157
					throw new Exception( __( 'Unable to store payment details. Please try again.', 'woocommerce-gateway-stripe' ) );
158
				}
159
160
				// Store source to order meta
161
				$this->save_source( $order, $source );
162
163
				// Remove cart
164
				WC()->cart->empty_cart();
165
166
				// Is pre ordered!
167
				WC_Pre_Orders_Order::mark_order_as_pre_ordered( $order );
168
169
				// Return thank you page redirect
170
				return array(
171
					'result'   => 'success',
172
					'redirect' => $this->get_return_url( $order ),
173
				);
174
			} catch ( Exception $e ) {
175
				wc_add_notice( $e->getMessage(), 'error' );
176
				return;
177
			}
178
		} else {
179
			return parent::process_payment( $order_id, $retry, $force_customer );
0 ignored issues
show
Comprehensibility Bug introduced by
It seems like you call parent on a different method (process_payment() instead of process_pre_order()). Are you sure this is correct? If so, you might want to change this to $this->process_payment().

This check looks for a call to a parent method whose name is different than the method from which it is called.

Consider the following code:

class Daddy
{
    protected function getFirstName()
    {
        return "Eidur";
    }

    protected function getSurName()
    {
        return "Gudjohnsen";
    }
}

class Son
{
    public function getFirstName()
    {
        return parent::getSurname();
    }
}

The getFirstName() method in the Son calls the wrong method in the parent class.

Loading history...
180
		}
181
	}
182
183
	/**
184
	 * Process a pre-order payment when the pre-order is released
185
	 * @param WC_Order $order
186
	 * @return void
187
	 */
188
	public function process_pre_order_release_payment( $order ) {
189
		try {
190
			// Define some callbacks if the first attempt fails.
191
			$retry_callbacks = array(
192
				'remove_order_source_before_retry',
193
				'remove_order_customer_before_retry',
194
			);
195
196
			while ( 1 ) {
197
				$source   = $this->get_order_source( $order );
198
				$response = WC_Stripe_API::request( $this->generate_payment_request( $order, $source ) );
199
200
				if ( is_wp_error( $response ) ) {
201
					if ( 0 === sizeof( $retry_callbacks ) ) {
202
						throw new Exception( $response->get_error_message() );
203
					} else {
204
						$retry_callback = array_shift( $retry_callbacks );
205
						call_user_func( array( $this, $retry_callback ), $order );
206
					}
207
				} else {
208
					// Successful
209
					$this->process_response( $response, $order );
210
					break;
211
				}
212
			}
213
		} catch ( Exception $e ) {
214
			$order_note = sprintf( __( 'Stripe Transaction Failed (%s)', 'woocommerce-gateway-stripe' ), $e->getMessage() );
215
216
			// Mark order as failed if not already set,
217
			// otherwise, make sure we add the order note so we can detect when someone fails to check out multiple times
218
			if ( ! $order->has_status( 'failed' ) ) {
219
				$order->update_status( 'failed', $order_note );
220
			} else {
221
				$order->add_order_note( $order_note );
222
			}
223
		}
224
	}
225
226
	/**
227
	 * Don't transfer Stripe customer/token meta to resubscribe orders.
228
	 * @param int $resubscribe_order The order created for the customer to resubscribe to the old expired/cancelled subscription
229
	 */
230
	public function delete_resubscribe_meta( $resubscribe_order ) {
231
		delete_post_meta( $resubscribe_order->id, '_stripe_customer_id' );
232
		delete_post_meta( $resubscribe_order->id, '_stripe_card_id' );
233
		$this->delete_renewal_meta( $resubscribe_order );
234
	}
235
236
	/**
237
	 * Don't transfer Stripe fee/ID meta to renewal orders.
238
	 * @param int $resubscribe_order The order created for the customer to resubscribe to the old expired/cancelled subscription
0 ignored issues
show
Bug introduced by
There is no parameter named $resubscribe_order. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
239
	 */
240
	public function delete_renewal_meta( $renewal_order ) {
241
		delete_post_meta( $renewal_order->id, 'Stripe Fee' );
242
		delete_post_meta( $renewal_order->id, 'Net Revenue From Stripe' );
243
		delete_post_meta( $renewal_order->id, 'Stripe Payment ID' );
244
		return $renewal_order;
245
	}
246
247
	/**
248
	 * scheduled_subscription_payment function.
249
	 *
250
	 * @param $amount_to_charge float The amount to charge.
251
	 * @param $renewal_order WC_Order A WC_Order object created to record the renewal payment.
252
	 */
253
	public function scheduled_subscription_payment( $amount_to_charge, $renewal_order ) {
254
		$response = $this->process_subscription_payment( $renewal_order, $amount_to_charge );
255
256
		if ( is_wp_error( $response ) ) {
257
			$renewal_order->update_status( 'failed', sprintf( __( 'Stripe Transaction Failed (%s)', 'woocommerce-gateway-stripe' ), $response->get_error_message() ) );
258
		}
259
	}
260
261
	/**
262
	 * Remove order meta
263
	 * @param  object $order
264
	 */
265
	public function remove_order_source_before_retry( $order ) {
266
		delete_post_meta( $order->id, '_stripe_card_id' );
267
	}
268
269
	/**
270
	 * Remove order meta
271
	 * @param  object $order
272
	 */
273
	public function remove_order_customer_before_retry( $order ) {
274
		delete_post_meta( $order->id, '_stripe_customer_id' );
275
	}
276
277
	/**
278
	 * Update the customer_id for a subscription after using Stripe to complete a payment to make up for
279
	 * an automatic renewal payment which previously failed.
280
	 *
281
	 * @access public
282
	 * @param WC_Subscription $subscription The subscription for which the failing payment method relates.
283
	 * @param WC_Order $renewal_order The order which recorded the successful payment (to make up for the failed automatic payment).
284
	 * @return void
285
	 */
286
	public function update_failing_payment_method( $subscription, $renewal_order ) {
287
		update_post_meta( $subscription->id, '_stripe_customer_id', $renewal_order->stripe_customer_id );
288
		update_post_meta( $subscription->id, '_stripe_card_id', $renewal_order->stripe_card_id );
289
	}
290
291
	/**
292
	 * Include the payment meta data required to process automatic recurring payments so that store managers can
293
	 * manually set up automatic recurring payments for a customer via the Edit Subscriptions screen in 2.0+.
294
	 *
295
	 * @since 2.5
296
	 * @param array $payment_meta associative array of meta data required for automatic payments
297
	 * @param WC_Subscription $subscription An instance of a subscription object
298
	 * @return array
299
	 */
300
	public function add_subscription_payment_meta( $payment_meta, $subscription ) {
301
		$payment_meta[ $this->id ] = array(
302
			'post_meta' => array(
303
				'_stripe_customer_id' => array(
304
					'value' => get_post_meta( $subscription->id, '_stripe_customer_id', true ),
305
					'label' => 'Stripe Customer ID',
306
				),
307
				'_stripe_card_id' => array(
308
					'value' => get_post_meta( $subscription->id, '_stripe_card_id', true ),
309
					'label' => 'Stripe Card ID',
310
				),
311
			),
312
		);
313
		return $payment_meta;
314
	}
315
316
	/**
317
	 * Validate the payment meta data required to process automatic recurring payments so that store managers can
318
	 * manually set up automatic recurring payments for a customer via the Edit Subscriptions screen in 2.0+.
319
	 *
320
	 * @since 2.5
321
	 * @param string $payment_method_id The ID of the payment method to validate
322
	 * @param array $payment_meta associative array of meta data required for automatic payments
323
	 * @return array
324
	 */
325
	public function validate_subscription_payment_meta( $payment_method_id, $payment_meta ) {
326
		if ( $this->id === $payment_method_id ) {
327
328
			if ( ! isset( $payment_meta['post_meta']['_stripe_customer_id']['value'] ) || empty( $payment_meta['post_meta']['_stripe_customer_id']['value'] ) ) {
329
				throw new Exception( 'A "_stripe_customer_id" value is required.' );
330
			} elseif ( 0 !== strpos( $payment_meta['post_meta']['_stripe_customer_id']['value'], 'cus_' ) ) {
331
				throw new Exception( 'Invalid customer ID. A valid "_stripe_customer_id" must begin with "cus_".' );
332
			}
333
334
			if ( ! empty( $payment_meta['post_meta']['_stripe_card_id']['value'] ) && 0 !== strpos( $payment_meta['post_meta']['_stripe_card_id']['value'], 'card_' ) ) {
335
				throw new Exception( 'Invalid card ID. A valid "_stripe_card_id" must begin with "card_".' );
336
			}
337
		}
338
	}
339
340
	/**
341
	 * Render the payment method used for a subscription in the "My Subscriptions" table
342
	 *
343
	 * @since 1.7.5
344
	 * @param string $payment_method_to_display the default payment method text to display
345
	 * @param WC_Subscription $subscription the subscription details
346
	 * @return string the subscription payment method
347
	 */
348
	public function maybe_render_subscription_payment_method( $payment_method_to_display, $subscription ) {
349
		// bail for other payment methods
350
		if ( $this->id !== $subscription->payment_method || ! $subscription->customer_user ) {
351
			return $payment_method_to_display;
352
		}
353
354
		$stripe_customer    = new WC_Stripe_Customer();
355
		$stripe_customer_id = get_post_meta( $subscription->id, '_stripe_customer_id', true );
356
		$stripe_card_id     = get_post_meta( $subscription->id, '_stripe_card_id', true );
357
358
		// If we couldn't find a Stripe customer linked to the subscription, fallback to the user meta data.
359
		if ( ! $stripe_customer_id || ! is_string( $stripe_customer_id ) ) {
360
			$user_id            = $subscription->customer_user;
361
			$stripe_customer_id = get_user_meta( $user_id, '_stripe_customer_id', true );
362
			$stripe_card_id     = get_user_meta( $user_id, '_stripe_card_id', true );
363
		}
364
365
		// If we couldn't find a Stripe customer linked to the account, fallback to the order meta data.
366
		if ( ( ! $stripe_customer_id || ! is_string( $stripe_customer_id ) ) && false !== $subscription->order ) {
367
			$stripe_customer_id = get_post_meta( $subscription->order->id, '_stripe_customer_id', true );
368
			$stripe_card_id     = get_post_meta( $subscription->order->id, '_stripe_card_id', true );
369
		}
370
371
		$stripe_customer->set_id( $stripe_customer_id );
372
		$cards = $stripe_customer->get_cards();
373
374
		if ( $cards ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $cards of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
375
			$found_card = false;
376
			foreach ( $cards as $card ) {
377
				if ( $card->id === $stripe_card_id ) {
378
					$found_card                = true;
379
					$payment_method_to_display = sprintf( __( 'Via %1$s card ending in %2$s', 'woocommerce-gateway-stripe' ), ( isset( $card->type ) ? $card->type : $card->brand ), $card->last4 );
380
					break;
381
				}
382
			}
383
			if ( ! $found_card ) {
384
				$payment_method_to_display = sprintf( __( 'Via %1$s card ending in %2$s', 'woocommerce-gateway-stripe' ), ( isset( $cards[0]->type ) ? $cards[0]->type : $cards[0]->brand ), $cards[0]->last4 );
385
			}
386
		}
387
388
		return $payment_method_to_display;
389
	}
390
391
	/**
392
	 * Logs
393
	 *
394
	 * @since 3.1.0
395
	 * @version 3.1.0
396
	 *
397
	 * @param string $message
398
	 */
399
	public function log( $message ) {
400
		$options = get_option( 'woocommerce_stripe_settings' );
401
402
		if ( 'yes' === $options['logging'] ) {
403
			WC_Stripe::log( $message );
404
		}
405
	}
406
}
407