Code Duplication    Length = 483-484 lines in 2 locations

includes/compat/class-wc-stripe-compat.php 1 location

@@ 11-494 (lines=484) @@
8
 *
9
 * @extends WC_Gateway_Stripe
10
 */
11
class WC_Stripe_Compat extends WC_Gateway_Stripe {
12
	/**
13
	 * Constructor
14
	 */
15
	public function __construct() {
16
		parent::__construct();
17
18
		if ( class_exists( 'WC_Subscriptions_Order' ) ) {
19
			add_action( 'woocommerce_scheduled_subscription_payment_' . $this->id, array( $this, 'scheduled_subscription_payment' ), 10, 2 );
20
			add_action( 'wcs_resubscribe_order_created', array( $this, 'delete_resubscribe_meta' ), 10 );
21
			add_action( 'wcs_renewal_order_created', array( $this, 'delete_renewal_meta' ), 10 );
22
			add_action( 'woocommerce_subscription_failing_payment_method_updated_stripe', array( $this, 'update_failing_payment_method' ), 10, 2 );
23
24
			// display the credit card used for a subscription in the "My Subscriptions" table
25
			add_filter( 'woocommerce_my_subscriptions_payment_method', array( $this, 'maybe_render_subscription_payment_method' ), 10, 2 );
26
27
			// allow store managers to manually set Stripe as the payment method on a subscription
28
			add_filter( 'woocommerce_subscription_payment_meta', array( $this, 'add_subscription_payment_meta' ), 10, 2 );
29
			add_filter( 'woocommerce_subscription_validate_payment_meta', array( $this, 'validate_subscription_payment_meta' ), 10, 2 );
30
			add_filter( 'wc_stripe_display_save_payment_method_checkbox', array( $this, 'maybe_hide_save_checkbox' ) );
31
			add_filter( 'wc_stripe_payment_metadata', array( $this, 'add_subscription_meta_data' ), 10, 2 );
32
		}
33
34
		if ( class_exists( 'WC_Pre_Orders_Order' ) ) {
35
			add_action( 'wc_pre_orders_process_pre_order_completion_payment_' . $this->id, array( $this, 'process_pre_order_release_payment' ) );
36
		}
37
	}
38
39
	/**
40
	 * Checks to see if we need to hide the save checkbox field.
41
	 * Because when cart contains a subs product, it will save regardless.
42
	 *
43
	 * @since 4.0.0
44
	 * @version 4.0.0
45
	 */
46
	public function maybe_hide_save_checkbox( $display_tokenization ) {
47
		if ( WC_Subscriptions_Cart::cart_contains_subscription() ) {
48
			return false;
49
		}
50
51
		return $display_tokenization;
52
	}
53
54
	/**
55
	 * Is $order_id a subscription?
56
	 * @param  int  $order_id
57
	 * @return boolean
58
	 */
59
	public function has_subscription( $order_id ) {
60
		return ( function_exists( 'wcs_order_contains_subscription' ) && ( wcs_order_contains_subscription( $order_id ) || wcs_is_subscription( $order_id ) || wcs_order_contains_renewal( $order_id ) ) );
61
	}
62
63
	/**
64
	 * Is $order_id a pre-order?
65
	 * @param  int  $order_id
66
	 * @return boolean
67
	 */
68
	protected function is_pre_order( $order_id ) {
69
		return ( class_exists( 'WC_Pre_Orders_Order' ) && WC_Pre_Orders_Order::order_contains_pre_order( $order_id ) );
70
	}
71
72
	/**
73
	 * Process the payment based on type.
74
	 * @param  int $order_id
75
	 * @return array
76
	 */
77
	public function process_payment( $order_id, $retry = true, $force_save_source = false ) {
78
		if ( $this->has_subscription( $order_id ) ) {
79
			// Regular payment with force customer enabled
80
			return parent::process_payment( $order_id, true, true );
81
		} elseif ( $this->is_pre_order( $order_id ) ) {
82
			return $this->process_pre_order( $order_id, $retry, $force_save_source );
83
		} else {
84
			return parent::process_payment( $order_id, $retry, $force_save_source );
85
		}
86
	}
87
88
	/**
89
	 * Adds subscription related meta data on charge request.
90
	 *
91
	 * @since 4.0.0
92
	 * @param array $metadata
93
	 * @param object $order
94
	 */
95
	public function add_subscription_meta_data( $metadata, $order ) {
96
		if ( ! $this->has_subscription( $order->get_id() ) ) {
97
			return $metadata;
98
		}
99
100
		return $metadata += array(
101
			'payment_type'   => 'recurring',
102
			'site_url'       => esc_url( get_site_url() ),
103
		);
104
	}
105
106
	/**
107
	 * Updates other subscription sources.
108
	 *
109
	 * @since 3.1.0
110
	 * @version 4.0.0
111
	 */
112
	public function save_source( $order, $source ) {
113
		parent::save_source( $order, $source );
114
115
		$order_id  = WC_Stripe_Helper::is_pre_30() ? $order->id : $order->get_id();
116
117
		// Also store it on the subscriptions being purchased or paid for in the order
118
		if ( function_exists( 'wcs_order_contains_subscription' ) && wcs_order_contains_subscription( $order_id ) ) {
119
			$subscriptions = wcs_get_subscriptions_for_order( $order_id );
120
		} elseif ( function_exists( 'wcs_order_contains_renewal' ) && wcs_order_contains_renewal( $order_id ) ) {
121
			$subscriptions = wcs_get_subscriptions_for_renewal_order( $order_id );
122
		} else {
123
			$subscriptions = array();
124
		}
125
126
		foreach ( $subscriptions as $subscription ) {
127
			$subscription_id = WC_Stripe_Helper::is_pre_30() ? $subscription->id : $subscription->get_id();
128
			update_post_meta( $subscription_id, '_stripe_customer_id', $source->customer );
129
			update_post_meta( $subscription_id, '_stripe_source_id', $source->source );
130
		}
131
	}
132
133
	/**
134
	 * process_subscription_payment function.
135
	 * @param mixed $order
136
	 * @param int $amount (default: 0)
137
	 * @param string $stripe_token (default: '')
138
	 * @param  bool initial_payment
139
	 */
140
	public function process_subscription_payment( $order = '', $amount = 0 ) {
141
		if ( $amount * 100 < WC_Stripe_Helper::get_minimum_amount() ) {
142
			/* translators: minimum amount */
143
			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_Helper::get_minimum_amount() / 100 ) ) );
