Test Failed
Push — develop ( ae4ebe...20404f )
by Reüel
07:23 queued 11s
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 21
CRAP Score 16.8164

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 21
cts 33
cp 0.6364
crap 16.8164
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 Pronamic\WordPress\DateTime\DateTime;
14
use Pronamic\WordPress\Pay\Banks\BankAccountDetails;
15
use Pronamic\WordPress\Pay\Banks\BankTransferDetails;
16
use Pronamic\WordPress\Pay\Core\Gateway as Core_Gateway;
17
use Pronamic\WordPress\Pay\Core\PaymentMethods;
18
use Pronamic\WordPress\Pay\Payments\FailureReason;
19
use Pronamic\WordPress\Pay\Payments\Payment;
20
use Pronamic\WordPress\Pay\Payments\PaymentStatus;
21
use Pronamic\WordPress\Pay\Subscriptions\Subscription;
22
23
/**
24
 * Title: Mollie
25
 * Description:
26
 * Copyright: 2005-2020 Pronamic
27
 * Company: Pronamic
28
 *
29
 * @author  Remco Tolsma
30
 * @version 2.1.4
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
	 * Profile data store.
43
	 *
44
	 * @var ProfileDataStore
45
	 */
46
	private $profile_data_store;
47
48
	/**
49
	 * Customer data store.
50
	 *
51
	 * @var CustomerDataStore
52
	 */
53
	private $customer_data_store;
54
55
	/**
56
	 * Constructs and initializes an Mollie gateway
57
	 *
58
	 * @param Config $config Config.
59
	 */
60 39
	public function __construct( Config $config ) {
61 39
		parent::__construct( $config );
62
63 39
		$this->set_method( self::METHOD_HTTP_REDIRECT );
64
65
		// Supported features.
66 39
		$this->supports = array(
67
			'payment_status_request',
68
			'recurring_direct_debit',
69
			'recurring_credit_card',
70
			'recurring',
71
			'webhook',
72
			'webhook_log',
73
			'webhook_no_config',
74
		);
75
76
		// Client.
77 39
		$this->client = new Client( \strval( $config->api_key ) );
78
79
		// Data Stores.
80 39
		$this->profile_data_store  = new ProfileDataStore();
81 39
		$this->customer_data_store = new CustomerDataStore();
82
83
		// Actions.
84 39
		add_action( 'pronamic_payment_status_update', array( $this, 'copy_customer_id_to_wp_user' ), 99, 1 );
85 39
	}
86
87
	/**
88
	 * Get issuers
89
	 *
90
	 * @see Core_Gateway::get_issuers()
91
	 * @return array<int, array<string, array<string>>>
92
	 */
93 3
	public function get_issuers() {
94 3
		$groups = array();
95
96
		try {
97 3
			$result = $this->client->get_issuers();
98
99
			$groups[] = array(
100
				'options' => $result,
101
			);
102 3
		} catch ( Error $e ) {
103
			// Catch Mollie error.
104 3
			$error = new \WP_Error(
105 3
				'mollie_error',
106 3
				sprintf( '%1$s (%2$s) - %3$s', $e->get_title(), $e->getCode(), $e->get_detail() )
107
			);
108
109 3
			$this->set_error( $error );
110
		} catch ( \Exception $e ) {
111
			// Catch exceptions.
112
			$error = new \WP_Error( 'mollie_error', $e->getMessage() );
113
114
			$this->set_error( $error );
115
		}
116
117 3
		return $groups;
118
	}
119
120
	/**
121
	 * Get available payment methods.
122
	 *
123
	 * @see Core_Gateway::get_available_payment_methods()
124
	 * @return array<string>
125
	 */
