Completed
Push — master ( b352f5...4ff6e2 )
by Roy
02:36
created

includes/class-wc-gateway-stripe-addons.php (2 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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: '')
100
	 * @param  bool initial_payment
101
	 */
102
	public function process_subscription_payment( $order = '', $amount = 0 ) {
103
		
104 View Code Duplication
		if ( $amount * 100 < WC_Stripe::get_minimum_amount() ) {
0 ignored issues
show
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...
105
			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 ) ) );
106
		}
107
108
		// Get source from order
109
		$source = $this->get_order_source( $order );
110
111
		// If no order source was defined, use user source instead.
112
		if ( ! $source->customer ) {
113
			$source = $this->get_source( $order->customer_user );
114
		}
115
116
		// Or fail :(
117
		if ( ! $source->customer ) {
118
			return new WP_Error( 'stripe_error', __( 'Customer not found', 'woocommerce-gateway-stripe' ) );
119
		}
120
121
		WC_Stripe::log( "Info: Begin processing subscription payment for order {$order->id} for the amount of {$amount}" );
122
123
		// Make the request
124
		$request             = $this->generate_payment_request( $order, $source );
125
		$request['capture']  = 'true';
126
		$request['amount']   = $this->get_stripe_amount( $amount, $request['currency'] );
127
		$request['metadata'] = array(
128
			'payment_type'   => 'recurring'
129
		);
130
		$response            = WC_Stripe_API::request( $request );
131
132
		// Process valid response
133
		if ( ! is_wp_error( $response ) ) {
134
			$this->process_response( $response, $order );
135
		}
136
137
		return $response;
138
	}
139
140
	/**
141
	 * Process the pre-order
142
	 * @param int $order_id
143
	 * @return array
144
	 */
145
	public function process_pre_order( $order_id, $retry, $force_customer ) {
146
		if ( WC_Pre_Orders_Order::order_requires_payment_tokenization( $order_id ) ) {
147
			try {
148
				$order = wc_get_order( $order_id );
149
150 View Code Duplication
				if ( $order->get_total() * 100 < WC_Stripe::get_minimum_amount() ) {
0 ignored issues
show
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...
151
					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 ) ) );
152
				}
153
154
				$source = $this->get_source( get_current_user_id(), true );
155
156
				// We need a source on file to continue.
157
				if ( empty( $source->customer ) || empty( $source->source ) ) {
158
					throw new Exception( __( 'Unable to store payment details. Please try again.', 'woocommerce-gateway-stripe' ) );
159
				}
160
161
				// Store source to order meta
162
				$this->save_source( $order, $source );
163
164
				// Reduce stock levels
165
				$order->reduce_order_stock();
166
167
				// Remove cart
168
				WC()->cart->empty_cart();
169
170
				// Is pre ordered!
171
				WC_Pre_Orders_Order::mark_order_as_pre_ordered( $order );
172
173
				// Return thank you page redirect
174
				return array(
175
					'result'   => 'success',
176
					'redirect' => $this->get_return_url( $order )
177
				);
178
			} catch ( Exception $e ) {
179
				wc_add_notice( $e->getMessage(), 'error' );
180
				return;
181
			}
182
		} else {
183
			return parent::process_payment( $order_id, $retry, $force_customer );
184
		}
185
	}
186
187
	/**
188
	 * Process a pre-order payment when the pre-order is released
189
	 * @param WC_Order $order
190
	 * @return void
191
	 */
192
	public function process_pre_order_release_payment( $order ) {
193
		try {
194
			// Define some callbacks if the first attempt fails.
195
			$retry_callbacks = array(
196
				'remove_order_source_before_retry',
197
				'remove_order_customer_before_retry',
198
			);
199
200
			while ( 1 ) {
201
				$source   = $this->get_order_source( $order );
202
				$response = WC_Stripe_API::request( $this->generate_payment_request( $order, $source ) );
203
204
				if ( is_wp_error( $response ) ) {
205
					if ( 0 === sizeof( $retry_callbacks ) ) {
206
						throw new Exception( $response->get_error_message() );
207
					} else {
208
						$retry_callback = array_shift( $retry_callbacks );
209
						call_user_func( array( $this, $retry_callback ), $order );
210
					}
211
				} else {
212
					// Successful
213
					$this->process_response( $response, $order );
214
					break;
215
				}
216
			}
217
218
		} catch ( Exception $e ) {
219
			$order_note = sprintf( __( 'Stripe Transaction Failed (%s)', 'woocommerce-gateway-stripe' ), $e->getMessage() );
220
221
			// Mark order as failed if not already set,
222
			// otherwise, make sure we add the order note so we can detect when someone fails to check out multiple times
223
			if ( ! $order->has_status( 'failed' ) ) {
224
				$order->update_status( 'failed', $order_note );
225
			} else {
226
				$order->add_order_note( $order_note );
227
			}
228
		}
229
	}
