Failed Conditions
Push — master ( 85457b...1516a3 )
by Reüel
15:24 queued 05:52
created

Extension::bootstrap()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 2
ccs 0
cts 2
cp 0
crap 2
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * Extension
4
 *
5
 * @author    Pronamic <[email protected]>
6
 * @copyright 2005-2020 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\Payments\PaymentStatus;
20
use Pronamic\WordPress\Pay\Extensions\MemberPress\Gateways\Gateway;
21
use Pronamic\WordPress\Pay\Payments\Payment;
22
use Pronamic\WordPress\Pay\Subscriptions\Subscription;
23
use Pronamic\WordPress\Pay\Subscriptions\SubscriptionStatus;
24
25
/**
26
 * WordPress pay MemberPress extension
27
 *
28
 * @author  Remco Tolsma
29
 * @version 2.0.4
30
 * @since   1.0.0
31
 */
32
class Extension {
33
	/**
34
	 * The slug of this addon
35
	 *
36
	 * @var string
37
	 */
38
	const SLUG = 'memberpress';
39
40
	/**
41
	 * Bootstrap
42
	 */
43
	public static function bootstrap() {
44
		new self();
45
	}
46
47
	/**
48
	 * Constructs and initializes the MemberPress extension.
49
	 */
50
	public function __construct() {
51
		// @link https://gitlab.com/pronamic/memberpress/blob/1.2.4/app/lib/MeprGatewayFactory.php#L48-50
52
		add_filter( 'mepr-gateway-paths', array( $this, 'gateway_paths' ) );
53
54
		add_filter( 'pronamic_payment_redirect_url_' . self::SLUG, array( __CLASS__, 'redirect_url' ), 10, 2 );
55
		add_action( 'pronamic_payment_status_update_' . self::SLUG, array( __CLASS__, 'status_update' ), 10, 1 );
56
57
		add_filter( 'pronamic_payment_source_text_' . self::SLUG, array( __CLASS__, 'source_text' ), 10, 2 );
58
		add_filter( 'pronamic_payment_source_description_' . self::SLUG, array( __CLASS__, 'source_description' ), 10, 2 );
59
		add_filter( 'pronamic_payment_source_url_' . self::SLUG, array( __CLASS__, 'source_url' ), 10, 2 );
60
		add_filter( 'pronamic_subscription_source_text_' . self::SLUG, array( __CLASS__, 'subscription_source_text' ), 10, 2 );
61
		add_filter( 'pronamic_subscription_source_description_' . self::SLUG, array( __CLASS__, 'subscription_source_description' ), 10, 2 );
62
		add_filter( 'pronamic_subscription_source_url_' . self::SLUG, array( __CLASS__, 'subscription_source_url' ), 10, 2 );
63
64
		add_action( 'mepr_subscription_pre_delete', array( $this, 'subscription_pre_delete' ), 10, 1 );
65
66
		add_action( 'mepr_subscription_transition_status', array( $this, 'memberpress_subscription_transition_status' ), 10, 3 );
67
68
		// Hide MemberPress columns for payments and subscriptions.
69
		add_filter( 'registered_post_type', array( $this, 'post_type_columns_hide' ), 15, 1 );
70
71
		if ( is_admin() ) {
72
			$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...
73
			$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...
74
75
			$this->admin_subscriptions->setup();
76
			$this->admin_transactions->setup();
77
		}
78
	}
79
80
	/**
81
	 * Gateway paths.
82
	 *
83
	 * @link https://gitlab.com/pronamic/memberpress/blob/1.2.4/app/lib/MeprGatewayFactory.php#L48-50
84
	 *
85
	 * @param array $paths Array with gateway paths.
86
	 * @return array
87
	 */
88
	public function gateway_paths( $paths ) {
89
		$paths[] = dirname( __FILE__ ) . '/../gateways/';
90
91
		return $paths;
92
	}
93
94
	/**
95
	 * Hide MemberPress columns for payments and subscriptions.
96
	 *
97
	 * @link https://gitlab.com/pronamic/memberpress/blob/1.2.4/app/controllers/MeprAppCtrl.php#L129-146
98
	 *
99
	 * @param string $post_type Registered post type.
100
	 *
101
	 * @return void
102
	 */
103
	public function post_type_columns_hide( $post_type ) {
104
		if ( ! in_array( $post_type, array( 'pronamic_payment', 'pronamic_pay_subscr' ), true ) ) {
105
			return;
106
		}
107
108
		remove_filter( 'manage_edit-' . $post_type . '_columns', 'MeprAppCtrl::columns' );
109
	}
110
111
	/**
112
	 * Payment redirect URL filter.
113
	 *
114
	 * @since 1.0.1
115
	 *
116
	 * @param string  $url     Payment redirect URL.
117
	 * @param Payment $payment Payment to redirect for.
118
	 *
119
	 * @return string
120
	 */
121
	public static function redirect_url( $url, Payment $payment ) {
122
		global $transaction;
123
124
		$transaction_id = $payment->get_source_id();
125
126
		$transaction = new MeprTransaction( $transaction_id );
127
128
		switch ( $payment->get_status() ) {
129
			case PaymentStatus::CANCELLED:
130
			case PaymentStatus::EXPIRED:
131
			case PaymentStatus::FAILURE:
132
				$product = $transaction->product();
133
134
				$url = add_query_arg(
135
					array(
136
						'action'   => 'payment_form',
137
						'txn'      => $transaction->trans_num,
138
						'errors'   => array(
139
							__( 'Payment failed. Please try again.', 'pronamic_ideal' ),
140
						),
141
						'_wpnonce' => wp_create_nonce( 'mepr_payment_form' ),
142
					),
143
					$product->url()
144
				);
145
146
				break;
147
			case PaymentStatus::SUCCESS:
148
				// @link https://gitlab.com/pronamic/memberpress/blob/1.2.4/app/models/MeprOptions.php#L768-782
149
				$mepr_options = MeprOptions::fetch();
150
151
				$product         = new MeprProduct( $transaction->product_id );
152
				$sanitized_title = sanitize_title( $product->post_title );
153
154
				$args = array(
155
					'membership_id' => $product->ID,
156
					'membership'    => $sanitized_title,
157
					'trans_num'     => $transaction->trans_num,
158
				);
159
160
				$url = $mepr_options->thankyou_page_url( http_build_query( $args ) );
161
162
				break;
163
			case PaymentStatus::OPEN:
164
			default:
165
				break;
166
		}
167
168
		return $url;
169
	}
170
171
	/**
172
	 * Update lead status of the specified payment.
173
	 *
174
	 * @link https://github.com/Charitable/Charitable/blob/1.1.4/includes/gateways/class-charitable-gateway-paypal.php#L229-L357
175
	 *
176
	 * @param Payment $payment The payment whose status is updated.
177
	 */
178
	public static function status_update( Payment $payment ) {
179
		$transaction = new MeprTransaction( $payment->get_source_id() );
180
181
		if ( $payment->get_recurring() ) {
182
			$subscription_id = $payment->get_subscription()->get_source_id();
183
			$subscription    = new MeprSubscription( $subscription_id );
184
185
			// Same source ID and first transaction ID for recurring payment means we need to add a new transaction.
186
			if ( $payment->get_source_id() === $subscription->id ) {
187
				// First transaction.
188
				$first_txn = $subscription->first_txn();
189
190
				if ( false === $first_txn || ! ( $first_txn instanceof MeprTransaction ) ) {
191
					$first_txn             = new MeprTransaction();
192
					$first_txn->user_id    = $subscription->user_id;
193
					$first_txn->product_id = $subscription->product_id;
194
					$first_txn->coupon_id  = $subscription->coupon_id;
195
					$first_txn->gateway    = null;
196
				}
197
198
				// Transaction number.
199
				$trans_num = $payment->get_transaction_id();
200
201
				if ( empty( $trans_num ) ) {
202
					$trans_num = uniqid();
203
				}
204
205
				// New transaction.
206
				$transaction                  = new MeprTransaction();
207
				$transaction->created_at      = $payment->post->post_date_gmt;
208
				$transaction->user_id         = $first_txn->user_id;
209
				$transaction->product_id      = $first_txn->product_id;
210
				$transaction->coupon_id       = $first_txn->coupon_id;
211
				$transaction->gateway         = $first_txn->gateway;
212
				$transaction->trans_num       = $trans_num;
213
				$transaction->txn_type        = MeprTransaction::$payment_str;
214
				$transaction->status          = MeprTransaction::$pending_str;
215
				$transaction->expires_at      = MeprUtils::ts_to_mysql_date( $payment->get_end_date()->getTimestamp(), 'Y-m-d 23:59:59' );
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...
216
				$transaction->subscription_id = $subscription->id;
217
218
				$transaction->set_gross( $payment->get_total_amount()->get_value() );
219
220
				$transaction->store();
221
222
				// Set source ID.
223
				$payment->set_meta( 'source_id', $transaction->id );
224
225
				$payment->source_id = $transaction->id;
226
227
				if ( MeprSubscription::$active_str === $subscription->status ) {
228
					/*
229
					 * We create a 'confirmed' 'subscription_confirmation'
230
					 * transaction for a grace period of 15 days.
231
					 *
232
					 * Transactions of type "subscription_confirmation" with a
233
					 * status of "confirmed" are hidden in the UI, and are used
234
					 * as a way to provide free trial periods and the 24 hour
235
					 * grace period on a recurring subscription signup.
236
					 *
237
					 * @link https://docs.memberpress.com/article/219-where-is-data-stored.
238
					 */
239
					$subscription_confirmation                  = new MeprTransaction();
240
					$subscription_confirmation->created_at      = $payment->post->post_date_gmt;
241
					$subscription_confirmation->user_id         = $first_txn->user_id;
242
					$subscription_confirmation->product_id      = $first_txn->product_id;
243
					$subscription_confirmation->coupon_id       = $first_txn->coupon_id;
244
					$subscription_confirmation->gateway         = $first_txn->gateway;
245
					$subscription_confirmation->trans_num       = $trans_num;
246
					$subscription_confirmation->txn_type        = MeprTransaction::$subscription_confirmation_str;
247
					$subscription_confirmation->status          = MeprTransaction::$confirmed_str;
248
					$subscription_confirmation->subscription_id = $subscription->id;
249
					$subscription_confirmation->expires_at      = MeprUtils::ts_to_mysql_date( strtotime( $payment->post->post_date_gmt ) + MeprUtils::days( 15 ), 'Y-m-d 23:59:59' );
250
251
					$subscription_confirmation->set_subtotal( 0.00 );
252
253
					$subscription_confirmation->store();
254
				}
255
			}
256
		}
257
258
		$should_update = ! MemberPress::transaction_has_status(
259
			$transaction,
260
			array(
261
				MeprTransaction::$failed_str,
262
				MeprTransaction::$complete_str,
263
			)
264
		);
265
266
		// Allow successful recurring payments to update failed transaction.
267
		if ( $payment->get_recurring() && PaymentStatus::SUCCESS === $payment->get_status() && MeprTransaction::$failed_str === $transaction->status ) {
268
			$should_update = true;
269
		}
270
271
		if ( $should_update ) {
272
			$gateway = new Gateway();
273
274
			$gateway->pronamic_payment = $payment;
275
			$gateway->mp_txn           = $transaction;
276
277
			switch ( $payment->get_status() ) {
278
				case PaymentStatus::CANCELLED:
279
				case PaymentStatus::EXPIRED:
280
				case PaymentStatus::FAILURE:
281
					$gateway->record_payment_failure();
282
283
					break;
284
				case PaymentStatus::SUCCESS:
285
					if ( $payment->get_recurring() ) {
286
						$gateway->record_subscription_payment();
287
					} else {
288
						$gateway->record_payment();
289
					}
290
291
					break;
292
				case PaymentStatus::OPEN:
293
				default:
294
					break;
295
			}
296
		}
297
	}
298
299
	/**
300
	 * Subscription deleted.
301
	 *
302
	 * @param int $subscription_id MemberPress subscription id.
303
	 */
304
	public function subscription_pre_delete( $subscription_id ) {
305
		$subscription = get_pronamic_subscription_by_meta( '_pronamic_subscription_source_id', $subscription_id );
306
307
		if ( ! $subscription ) {
308
			return;
309
		}
310
311
		// Add note.
312
		$note = sprintf(
313
			/* translators: %s: MemberPress */
314
			__( '%s subscription deleted.', 'pronamic_ideal' ),
315
			__( 'MemberPress', 'pronamic_ideal' )
316
		);
317
318
		$subscription->add_note( $note );
319
320
		// The status of canceled or completed subscriptions will not be changed automatically.
321
		if ( ! in_array( $subscription->get_status(), array( SubscriptionStatus::CANCELLED, SubscriptionStatus::COMPLETED ), true ) ) {
322
			$subscription->set_status( SubscriptionStatus::CANCELLED );
323
324
			$subscription->save();
325
		}
326
	}
327
328
	/**
329
	 * Source text.
330
	 *
331
	 * @param string  $text    Source text.
332
	 * @param Payment $payment Payment to create the source text for.
333
	 *
334
	 * @return string
335
	 */
336
	public static function source_text( $text, Payment $payment ) {
337
		$text = __( 'MemberPress', 'pronamic_ideal' ) . '<br />';
338
339
		$text .= sprintf(
340
			'<a href="%s">%s</a>',
341
			add_query_arg(
342
				array(
343
					'page'   => 'memberpress-trans',
344
					'action' => 'edit',
345
					'id'     => $payment->source_id,
346
				),
347
				admin_url( 'admin.php' )
348
			),
349
			/* translators: %s: payment source id */
350
			sprintf( __( 'Transaction %s', 'pronamic_ideal' ), $payment->source_id )
351
		);
352
353
		return $text;
354
	}
355
356
	/**
357
	 * Subscription source text.
358
	 *
359
	 * @param string       $text         Source text.
360
	 * @param Subscription $subscription Subscription to create the source text for.
361
	 *
362
	 * @return string
363
	 */
364
	public static function subscription_source_text( $text, Subscription $subscription ) {
365
		$text = __( 'MemberPress', 'pronamic_ideal' ) . '<br />';
366
367
		$text .= sprintf(
368
			'<a href="%s">%s</a>',
369
			add_query_arg(
370
				array(
371
					'page'         => 'memberpress-subscriptions',
372
					'subscription' => $subscription->source_id,
373
				),
374
				admin_url( 'admin.php' )
375
			),
376
			/* translators: %s: payment source id */
377
			sprintf( __( 'Subscription %s', 'pronamic_ideal' ), $subscription->source_id )
378
		);
379
380
		return $text;
381
	}
382
383
	/**
384
	 * Source description.
385
	 *
386
	 * @param string  $description Description.
387
	 * @param Payment $payment     Payment to create the description for.
388
	 *
389
	 * @return string
390
	 */
391
	public static function source_description( $description, Payment $payment ) {
392
		return __( 'MemberPress Transaction', 'pronamic_ideal' );
393
	}
394
395
	/**
396
	 * Subscription source description.
397
	 *
398
	 * @param string       $description  Description.
399
	 * @param Subscription $subscription Subscription to create the description for.
400
	 *
401
	 * @return string
402
	 */
403
	public static function subscription_source_description( $description, Subscription $subscription ) {
404
		return __( 'MemberPress Subscription', 'pronamic_ideal' );
405
	}
406
407
	/**
408
	 * Source URL.
409
	 *
410
	 * @param string  $url     URL.
411
	 * @param Payment $payment The payment to create the source URL for.
412
	 *
413
	 * @return string
414
	 */
415
	public static function source_url( $url, Payment $payment ) {
416
		$url = add_query_arg(
417
			array(
418
				'page'   => 'memberpress-trans',
419
				'action' => 'edit',
420
				'id'     => $payment->source_id,
421
			),
422
			admin_url( 'admin.php' )
423
		);
424
425
		return $url;
426
	}
427
428
	/**
429
	 * Subscription source URL.
430
	 *
431
	 * @param string       $url          URL.
432
	 * @param Subscription $subscription Subscription.
433
	 *
434
	 * @return string
435
	 */
436
	public static function subscription_source_url( $url, Subscription $subscription ) {
437
		$url = add_query_arg(
438
			array(
439
				'page'         => 'memberpress-subscriptions',
440
				'subscription' => $subscription->source_id,
441
			),
442
			admin_url( 'admin.php' )
443
		);
444
445
		return $url;
446
	}
447
448
	/**
449
	 * MemberPress update subscription.
450
	 *
451
	 * @link https://github.com/wp-premium/memberpress-basic/blob/1.3.18/app/controllers/MeprSubscriptionsCtrl.php#L92-L111
452
	 * @link https://github.com/wp-premium/memberpress-basic/blob/1.3.18/app/models/MeprSubscription.php#L100-L123
453
	 * @link https://github.com/wp-premium/memberpress-basic/blob/1.3.18/app/models/MeprSubscription.php#L112
454
	 *
455
	 * @param string           $status_old               Old status identifier.
456
	 * @param string           $status_new               New status identifier.
457
	 * @param MeprSubscription $memberpress_subscription MemberPress subscription object.
458
	 */
459
	public function memberpress_subscription_transition_status( $status_old, $status_new, $memberpress_subscription ) {
460
		$subscription = get_pronamic_subscription_by_meta( '_pronamic_subscription_source_id', $memberpress_subscription->id );
461
462
		if ( empty( $subscription ) ) {
463
			return;
464
		}
465
466
		$status = SubscriptionStatuses::transform( $status_new );
467
468
		$subscription->set_status( $status );
469
470
		$subscription->save();
471
	}
472
}
473