Failed Conditions
Push — develop ( df9600...36a6a6 )
by Reüel
06:36
created

src/Extension.php (4 issues)

1
<?php
2
/**
3
 * Extension
4
 *
5
 * @author    Pronamic <[email protected]>
6
 * @copyright 2005-2021 Pronamic
7
 * @license   GPL-3.0-or-later
8
 * @package   Pronamic\WordPress\Pay\Extensions\MemberPress
9
 */
10
11
namespace Pronamic\WordPress\Pay\Extensions\MemberPress;
12
13
use MeprDb;
14
use MeprOptions;
15
use MeprProduct;
16
use MeprSubscription;
17
use MeprTransaction;
18
use MeprUtils;
19
use Pronamic\WordPress\Pay\AbstractPluginIntegration;
20
use Pronamic\WordPress\Pay\Payments\PaymentStatus;
21
use Pronamic\WordPress\Pay\Extensions\MemberPress\Gateways\Gateway;
22
use Pronamic\WordPress\Pay\Payments\Payment;
23
use Pronamic\WordPress\Pay\Subscriptions\Subscription;
24
use Pronamic\WordPress\Pay\Subscriptions\SubscriptionStatus;
25
26
/**
27
 * WordPress pay MemberPress extension
28
 *
29
 * @author  Remco Tolsma
30
 * @version 2.2.3
31
 * @since   1.0.0
32
 */