144
		}
145
146
		$customer_id = WC_Stripe_Helper::is_pre_30() ? $order->customer_user : $order->get_customer_id();
147
		$order_id    = WC_Stripe_Helper::is_pre_30() ? $order->id : $order->get_id();
148
149
		// Get source from order
150
		$prepared_source = $this->prepare_order_source( $order );
151
152
		// Or fail :(
153
		if ( ! $prepared_source->customer ) {
154
			return new WP_Error( 'stripe_error', __( 'Customer not found', 'woocommerce-gateway-stripe' ) );
155
		}
156
157
		WC_Stripe_Logger::log( "Info: Begin processing subscription payment for order {$order_id} for the amount of {$amount}" );
158
159
		// Make the request
160
		$request             = $this->generate_payment_request( $order, $prepared_source );
161
		$request['capture']  = 'true';
162
		$request['amount']   = WC_Stripe_Helper::get_stripe_amount( $amount, $request['currency'] );
163
		$response            = WC_Stripe_API::request( $request );
164
165
		// Process valid response
166
		if ( ! empty( $response->error ) ) {
167
			return $response; // Default catch all errors.
168
		}
169
170
		$this->process_response( $response, $order );
171
172
		return $response;
173
	}
174
175
	/**
176
	 * Don't transfer Stripe customer/token meta to resubscribe orders.
177
	 * @param int $resubscribe_order The order created for the customer to resubscribe to the old expired/cancelled subscription
178
	 */
179
	public function delete_resubscribe_meta( $resubscribe_order ) {
180
		delete_post_meta( ( WC_Stripe_Helper::is_pre_30() ? $resubscribe_order->id : $resubscribe_order->get_id() ), '_stripe_customer_id' );
181
		delete_post_meta( ( WC_Stripe_Helper::is_pre_30() ? $resubscribe_order->id : $resubscribe_order->get_id() ), '_stripe_source_id' );
182
		// For BW compat will remove in future
183
		delete_post_meta( ( WC_Stripe_Helper::is_pre_30() ? $resubscribe_order->id : $resubscribe_order->get_id() ), '_stripe_card_id' );
184
		$this->delete_renewal_meta( $resubscribe_order );
185
	}
186
187
	/**
188
	 * Don't transfer Stripe fee/ID meta to renewal orders.
189
	 * @param int $resubscribe_order The order created for the customer to resubscribe to the old expired/cancelled subscription
190
	 */
191
	public function delete_renewal_meta( $renewal_order ) {
192
		delete_post_meta( ( WC_Stripe_Helper::is_pre_30() ? $renewal_order->id : $renewal_order->get_id() ), 'Stripe Fee' );
193
		delete_post_meta( ( WC_Stripe_Helper::is_pre_30() ? $renewal_order->id : $renewal_order->get_id() ), 'Net Revenue From Stripe' );
194
		return $renewal_order;
195
	}
196
197
	/**
198
	 * scheduled_subscription_payment function.
199
	 *
200
	 * @param $amount_to_charge float The amount to charge.
201
	 * @param $renewal_order WC_Order A WC_Order object created to record the renewal payment.
202
	 */
203
	public function scheduled_subscription_payment( $amount_to_charge, $renewal_order ) {
204
		$response = $this->process_subscription_payment( $renewal_order, $amount_to_charge );
205
206
		if ( is_wp_error( $response ) ) {
207
			/* translators: error message */
208
			$renewal_order->update_status( 'failed', sprintf( __( 'Stripe Transaction Failed (%s)', 'woocommerce-gateway-stripe' ), $response->get_error_message() ) );
209
		}
210
211
		if ( ! empty( $response->error ) ) {
212
			/* translators: error message */
213
			$renewal_order->update_status( 'failed', sprintf( __( 'Stripe Transaction Failed (%s)', 'woocommerce-gateway-stripe' ), $response->error->message ) );
214
		}
215
	}
216
217
	/**
218
	 * Remove order meta
219
	 * @param  object $order
220
	 */
221
	public function remove_order_source_before_retry( $order ) {
222
		$order_id = WC_Stripe_Helper::is_pre_30() ? $order->id : $order->get_id();
223
		delete_post_meta( $order_id, '_stripe_source_id' );
224
		// For BW compat will remove in the future.
225
		delete_post_meta( $order_id, '_stripe_card_id' );
226
	}
227
228
	/**
229
	 * Remove order meta
230
	 * @param  object $order
231
	 */
232
	public function remove_order_customer_before_retry( $order ) {
233
		$order_id = WC_Stripe_Helper::is_pre_30() ? $order->id : $order->get_id();
234
		delete_post_meta( $order_id, '_stripe_customer_id' );
235
	}
236
237
	/**
238
	 * Update the customer_id for a subscription after using Stripe to complete a payment to make up for
239
	 * an automatic renewal payment which previously failed.
240
	 *
241
	 * @access public
242
	 * @param WC_Subscription $subscription The subscription for which the failing payment method relates.
243
	 * @param WC_Order $renewal_order The order which recorded the successful payment (to make up for the failed automatic payment).
244
	 * @return void
245
	 */
246
	public function update_failing_payment_method( $subscription, $renewal_order ) {
247
		if ( WC_Stripe_Helper::is_pre_30() ) {
248
			update_post_meta( $subscription->id, '_stripe_customer_id', $renewal_order->stripe_customer_id );
249
			update_post_meta( $subscription->id, '_stripe_source_id', $renewal_order->stripe_source_id );
250
251
		} else {
252
			update_post_meta( $subscription->get_id(), '_stripe_customer_id', $renewal_order->get_meta( '_stripe_customer_id', true ) );
253
			update_post_meta( $subscription->get_id(), '_stripe_source_id', $renewal_order->get_meta( '_stripe_source_id', true ) );
254
		}
255
	}
