Failed Conditions
Push — develop ( 71f6f4...39d05f )
by Reüel
08:20
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->bankReason ) ) {
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
		}
708
709
		// Update payment method.
710
		$old_method = $subscription->payment_method;
711
		$new_method = ( null === $payment_method ? Methods::transform_gateway_method( $mandate->method ) : $payment_method );
712
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
		}
722
723
		if ( ! empty( $old_method ) && $old_method !== $new_method ) {
724
			$subscription->payment_method = $new_method;
725
726
			// Add note.
727
			$note = \sprintf(
728
				/* translators: 1: old payment method, 2: new payment method */
729
				\__( 'Payment method for subscription changed from "%1$s" to "%2$s".', 'pronamic_ideal' ),
730
				\esc_html( PaymentMethods::get_name( $old_method ) ),
731
				\esc_html( PaymentMethods::get_name( $new_method ) )
732
			);
733
734
			$subscription->add_note( $note );
735
		}
736
737
		$subscription->save();
738
	}
739
740
	/**
741
	 * Get Mollie customer ID for payment.
742
	 *
743
	 * @param Payment $payment Payment.
744
	 * @return string|null
745
	 */
746 10
	public function get_customer_id_for_payment( Payment $payment ) {
747 10
		$customer_ids = $this->get_customer_ids_for_payment( $payment );
748
749 10
		$customer_id = $this->get_first_existing_customer_id( $customer_ids );
750
751 10
		return $customer_id;
752
	}
753
754
	/**
755
	 * Get Mollie customers for the specified payment.
756
	 *
757
	 * @param Payment $payment Payment.
758
	 * @return array<string>
759
	 */
760 10
	private function get_customer_ids_for_payment( Payment $payment ) {
761 10
		$customer_ids = array();
762
763
		// Customer ID from subscription meta.
764 10
		$subscription = $payment->get_subscription();
765
766 10
		if ( null !== $subscription ) {
767 10
			$customer_id = $this->get_customer_id_for_subscription( $subscription );
768
769 10
			if ( null !== $customer_id ) {
770 4
				$customer_ids[] = $customer_id;
771
			}
772
		}
773
774
		// Customer ID from WordPress user.
775 10
		$customer = $payment->get_customer();
776
777 10
		if ( null !== $customer ) {
778 9
			$user_id = $customer->get_user_id();
779
780 9
			if ( ! empty( $user_id ) ) {
781 7
				$user_customer_ids = $this->get_customer_ids_for_user( $user_id );
782
783 7
				$customer_ids = \array_merge( $customer_ids, $user_customer_ids );
784
			}
785
		}
786
787 10
		return $customer_ids;
788
	}
789
790
	/**
791
	 * Get Mollie customers for the specified WordPress user ID.
792
	 *
793
	 * @param int $user_id WordPress user ID.
794
	 * @return array<string>
795
	 */
796 24
	public function get_customer_ids_for_user( $user_id ) {
797 24
		$customer_query = new CustomerQuery(
798
			array(
799 24
				'user_id' => $user_id,
800
			)
801
		);
802
803 24
		$customers = $customer_query->get_customers();
804
805 24
		$customer_ids = wp_list_pluck( $customers, 'mollie_id' );
806
807 24
		return $customer_ids;
808
	}
809
810
	/**
811
	 * Get customer ID for subscription.
812
	 *
813
	 * @param Subscription $subscription Subscription.
814
	 * @return string|null
815
	 */
816 10
	private function get_customer_id_for_subscription( Subscription $subscription ) {
817 10
		$customer_id = $subscription->get_meta( 'mollie_customer_id' );
818
819 10
		if ( empty( $customer_id ) ) {
820
			// Try to get (legacy) customer ID from first payment.
821 7
			$first_payment = $subscription->get_first_payment();
822
823 7
			if ( null !== $first_payment ) {
824 7
				$customer_id = $first_payment->get_meta( 'mollie_customer_id' );
825
			}
826
		}
827
828 10
		if ( empty( $customer_id ) ) {
829 6
			return null;
830
		}
831
832 4
		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 10
	private function get_first_existing_customer_id( $customer_ids ) {
842 10
		$customer_ids = \array_filter( $customer_ids );
843
844 10
		$customer_ids = \array_unique( $customer_ids );
845
846 10
		foreach ( $customer_ids as $customer_id ) {
847 4
			$customer = $this->client->get_customer( $customer_id );
848
849 4
			if ( null !== $customer ) {
850 4
				return $customer_id;
851
			}
852
		}
853
854 6
		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
			if ( null !== $name ) {
894
				$mollie_customer->set_name( $name );
895
			}
896
		}
897
898
		// Create customer.
899
		$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
903
		// Connect to user.
904
		if ( null !== $pronamic_customer ) {
905
			$user_id = $pronamic_customer->get_user_id();
906
907
			if ( null !== $user_id ) {
908
				$user = \get_user_by( 'id', $user_id );
909
910
				if ( false !== $user ) {
911
					$this->customer_data_store->connect_mollie_customer_to_wp_user( $mollie_customer, $user );
912
				}
913
			}
914
		}
915
916
		// Store customer ID in subscription meta.
917
		$subscription = $payment->get_subscription();
918
919
		if ( null !== $subscription ) {
920
			$subscription->set_meta( 'mollie_customer_id', $mollie_customer->get_id() );
921
		}
922
923
		return $mollie_customer->get_id();
924
	}
925
926
	/**
927
	 * Copy Mollie customer ID from subscription meta to WordPress user meta.
928
	 *
929
	 * @param Payment $payment Payment.
930
	 * @return void
931
	 */
932 27
	public function copy_customer_id_to_wp_user( Payment $payment ) {
933 27
		if ( $this->config->id !== $payment->config_id ) {
934 1
			return;
935
		}
936
937
		// Subscription.
938 26
		$subscription = $payment->get_subscription();
939
940
		// Customer.
941 26
		$customer = $payment->get_customer();
942
943 26
		if ( null === $customer && null !== $subscription ) {
944 17
			$customer = $subscription->get_customer();
945
		}
946
947 26
		if ( null === $customer ) {
948 1
			return;
949
		}
950
951
		// WordPress user.
952 25
		$user_id = $customer->get_user_id();
953
954 25
		if ( null === $user_id ) {
955 2
			return;
956
		}
957
958 23
		$user = \get_user_by( 'id', $user_id );
959
960 23
		if ( false === $user ) {
961 12
			return;
962
		}
963
964
		// Customer IDs.
965 11
		$customer_ids = array();
966
967
		// Payment.
968 11
		$customer_ids[] = $payment->get_meta( 'mollie_customer_id' );
969
970
		// Subscription.
971 11
		if ( null !== $subscription ) {
972 11
			$customer_ids[] = $subscription->get_meta( 'mollie_customer_id' );
973
		}
974
975
		// Connect.
976 11
		$customer_ids = \array_filter( $customer_ids );
977 11
		$customer_ids = \array_unique( $customer_ids );
978
979 11
		foreach ( $customer_ids as $customer_id ) {
980 2
			$customer = new Customer( $customer_id );
981
982 2
			$this->customer_data_store->get_or_insert_customer( $customer );
983
984 2
			$this->customer_data_store->connect_mollie_customer_to_wp_user( $customer, $user );
985
		}
986 11
	}
987
}
988