230
231
	/**
232
	 * Don't transfer Stripe customer/token meta to resubscribe orders.
233
	 * @param int $resubscribe_order The order created for the customer to resubscribe to the old expired/cancelled subscription
234
	 */
235
	public function delete_resubscribe_meta( $resubscribe_order ) {
236
		delete_post_meta( $resubscribe_order->id, '_stripe_customer_id' );
237
		delete_post_meta( $resubscribe_order->id, '_stripe_card_id' );
238
		$this->delete_renewal_meta( $resubscribe_order );
239
	}
240
241
	/**
242
	 * Don't transfer Stripe fee/ID meta to renewal orders.
243
	 * @param int $resubscribe_order The order created for the customer to resubscribe to the old expired/cancelled subscription
244
	 */
245
	public function delete_renewal_meta( $renewal_order ) {
246
		delete_post_meta( $renewal_order->id, 'Stripe Fee' );
247
		delete_post_meta( $renewal_order->id, 'Net Revenue From Stripe' );
248
		delete_post_meta( $renewal_order->id, 'Stripe Payment ID' );
249
		return $renewal_order;
250
	}
251
252
	/**
253
	 * scheduled_subscription_payment function.
254
	 *
255
	 * @param $amount_to_charge float The amount to charge.
256
	 * @param $renewal_order WC_Order A WC_Order object created to record the renewal payment.
257
	 */
258
	public function scheduled_subscription_payment( $amount_to_charge, $renewal_order ) {
259
		$response = $this->process_subscription_payment( $renewal_order, $amount_to_charge );
260
261
		if ( is_wp_error( $response ) ) {
262
			$renewal_order->update_status( 'failed', sprintf( __( 'Stripe Transaction Failed (%s)', 'woocommerce-gateway-stripe' ), $response->get_error_message() ) );
263
		}
264
	}
265
266
	/**
267
	 * Remove order meta
268
	 * @param  object $order
269
	 */
270
	public function remove_order_source_before_retry( $order ) {
271
		delete_post_meta( $order->id, '_stripe_card_id' );
272
	}
273
274
	/**
275
	 * Remove order meta
276
	 * @param  object $order
277
	 */
278
	public function remove_order_customer_before_retry( $order ) {
279
		delete_post_meta( $order->id, '_stripe_customer_id' );
280
	}
281
282
	/**
283
	 * Update the customer_id for a subscription after using Stripe to complete a payment to make up for
284
	 * an automatic renewal payment which previously failed.
285
	 *
286
	 * @access public
287
	 * @param WC_Subscription $subscription The subscription for which the failing payment method relates.
288
	 * @param WC_Order $renewal_order The order which recorded the successful payment (to make up for the failed automatic payment).
289
	 * @return void
290
	 */
291
	public function update_failing_payment_method( $subscription, $renewal_order ) {
292
		update_post_meta( $subscription->id, '_stripe_customer_id', $renewal_order->stripe_customer_id );
293
		update_post_meta( $subscription->id, '_stripe_card_id', $renewal_order->stripe_card_id );
294
	}
295
296
	/**
297
	 * Include the payment meta data required to process automatic recurring payments so that store managers can
298
	 * manually set up automatic recurring payments for a customer via the Edit Subscriptions screen in 2.0+.
299
	 *
300
	 * @since 2.5
301
	 * @param array $payment_meta associative array of meta data required for automatic payments
302
	 * @param WC_Subscription $subscription An instance of a subscription object
303
	 * @return array
304
	 */
305
	public function add_subscription_payment_meta( $payment_meta, $subscription ) {
306
		$payment_meta[ $this->id ] = array(
307
			'post_meta' => array(
308
				'_stripe_customer_id' => array(
309
					'value' => get_post_meta( $subscription->id, '_stripe_customer_id', true ),
310
					'label' => 'Stripe Customer ID',
311
				),
312
				'_stripe_card_id' => array(
313
					'value' => get_post_meta( $subscription->id, '_stripe_card_id', true ),
314
					'label' => 'Stripe Card ID',
315
				),
316
			),
317
		);
318
		return $payment_meta;
319
	}
