Test Failed
Push — develop ( 13fc2a...744f0f )
by Reüel
04:50
created

Gateway::get_available_payment_methods()   B

Complexity

Conditions 11
Paths 35

Size

Total Lines 66
Code Lines 34

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 19
CRAP Score 18.0186

Importance

Changes 6
Bugs 0 Features 0
Metric Value
cc 11
eloc 34
c 6
b 0
f 0
nc 35
nop 0
dl 0
loc 66
ccs 19
cts 31
cp 0.6129
crap 18.0186
rs 7.3166

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * Mollie gateway.
4
 *
5
 * @author    Pronamic <[email protected]>
6
 * @copyright 2005-2020 Pronamic
7
 * @license   GPL-3.0-or-later
8
 * @package   Pronamic\WordPress\Pay
9
 */
10
11
namespace Pronamic\WordPress\Pay\Gateways\Mollie;
12
13
use DateInterval;
14
use Pronamic\WordPress\DateTime\DateTime;
15
use Pronamic\WordPress\Pay\Banks\BankAccountDetails;
16
use Pronamic\WordPress\Pay\Banks\BankTransferDetails;
17
use Pronamic\WordPress\Pay\Core\Gateway as Core_Gateway;
18
use Pronamic\WordPress\Pay\Core\PaymentMethods;
19
use Pronamic\WordPress\Pay\Core\Recurring as Core_Recurring;
20
use Pronamic\WordPress\Pay\Payments\PaymentStatus;
21
use Pronamic\WordPress\Pay\Payments\Payment;
22
23
/**
24
 * Title: Mollie
25
 * Description:
26
 * Copyright: 2005-2020 Pronamic
27
 * Company: Pronamic
28
 *
29
 * @author  Remco Tolsma
30
 * @version 2.0.9
31
 * @since   1.1.0
32
 */