256
257
	/**
258
	 * Include the payment meta data required to process automatic recurring payments so that store managers can
259
	 * manually set up automatic recurring payments for a customer via the Edit Subscriptions screen in 2.0+.
260
	 *
261
	 * @since 2.5
262
	 * @param array $payment_meta associative array of meta data required for automatic payments
263
	 * @param WC_Subscription $subscription An instance of a subscription object
264
	 * @return array
265
	 */
266
	public function add_subscription_payment_meta( $payment_meta, $subscription ) {
267
		$source_id = get_post_meta( ( WC_Stripe_Helper::is_pre_30() ? $subscription->id : $subscription->get_id() ), '_stripe_source_id', true );
268
269
		// For BW compat will remove in future.
270
		if ( empty( $source_id ) ) {
271
			$source_id = get_post_meta( ( WC_Stripe_Helper::is_pre_30() ? $subscription->id : $subscription->get_id() ), '_stripe_card_id', true );
272
273
			// Take this opportunity to update the key name.
274
			WC_Stripe_Helper::is_pre_30() ? update_post_meta( $subscription->id, '_stripe_source_id', $source_id ) : update_post_meta( $subscription->get_id(), '_stripe_source_id', $source_id );
275
		}
276
277
		$payment_meta[ $this->id ] = array(
278
			'post_meta' => array(
279
				'_stripe_customer_id' => array(
280
					'value' => get_post_meta( ( WC_Stripe_Helper::is_pre_30() ? $subscription->id : $subscription->get_id() ), '_stripe_customer_id', true ),
281
					'label' => 'Stripe Customer ID',
282
				),
283
				'_stripe_source_id' => array(
284
					'value' => $source_id,
285
					'label' => 'Stripe Source ID',
286
				),
287
			),
288
		);
289
290
		return $payment_meta;
291
	}
292
293
	/**
294
	 * Validate the payment meta data required to process automatic recurring payments so that store managers can
295
	 * manually set up automatic recurring payments for a customer via the Edit Subscriptions screen in 2.0+.
296
	 *
297
	 * @since 2.5
298
	 * @param string $payment_method_id The ID of the payment method to validate
299
	 * @param array $payment_meta associative array of meta data required for automatic payments
300
	 * @return array
301
	 */
302
	public function validate_subscription_payment_meta( $payment_method_id, $payment_meta ) {
303
		if ( $this->id === $payment_method_id ) {
304
305
			if ( ! isset( $payment_meta['post_meta']['_stripe_customer_id']['value'] ) || empty( $payment_meta['post_meta']['_stripe_customer_id']['value'] ) ) {
306
				throw new Exception( 'A "_stripe_customer_id" value is required.' );
307
			} elseif ( 0 !== strpos( $payment_meta['post_meta']['_stripe_customer_id']['value'], 'cus_' ) ) {
308
				throw new Exception( 'Invalid customer ID. A valid "_stripe_customer_id" must begin with "cus_".' );
309
			}
310
311
			if ( ! isset( $payment_meta['post_meta']['_stripe_source_id']['value'] ) || empty( $payment_meta['post_meta']['_stripe_source_id']['value'] ) ) {
312
				throw new Exception( 'A "_stripe_source_id" value is required.' );
313
			}
314
		}
315
	}
316
317
	/**
318
	 * Render the payment method used for a subscription in the "My Subscriptions" table
319
	 *
320
	 * @since 1.7.5
321
	 * @param string $payment_method_to_display the default payment method text to display
322
	 * @param WC_Subscription $subscription the subscription details
323
	 * @return string the subscription payment method
324
	 */