126 2
	public function get_available_payment_methods() {
127 2
		$payment_methods = array();
128
129
		// Set sequence types to get payment methods for.
130 2
		$sequence_types = array( Sequence::ONE_OFF, Sequence::RECURRING, Sequence::FIRST );
131
132 2
		$results = array();
133
134 2
		foreach ( $sequence_types as $sequence_type ) {
135
			// Get active payment methods for Mollie account.
136
			try {
137 2
				$result = $this->client->get_payment_methods( $sequence_type );
138 2
			} catch ( Error $e ) {
139
				// Catch Mollie error.
140
				$error = new \WP_Error(
141
					'mollie_error',
142
					sprintf( '%1$s (%2$s) - %3$s', $e->get_title(), $e->getCode(), $e->get_detail() )
143
				);
144
145
				$this->set_error( $error );
146
147
				break;
148 2
			} catch ( \Exception $e ) {
149
				// Catch exceptions.
150 2
				$error = new \WP_Error( 'mollie_error', $e->getMessage() );
151
152 2
				$this->set_error( $error );
153
154 2
				break;
155
			}
156
157 2
			if ( Sequence::FIRST === $sequence_type ) {
158
				foreach ( $result as $method => $title ) {
159
					unset( $result[ $method ] );
160
161
					// Get WordPress payment method for direct debit method.
162
					$method         = Methods::transform_gateway_method( $method );
163
					$payment_method = array_search( $method, PaymentMethods::get_recurring_methods(), true );
164
165
					if ( $payment_method ) {
166
						$results[ $payment_method ] = $title;
167
					}
168
				}
169
			}
170
171 2
			if ( is_array( $result ) ) {
172 2
				$results = array_merge( $results, $result );
173
			}
174
		}
175
176
		// Transform to WordPress payment methods.
177 2
		foreach ( $results as $method => $title ) {
178 2
			if ( PaymentMethods::is_recurring_method( $method ) ) {
179
				$payment_method = $method;
180
			} else {
181 2
				$payment_method = Methods::transform_gateway_method( $method );
182
			}
183
184 2
			if ( $payment_method ) {
185 2
				$payment_methods[] = $payment_method;
186
			}
187
		}
188
189 2
		$payment_methods = array_unique( $payment_methods );
190
191 2
		return $payment_methods;
192
	}
193
194
	/**
195
	 * Get supported payment methods
196
	 *
197
	 * @see Pronamic_WP_Pay_Gateway::get_supported_payment_methods()
198
	 * @return array<string>
199
	 */
200 2
	public function get_supported_payment_methods() {
201
		return array(
202 2
			PaymentMethods::APPLE_PAY,
203
			PaymentMethods::BANCONTACT,
204
			PaymentMethods::BANK_TRANSFER,
205
			PaymentMethods::BELFIUS,
206
			PaymentMethods::CREDIT_CARD,
207
			PaymentMethods::DIRECT_DEBIT,
208
			PaymentMethods::DIRECT_DEBIT_BANCONTACT,
209
			PaymentMethods::DIRECT_DEBIT_IDEAL,
210
			PaymentMethods::DIRECT_DEBIT_SOFORT,
211
			PaymentMethods::EPS,
212
			PaymentMethods::GIROPAY,
213
			PaymentMethods::IDEAL,
214
			PaymentMethods::KBC,
215
			PaymentMethods::PAYPAL,
216
			PaymentMethods::SOFORT,
217
		);
218
	}
219
220
	/**
221
	 * Get webhook URL for Mollie.
222
	 *
223
	 * @return string|null
224
	 */
225 4
	public function get_webhook_url() {
226 4
		$url = \rest_url( Integration::REST_ROUTE_NAMESPACE . '/webhook' );
227
228 4
		$host = wp_parse_url( $url, PHP_URL_HOST );
229
230 4
		if ( is_array( $host ) ) {
231
			// Parsing failure.
232
			$host = '';
233
		}
234
235 4
		if ( 'localhost' === $host ) {
236
			// Mollie doesn't allow localhost.
237 1
			return null;
238 3
		} elseif ( '.dev' === substr( $host, -4 ) ) {
239
			// Mollie doesn't allow the .dev TLD.
240 1
			return null;
241 2
		} elseif ( '.local' === substr( $host, -6 ) ) {
242
			// Mollie doesn't allow the .local TLD.
243 1
			return null;
244 1
		} elseif ( '.test' === substr( $host, -5 ) ) {
245
			// Mollie doesn't allow the .test TLD.
246
			return null;
247
		}
248
249 1
		return $url;
250
	}
251
252
	/**
253
	 * Start
254
	 *
255
	 * @see Pronamic_WP_Pay_Gateway::start()
256
	 * @param Payment $payment Payment.
257
	 * @return void
258
	 */