33
class Gateway extends Core_Gateway {
34
	/**
35
	 * Client.
36
	 *
37
	 * @var Client
38
	 */
39
	protected $client;
40
41
	/**
42
	 * Meta key for customer ID.
43
	 *
44
	 * @var string
45
	 */
46
	private $meta_key_customer_id = '_pronamic_pay_mollie_customer_id';
47
48
	/**
49
	 * Constructs and initializes an Mollie gateway
50
	 *
51
	 * @param Config $config Config.
52
	 */
53 39
	public function __construct( Config $config ) {
54 39
		parent::__construct( $config );
55
56 39
		$this->set_method( self::METHOD_HTTP_REDIRECT );
57
58
		// Supported features.
59 39
		$this->supports = array(
60
			'payment_status_request',
61
			'recurring_direct_debit',
62
			'recurring_credit_card',
63
			'recurring',
64
			'webhook',
65
			'webhook_log',
66
			'webhook_no_config',
67
		);
68
69
		// Client.
70 39
		$this->client = new Client( \strval( $config->api_key ) );
71 39
		$this->client->set_mode( $config->mode );
72
73
		// Mollie customer ID meta key.
74 39
		if ( self::MODE_TEST === $config->mode ) {
75 38
			$this->meta_key_customer_id = '_pronamic_pay_mollie_customer_id_test';
76
		}
77
78
		// Actions.
79 39
		add_action( 'pronamic_payment_status_update', array( $this, 'copy_customer_id_to_wp_user' ), 99, 1 );
80 39
	}
81
82
	/**
83
	 * Get issuers
84
	 *
85
	 * @see Core_Gateway::get_issuers()
86
	 * @return array<int, array<string, array<string>>>
87 3
	 */
88 3
	public function get_issuers() {
89
		$groups = array();
90
91 3
		try {
92
			$result = $this->client->get_issuers();
93
94
			$groups[] = array(
95
				'options' => $result,
96 3
			);
97
		} catch ( Error $e ) {
98 3
			// Catch Mollie error.
99 3
			$error = new \WP_Error(
100 3
				'mollie_error',
101
				sprintf( '%1$s (%2$s) - %3$s', $e->get_title(), $e->getCode(), $e->get_detail() )
102
			);
103 3
104
			$this->set_error( $error );
105
		} catch ( \Exception $e ) {
106
			// Catch exceptions.
107
			$error = new \WP_Error( 'mollie_error', $e->getMessage() );
108
109
			$this->set_error( $error );
110
		}
111 3
112
		return $groups;
113
	}
114
115
	/**
116
	 * Get available payment methods.
117
	 *
118
	 * @see Core_Gateway::get_available_payment_methods()
119 2
	 * @return array<string>
120 2
	 */
121
	public function get_available_payment_methods() {
122
		$payment_methods = array();
123 2
124
		// Set sequence types to get payment methods for.
125 2
		$sequence_types = array( Sequence::ONE_OFF, Sequence::RECURRING, Sequence::FIRST );
126
127 2
		$results = array();
128
129
		foreach ( $sequence_types as $sequence_type ) {
130 2
			// Get active payment methods for Mollie account.
131 2
			try {
132
				$result = $this->client->get_payment_methods( $sequence_type );
133
			} catch ( Error $e ) {
134
				// Catch Mollie error.
135
				$error = new \WP_Error(
136
					'mollie_error',
137
					sprintf( '%1$s (%2$s) - %3$s', $e->get_title(), $e->getCode(), $e->get_detail() )
138
				);
139
140
				$this->set_error( $error );
141 2
142
				break;
143 2
			} catch ( \Exception $e ) {
144
				// Catch exceptions.
145 2
				$error = new \WP_Error( 'mollie_error', $e->getMessage() );
146
147 2
				$this->set_error( $error );
148
149
				break;
150 2
			}
151
152
			if ( Sequence::FIRST === $sequence_type ) {
153
				foreach ( $result as $method => $title ) {
154
					unset( $result[ $method ] );
155
156
					// Get WordPress payment method for direct debit method.
157
					$method         = Methods::transform_gateway_method( $method );
158
					$payment_method = array_search( $method, PaymentMethods::get_recurring_methods(), true );
159
160
					if ( $payment_method ) {
161
						$results[ $payment_method ] = $title;
162
					}
163
				}
164 2
			}
165 2
166
			if ( is_array( $result ) ) {
167
				$results = array_merge( $results, $result );
168
			}
169
		}
170 2
171 2
		// Transform to WordPress payment methods.
172
		foreach ( $results as $method => $title ) {
173
			if ( PaymentMethods::is_recurring_method( $method ) ) {
174 2
				$payment_method = $method;
175
			} else {
176
				$payment_method = Methods::transform_gateway_method( $method );
177 2
			}
178 2
179
			if ( $payment_method ) {
180
				$payment_methods[] = $payment_method;
181
			}
182 2
		}
183
184 2
		$payment_methods = array_unique( $payment_methods );
185
186
		return $payment_methods;
187
	}
188
189
	/**
190
	 * Get supported payment methods
191
	 *
192
	 * @see Pronamic_WP_Pay_Gateway::get_supported_payment_methods()
193 2
	 * @return array<string>
194
	 */
195 2
	public function get_supported_payment_methods() {
196
		return array(
197
			PaymentMethods::BANCONTACT,
198
			PaymentMethods::BANK_TRANSFER,
199
			PaymentMethods::BELFIUS,
200
			PaymentMethods::CREDIT_CARD,
201
			PaymentMethods::DIRECT_DEBIT,
202
			PaymentMethods::DIRECT_DEBIT_BANCONTACT,
203
			PaymentMethods::DIRECT_DEBIT_IDEAL,
204
			PaymentMethods::DIRECT_DEBIT_SOFORT,
205
			PaymentMethods::EPS,
206
			PaymentMethods::GIROPAY,
207
			PaymentMethods::IDEAL,
208
			PaymentMethods::KBC,
209
			PaymentMethods::PAYPAL,
210
			PaymentMethods::SOFORT,
211
		);
212
	}
213
214
	/**
215
	 * Get webhook URL for Mollie.
216
	 *
217 4
	 * @return string|null
218 4
	 */
219
	public function get_webhook_url() {
220 4
		$url = home_url( '/' );
221
222 4
		$host = wp_parse_url( $url, PHP_URL_HOST );
223
224
		if ( is_array( $host ) ) {
225
			// Parsing failure.
226
			$host = '';
227 4
		}
228
229 1
		if ( 'localhost' === $host ) {
230 3
			// Mollie doesn't allow localhost.
231
			return null;
232 1
		} elseif ( '.dev' === substr( $host, -4 ) ) {
233 2
			// Mollie doesn't allow the .dev TLD.
234
			return null;
235 1
		} elseif ( '.local' === substr( $host, -6 ) ) {
236 1
			// Mollie doesn't allow the .local TLD.
237
			return null;
238
		} elseif ( '.test' === substr( $host, -5 ) ) {
239
			// Mollie doesn't allow the .test TLD.
240
			return null;
241 1
		}
242
243 1
		$url = add_query_arg( 'mollie_webhook', '', $url );
244
245
		return $url;
246
	}
247
248
	/**
249
	 * Start
250
	 *
251
	 * @see Pronamic_WP_Pay_Gateway::start()
252
	 * @param Payment $payment Payment.
253
	 * @return void
254
	 */
255
	public function start( Payment $payment ) {
256
		$request = new PaymentRequest(
257
			AmountTransformer::transform( $payment->get_total_amount() ),
258
			\strval( $payment->get_description() )
259
		);
260
261
		$request->redirect_url = $payment->get_return_url();
262
		$request->webhook_url  = $this->get_webhook_url();
263
264
		// Locale.
265
		$customer = $payment->get_customer();
266
267
		if ( null !== $customer ) {
268
			$request->locale = LocaleHelper::transform( $customer->get_locale() );
269
		}
270
271
		// Customer ID.
272
		$customer_id = $this->get_customer_id_for_payment( $payment );
273
274
		if ( is_string( $customer_id ) && ! empty( $customer_id ) ) {
275
			$request->customer_id = $customer_id;
276
		}
277
278
		// Payment method.
279
		$payment_method = $payment->get_method();
280
281
		// Recurring payment method.
282
		$is_recurring_method = ( $payment->get_subscription() && PaymentMethods::is_recurring_method( $payment_method ) );
283
284
		if ( false === $is_recurring_method ) {
285
			// Always use 'direct debit mandate via iDEAL/Bancontact/Sofort' payment methods as recurring method.
286
			$is_recurring_method = PaymentMethods::is_direct_debit_method( $payment_method );
287
		}
288
289
		if ( $is_recurring_method ) {
290
			$request->sequence_type = $payment->get_recurring() ? Sequence::RECURRING : Sequence::FIRST;
291
292
			if ( Sequence::FIRST === $request->sequence_type ) {
293
				$payment_method = PaymentMethods::get_first_payment_method( $payment_method );
294
			}
295
296
			if ( Sequence::RECURRING === $request->sequence_type ) {
297
				$payment->set_action_url( $payment->get_return_url() );
298
			}
299
		}
300
301
		// Leap of faith if the WordPress payment method could not transform to a Mollie method?
302
		$request->method = Methods::transform( $payment_method, $payment_method );
303
304
		// Issuer.
305
		if ( Methods::IDEAL === $request->method ) {
306
			$request->issuer = $payment->get_issuer();
307
		}
308
309
		// Due date.
310
		try {
311
			$due_date = new DateTime( sprintf( '+%s days', $this->config->due_date_days ) );
312
		} catch ( \Exception $e ) {
313
			$due_date = null;
314
		}
315
316
		$request->set_due_date( $due_date );
317
318
		// Create payment.
319
		$result = $this->client->create_payment( $request );
320
321
		// Set transaction ID.
322
		if ( isset( $result->id ) ) {
323
			$payment->set_transaction_id( $result->id );
324
		}
325
326
		// Set expiry date.
327
		/* phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase */
328
		if ( isset( $result->expiresAt ) ) {
329
			try {
330
				$expires_at = new DateTime( $result->expiresAt );
331
			} catch ( \Exception $e ) {
332
				$expires_at = null;
333
			}
334
335
			$payment->set_expiry_date( $expires_at );
336
		}
337
		/* phpcs:enable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase */
338
339
		// Set status.
340
		if ( isset( $result->status ) ) {
341
			$payment->set_status( Statuses::transform( $result->status ) );
342
		}
343
344
		// Set bank transfer recipient details.
345
		if ( isset( $result->details ) ) {
346
			$bank_transfer_recipient_details = $payment->get_bank_transfer_recipient_details();
347
348
			if ( null === $bank_transfer_recipient_details ) {
349
				$bank_transfer_recipient_details = new BankTransferDetails();
350
351
				$payment->set_bank_transfer_recipient_details( $bank_transfer_recipient_details );
352
			}
353
354
			$bank_details = $bank_transfer_recipient_details->get_bank_account();
355
356
			if ( null === $bank_details ) {
357
				$bank_details = new BankAccountDetails();
358
359
				$bank_transfer_recipient_details->set_bank_account( $bank_details );
360
			}
361
362
			$details = $result->details;
363
364
			/*
365
			 * @codingStandardsIgnoreStart
366
			 *
367
			 * Ignore coding standards because of sniff WordPress.NamingConventions.ValidVariableName.NotSnakeCaseMemberVar
368
			 */
369
			if ( isset( $details->bankName ) ) {
370
				/**
371
				 * Set `bankName` as bank details name, as result "Stichting Mollie Payments"
372
				 * is not the name of a bank, but the account holder name.
373
				 */
374
				$bank_details->set_name( $details->bankName );
375
			}
376
377
			if ( isset( $details->bankAccount ) ) {
378
				$bank_details->set_iban( $details->bankAccount );
379
			}
380
381
			if ( isset( $details->bankBic ) ) {
382
				$bank_details->set_bic( $details->bankBic );
383
			}
384
385
			if ( isset( $details->transferReference ) ) {
386
				$bank_transfer_recipient_details->set_reference( $details->transferReference );
387
			}
388
			// @codingStandardsIgnoreEnd
389
		}
390
391
		// Set action URL.
392
		if ( isset( $result->_links ) ) {
393
			if ( isset( $result->_links->checkout->href ) ) {
394
				$payment->set_action_url( $result->_links->checkout->href );
395
			}
396
		}
397
	}
398
399
	/**
400
	 * Update status of the specified payment
401
	 *
402
	 * @param Payment $payment Payment.
403
	 * @return void
404
	 */
405
	public function update_status( Payment $payment ) {
406
		$transaction_id = $payment->get_transaction_id();
407
408
		if ( null === $transaction_id ) {
409
			return;
410
		}
411
412
		$mollie_payment = $this->client->get_payment( $transaction_id );
413
414
		if ( isset( $mollie_payment->status ) ) {
415
			$payment->set_status( Statuses::transform( $mollie_payment->status ) );
416
		}
417
418
		if ( isset( $mollie_payment->details ) ) {
419
			$consumer_bank_details = $payment->get_consumer_bank_details();
420
421
			if ( null === $consumer_bank_details ) {
422
				$consumer_bank_details = new BankAccountDetails();
423
424
				$payment->set_consumer_bank_details( $consumer_bank_details );
425
			}
426
427
			$details = $mollie_payment->details;
428
429
			/*
430
			 * @codingStandardsIgnoreStart
431
			 *
432
			 * Ignore coding standards because of sniff WordPress.NamingConventions.ValidVariableName.NotSnakeCaseMemberVar
433
			 */
434
			if ( isset( $details->consumerName ) ) {
435
				$consumer_bank_details->set_name( $details->consumerName );
436
			}
437
438
			if ( isset( $details->cardHolder ) ) {
439
				$consumer_bank_details->set_name( $details->cardHolder );
440
			}
441
442
			if ( isset( $details->cardNumber ) ) {
443
				// The last four digits of the card number.
444
				$consumer_bank_details->set_account_number( $details->cardNumber );
445
			}
446
447
			if ( isset( $details->cardCountryCode ) ) {
448
				// The ISO 3166-1 alpha-2 country code of the country the card was issued in.
449
				$consumer_bank_details->set_country( $details->cardCountryCode );
450
			}
451
452
			if ( isset( $details->consumerAccount ) ) {
453
				switch ( $mollie_payment->method ) {
454
					case Methods::BELFIUS:
455
					case Methods::DIRECT_DEBIT:
456
					case Methods::IDEAL:
457
					case Methods::KBC:
458
					case Methods::SOFORT:
459
						$consumer_bank_details->set_iban( $details->consumerAccount );
460
461
						break;
462
					case Methods::BANCONTACT:
463
					case Methods::BANKTRANSFER:
464
					case Methods::PAYPAL:
465
					default:
466
						$consumer_bank_details->set_account_number( $details->consumerAccount );
467
468
						break;
469
				}
470
			}
471
472
			if ( isset( $details->consumerBic ) ) {
473
				$consumer_bank_details->set_bic( $details->consumerBic );
474
			}
475
			// @codingStandardsIgnoreEnd
476
		}
477
	}
478
479
	/**
480
	 * Get Mollie customers for the specified payment.
481
	 *
482
	 * @param Payment $payment Payment.
483 10
	 * @return array<string>
484 10
	 */
485
	private function get_customer_ids_for_payment( Payment $payment ) {
0 ignored issues
show
Unused Code introduced by
The method get_customer_ids_for_payment() is not used, and could be removed.

This check looks for private methods that have been defined, but are not used inside the class.

Loading history...
486
		$customer = $payment->get_customer();
487 10
488
		if ( null === $customer ) {
489
			return array();
490 10
		}
491
492 10
		$user_id = $customer->get_user_id();
493
494
		if ( empty( $user_id ) ) {
495 10
			return array();
496 10
		}
497
498
		return $this->get_customer_ids_for_user( $user_id );
499 10
	}
500 7
501
	/**
502 7
	 * Get Mollie customers for the specified WordPress user ID.
503
	 *
504
	 * @param int $user_id WordPress user ID.
505 10
	 * @return array<string>
506 4
	 */
507
	private function get_customer_ids_for_user( $user_id ) {
508
		$customer_query = new CustomerQuery( array(
509
			'user_id' => $user_id,
510
		) );
511 10
512
		$customers = $customer_query->get_customers();
513
514
		$customer_ids = wp_list_pluck( $customers, 'mollie_id' );
515
516
		return $customer_ids;
517
	}
518
519
	/**
520
	 * Get first existing customer from customers list.
521
	 *
522
	 * @param array $customers Customers.
523
	 * @return string|null
524 10
	 */
525
	private function get_first_existing_customer_id( $customers ) {
0 ignored issues
show
Unused Code introduced by
The parameter $customers is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

525
	private function get_first_existing_customer_id( /** @scrutinizer ignore-unused */ $customers ) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The method get_first_existing_customer_id() is not used, and could be removed.

This check looks for private methods that have been defined, but are not used inside the class.

Loading history...
526
		foreach ( $customer_ids as $customer_id ) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $customer_ids does not exist. Did you maybe mean $customer?
Loading history...
527
			$customer = $this->client->get_customer( $customer_id );
528
529 10
			if ( null !== $customer ) {
530
				return $customer;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $customer returns the type object which is incompatible with the documented return type null|string.
Loading history...
531 10
			}
532
		}
533
534
		return null;
535
	}