325
	public function maybe_render_subscription_payment_method( $payment_method_to_display, $subscription ) {
326
		$customer_user = WC_Stripe_Helper::is_pre_30() ? $subscription->customer_user : $subscription->get_customer_id();
327
328
		// bail for other payment methods
329
		if ( ( WC_Stripe_Helper::is_pre_30() ? $subscription->payment_method : $subscription->get_payment_method() ) !== $this->id || ! $customer_user ) {
330
			return $payment_method_to_display;
331
		}
332
333
		$stripe_source_id = get_post_meta( ( WC_Stripe_Helper::is_pre_30() ? $subscription->id : $subscription->get_id() ), '_stripe_source_id', true );
334
335
		// For BW compat will remove in future.
336
		if ( empty( $stripe_source_id ) ) {
337
			$stripe_source_id = get_post_meta( ( WC_Stripe_Helper::is_pre_30() ? $subscription->id : $subscription->get_id() ), '_stripe_card_id', true );
338
339
			// Take this opportunity to update the key name.
340
			WC_Stripe_Helper::is_pre_30() ? update_post_meta( $subscription->id, '_stripe_source_id', $stripe_source_id ) : update_post_meta( $subscription->get_id(), '_stripe_source_id', $stripe_source_id );
341
		}
342
343
		$stripe_customer    = new WC_Stripe_Customer();
344
		$stripe_customer_id = get_post_meta( ( WC_Stripe_Helper::is_pre_30() ? $subscription->id : $subscription->get_id() ), '_stripe_customer_id', true );
345
346
		// If we couldn't find a Stripe customer linked to the subscription, fallback to the user meta data.
347
		if ( ! $stripe_customer_id || ! is_string( $stripe_customer_id ) ) {
348
			$user_id            = $customer_user;
349
			$stripe_customer_id = get_user_meta( $user_id, '_stripe_customer_id', true );
350
			$stripe_source_id   = get_user_meta( $user_id, '_stripe_source_id', true );
351
352
			// For BW compat will remove in future.
353
			if ( empty( $stripe_source_id ) ) {
354
				$stripe_source_id = get_user_meta( $user_id, '_stripe_card_id', true );
355
356
				// Take this opportunity to update the key name.
357
				update_user_meta( $user_id, '_stripe_source_id', $stripe_source_id );
358
			}
359
		}
360
361
		// If we couldn't find a Stripe customer linked to the account, fallback to the order meta data.
362
		if ( ( ! $stripe_customer_id || ! is_string( $stripe_customer_id ) ) && false !== $subscription->order ) {
363
			$stripe_customer_id = get_post_meta( ( WC_Stripe_Helper::is_pre_30() ? $subscription->order->id : $subscription->get_parent_id() ), '_stripe_customer_id', true );
364
			$stripe_source_id   = get_post_meta( ( WC_Stripe_Helper::is_pre_30() ? $subscription->order->id : $subscription->get_parent_id() ), '_stripe_source_id', true );
365
366
			// For BW compat will remove in future.
367
			if ( empty( $stripe_source_id ) ) {
368
				$stripe_source_id = get_post_meta( ( WC_Stripe_Helper::is_pre_30() ? $subscription->order->id : $subscription->get_parent_id() ), '_stripe_card_id', true );
369
370
				// Take this opportunity to update the key name.
371
				WC_Stripe_Helper::is_pre_30() ? update_post_meta( $subscription->order->id, '_stripe_source_id', $stripe_source_id ) : update_post_meta( $subscription->get_parent_id(), '_stripe_source_id', $stripe_source_id );
372
			}
373
		}
374
375
		$stripe_customer->set_id( $stripe_customer_id );
376
		$sources = $stripe_customer->get_sources();
377
378
		if ( $sources ) {
379
			$found_source = false;
380
			foreach ( $sources as $source ) {
381
				if ( isset( $source->type ) && 'card' === $source->type ) {
382
					$card = $source->card;
383
				}
384
385
				if ( $source->id === $stripe_source_id ) {
386
					$found_source = true;
387
					/* translators: 1) card brand 2) last 4 digits */
388
					$payment_method_to_display = sprintf( __( 'Via %1$s card ending in %2$s', 'woocommerce-gateway-stripe' ), ( isset( $card->brand ) ? $card->brand : __( 'N/A', 'woocommerce-gateway-stripe' ) ), $card->last4 );
389
					break;
390
				}
391
			}
392
393
			if ( ! $found_source ) {
394
				if ( 'card' === $sources[0]->type ) {
395
					$card = $sources[0]->card;
396
				}
397
398
				/* translators: 1) card brand 2) last 4 digits */
399
				$payment_method_to_display = sprintf( __( 'Via %1$s card ending in %2$s', 'woocommerce-gateway-stripe' ), ( isset( $card->brand ) ? $card->brand : __( 'N/A', 'woocommerce-gateway-stripe' ) ), $card->last4 );
400
			}
401
		}
402
403
		return $payment_method_to_display;
404
	}
405
406
	/**
407
	 * Process the pre-order
408
	 * @param int $order_id
409
	 * @return array
410
	 */
411
	public function process_pre_order( $order_id, $retry, $force_save_source ) {
412
		if ( WC_Pre_Orders_Order::order_requires_payment_tokenization( $order_id ) ) {
413
			try {
414
				$order = wc_get_order( $order_id );
415
416
				if ( $order->get_total() * 100 < WC_Stripe_Helper::get_minimum_amount() ) {
417
					/* translators: minimum amount */
418
					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_Helper::get_minimum_amount() / 100 ) ) );
419
				}
420
421
				$source = $this->prepare_source( get_current_user_id(), true );
422
423
				// We need a source on file to continue.
424
				if ( empty( $source->customer ) || empty( $source->source ) ) {
425
					throw new Exception( __( 'Unable to store payment details. Please try again.', 'woocommerce-gateway-stripe' ) );
426
				}
427
428
				// Store source to order meta
429
				$this->save_source( $order, $source );
430
431
				// Remove cart
432
				WC()->cart->empty_cart();
433
434
				// Is pre ordered!
435
				WC_Pre_Orders_Order::mark_order_as_pre_ordered( $order );
436
437
				// Return thank you page redirect
438
				return array(
439
					'result'   => 'success',
440
					'redirect' => $this->get_return_url( $order ),
441
				);
442
			} catch ( Exception $e ) {
443
				wc_add_notice( $e->getMessage(), 'error' );
444
				return;
445
			}
446
		} else {
447
			return parent::process_payment( $order_id, $retry, $force_save_source );
448
		}
449
	}
450
451
	/**
452
	 * Process a pre-order payment when the pre-order is released
453
	 * @param WC_Order $order
454
	 * @return void
455
	 */
456
	public function process_pre_order_release_payment( $order ) {
457
		try {
458
			// Define some callbacks if the first attempt fails.
459
			$retry_callbacks = array(
460
				'remove_order_source_before_retry',
461
				'remove_order_customer_before_retry',
462
			);
463
464
			while ( 1 ) {
465
				$source   = $this->prepare_order_source( $order );
466
				$response = WC_Stripe_API::request( $this->generate_payment_request( $order, $source ) );
467
468
				if ( ! empty( $response->error ) ) {
469
					if ( 0 === sizeof( $retry_callbacks ) ) {
470
						throw new Exception( $response->error->message );
471
					} else {
472
						$retry_callback = array_shift( $retry_callbacks );
473
						call_user_func( array( $this, $retry_callback ), $order );
474
					}
475
				} else {
476
					// Successful
477
					$this->process_response( $response, $order );
478
					break;
479
				}
480
			}
481
		} catch ( Exception $e ) {
482
			/* translators: error message */
483
			$order_note = sprintf( __( 'Stripe Transaction Failed (%s)', 'woocommerce-gateway-stripe' ), $e->getMessage() );
484
485
			// Mark order as failed if not already set,
486
			// otherwise, make sure we add the order note so we can detect when someone fails to check out multiple times
487
			if ( ! $order->has_status( 'failed' ) ) {
488
				$order->update_status( 'failed', $order_note );
489
			} else {
490
				$order->add_order_note( $order_note );
491
			}
492
		}
493
	}