259
	public function start( Payment $payment ) {
260
		$request = new PaymentRequest(
261
			AmountTransformer::transform( $payment->get_total_amount() ),
262
			\strval( $payment->get_description() )
263
		);
264
265
		$request->redirect_url = $payment->get_return_url();
266
		$request->webhook_url  = $this->get_webhook_url();
267
268
		// Locale.
269
		$customer = $payment->get_customer();
270
271
		if ( null !== $customer ) {
272
			$request->locale = LocaleHelper::transform( $customer->get_locale() );
273
		}
274
275
		// Customer ID.
276
		$customer_id = $this->get_customer_id_for_payment( $payment );
277
278
		if ( null === $customer_id ) {
279
			$customer_id = $this->create_customer_for_payment( $payment );
280
		}
281
282
		if ( null !== $customer_id ) {
283
			$request->customer_id = $customer_id;
284
		}
285
286
		// Payment method.
287
		$payment_method = $payment->get_method();
288
289
		// Recurring payment method.
290
		$subscription = $payment->get_subscription();
291
292
		$is_recurring_method = ( $subscription && PaymentMethods::is_recurring_method( $payment_method ) );
293
294
		// Consumer bank details.
295
		$consumer_bank_details = $payment->get_consumer_bank_details();
296
297
		if ( PaymentMethods::DIRECT_DEBIT === $payment_method && null !== $consumer_bank_details ) {
298
			$consumer_name = $consumer_bank_details->get_name();
299
			$consumer_iban = $consumer_bank_details->get_iban();
300
301
			$request->consumer_name    = $consumer_name;
302
			$request->consumer_account = $consumer_iban;
303
304
			// Check if one-off SEPA Direct Debit can be used, otherwise short circuit payment.
305
			if ( null !== $customer_id ) {
306
				// Find or create mandate.
307
				$mandate_id = $this->client->has_valid_mandate( $customer_id, PaymentMethods::DIRECT_DEBIT, $consumer_iban );
308
309
				if ( false === $mandate_id ) {
310
					$mandate = $this->client->create_mandate( $customer_id, $consumer_bank_details );
311
312
					$mandate_id = $mandate->id;
313
				}
314
315
				// Charge immediately on-demand.
316
				$request->set_sequence_type( Sequence::RECURRING );
317
				$request->set_mandate_id( $mandate_id );
318
319
				$is_recurring_method = true;
320
321
				$payment->recurring = true;
322
			}
323
		}
324
325
		if ( false === $is_recurring_method ) {
326
			// Always use 'direct debit mandate via iDEAL/Bancontact/Sofort' payment methods as recurring method.
327
			$is_recurring_method = PaymentMethods::is_direct_debit_method( $payment_method );
328
		}
329
330
		if ( $is_recurring_method ) {
331
			$request->sequence_type = $payment->get_recurring() ? Sequence::RECURRING : Sequence::FIRST;
332
333
			if ( Sequence::FIRST === $request->sequence_type ) {
334
				$payment_method = PaymentMethods::get_first_payment_method( $payment_method );
335
			}
336
337
			if ( Sequence::RECURRING === $request->sequence_type ) {
338
				// Use mandate from subscription.
339
				if ( $subscription && empty( $request->mandate_id ) ) {
340
					$request->set_mandate_id( $subscription->get_meta( 'mollie_mandate_id' ) );
0 ignored issues
show
Bug introduced by
It seems like $subscription->get_meta('mollie_mandate_id') can also be of type false; however, parameter $mandate_id of Pronamic\WordPress\Pay\G...quest::set_mandate_id() does only seem to accept null|string, maybe add an additional type check? ( Ignorable by Annotation )

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

340
					$request->set_mandate_id( /** @scrutinizer ignore-type */ $subscription->get_meta( 'mollie_mandate_id' ) );
Loading history...
341
				}
342
343
				$payment->set_action_url( $payment->get_return_url() );
344
			}
345
		}
346
347
		// Leap of faith if the WordPress payment method could not transform to a Mollie method?
348
		$request->method = Methods::transform( $payment_method, $payment_method );
349
350
		/**
351
		 * Metadata.
352
		 *
353
		 * Provide any data you like, for example a string or a JSON object.
354
		 * We will save the data alongside the payment. Whenever you fetch
355
		 * the payment with our API, we’ll also include the metadata. You
356
		 * can use up to approximately 1kB.
357
		 *
358
		 * @link https://docs.mollie.com/reference/v2/payments-api/create-payment
359
		 * @link https://en.wikipedia.org/wiki/Metadata
360
		 */
361
		$metadata = null;
362
363
		/**
364
		 * Filters the Mollie metadata.
365
		 *
366
		 * @since 2.2.0
367
		 *
368
		 * @param mixed   $metadata Metadata.
369
		 * @param Payment $payment  Payment.
370
		 */
371
		$metadata = \apply_filters( 'pronamic_pay_mollie_payment_metadata', $metadata, $payment );
372
373
		$request->set_metadata( $metadata );
374
375
		// Issuer.
376
		if ( Methods::IDEAL === $request->method ) {
377
			$request->issuer = $payment->get_issuer();
378
		}
379
380
		// Billing email.
381
		$billing_email = $payment->get_email();
382
383
		/**
384
		 * Filters the Mollie payment billing email used for bank transfer payment instructions.
385
		 *
386
		 * @since 2.2.0
387
		 *
388
		 * @param string|null $billing_email Billing email.
389
		 * @param Payment     $payment       Payment.
390
		 */
391
		$billing_email = \apply_filters( 'pronamic_pay_mollie_payment_billing_email', $billing_email, $payment );
392
393
		$request->set_billing_email( $billing_email );
394
395
		// Due date.
396
		try {
397
			$due_date = new DateTime( sprintf( '+%s days', $this->config->due_date_days ) );
398
		} catch ( \Exception $e ) {
399
			$due_date = null;
400
		}
401
402
		$request->set_due_date( $due_date );
403
404
		// Create payment.
405
		$result = $this->client->create_payment( $request );
406
407
		// Set transaction ID.
408
		if ( isset( $result->id ) ) {
409
			$payment->set_transaction_id( $result->id );
410
		}
411
412
		// Set expiry date.
413
		/* phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase */
414
		if ( isset( $result->expiresAt ) ) {
415
			try {
416
				$expires_at = new DateTime( $result->expiresAt );
417
			} catch ( \Exception $e ) {
418
				$expires_at = null;
419
			}
420
421
			$payment->set_expiry_date( $expires_at );
422
		}
423
		/* phpcs:enable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase */
424
425
		// Set status.
426
		if ( isset( $result->status ) ) {
427
			$payment->set_status( Statuses::transform( $result->status ) );
428
		}
429
430
		// Set bank transfer recipient details.
431
		if ( isset( $result->details ) ) {
432
			$bank_transfer_recipient_details = $payment->get_bank_transfer_recipient_details();
433
434
			if ( null === $bank_transfer_recipient_details ) {
435
				$bank_transfer_recipient_details = new BankTransferDetails();
436
437
				$payment->set_bank_transfer_recipient_details( $bank_transfer_recipient_details );
438
			}
439
440
			$bank_details = $bank_transfer_recipient_details->get_bank_account();
441
442
			if ( null === $bank_details ) {
443
				$bank_details = new BankAccountDetails();
444
445
				$bank_transfer_recipient_details->set_bank_account( $bank_details );
446
			}
447
448
			$details = $result->details;
449
450
			/*
451
			 * @codingStandardsIgnoreStart
452
			 *
453
			 * Ignore coding standards because of sniff WordPress.NamingConventions.ValidVariableName.NotSnakeCaseMemberVar
454
			 */
455
			if ( isset( $details->bankName ) ) {
456
				/**
457
				 * Set `bankName` as bank details name, as result "Stichting Mollie Payments"
458
				 * is not the name of a bank, but the account holder name.
459
				 */
460
				$bank_details->set_name( $details->bankName );
461
			}
462
463
			if ( isset( $details->bankAccount ) ) {
464
				$bank_details->set_iban( $details->bankAccount );
465
			}
466
467
			if ( isset( $details->bankBic ) ) {
468
				$bank_details->set_bic( $details->bankBic );
469
			}
470
471
			if ( isset( $details->transferReference ) ) {
472
				$bank_transfer_recipient_details->set_reference( $details->transferReference );
473
			}
474
			// @codingStandardsIgnoreEnd
475
		}
476
477
		// Set action URL.
478
		if ( isset( $result->_links ) ) {
479
			if ( isset( $result->_links->checkout->href ) ) {
480
				$payment->set_action_url( $result->_links->checkout->href );
481
			}
482
		}
483
	}