320
321
	/**
322
	 * Validate the payment meta data required to process automatic recurring payments so that store managers can
323
	 * manually set up automatic recurring payments for a customer via the Edit Subscriptions screen in 2.0+.
324
	 *
325
	 * @since 2.5
326
	 * @param string $payment_method_id The ID of the payment method to validate
327
	 * @param array $payment_meta associative array of meta data required for automatic payments
328
	 * @return array
329
	 */
330
	public function validate_subscription_payment_meta( $payment_method_id, $payment_meta ) {
331
		if ( $this->id === $payment_method_id ) {
332
333
			if ( ! isset( $payment_meta['post_meta']['_stripe_customer_id']['value'] ) || empty( $payment_meta['post_meta']['_stripe_customer_id']['value'] ) ) {
334
				throw new Exception( 'A "_stripe_customer_id" value is required.' );
335
			} elseif ( 0 !== strpos( $payment_meta['post_meta']['_stripe_customer_id']['value'], 'cus_' ) ) {
336
				throw new Exception( 'Invalid customer ID. A valid "_stripe_customer_id" must begin with "cus_".' );
337
			}
338
339
			if ( ! empty( $payment_meta['post_meta']['_stripe_card_id']['value'] ) && 0 !== strpos( $payment_meta['post_meta']['_stripe_card_id']['value'], 'card_' ) ) {
340
				throw new Exception( 'Invalid card ID. A valid "_stripe_card_id" must begin with "card_".' );
341
			}
342
		}
343
	}
344
345
	/**
346
	 * Render the payment method used for a subscription in the "My Subscriptions" table
347
	 *
348
	 * @since 1.7.5
349
	 * @param string $payment_method_to_display the default payment method text to display
350
	 * @param WC_Subscription $subscription the subscription details
351
	 * @return string the subscription payment method
352
	 */
353
	public function maybe_render_subscription_payment_method( $payment_method_to_display, $subscription ) {
354
		// bail for other payment methods
355
		if ( $this->id !== $subscription->payment_method || ! $subscription->customer_user ) {
356
			return $payment_method_to_display;
357
		}
358
359
		$stripe_customer    = new WC_Stripe_Customer();
360
		$stripe_customer_id = get_post_meta( $subscription->id, '_stripe_customer_id', true );
361
		$stripe_card_id     = get_post_meta( $subscription->id, '_stripe_card_id', true );
362
363
		// If we couldn't find a Stripe customer linked to the subscription, fallback to the user meta data.
364
		if ( ! $stripe_customer_id || ! is_string( $stripe_customer_id ) ) {
365
			$user_id            = $subscription->customer_user;
366
			$stripe_customer_id = get_user_meta( $user_id, '_stripe_customer_id', true );
367
			$stripe_card_id     = get_user_meta( $user_id, '_stripe_card_id', true );
368
		}
369
370
		// If we couldn't find a Stripe customer linked to the account, fallback to the order meta data.
371
		if ( ( ! $stripe_customer_id || ! is_string( $stripe_customer_id ) ) && false !== $subscription->order ) {
372
			$stripe_customer_id = get_post_meta( $subscription->order->id, '_stripe_customer_id', true );
373
			$stripe_card_id     = get_post_meta( $subscription->order->id, '_stripe_card_id', true );
374
		}
375
376
		$stripe_customer->set_id( $stripe_customer_id );
377
		$cards = $stripe_customer->get_cards();
378
379
		if ( $cards ) {
380
			$found_card = false;
381
			foreach ( $cards as $card ) {
382
				if ( $card->id === $stripe_card_id ) {
383
					$found_card                = true;
384
					$payment_method_to_display = sprintf( __( 'Via %s card ending in %s', 'woocommerce-gateway-stripe' ), ( isset( $card->type ) ? $card->type : $card->brand ), $card->last4 );
385
					break;
386
				}
387
			}
388
			if ( ! $found_card ) {
389
				$payment_method_to_display = sprintf( __( 'Via %s card ending in %s', 'woocommerce-gateway-stripe' ), ( isset( $cards[0]->type ) ? $cards[0]->type : $cards[0]->brand ), $cards[0]->last4 );
390
			}
391
		}
392
393
		return $payment_method_to_display;
394
	}
395
}
396