494
}
495

includes/compat/class-wc-stripe-sepa-compat.php 1 location

@@ 11-493 (lines=483) @@
8
 *
9
 * @extends WC_Gateway_Stripe_Sepa
10
 */
11
class WC_Stripe_Sepa_Compat extends WC_Gateway_Stripe_Sepa {
12
	/**
13
	 * Constructor
14
	 */
15
	public function __construct() {
16
		parent::__construct();
17
18
		if ( class_exists( 'WC_Subscriptions_Order' ) ) {
19
			add_action( 'woocommerce_scheduled_subscription_payment_' . $this->id, array( $this, 'scheduled_subscription_payment' ), 10, 2 );
20
			add_action( 'wcs_resubscribe_order_created', array( $this, 'delete_resubscribe_meta' ), 10 );
21
			add_action( 'wcs_renewal_order_created', array( $this, 'delete_renewal_meta' ), 10 );
22
			add_action( 'woocommerce_subscription_failing_payment_method_updated_stripe', array( $this, 'update_failing_payment_method' ), 10, 2 );
23
24
			// display the credit card used for a subscription in the "My Subscriptions" table
25
			add_filter( 'woocommerce_my_subscriptions_payment_method', array( $this, 'maybe_render_subscription_payment_method' ), 10, 2 );
26
27
			// allow store managers to manually set Stripe as the payment method on a subscription
28
			add_filter( 'woocommerce_subscription_payment_meta', array( $this, 'add_subscription_payment_meta' ), 10, 2 );
29
			add_filter( 'woocommerce_subscription_validate_payment_meta', array( $this, 'validate_subscription_payment_meta' ), 10, 2 );
30
			add_filter( 'wc_stripe_display_save_payment_method_checkbox', array( $this, 'maybe_hide_save_checkbox' ) );
31
			add_filter( 'wc_stripe_payment_metadata', array( $this, 'add_subscription_meta_data' ), 10, 2 );
32
		}
33
34
		if ( class_exists( 'WC_Pre_Orders_Order' ) ) {
35
			add_action( 'wc_pre_orders_process_pre_order_completion_payment_' . $this->id, array( $this, 'process_pre_order_release_payment' ) );
36
		}
37
	}
38
39
	/**
40
	 * Checks to see if we need to hide the save checkbox field.
41
	 * Because when cart contains a subs product, it will save regardless.
42
	 *
43
	 * @since 4.0.0
44
	 * @version 4.0.0
45
	 */
46
	public function maybe_hide_save_checkbox( $display_tokenization ) {
47
		if ( WC_Subscriptions_Cart::cart_contains_subscription() ) {
48
			return false;
49
		}
50
51
		return $display_tokenization;
52
	}
53
54
	/**
55
	 * Is $order_id a subscription?
56
	 * @param  int  $order_id
57
	 * @return boolean
58
	 */
59
	public function has_subscription( $order_id ) {
60
		return ( function_exists( 'wcs_order_contains_subscription' ) && ( wcs_order_contains_subscription( $order_id ) || wcs_is_subscription( $order_id ) || wcs_order_contains_renewal( $order_id ) ) );
61
	}
62
63
	/**
64
	 * Is $order_id a pre-order?
65
	 * @param  int  $order_id
66
	 * @return boolean
67
	 */
68
	protected function is_pre_order( $order_id ) {
69
		return ( class_exists( 'WC_Pre_Orders_Order' ) && WC_Pre_Orders_Order::order_contains_pre_order( $order_id ) );
70
	}
71
72
	/**
73
	 * Process the payment based on type.
74
	 * @param  int $order_id
75
	 * @return array
76
	 */
77
	public function process_payment( $order_id, $retry = true, $force_save_source = false ) {
78
		if ( $this->has_subscription( $order_id ) ) {
79
			// Regular payment with force customer enabled
80
			return parent::process_payment( $order_id, true, true );
81
		} elseif ( $this->is_pre_order( $order_id ) ) {
82
			return $this->process_pre_order( $order_id, $retry, $force_save_source );
83
		} else {
84
			return parent::process_payment( $order_id, $retry, $force_save_source );
85
		}
86
	}
87
88
	/**
89
	 * Adds subscription related meta data on charge request.
90
	 *
91
	 * @since 4.0.0
92
	 * @param array $metadata
93
	 * @param object $order
94
	 */
95
	public function add_subscription_meta_data( $metadata, $order ) {
96
		if ( ! $this->has_subscription( $order->get_id() ) ) {
97
			return $metadata;
98
		}
99
100
		return $metadata += array(
101
			'payment_type'   => 'recurring',
102
			'site_url'       => esc_url( get_site_url() ),
103
		);
104
	}
105
106
	/**
107
	 * Updates other subscription sources.
108
	 *
109
	 * @since 3.1.0
110
	 * @version 4.0.0
111
	 */
112
	public function save_source( $order, $source ) {
113
		parent::save_source( $order, $source );
114
115
		$order_id  = WC_Stripe_Helper::is_pre_30() ? $order->id : $order->get_id();
116
117
		// Also store it on the subscriptions being purchased or paid for in the order
118
		if ( function_exists( 'wcs_order_contains_subscription' ) && wcs_order_contains_subscription( $order_id ) ) {
119
			$subscriptions = wcs_get_subscriptions_for_order( $order_id );
120
		} elseif ( function_exists( 'wcs_order_contains_renewal' ) && wcs_order_contains_renewal( $order_id ) ) {
121
			$subscriptions = wcs_get_subscriptions_for_renewal_order( $order_id );
122
		} else {
123
			$subscriptions = array();
124
		}
125
126
		foreach ( $subscriptions as $subscription ) {
127
			$subscription_id = WC_Stripe_Helper::is_pre_30() ? $subscription->id : $subscription->get_id();
128
			update_post_meta( $subscription_id, '_stripe_customer_id', $source->customer );
129
			update_post_meta( $subscription_id, '_stripe_source_id', $source->source );
130
		}
131
	}
132
133
	/**
134
	 * process_subscription_payment function.
135
	 * @param mixed $order
136
	 * @param int $amount (default: 0)
137
	 * @param string $stripe_token (default: '')
138
	 * @param  bool initial_payment
139
	 */
140
	public function process_subscription_payment( $order = '', $amount = 0 ) {
141
		if ( $amount * 100 < WC_Stripe_Helper::get_minimum_amount() ) {
142
			/* translators: minimum amount */
143
			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_Helper::get_minimum_amount() / 100 ) ) );