484
485
	/**
486
	 * Update status of the specified payment
487
	 *
488
	 * @param Payment $payment Payment.
489
	 * @return void
490
	 */
491
	public function update_status( Payment $payment ) {
492
		$transaction_id = $payment->get_transaction_id();
493
494
		if ( null === $transaction_id ) {
495
			return;
496
		}
497
498
		$mollie_payment = $this->client->get_payment( $transaction_id );
499
500
		if ( isset( $mollie_payment->status ) ) {
501
			$payment->set_status( Statuses::transform( $mollie_payment->status ) );
502
		}
503
504
		/**
505
		 * Mollie profile.
506
		 */
507
		$mollie_profile = new Profile();
508
509
		// phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase -- Mollie.
510
		$mollie_profile->set_id( $mollie_payment->profileId );
511
512
		$profile_internal_id = $this->profile_data_store->get_or_insert_profile( $mollie_profile );
513
514
		/**
515
		 * If the Mollie payment contains a customer ID we will try to connect
516
		 * this Mollie customer ID the WordPress user and subscription.
517
		 * This can be usefull in case when a WordPress user is created after
518
		 * a succesfull payment.
519
		 *
520
		 * @link https://www.gravityforms.com/add-ons/user-registration/
521
		 */
522
523
		// phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase -- Mollie.
524
		if ( isset( $mollie_payment->customerId ) ) {
525
			// phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase -- Mollie.
526
			$mollie_customer = new Customer( $mollie_payment->customerId );
527
528
			$customer_internal_id = $this->customer_data_store->get_or_insert_customer(
0 ignored issues
show
Unused Code introduced by
The assignment to $customer_internal_id is dead and can be removed.
Loading history...
529
				$mollie_customer,
530
				array(
531
					'profile_id' => $profile_internal_id,
532
				),
533
				array(
534
					'profile_id' => '%s',
535
				)
536
			);
537
538
			// Meta.
539
			$customer_id = $payment->get_meta( 'mollie_customer_id' );
540
541
			if ( empty( $customer_id ) ) {
542
				$payment->set_meta( 'mollie_customer_id', $mollie_customer->get_id() );
543
			}
544
545
			// Customer.
546
			$customer = $payment->get_customer();
547
548
			if ( null !== $customer ) {
549
				// Connect to user.
550
				$user_id = $customer->get_user_id();
551
552
				if ( null !== $user_id ) {
553
					$user = \get_user_by( 'id', $user_id );
554
555
					if ( false !== $user ) {
556
						$this->customer_data_store->connect_mollie_customer_to_wp_user( $mollie_customer, $user );
557
					}
558
				}
559
			}
560
561
			// Subscription.
562
			$subscription = $payment->get_subscription();
563
564
			if ( null !== $subscription ) {
565
				$customer_id = $subscription->get_meta( 'mollie_customer_id' );
566
567
				if ( empty( $customer_id ) ) {
568
					$subscription->set_meta( 'mollie_customer_id', $mollie_customer->get_id() );
569
				}
570
571
				// Update mandate in subscription meta.
572
				// phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase -- Mollie.
573
				if ( isset( $mollie_payment->mandateId ) ) {
574
					$mandate_id = $subscription->get_meta( 'mollie_mandate_id' );
575
576
					// Only update if no mandate has been set yet or if payment succeeded.
577
					if ( empty( $mandate_id ) || PaymentStatus::SUCCESS === $payment->get_status() ) {
578
						$this->update_subscription_mandate( $subscription, $mollie_payment->mandateId, $payment->get_method() );
579
					}
580
				}
581
				// phpcs:enable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase -- Mollie.
582
			}
583
		}
584
585
		if ( isset( $mollie_payment->details ) ) {
586
			$consumer_bank_details = $payment->get_consumer_bank_details();
587
588
			if ( null === $consumer_bank_details ) {
589
				$consumer_bank_details = new BankAccountDetails();
590
591
				$payment->set_consumer_bank_details( $consumer_bank_details );
592
			}
593
594
			$details = $mollie_payment->details;
595
596
			/*
597
			 * @codingStandardsIgnoreStart
598
			 *
599
			 * Ignore coding standards because of sniff WordPress.NamingConventions.ValidVariableName.NotSnakeCaseMemberVar
600
			 */
601
			if ( isset( $details->consumerName ) ) {
602
				$consumer_bank_details->set_name( $details->consumerName );
603
			}
604
605
			if ( isset( $details->cardHolder ) ) {
606
				$consumer_bank_details->set_name( $details->cardHolder );
607
			}
608
609
			if ( isset( $details->cardNumber ) ) {
610
				// The last four digits of the card number.
611
				$consumer_bank_details->set_account_number( $details->cardNumber );
612
			}
613
614
			if ( isset( $details->cardCountryCode ) ) {
615
				// The ISO 3166-1 alpha-2 country code of the country the card was issued in.
616
				$consumer_bank_details->set_country( $details->cardCountryCode );
617
			}
618
619
			if ( isset( $details->consumerAccount ) ) {
620
				switch ( $mollie_payment->method ) {
621
					case Methods::BELFIUS:
622
					case Methods::DIRECT_DEBIT:
623
					case Methods::IDEAL:
624
					case Methods::KBC:
625
					case Methods::SOFORT:
626
						$consumer_bank_details->set_iban( $details->consumerAccount );
627
628
						break;
629
					case Methods::BANCONTACT:
630
					case Methods::BANKTRANSFER:
631
					case Methods::PAYPAL:
632
					default:
633
						$consumer_bank_details->set_account_number( $details->consumerAccount );
634
635
						break;
636
				}
637
			}
638
639
			if ( isset( $details->consumerBic ) ) {
640
				$consumer_bank_details->set_bic( $details->consumerBic );
641
			}
642
643
			/*
644
			 * Failure reason.
645
			 */
646
			$failure_reason = $payment->get_failure_reason();
647
648
			if ( null === $failure_reason ) {
649
				$failure_reason = new FailureReason();
650
651
				$payment->set_failure_reason( $failure_reason );
652
			}
653
654
			// SEPA Direct Debit.
655
			if ( isset( $details->bankReasonCode ) ) {
656
				$failure_reason->set_code( $details->bankReasonCode );
657
			}
658
659
			if ( isset( $details->bankReasonCode ) ) {
660
				$failure_reason->set_message( $details->bankReason );
661
			}
662
663
			// Credit card.
664
			if ( isset( $details->failureReason ) ) {
665
				$failure_reason->set_code( $details->failureReason );
666
			}
667
668
			if ( isset( $details->failureMessage ) ) {
669
				$failure_reason->set_message( $details->failureMessage );
670
			}
671
			// @codingStandardsIgnoreEnd
672
		}
673
	}