33
class Extension extends AbstractPluginIntegration {
34
	/**
35
	 * The slug of this addon
36
	 *
37
	 * @var string
38
	 */
39
	const SLUG = 'memberpress';
40
41
	/**
42
	 * Construct MemberPress plugin integration.
43
	 */
44
	public function __construct() {
45
		parent::__construct(
46
			array(
47
				'name' => __( 'MemberPress', 'pronamic_ideal' ),
48
			)
49
		);
50
51
		// Dependencies.
52
		$dependencies = $this->get_dependencies();
53
54
		$dependencies->add( new MemberPressDependency() );
55
	}
56
57
	/**
58
	 * Setup.
59
	 */
60
	public function setup() {
61
		\add_filter( 'pronamic_subscription_source_description_' . self::SLUG, array( $this, 'subscription_source_description' ), 10, 2 );
62
		\add_filter( 'pronamic_payment_source_description_' . self::SLUG, array( $this, 'source_description' ), 10, 2 );
63
64
		// Check if dependencies are met and integration is active.
65
		if ( ! $this->is_active() ) {
66
			return;
67
		}
68
69
		// @link https://gitlab.com/pronamic/memberpress/blob/1.2.4/app/lib/MeprGatewayFactory.php#L48-50
70
		\add_filter( 'mepr-gateway-paths', array( $this, 'gateway_paths' ) );
71
72
		\add_filter( 'pronamic_payment_redirect_url_' . self::SLUG, array( $this, 'redirect_url' ), 10, 2 );
73
		\add_action( 'pronamic_payment_status_update_' . self::SLUG, array( $this, 'status_update' ), 10, 1 );
74
75
		\add_filter( 'pronamic_subscription_source_text_' . self::SLUG, array( $this, 'subscription_source_text' ), 10, 2 );
76
		\add_filter( 'pronamic_subscription_source_url_' . self::SLUG, array( $this, 'subscription_source_url' ), 10, 2 );
77
		\add_filter( 'pronamic_payment_source_text_' . self::SLUG, array( $this, 'source_text' ), 10, 2 );
78
		\add_filter( 'pronamic_payment_source_url_' . self::SLUG, array( $this, 'source_url' ), 10, 2 );
79
80
		\add_action( 'mepr_subscription_pre_delete', array( $this, 'subscription_pre_delete' ), 10, 1 );
81
82
		\add_action( 'mepr_subscription_transition_status', array( $this, 'memberpress_subscription_transition_status' ), 10, 3 );
83
84
		// Hide MemberPress columns for payments and subscriptions.
85
		\add_filter( 'registered_post_type', array( $this, 'post_type_columns_hide' ), 15, 1 );
86
87
		if ( \is_admin() ) {
88
			$this->admin_subscriptions = new Admin\AdminSubscriptions();
0 ignored issues
show
Bug Best Practice introduced by
The property admin_subscriptions does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
89
			$this->admin_transactions  = new Admin\AdminTransactions();
0 ignored issues
show
Bug Best Practice introduced by
The property admin_transactions does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
90
91
			$this->admin_subscriptions->setup();
92
			$this->admin_transactions->setup();
93
		}
94
	}
95
96
	/**
97
	 * Gateway paths.
98
	 *
99
	 * @link https://gitlab.com/pronamic/memberpress/blob/1.2.4/app/lib/MeprGatewayFactory.php#L48-50
100
	 *
101
	 * @param array $paths Array with gateway paths.
102
	 * @return array
103
	 */
104
	public function gateway_paths( $paths ) {
105
		$paths[] = dirname( __FILE__ ) . '/../gateways/';
106
107
		return $paths;
108
	}
109
110
	/**
111
	 * Hide MemberPress columns for payments and subscriptions.
112
	 *
113
	 * @link https://gitlab.com/pronamic/memberpress/blob/1.2.4/app/controllers/MeprAppCtrl.php#L129-146
114
	 *
115
	 * @param string $post_type Registered post type.
116
	 *
117
	 * @return void
118
	 */
119
	public function post_type_columns_hide( $post_type ) {
120
		if ( ! in_array( $post_type, array( 'pronamic_payment', 'pronamic_pay_subscr' ), true ) ) {
121
			return;
122
		}
123
124
		remove_filter( 'manage_edit-' . $post_type . '_columns', 'MeprAppCtrl::columns' );
125
	}
126
127
	/**
128
	 * Payment redirect URL filter.
129
	 *
130
	 * @since 1.0.1
131
	 *
132
	 * @param string  $url     Payment redirect URL.
133
	 * @param Payment $payment Payment to redirect for.
134
	 *
135
	 * @return string
136
	 */
137
	public function redirect_url( $url, Payment $payment ) {
138
		global $transaction;
139
140
		$transaction_id = $payment->get_source_id();
141
142
		$transaction = new MeprTransaction( $transaction_id );
143
144
		switch ( $payment->get_status() ) {
145
			case PaymentStatus::CANCELLED:
146
			case PaymentStatus::EXPIRED:
147
			case PaymentStatus::FAILURE:
148
				$product = $transaction->product();
149
150
				$url = add_query_arg(
151
					array(
152
						'action'   => 'payment_form',
153
						'txn'      => $transaction->trans_num,
154
						'errors'   => array(
155
							__( 'Payment failed. Please try again.', 'pronamic_ideal' ),
156
						),
157
						'_wpnonce' => wp_create_nonce( 'mepr_payment_form' ),
158
					),
159
					$product->url()
160
				);
161
162
				break;
163
			case PaymentStatus::SUCCESS:
164
				// @link https://gitlab.com/pronamic/memberpress/blob/1.2.4/app/models/MeprOptions.php#L768-782
165
				$mepr_options = MeprOptions::fetch();
166
167
				$product         = new MeprProduct( $transaction->product_id );
168
				$sanitized_title = sanitize_title( $product->post_title );
169
170
				$args = array(
171
					'membership_id' => $product->ID,
172
					'membership'    => $sanitized_title,
173
					'trans_num'     => $transaction->trans_num,
174
				);
175
176
				$url = $mepr_options->thankyou_page_url( http_build_query( $args ) );
177
178
				break;
179
			case PaymentStatus::OPEN:
180
			default:
181
				break;
182
		}
183
184
		return $url;
185
	}
186
187
	/**
188
	 * Update lead status of the specified payment.
189
	 *
190
	 * @link https://github.com/Charitable/Charitable/blob/1.1.4/includes/gateways/class-charitable-gateway-paypal.php#L229-L357
191
	 *
192
	 * @param Payment $payment The payment whose status is updated.
193
	 */
194
	public function status_update( Payment $payment ) {
195
		$transaction = new MeprTransaction( $payment->get_source_id() );
196
197
		if ( $payment->get_recurring() || empty( $transaction->id ) ) {
198
			$subscription_id = $payment->get_subscription()->get_source_id();
199
			$subscription    = new MeprSubscription( $subscription_id );
200
201
			// Same source ID and first transaction ID for recurring payment means we need to add a new transaction.
202
			if ( $payment->get_source_id() === $subscription->id ) {
203
				// First transaction.
204
				$first_txn = $subscription->first_txn();
205
206
				if ( false === $first_txn || ! ( $first_txn instanceof MeprTransaction ) ) {
207
					$first_txn             = new MeprTransaction();
208
					$first_txn->user_id    = $subscription->user_id;
209
					$first_txn->product_id = $subscription->product_id;
210
					$first_txn->coupon_id  = $subscription->coupon_id;
211
					$first_txn->gateway    = null;
212
				}
213
214
				// Transaction number.
215
				$trans_num = $payment->get_transaction_id();
216
217
				if ( empty( $trans_num ) ) {
218
					$trans_num = uniqid();
219
				}
220
221
				// New transaction.
222
				$end_date = $payment->get_end_date();
223
224
				if ( null === $end_date ) {
225
					$periods = $payment->get_periods();
226
227
					if ( ! empty( $periods ) ) {
228
						$end_date = $periods[0]->get_end_date();
229
					}
230
				}
231
232
				$expires_at = MeprUtils::ts_to_mysql_date( $end_date->getTimestamp(), 'Y-m-d 23:59:59' );
233
234
				// Determine gateway (can be different from original, i.e. via mandate update).
235
				$old_gateway = $subscription->gateway;
236
237
				$new_gateway = $old_gateway;
238
239
				$mepr_options = MeprOptions::fetch();
240
241
				$mepr_gateways = $mepr_options->payment_methods();
242
243
				foreach ( $mepr_gateways as $mepr_gateway ) {
244
					// Check valid gateway.
245
					if ( ! ( $mepr_gateway instanceof \MeprBaseRealGateway ) ) {
246
						continue;
247
					}
248
249
					// Check payment method.
250
					$payment_method = \str_replace( 'pronamic_pay_', '', $mepr_gateway->key );
251
252
					if ( $payment_method !== $payment->get_method() ) {
253
						continue;
254
					}
255
256
					// Set gateway, as payment method matches with this gateway.
257
					$new_gateway = $mepr_gateway->id;
258
259
					$subscription->gateway = $new_gateway;
260
261
					$subscription->store();
262
263
					// Set expires at to subscription expiration date.
264
					$expires_at = $subscription->expires_at;
265
				}
266
267
				$transaction                  = new MeprTransaction();
268
				$transaction->created_at      = $payment->post->post_date_gmt;
269
				$transaction->user_id         = $first_txn->user_id;
270
				$transaction->product_id      = $first_txn->product_id;
271
				$transaction->coupon_id       = $first_txn->coupon_id;
272
				$transaction->gateway         = $new_gateway;
273
				$transaction->trans_num       = $trans_num;
274
				$transaction->txn_type        = MeprTransaction::$payment_str;
275
				$transaction->status          = MeprTransaction::$pending_str;
276
				$transaction->expires_at      = $expires_at;
0 ignored issues
show
Bug Best Practice introduced by
The property expires_at does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
277
				$transaction->subscription_id = $subscription->id;
278
279
				$transaction->set_gross( $payment->get_total_amount()->get_value() );
280
281
				$transaction->store();
282
283
				// Set source ID.
284
				$payment->set_meta( 'source_id', $transaction->id );
285
286
				$payment->source_id = $transaction->id;
287
288
				if ( MeprSubscription::$active_str === $subscription->status && $old_gateway === $new_gateway ) {
289
					/*
290
					 * We create a 'confirmed' 'subscription_confirmation'
291
					 * transaction for a grace period of 15 days.
292
					 *
293
					 * Transactions of type "subscription_confirmation" with a
294
					 * status of "confirmed" are hidden in the UI, and are used
295
					 * as a way to provide free trial periods and the 24 hour
296
					 * grace period on a recurring subscription signup.
297
					 *
298
					 * @link https://docs.memberpress.com/article/219-where-is-data-stored.
299
					 */
300
					$subscription_confirmation                  = new MeprTransaction();
301
					$subscription_confirmation->created_at      = $payment->post->post_date_gmt;
302
					$subscription_confirmation->user_id         = $first_txn->user_id;
303
					$subscription_confirmation->product_id      = $first_txn->product_id;
304
					$subscription_confirmation->coupon_id       = $first_txn->coupon_id;
305
					$subscription_confirmation->gateway         = $new_gateway;
306
					$subscription_confirmation->trans_num       = $trans_num;
307
					$subscription_confirmation->txn_type        = MeprTransaction::$subscription_confirmation_str;
308
					$subscription_confirmation->status          = MeprTransaction::$confirmed_str;
309
					$subscription_confirmation->subscription_id = $subscription->id;
310
					$subscription_confirmation->expires_at      = MeprUtils::ts_to_mysql_date( strtotime( $payment->post->post_date_gmt ) + MeprUtils::days( 15 ), 'Y-m-d 23:59:59' );
311
312
					$subscription_confirmation->set_subtotal( 0.00 );
313
314
					$subscription_confirmation->store();
315
				}
316
			}
317
		}
318
319
		$should_update = ! MemberPress::transaction_has_status(
320
			$transaction,
321
			array(
322
				MeprTransaction::$failed_str,
323
				MeprTransaction::$complete_str,
324
			)
325
		);
326
327
		// Allow successful recurring payments to update failed transaction.
328
		if ( $payment->get_recurring() && PaymentStatus::SUCCESS === $payment->get_status() && MeprTransaction::$failed_str === $transaction->status ) {
329
			$should_update = true;
330
		}
331
332
		if ( $should_update ) {
333
			$gateway = new Gateway();
334
335
			$gateway->pronamic_payment = $payment;
336
			$gateway->mp_txn           = $transaction;
337
338
			switch ( $payment->get_status() ) {
339
				case PaymentStatus::FAILURE:
340
					$gateway->record_payment_failure();
341
342
					// Set subscription 'On Hold' to prevent subsequent
343
					// successful subscriptions from awaking subscription.
344
					if ( ! $payment->get_recurring() ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $payment->get_recurring() of type boolean|null is loosely compared to false; this is ambiguous if the boolean can be false. You might want to explicitly use !== null instead.

If an expression can have both false, and null as possible values. It is generally a good practice to always use strict comparison to clearly distinguish between those two values.

$a = canBeFalseAndNull();

// Instead of
if ( ! $a) { }

// Better use one of the explicit versions:
if ($a !== null) { }
if ($a !== false) { }
if ($a !== null && $a !== false) { }
Loading history...
345
						$subscription = $payment->get_subscription();
346
347
						if ( null !== $subscription ) {
348
							$subscription->set_status( SubscriptionStatus::ON_HOLD );
349
						}
350
					}
351
352
					break;
353
				case PaymentStatus::CANCELLED:
354
				case PaymentStatus::EXPIRED:
355
					$gateway->record_payment_failure();
356
357
					break;
358
				case PaymentStatus::SUCCESS:
359
					if ( $payment->get_recurring() ) {
360
						$gateway->record_subscription_payment();
361
					} else {
362
						$gateway->record_payment();
363
					}
364
365
					break;
366
				case PaymentStatus::OPEN:
367
				default:
368
					break;
369
			}
370
		}
371
	}
372
373
	/**
374
	 * Subscription deleted.
375
	 *
376
	 * @param int $subscription_id MemberPress subscription id.
377
	 */
378
	public function subscription_pre_delete( $subscription_id ) {
379
		$subscription = get_pronamic_subscription_by_meta( '_pronamic_subscription_source_id', $subscription_id );
380
381
		if ( ! $subscription ) {
382
			return;
383
		}
384
385
		// Add note.
386
		$note = sprintf(
387
			/* translators: %s: MemberPress */
388
			__( '%s subscription deleted.', 'pronamic_ideal' ),
389
			__( 'MemberPress', 'pronamic_ideal' )
390
		);
391
392
		$subscription->add_note( $note );
393
394
		// The status of canceled or completed subscriptions will not be changed automatically.
395
		if ( ! in_array( $subscription->get_status(), array( SubscriptionStatus::CANCELLED, SubscriptionStatus::COMPLETED ), true ) ) {
396
			$subscription->set_status( SubscriptionStatus::CANCELLED );
397
398
			$subscription->save();
399
		}
400
	}
401
402
	/**
403
	 * Source text.
404
	 *
405
	 * @param string  $text    Source text.
406
	 * @param Payment $payment Payment to create the source text for.
407
	 *
408
	 * @return string
409
	 */
410
	public function source_text( $text, Payment $payment ) {
411
		$text = __( 'MemberPress', 'pronamic_ideal' ) . '<br />';
412
413
		$text .= sprintf(
414
			'<a href="%s">%s</a>',
415
			add_query_arg(
416
				array(
417
					'page'   => 'memberpress-trans',
418
					'action' => 'edit',
419
					'id'     => $payment->source_id,
420
				),
421
				admin_url( 'admin.php' )
422
			),
423
			/* translators: %s: payment source id */
424
			sprintf( __( 'Transaction %s', 'pronamic_ideal' ), $payment->source_id )
425
		);
426
427
		return $text;
428
	}
429
430
	/**
431
	 * Subscription source text.
432
	 *
433
	 * @param string       $text         Source text.
434
	 * @param Subscription $subscription Subscription to create the source text for.
435
	 *
436
	 * @return string
437
	 */
438
	public function subscription_source_text( $text, Subscription $subscription ) {
439
		$text = __( 'MemberPress', 'pronamic_ideal' ) . '<br />';
440
441
		$text .= sprintf(
442
			'<a href="%s">%s</a>',
443
			add_query_arg(
444
				array(
445
					'page'         => 'memberpress-subscriptions',
446
					'subscription' => $subscription->source_id,
447
				),
448
				admin_url( 'admin.php' )
449
			),
450
			/* translators: %s: payment source id */
451
			sprintf( __( 'Subscription %s', 'pronamic_ideal' ), $subscription->source_id )
452
		);
453
454
		return $text;
455
	}
456
457
	/**
458
	 * Source description.
459
	 *
460
	 * @param string  $description Description.
461
	 * @param Payment $payment     Payment to create the description for.
462
	 *
463
	 * @return string
464
	 */
465
	public function source_description( $description, Payment $payment ) {
466
		return __( 'MemberPress Transaction', 'pronamic_ideal' );
467
	}
468
469
	/**
470
	 * Subscription source description.
471
	 *
472
	 * @param string       $description  Description.
473
	 * @param Subscription $subscription Subscription to create the description for.
474
	 *
475
	 * @return string
476
	 */
477
	public function subscription_source_description( $description, Subscription $subscription ) {
478
		return __( 'MemberPress Subscription', 'pronamic_ideal' );
479
	}
480
481
	/**
482
	 * Source URL.
483
	 *
484
	 * @param string  $url     URL.
485
	 * @param Payment $payment The payment to create the source URL for.
486
	 *
487
	 * @return string
488
	 */
489
	public function source_url( $url, Payment $payment ) {
490
		$url = add_query_arg(
491
			array(
492
				'page'   => 'memberpress-trans',
493
				'action' => 'edit',
494
				'id'     => $payment->source_id,
495
			),
496
			admin_url( 'admin.php' )
497
		);
498
499
		return $url;
500
	}
501
502
	/**
503
	 * Subscription source URL.
504
	 *
505
	 * @param string       $url          URL.
506
	 * @param Subscription $subscription Subscription.
507
	 *
508
	 * @return string
509
	 */
510
	public function subscription_source_url( $url, Subscription $subscription ) {
511
		$url = add_query_arg(
512
			array(
513
				'page'         => 'memberpress-subscriptions',
514
				'subscription' => $subscription->source_id,
515
			),
516
			admin_url( 'admin.php' )
517
		);
518
519
		return $url;
520
	}
521
522
	/**
523
	 * MemberPress update subscription.
524
	 *
525
	 * @link https://github.com/wp-premium/memberpress-basic/blob/1.3.18/app/controllers/MeprSubscriptionsCtrl.php#L92-L111
526
	 * @link https://github.com/wp-premium/memberpress-basic/blob/1.3.18/app/models/MeprSubscription.php#L100-L123
527
	 * @link https://github.com/wp-premium/memberpress-basic/blob/1.3.18/app/models/MeprSubscription.php#L112
528
	 *
529
	 * @param string           $status_old               Old status identifier.
530
	 * @param string           $status_new               New status identifier.
531
	 * @param MeprSubscription $memberpress_subscription MemberPress subscription object.
532
	 */
533
	public function memberpress_subscription_transition_status( $status_old, $status_new, $memberpress_subscription ) {
534
		$subscription = get_pronamic_subscription_by_meta( '_pronamic_subscription_source_id', $memberpress_subscription->id );
535
536
		if ( empty( $subscription ) ) {
537
			return;
538
		}
539
540
		$status = SubscriptionStatuses::transform( $status_new );
541
542
		if ( null === $status ) {
543
			return;
544
		}
545
546
		$subscription->set_status( $status );
547
548
		$subscription->save();
549
	}
550
}
551