144
		}
145
146
		$customer_id = WC_Stripe_Helper::is_pre_30() ? $order->customer_user : $order->get_customer_id();
147
		$order_id    = WC_Stripe_Helper::is_pre_30() ? $order->id : $order->get_id();
148
149
		// Get source from order
150
		$prepared_source = $this->prepare_order_source( $order );
151
152
		// Or fail :(
153
		if ( ! $prepared_source->customer ) {
154
			return new WP_Error( 'stripe_error', __( 'Customer not found', 'woocommerce-gateway-stripe' ) );
155
		}
156
157
		WC_Stripe_Logger::log( "Info: Begin processing subscription payment for order {$order_id} for the amount of {$amount}" );
158
159
		// Make the request
160
		$request             = $this->generate_payment_request( $order, $prepared_source );
161
		$request['capture']  = 'true';
162
		$request['amount']   = WC_Stripe_Helper::get_stripe_amount( $amount, $request['currency'] );
163
		$response            = WC_Stripe_API::request( $request );
164
165
		// Process valid response
166
		if ( ! empty( $response->error ) ) {
167
			return $response; // Default catch all errors.
168
		}
169
170
		$this->process_response( $response, $order );
171
172
		return $response;
173
	}
174
175
	/**
176
	 * Don't transfer Stripe customer/token meta to resubscribe orders.
177
	 * @param int $resubscribe_order The order created for the customer to resubscribe to the old expired/cancelled subscription
178
	 */
179
	public function delete_resubscribe_meta( $resubscribe_order ) {
180
		delete_post_meta( ( WC_Stripe_Helper::is_pre_30() ? $resubscribe_order->id : $resubscribe_order->get_id() ), '_stripe_customer_id' );
181
		delete_post_meta( ( WC_Stripe_Helper::is_pre_30() ? $resubscribe_order->id : $resubscribe_order->get_id() ), '_stripe_source_id' );
182
		// For BW compat will remove in future
183
		delete_post_meta( ( WC_Stripe_Helper::is_pre_30() ? $resubscribe_order->id : $resubscribe_order->get_id() ), '_stripe_card_id' );
184
		$this->delete_renewal_meta( $resubscribe_order );
185
	}
186
187
	/**
188
	 * Don't transfer Stripe fee/ID meta to renewal orders.
189
	 * @param int $resubscribe_order The order created for the customer to resubscribe to the old expired/cancelled subscription
190
	 */
191
	public function delete_renewal_meta( $renewal_order ) {
192
		delete_post_meta( ( WC_Stripe_Helper::is_pre_30() ? $renewal_order->id : $renewal_order->get_id() ), 'Stripe Fee' );
193
		delete_post_meta( ( WC_Stripe_Helper::is_pre_30() ? $renewal_order->id : $renewal_order->get_id() ), 'Net Revenue From Stripe' );
194
		return $renewal_order;
195
	}
196
197
	/**
198
	 * scheduled_subscription_payment function.
199
	 *
200
	 * @param $amount_to_charge float The amount to charge.
201
	 * @param $renewal_order WC_Order A WC_Order object created to record the renewal payment.
202
	 */
203
	public function scheduled_subscription_payment( $amount_to_charge, $renewal_order ) {
204
		$response = $this->process_subscription_payment( $renewal_order, $amount_to_charge );
205
206
		if ( is_wp_error( $response ) ) {
207
			/* translators: error message */
208
			$renewal_order->update_status( 'failed', sprintf( __( 'Stripe Transaction Failed (%s)', 'woocommerce-gateway-stripe' ), $response->get_error_message() ) );
209
		}
210
211
		if ( ! empty( $response->error ) ) {
212
			/* translators: error message */
213
			$renewal_order->update_status( 'failed', sprintf( __( 'Stripe Transaction Failed (%s)', 'woocommerce-gateway-stripe' ), $response->error->message ) );
214
		}
215
	}
216
217
	/**
218
	 * Remove order meta
219
	 * @param  object $order
220
	 */
221
	public function remove_order_source_before_retry( $order ) {
222
		$order_id = WC_Stripe_Helper::is_pre_30() ? $order->id : $order->get_id();
223
		delete_post_meta( $order_id, '_stripe_source_id' );
224
		// For BW compat will remove in the future.
225
		delete_post_meta( $order_id, '_stripe_card_id' );
226
	}
227
228
	/**
229
	 * Remove order meta
230
	 * @param  object $order
231
	 */
232
	public function remove_order_customer_before_retry( $order ) {
233
		$order_id = WC_Stripe_Helper::is_pre_30() ? $order->id : $order->get_id();
234
		delete_post_meta( $order_id, '_stripe_customer_id' );
235
	}
236
237
	/**
238
	 * Update the customer_id for a subscription after using Stripe to complete a payment to make up for
239
	 * an automatic renewal payment which previously failed.
240
	 *
241
	 * @access public
242
	 * @param WC_Subscription $subscription The subscription for which the failing payment method relates.
243
	 * @param WC_Order $renewal_order The order which recorded the successful payment (to make up for the failed automatic payment).
244
	 * @return void
245
	 */
246
	public function update_failing_payment_method( $subscription, $renewal_order ) {
247
		if ( WC_Stripe_Helper::is_pre_30() ) {
248
			update_post_meta( $subscription->id, '_stripe_customer_id', $renewal_order->stripe_customer_id );
249
			update_post_meta( $subscription->id, '_stripe_source_id', $renewal_order->stripe_source_id );
250
251
		} else {
252
			update_post_meta( $subscription->get_id(), '_stripe_customer_id', $renewal_order->get_meta( '_stripe_customer_id', true ) );
253
			update_post_meta( $subscription->get_id(), '_stripe_source_id', $renewal_order->get_meta( '_stripe_source_id', true ) );
254
		}
255
	}