674
675
	/**
676
	 * Update subscription mandate.
677
	 *
678
	 * @param Subscription $subscription   Subscription.
679
	 * @param string       $mandate_id     Mollie mandate ID.
680
	 * @param string|null  $payment_method Payment method.
681
	 * @return void
682
	 * @throws \Exception
683
	 */
684
	public function update_subscription_mandate( Subscription $subscription, $mandate_id, $payment_method = null ) {
685
		$customer_id = $subscription->get_meta( 'mollie_customer_id' );
686
687
		$mandate = $this->client->get_mandate( $mandate_id, $customer_id );
688
689
		if ( ! \is_object( $mandate ) ) {
690
			return;
691
		}
692
693
		// Update mandate.
694
		$old_mandate_id = $subscription->get_meta( 'mollie_mandate_id' );
695
696
		$subscription->set_meta( 'mollie_mandate_id', $mandate_id );
697
698
		if ( ! empty( $old_mandate_id ) && $old_mandate_id !== $mandate_id ) {
699
			$note = \sprintf(
700
			/* translators: 1: old mandate ID, 2: new mandate ID */
701
				\__( 'Mandate for subscription changed from "%1$s" to "%2$s".', 'pronamic_ideal' ),
702
				\esc_html( $old_mandate_id ),
703
				\esc_html( $mandate_id )
704
			);
705
706
			$subscription->add_note( $note );
707 10
		}
708 10
709
		// Update payment method.
710 10
		$old_method = $subscription->payment_method;
711
		$new_method = ( null === $payment_method ? Methods::transform_gateway_method( $mandate->method ) : $payment_method );
712 10
713
		// `Direct Debit` is not a recurring method, use `Direct Debit (mandate via ...)` instead.
714
		if ( PaymentMethods::DIRECT_DEBIT === $new_method ) {
715
			$new_method = PaymentMethods::DIRECT_DEBIT_IDEAL;
716
717
			// Use `Direct Debit (mandate via Bancontact)` if consumer account starts with `BE`.
718
			if ( 'BE' === \substr( $mandate->details->consumerAccount, 0, 2 ) ) {
719
				$new_method = PaymentMethods::DIRECT_DEBIT_BANCONTACT;
720
			}
721 10
		}
722 10
723
		if ( ! empty( $old_method ) && $old_method !== $new_method ) {
724
			$subscription->payment_method = $new_method;
725 10
726
			// Add note.
727 10
			$note = \sprintf(
728 10
				/* translators: 1: old payment method, 2: new payment method */
729
				\__( 'Payment method for subscription changed from "%1$s" to "%2$s".', 'pronamic_ideal' ),
730 10
				\esc_html( PaymentMethods::get_name( $old_method ) ),
731 4
				\esc_html( PaymentMethods::get_name( $new_method ) )
732
			);
733
734
			$subscription->add_note( $note );
735
		}
736 10
737
		$subscription->save();
738 10
	}