536
537
	/**
538
	 * Get Mollie customer ID for payment.
539
	 *
540 27
	 * @param Payment $payment Payment.
541 27
	 * @return bool|string
542 11
	 */
543
	public function get_customer_id_for_payment( Payment $payment ) {
544
		$customer = $payment->get_customer();
545 16
546
		// Get WordPress user ID from payment customer.
547
		$user_id = ( null === $customer ? null : $customer->get_user_id() );
548
549
		// Get Mollie customer ID from user meta.
550
		$customer_id = $this->get_customer_id_by_wp_user_id( $user_id );
551
552
		$subscription = $payment->get_subscription();
553
554
		// Get customer ID from subscription meta.
555 15
		if ( $subscription ) {
556 15
			$subscription_customer_id = $subscription->get_meta( 'mollie_customer_id' );
557 3
558
			// Try to get (legacy) customer ID from first payment.
559
			if ( empty( $subscription_customer_id ) && $subscription->get_first_payment() ) {
560 12
				$first_payment = $subscription->get_first_payment();
561 11
562
				$subscription_customer_id = $first_payment->get_meta( 'mollie_customer_id' );
563
			}
564 4
565 4
			if ( ! empty( $subscription_customer_id ) ) {
566
				$customer_id = $subscription_customer_id;
567
			}
568
		}
569
570
		// Create new customer if the customer does not exist at Mollie.
571
		if ( ( empty( $customer_id ) || null === $this->client->get_customer( $customer_id ) ) && Core_Recurring::RECURRING !== $payment->recurring_type ) {
572
			$customer_name = null;
573 27
574 27
			if ( null !== $customer && null !== $customer->get_name() ) {
575 1
				$customer_name = strval( $customer->get_name() );
576
			}
577
578 26
			$customer_id = $this->client->create_customer( $payment->get_email(), $customer_name );
579
580 26
			$this->update_wp_user_customer_id( $user_id, $customer_id );
581
		}
582
583
		// Store customer ID in subscription meta.
584 26
		if ( $subscription && empty( $subscription_customer_id ) && ! empty( $customer_id ) ) {
585
			$subscription->set_meta( 'mollie_customer_id', $customer_id );
586 26
		}
587 1
588
		// Copy customer ID from subscription to user meta.
589
		$this->copy_customer_id_to_wp_user( $payment );
590 25
591
		return $customer_id;
592 25
	}