256
257
	/**
258
	 * Include the payment meta data required to process automatic recurring payments so that store managers can
259
	 * manually set up automatic recurring payments for a customer via the Edit Subscriptions screen in 2.0+.
260
	 *
261
	 * @since 2.5
262
	 * @param array $payment_meta associative array of meta data required for automatic payments
263
	 * @param WC_Subscription $subscription An instance of a subscription object
264
	 * @return array
265
	 */
266
	public function add_subscription_payment_meta( $payment_meta, $subscription ) {
267
		$source_id = get_post_meta( ( WC_Stripe_Helper::is_pre_30() ? $subscription->id : $subscription->get_id() ), '_stripe_source_id', true );
268
269
		// For BW compat will remove in future.
270
		if ( empty( $source_id ) ) {
271
			$source_id = get_post_meta( ( WC_Stripe_Helper::is_pre_30() ? $subscription->id : $subscription->get_id() ), '_stripe_card_id', true );
272
273
			// Take this opportunity to update the key name.
274
			WC_Stripe_Helper::is_pre_30() ? update_post_meta( $subscription->id, '_stripe_source_id', $source_id ) : update_post_meta( $subscription->get_id(), '_stripe_source_id', $source_id );
275
		}
276
277
		$payment_meta[ $this->id ] = array(
278
			'post_meta' => array(
279
				'_stripe_customer_id' => array(
280
					'value' => get_post_meta( ( WC_Stripe_Helper::is_pre_30() ? $subscription->id : $subscription->get_id() ), '_stripe_customer_id', true ),
281
					'label' => 'Stripe Customer ID',
282
				),
283
				'_stripe_source_id' => array(
284
					'value' => $source_id,
285
					'label' => 'Stripe Source ID',
286
				),
287
			),
288
		);
289
		return $payment_meta;
290
	}
291
292
	/**
293
	 * Validate the payment meta data required to process automatic recurring payments so that store managers can
294
	 * manually set up automatic recurring payments for a customer via the Edit Subscriptions screen in 2.0+.
295
	 *
296
	 * @since 2.5
297
	 * @param string $payment_method_id The ID of the payment method to validate
298
	 * @param array $payment_meta associative array of meta data required for automatic payments
299
	 * @return array
300
	 */
301
	public function validate_subscription_payment_meta( $payment_method_id, $payment_meta ) {
302
		if ( $this->id === $payment_method_id ) {
303
304
			if ( ! isset( $payment_meta['post_meta']['_stripe_customer_id']['value'] ) || empty( $payment_meta['post_meta']['_stripe_customer_id']['value'] ) ) {
305
				throw new Exception( 'A "_stripe_customer_id" value is required.' );
306
			} elseif ( 0 !== strpos( $payment_meta['post_meta']['_stripe_customer_id']['value'], 'cus_' ) ) {
307
				throw new Exception( 'Invalid customer ID. A valid "_stripe_customer_id" must begin with "cus_".' );
308
			}
309
310
			if ( ! isset( $payment_meta['post_meta']['_stripe_source_id']['value'] ) || empty( $payment_meta['post_meta']['_stripe_source_id']['value'] ) ) {
311
				throw new Exception( 'A "_stripe_source_id" value is required.' );
312
			}
313
		}
314
	}
315
316
	/**
317
	 * Render the payment method used for a subscription in the "My Subscriptions" table
318
	 *
319
	 * @since 1.7.5
320
	 * @param string $payment_method_to_display the default payment method text to display
321
	 * @param WC_Subscription $subscription the subscription details
322
	 * @return string the subscription payment method
323
	 */