739 9
740
	/**
741 9
	 * Get Mollie customer ID for payment.
742 7
	 *
743
	 * @param Payment $payment Payment.
744 7
	 * @return string|null
745
	 */
746
	public function get_customer_id_for_payment( Payment $payment ) {
747
		$customer_ids = $this->get_customer_ids_for_payment( $payment );
748 10
749
		$customer_id = $this->get_first_existing_customer_id( $customer_ids );
750
751
		return $customer_id;
752
	}
753
754
	/**
755
	 * Get Mollie customers for the specified payment.
756
	 *
757 24
	 * @param Payment $payment Payment.
758 24
	 * @return array<string>
759
	 */
760 24
	private function get_customer_ids_for_payment( Payment $payment ) {
761
		$customer_ids = array();
762
763
		// Customer ID from subscription meta.
764 24
		$subscription = $payment->get_subscription();
765
766 24
		if ( null !== $subscription ) {
767
			$customer_id = $this->get_customer_id_for_subscription( $subscription );
768 24
769
			if ( null !== $customer_id ) {
770
				$customer_ids[] = $customer_id;
771
			}
772
		}
773
774
		// Customer ID from WordPress user.
775
		$customer = $payment->get_customer();
776
777 10
		if ( null !== $customer ) {
778 10
			$user_id = $customer->get_user_id();
779
780 10
			if ( ! empty( $user_id ) ) {
781
				$user_customer_ids = $this->get_customer_ids_for_user( $user_id );
782 7
783
				$customer_ids = \array_merge( $customer_ids, $user_customer_ids );
784 7
			}
785 7
		}
786
787
		return $customer_ids;
788
	}