593 10
594
	/**
595
	 * Get Mollie customer ID by the specified WordPress user ID.
596
	 *
597 15
	 * @param int $user_id WordPress user ID.
598
	 * @return string|bool
599 15
	 */
600
	public function get_customer_id_by_wp_user_id( $user_id ) {
601 15
		if ( empty( $user_id ) ) {
602
			return false;
603 15
		}
604
605 15
		return get_user_meta( $user_id, $this->meta_key_customer_id, true );
606
	}
607
608
	/**
609
	 * Update Mollie customer ID meta for WordPress user.
610
	 *
611
	 * @param int    $user_id     WordPress user ID.
612
	 * @param string $customer_id Mollie Customer ID.
613
	 * @return void
614
	 */
615
	private function update_wp_user_customer_id( $user_id, $customer_id ) {
616
		if ( empty( $user_id ) || is_bool( $user_id ) ) {
617
			return;
618
		}
619
620
		if ( ! is_string( $customer_id ) || empty( $customer_id ) || 1 === strlen( $customer_id ) ) {
0 ignored issues
show
introduced by
The condition is_string($customer_id) is always true.
Loading history...
621
			return;
622
		}
623
624
		update_user_meta( $user_id, $this->meta_key_customer_id, $customer_id );
625
	}
626
627
	/**
628
	 * Copy Mollie customer ID from subscription meta to WordPress user meta.
629
	 *
630
	 * @param Payment $payment Payment.
631
	 * @return void
632
	 */
633
	public function copy_customer_id_to_wp_user( Payment $payment ) {
634
		if ( $this->config->id !== $payment->config_id ) {
635
			return;
636
		}
637
638
		$subscription = $payment->get_subscription();
639
640
		if ( ! $subscription ) {
641
			return;
642
		}
643
644
		$customer = $subscription->get_customer();
645
646
		if ( null === $customer ) {
647
			return;
648
		}
649
650
		$user_id = $customer->get_user_id();
651
652
		if ( empty( $user_id ) ) {
653
			return;
654
		}
655
656
		// Get customer ID from subscription meta.
657
		$customer_id = $subscription->get_meta( 'mollie_customer_id' );
658
659
		$user_customer_id = $this->get_customer_id_by_wp_user_id( $user_id );
660
661
		if ( empty( $user_customer_id ) ) {
662
			// Set customer ID as user meta.
663
			$this->update_wp_user_customer_id( $user_id, (string) $customer_id );
664
		}
665
	}
666
}
667