324
	public function maybe_render_subscription_payment_method( $payment_method_to_display, $subscription ) {
325
		$customer_user = WC_Stripe_Helper::is_pre_30() ? $subscription->customer_user : $subscription->get_customer_id();
326
327
		// bail for other payment methods
328
		if ( ( WC_Stripe_Helper::is_pre_30() ? $subscription->payment_method : $subscription->get_payment_method() ) !== $this->id || ! $customer_user ) {
329
			return $payment_method_to_display;
330
		}
331
332
		$stripe_source_id = get_post_meta( ( WC_Stripe_Helper::is_pre_30() ? $subscription->id : $subscription->get_id() ), '_stripe_source_id', true );
333
334
		// For BW compat will remove in future.
335
		if ( empty( $stripe_source_id ) ) {
336
			$stripe_source_id = get_post_meta( ( WC_Stripe_Helper::is_pre_30() ? $subscription->id : $subscription->get_id() ), '_stripe_card_id', true );
337
338
			// Take this opportunity to update the key name.
339
			WC_Stripe_Helper::is_pre_30() ? update_post_meta( $subscription->id, '_stripe_source_id', $stripe_source_id ) : update_post_meta( $subscription->get_id(), '_stripe_source_id', $stripe_source_id );
340
		}
341
342
		$stripe_customer    = new WC_Stripe_Customer();
343
		$stripe_customer_id = get_post_meta( ( WC_Stripe_Helper::is_pre_30() ? $subscription->id : $subscription->get_id() ), '_stripe_customer_id', true );
344
345
		// If we couldn't find a Stripe customer linked to the subscription, fallback to the user meta data.
346
		if ( ! $stripe_customer_id || ! is_string( $stripe_customer_id ) ) {
347
			$user_id            = $customer_user;
348
			$stripe_customer_id = get_user_meta( $user_id, '_stripe_customer_id', true );
349
			$stripe_source_id   = get_user_meta( $user_id, '_stripe_source_id', true );
350
351
			// For BW compat will remove in future.
352
			if ( empty( $stripe_source_id ) ) {
353
				$stripe_source_id = get_user_meta( $user_id, '_stripe_card_id', true );
354
355
				// Take this opportunity to update the key name.
356
				update_user_meta( $user_id, '_stripe_source_id', $stripe_source_id );
357
			}
358
		}
359
360
		// If we couldn't find a Stripe customer linked to the account, fallback to the order meta data.
361
		if ( ( ! $stripe_customer_id || ! is_string( $stripe_customer_id ) ) && false !== $subscription->order ) {
362
			$stripe_customer_id = get_post_meta( ( WC_Stripe_Helper::is_pre_30() ? $subscription->order->id : $subscription->get_parent_id() ), '_stripe_customer_id', true );
363
			$stripe_source_id   = get_post_meta( ( WC_Stripe_Helper::is_pre_30() ? $subscription->order->id : $subscription->get_parent_id() ), '_stripe_source_id', true );
364
365
			// For BW compat will remove in future.
366
			if ( empty( $stripe_source_id ) ) {
367
				$stripe_source_id = get_post_meta( ( WC_Stripe_Helper::is_pre_30() ? $subscription->order->id : $subscription->get_parent_id() ), '_stripe_card_id', true );
368
369
				// Take this opportunity to update the key name.
370
				WC_Stripe_Helper::is_pre_30() ? update_post_meta( $subscription->order->id, '_stripe_source_id', $stripe_source_id ) : update_post_meta( $subscription->get_parent_id(), '_stripe_source_id', $stripe_source_id );
371
			}
372
		}
373
374
		$stripe_customer->set_id( $stripe_customer_id );
375
		$sources = $stripe_customer->get_sources();
376
377
		if ( $sources ) {
378
			$found_source = false;
379
			foreach ( $sources as $source ) {
380
				if ( isset( $source->type ) && 'card' === $source->type ) {
381
					$card = $source->card;
382
				}
383
384
				if ( $source->id === $stripe_source_id ) {
385
					$found_source = true;
386
					/* translators: 1) card brand 2) last 4 digits */
387
					$payment_method_to_display = sprintf( __( 'Via %1$s card ending in %2$s', 'woocommerce-gateway-stripe' ), ( isset( $card->brand ) ? $card->brand : __( 'N/A', 'woocommerce-gateway-stripe' ) ), $card->last4 );
388
					break;
389
				}
390
			}
391
392
			if ( ! $found_source ) {
393
				if ( 'card' === $sources[0]->type ) {
394
					$card = $sources[0]->card;
395
				}
396
397
				/* translators: 1) card brand 2) last 4 digits */
398
				$payment_method_to_display = sprintf( __( 'Via %1$s card ending in %2$s', 'woocommerce-gateway-stripe' ), ( isset( $card->brand ) ? $card->brand : __( 'N/A', 'woocommerce-gateway-stripe' ) ), $card->last4 );
399
			}
400
		}
401
402
		return $payment_method_to_display;
403
	}
404
405
	/**
406
	 * Process the pre-order
407
	 * @param int $order_id
408
	 * @return array
409
	 */
410
	public function process_pre_order( $order_id, $retry, $force_save_source ) {
411
		if ( WC_Pre_Orders_Order::order_requires_payment_tokenization( $order_id ) ) {
412
			try {
413
				$order = wc_get_order( $order_id );
414
415
				if ( $order->get_total() * 100 < WC_Stripe_Helper::get_minimum_amount() ) {
416
					/* translators: minimum amount */
417
					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_Helper::get_minimum_amount() / 100 ) ) );
418
				}
419
420
				$source = $this->prepare_source( get_current_user_id(), true );
421
422
				// We need a source on file to continue.
423
				if ( empty( $source->customer ) || empty( $source->source ) ) {
424
					throw new Exception( __( 'Unable to store payment details. Please try again.', 'woocommerce-gateway-stripe' ) );
425
				}
426
427
				// Store source to order meta
428
				$this->save_source( $order, $source );
429
430
				// Remove cart
431
				WC()->cart->empty_cart();
432
433
				// Is pre ordered!
434
				WC_Pre_Orders_Order::mark_order_as_pre_ordered( $order );
435
436
				// Return thank you page redirect
437
				return array(
438
					'result'   => 'success',
439
					'redirect' => $this->get_return_url( $order ),
440
				);
441
			} catch ( Exception $e ) {
442
				wc_add_notice( $e->getMessage(), 'error' );
443
				return;
444
			}
445
		} else {
446
			return parent::process_payment( $order_id, $retry, $force_save_source );
447
		}
448
	}
449
450
	/**
451
	 * Process a pre-order payment when the pre-order is released
452
	 * @param WC_Order $order
453
	 * @return void
454
	 */
455
	public function process_pre_order_release_payment( $order ) {
456
		try {
457
			// Define some callbacks if the first attempt fails.
458
			$retry_callbacks = array(
459
				'remove_order_source_before_retry',
460
				'remove_order_customer_before_retry',
461
			);
462
463
			while ( 1 ) {
464
				$source   = $this->prepare_order_source( $order );
465
				$response = WC_Stripe_API::request( $this->generate_payment_request( $order, $source ) );
466
467
				if ( ! empty( $response->error ) ) {
468
					if ( 0 === sizeof( $retry_callbacks ) ) {
469
						throw new Exception( $response->error->message );
470
					} else {
471
						$retry_callback = array_shift( $retry_callbacks );
472
						call_user_func( array( $this, $retry_callback ), $order );
473
					}
474
				} else {
475
					// Successful
476
					$this->process_response( $response, $order );
477
					break;
478
				}
479
			}
480
		} catch ( Exception $e ) {
481
			/* translators: error message */
482
			$order_note = sprintf( __( 'Stripe Transaction Failed (%s)', 'woocommerce-gateway-stripe' ), $e->getMessage() );
483
484
			// Mark order as failed if not already set,
485
			// otherwise, make sure we add the order note so we can detect when someone fails to check out multiple times
486
			if ( ! $order->has_status( 'failed' ) ) {
487
				$order->update_status( 'failed', $order_note );
488
			} else {
489
				$order->add_order_note( $order_note );
490
			}
491
		}
492
	}
493
}
494