789 10
790 6
	/**
791
	 * Get Mollie customers for the specified WordPress user ID.
792
	 *
793 4
	 * @param int $user_id WordPress user ID.
794
	 * @return array<string>
795
	 */
796
	public function get_customer_ids_for_user( $user_id ) {
797
		$customer_query = new CustomerQuery(
798
			array(
799
				'user_id' => $user_id,
800
			)
801
		);
802 10
803 10
		$customers = $customer_query->get_customers();
804
805 10
		$customer_ids = wp_list_pluck( $customers, 'mollie_id' );
806
807 10
		return $customer_ids;
808 4
	}
809
810 4
	/**
811 4
	 * Get customer ID for subscription.
812
	 *
813
	 * @param Subscription $subscription Subscription.
814
	 * @return string|null
815 6
	 */
816
	private function get_customer_id_for_subscription( Subscription $subscription ) {
817
		$customer_id = $subscription->get_meta( 'mollie_customer_id' );
818
819
		if ( empty( $customer_id ) ) {
820
			// Try to get (legacy) customer ID from first payment.
821
			$first_payment = $subscription->get_first_payment();
822
823
			if ( null !== $first_payment ) {
824
				$customer_id = $first_payment->get_meta( 'mollie_customer_id' );
825
			}
826
		}
827
828
		if ( empty( $customer_id ) ) {
829
			return null;
830
		}
831
832
		return $customer_id;
833
	}
834
835
	/**
836
	 * Get first existing customer from customers list.
837
	 *
838
	 * @param array<string> $customer_ids Customers.
839
	 * @return string|null
840
	 */
841
	private function get_first_existing_customer_id( $customer_ids ) {
842
		$customer_ids = \array_filter( $customer_ids );
843
844
		$customer_ids = \array_unique( $customer_ids );
845
846
		foreach ( $customer_ids as $customer_id ) {
847
			$customer = $this->client->get_customer( $customer_id );
848
849
			if ( null !== $customer ) {
850
				return $customer_id;
851
			}
852
		}
853
854
		return null;
855
	}
856
857
	/**
858
	 * Create customer for payment.
859
	 *
860
	 * @param Payment $payment Payment.
861
	 * @return string|null
862
	 * @throws Error Throws Error when Mollie error occurs.
863
	 */
864
	private function create_customer_for_payment( Payment $payment ) {
865
		$mollie_customer = new Customer();
866
		$mollie_customer->set_mode( $this->config->is_test_mode() ? 'test' : 'live' );
867
		$mollie_customer->set_email( $payment->get_email() );
868
869
		$pronamic_customer = $payment->get_customer();
870
871
		if ( null !== $pronamic_customer ) {
872
			// Name.
873
			$name = \strval( $pronamic_customer->get_name() );
874
875
			if ( '' !== $name ) {
876
				$mollie_customer->set_name( $name );
877
			}
878
879
			// Locale.
880
			$locale = $pronamic_customer->get_locale();
881
882
			if ( null !== $locale ) {
883
				$mollie_customer->set_locale( LocaleHelper::transform( $locale ) );
884
			}
885
		}
886
887
		// Try to get name from consumer bank details.
888
		$consumer_bank_details = $payment->get_consumer_bank_details();
889
890
		if ( null === $mollie_customer->get_name() && null !== $consumer_bank_details ) {
891
			$name = $consumer_bank_details->get_name();
892
893 27
			if ( null !== $name ) {
894 27
				$mollie_customer->set_name( $name );
895 1
			}
896
		}
897
898
		// Create customer.
899 26
		$mollie_customer = $this->client->create_customer( $mollie_customer );
900
901
		$customer_id = $this->customer_data_store->insert_customer( $mollie_customer );
0 ignored issues
show
Unused Code introduced by
The assignment to $customer_id is dead and can be removed.
Loading history...
902 26
903
		// Connect to user.
904 26
		if ( null !== $pronamic_customer ) {
905 17
			$user_id = $pronamic_customer->get_user_id();
906
907
			if ( null !== $user_id ) {
908 26
				$user = \get_user_by( 'id', $user_id );
909 1
910
				if ( false !== $user ) {
911
					$this->customer_data_store->connect_mollie_customer_to_wp_user( $mollie_customer, $user );
912
				}
913 25
			}
914
		}
915 25
916 2
		// Store customer ID in subscription meta.
917
		$subscription = $payment->get_subscription();
918
919 23
		if ( null !== $subscription ) {
920
			$subscription->set_meta( 'mollie_customer_id', $mollie_customer->get_id() );
921 23
		}
922 12
923
		return $mollie_customer->get_id();
924
	}
925
926 11
	/**
927
	 * Copy Mollie customer ID from subscription meta to WordPress user meta.
928
	 *
929 11
	 * @param Payment $payment Payment.
930
	 * @return void
931
	 */
932 11
	public function copy_customer_id_to_wp_user( Payment $payment ) {
933 11
		if ( $this->config->id !== $payment->config_id ) {
934
			return;
935
		}
936
937 11
		// Subscription.
938 11
		$subscription = $payment->get_subscription();
939
940 11
		// Customer.
941 2
		$customer = $payment->get_customer();
942
943 2
		if ( null === $customer && null !== $subscription ) {
944
			$customer = $subscription->get_customer();
945 2
		}
946
947 11
		if ( null === $customer ) {
948
			return;
949
		}
950
951
		// WordPress user.
952
		$user_id = $customer->get_user_id();
953
954
		if ( null === $user_id ) {
955
			return;
956
		}
957
958
		$user = \get_user_by( 'id', $user_id );
959
960
		if ( false === $user ) {
961
			return;
962
		}
963
964
		// Customer IDs.
965
		$customer_ids = array();
966
967
		// Payment.
968
		$customer_ids[] = $payment->get_meta( 'mollie_customer_id' );
969
970
		// Subscription.
971
		if ( null !== $subscription ) {
972
			$customer_ids[] = $subscription->get_meta( 'mollie_customer_id' );
973
		}
974
975
		// Connect.
976
		$customer_ids = \array_filter( $customer_ids );
977
		$customer_ids = \array_unique( $customer_ids );
978
979
		foreach ( $customer_ids as $customer_id ) {
980
			$customer = new Customer( $customer_id );
981
982
			$this->customer_data_store->get_or_insert_customer( $customer );
983
984
			$this->customer_data_store->connect_mollie_customer_to_wp_user( $customer, $user );
985
		}
986
	}
987
}
988