Test Failed
Push — develop ( 57eedf...958abc )
by Reüel
07:30
created

Gateway::get_available_payment_methods()   B

Complexity

Conditions 11
Paths 35

Size

Total Lines 68
Code Lines 34

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 22
CRAP Score 16.3179

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 68
ccs 22
cts 34
cp 0.6471
crap 16.3179
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( (string) $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<int, 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
			$method = (string) $method;
179
180 2
			$payment_method = Methods::transform_gateway_method( $method );
181
182 2
			if ( PaymentMethods::is_recurring_method( $method ) ) {
183
				$payment_method = $method;
184
			}
185
186 2
			if ( null !== $payment_method ) {
187 2
				$payment_methods[] = (string) $payment_method;
188
			}
189
		}
190
191 2
		$payment_methods = array_unique( $payment_methods );
192
193 2
		return $payment_methods;
194
	}
195
196
	/**
197
	 * Get supported payment methods
198
	 *
199
	 * @see Pronamic_WP_Pay_Gateway::get_supported_payment_methods()
200
	 * @return array<string>
201
	 */
202 2
	public function get_supported_payment_methods() {
203
		return array(
204 2
			PaymentMethods::APPLE_PAY,
205
			PaymentMethods::BANCONTACT,
206
			PaymentMethods::BANK_TRANSFER,
207
			PaymentMethods::BELFIUS,
208
			PaymentMethods::CREDIT_CARD,
209
			PaymentMethods::DIRECT_DEBIT,
210
			PaymentMethods::DIRECT_DEBIT_BANCONTACT,
211
			PaymentMethods::DIRECT_DEBIT_IDEAL,
212
			PaymentMethods::DIRECT_DEBIT_SOFORT,
213
			PaymentMethods::EPS,
214
			PaymentMethods::GIROPAY,
215
			PaymentMethods::IDEAL,
216
			PaymentMethods::KBC,
217
			PaymentMethods::PAYPAL,
218
			PaymentMethods::PRZELEWY24,
0 ignored issues
show
Bug introduced by
The constant Pronamic\WordPress\Pay\C...mentMethods::PRZELEWY24 was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
219
			PaymentMethods::SOFORT,
220
		);
221
	}
222
223
	/**
224
	 * Get webhook URL for Mollie.
225
	 *
226
	 * @return string|null
227 4
	 */
228 4
	public function get_webhook_url() {
229
		$url = \rest_url( Integration::REST_ROUTE_NAMESPACE . '/webhook' );
230 4
231
		$host = wp_parse_url( $url, PHP_URL_HOST );
232 4
233
		if ( is_array( $host ) ) {
234
			// Parsing failure.
235
			$host = '';
236
		}
237 4
238
		if ( 'localhost' === $host ) {
239 1
			// Mollie doesn't allow localhost.
240 3
			return null;
241
		} elseif ( '.dev' === substr( $host, -4 ) ) {
242 1
			// Mollie doesn't allow the .dev TLD.
243 2
			return null;
244
		} elseif ( '.local' === substr( $host, -6 ) ) {
245 1
			// Mollie doesn't allow the .local TLD.
246 1
			return null;
247
		} elseif ( '.test' === substr( $host, -5 ) ) {
248
			// Mollie doesn't allow the .test TLD.
249
			return null;
250
		}
251 1
252
		return $url;
253
	}
254
255
	/**
256
	 * Start
257
	 *
258
	 * @see Pronamic_WP_Pay_Gateway::start()
259
	 * @param Payment $payment Payment.
260
	 * @return void
261
	 * @throws \Exception Throws exception on error creating Mollie customer for payment.
262
	 */
263
	public function start( Payment $payment ) {
264
		$request = new PaymentRequest(
265
			AmountTransformer::transform( $payment->get_total_amount() ),
266
			(string) $payment->get_description()
267
		);
268
269
		$request->redirect_url = $payment->get_return_url();
270
		$request->webhook_url  = $this->get_webhook_url();
271
272
		// Locale.
273
		$customer = $payment->get_customer();
274
275
		if ( null !== $customer ) {
276
			$request->locale = LocaleHelper::transform( $customer->get_locale() );
277
		}
278
279
		// Customer ID.
280
		$customer_id = $this->get_customer_id_for_payment( $payment );
281
282
		if ( null === $customer_id ) {
283
			$customer_id = $this->create_customer_for_payment( $payment );
284
		}
285
286
		if ( null !== $customer_id ) {
287
			$request->customer_id = $customer_id;
288
		}
289
290
		// Payment method.
291
		$payment_method = $payment->get_method();
292
293
		// Recurring payment method.
294
		$subscription = $payment->get_subscription();
295
296
		$is_recurring_method = ( $subscription && PaymentMethods::is_recurring_method( (string) $payment_method ) );
297
298
		// Consumer bank details.
299
		$consumer_bank_details = $payment->get_consumer_bank_details();
300
301
		if ( PaymentMethods::DIRECT_DEBIT === $payment_method && null !== $consumer_bank_details ) {
302
			$consumer_name = $consumer_bank_details->get_name();
303
			$consumer_iban = $consumer_bank_details->get_iban();
304
305
			$request->consumer_name    = $consumer_name;
306
			$request->consumer_account = $consumer_iban;
307
308
			// Check if one-off SEPA Direct Debit can be used, otherwise short circuit payment.
309
			if ( null !== $customer_id ) {
310
				// Find or create mandate.
311
				$mandate_id = $this->client->has_valid_mandate( $customer_id, PaymentMethods::DIRECT_DEBIT, $consumer_iban );
312
313
				if ( false === $mandate_id ) {
314
					$mandate = $this->client->create_mandate( $customer_id, $consumer_bank_details );
315
316
					if ( ! \property_exists( $mandate, 'id' ) ) {
317
						throw new \Exception( 'Missing mandate ID.' );
318
					}
319
320
					$mandate_id = $mandate->id;
321
				}
322
323
				// Charge immediately on-demand.
324
				$request->set_sequence_type( Sequence::RECURRING );
325
				$request->set_mandate_id( (string) $mandate_id );
326
327
				$is_recurring_method = true;
328
329
				$payment->recurring = true;
330
			}
331
		}
332
333
		if ( false === $is_recurring_method && null !== $payment_method ) {
334
			// Always use 'direct debit mandate via iDEAL/Bancontact/Sofort' payment methods as recurring method.
335
			$is_recurring_method = PaymentMethods::is_direct_debit_method( $payment_method );
336
		}
337
338
		if ( $is_recurring_method ) {
339
			$request->sequence_type = $payment->get_recurring() ? Sequence::RECURRING : Sequence::FIRST;
340
341
			if ( Sequence::FIRST === $request->sequence_type ) {
342
				$payment_method = PaymentMethods::get_first_payment_method( $payment_method );
343
			}
344
345
			if ( Sequence::RECURRING === $request->sequence_type ) {
346
				// Use mandate from subscription.
347
				if ( $subscription && empty( $request->mandate_id ) ) {
348
					$subscription_mandate_id = $subscription->get_meta( 'mollie_mandate_id' );
349
350
					if ( false !== $subscription_mandate_id ) {
351
						$request->set_mandate_id( $subscription_mandate_id );
352
					}
353
				}
354
355
				$payment->set_action_url( $payment->get_return_url() );
356
			}
357
		}
358
359
		// Leap of faith if the WordPress payment method could not transform to a Mollie method?
360
		$request->method = Methods::transform( $payment_method, $payment_method );
361
362
		/**
363
		 * Metadata.
364
		 *
365
		 * Provide any data you like, for example a string or a JSON object.
366
		 * We will save the data alongside the payment. Whenever you fetch
367
		 * the payment with our API, we’ll also include the metadata. You
368
		 * can use up to approximately 1kB.
369
		 *
370
		 * @link https://docs.mollie.com/reference/v2/payments-api/create-payment
371
		 * @link https://en.wikipedia.org/wiki/Metadata
372
		 */
373
		$metadata = null;
374
375
		/**
376
		 * Filters the Mollie metadata.
377
		 *
378
		 * @since 2.2.0
379
		 *
380
		 * @param mixed   $metadata Metadata.
381
		 * @param Payment $payment  Payment.
382
		 */
383
		$metadata = \apply_filters( 'pronamic_pay_mollie_payment_metadata', $metadata, $payment );
384
385
		$request->set_metadata( $metadata );
386
387
		// Issuer.
388
		if ( Methods::IDEAL === $request->method ) {
389
			$request->issuer = $payment->get_issuer();
390
		}
391
392
		// Billing email.
393
		$billing_email = $payment->get_email();
394
395
		/**
396
		 * Filters the Mollie payment billing email used for bank transfer payment instructions.
397
		 *
398
		 * @since 2.2.0
399
		 *
400
		 * @param string|null $billing_email Billing email.
401
		 * @param Payment     $payment       Payment.
402
		 */
403
		$billing_email = \apply_filters( 'pronamic_pay_mollie_payment_billing_email', $billing_email, $payment );
404
405
		$request->set_billing_email( $billing_email );
406
407
		// Due date.
408
		try {
409
			$due_date = new DateTime( sprintf( '+%s days', $this->config->due_date_days ) );
410
		} catch ( \Exception $e ) {
411
			$due_date = null;
412
		}
413
414
		$request->set_due_date( $due_date );
415
416
		// Create payment.
417
		$result = $this->client->create_payment( $request );
418
419
		// Set transaction ID.
420
		if ( isset( $result->id ) ) {
421
			$payment->set_transaction_id( $result->id );
422
		}
423
424
		// Set expiry date.
425
		/* phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase */
426
		if ( isset( $result->expiresAt ) ) {
427
			try {
428
				$expires_at = new DateTime( $result->expiresAt );
429
			} catch ( \Exception $e ) {
430
				$expires_at = null;
431
			}
432
433
			$payment->set_expiry_date( $expires_at );
434
		}
435
		/* phpcs:enable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase */
436
437
		// Set status.
438
		if ( isset( $result->status ) ) {
439
			$payment->set_status( Statuses::transform( $result->status ) );
440
		}
441
442
		// Set bank transfer recipient details.
443
		if ( isset( $result->details ) ) {
444
			$bank_transfer_recipient_details = $payment->get_bank_transfer_recipient_details();
445
446
			if ( null === $bank_transfer_recipient_details ) {
447
				$bank_transfer_recipient_details = new BankTransferDetails();
448
449
				$payment->set_bank_transfer_recipient_details( $bank_transfer_recipient_details );
450
			}
451
452
			$bank_details = $bank_transfer_recipient_details->get_bank_account();
453
454
			if ( null === $bank_details ) {
455
				$bank_details = new BankAccountDetails();
456
457
				$bank_transfer_recipient_details->set_bank_account( $bank_details );
458
			}
459
460
			$details = $result->details;
461
462
			/*
463
			 * @codingStandardsIgnoreStart
464
			 *
465
			 * Ignore coding standards because of sniff WordPress.NamingConventions.ValidVariableName.NotSnakeCaseMemberVar
466
			 */
467
			if ( isset( $details->bankName ) ) {
468
				/**
469
				 * Set `bankName` as bank details name, as result "Stichting Mollie Payments"
470
				 * is not the name of a bank, but the account holder name.
471
				 */
472
				$bank_details->set_name( $details->bankName );
473
			}
474
475
			if ( isset( $details->bankAccount ) ) {
476
				$bank_details->set_iban( $details->bankAccount );
477
			}
478
479
			if ( isset( $details->bankBic ) ) {
480
				$bank_details->set_bic( $details->bankBic );
481
			}
482
483
			if ( isset( $details->transferReference ) ) {
484
				$bank_transfer_recipient_details->set_reference( $details->transferReference );
485
			}
486
			// @codingStandardsIgnoreEnd
487
		}
488
489
		// Set action URL.
490
		if ( isset( $result->_links ) ) {
491
			if ( isset( $result->_links->checkout->href ) ) {
492
				$payment->set_action_url( $result->_links->checkout->href );
493
			}
494
		}
495
	}
496
497
	/**
498
	 * Update status of the specified payment
499
	 *
500
	 * @param Payment $payment Payment.
501
	 * @return void
502
	 */
503
	public function update_status( Payment $payment ) {
504
		$transaction_id = $payment->get_transaction_id();
505
506
		if ( null === $transaction_id ) {
507
			return;
508
		}
509
510
		$mollie_payment = $this->client->get_payment( $transaction_id );
511
512
		if ( isset( $mollie_payment->status ) ) {
513
			$payment->set_status( Statuses::transform( $mollie_payment->status ) );
514
		}
515
516
		/**
517
		 * Mollie profile.
518
		 */
519
		$mollie_profile = new Profile();
520
521
		if ( \property_exists( $mollie_payment, 'profileId' ) ) {
522
			// phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase -- Mollie.
523
			$mollie_profile->set_id( $mollie_payment->profileId );
524
		}
525
526
		$profile_internal_id = $this->profile_data_store->get_or_insert_profile( $mollie_profile );
527
528
		/**
529
		 * If the Mollie payment contains a customer ID we will try to connect
530
		 * this Mollie customer ID the WordPress user and subscription.
531
		 * This can be usefull in case when a WordPress user is created after
532
		 * a succesfull payment.
533
		 *
534
		 * @link https://www.gravityforms.com/add-ons/user-registration/
535
		 */
536
537
		// phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase -- Mollie.
538
		if ( isset( $mollie_payment->customerId ) ) {
539
			// phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase -- Mollie.
540
			$mollie_customer = new Customer( $mollie_payment->customerId );
541
542
			$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...
543
				$mollie_customer,
544
				array(
545
					'profile_id' => $profile_internal_id,
546
				),
547
				array(
548
					'profile_id' => '%s',
549
				)
550
			);
551
552
			// Meta.
553
			$customer_id = $payment->get_meta( 'mollie_customer_id' );
554
555
			if ( empty( $customer_id ) ) {
556
				$payment->set_meta( 'mollie_customer_id', $mollie_customer->get_id() );
557
			}
558
559
			// Customer.
560
			$customer = $payment->get_customer();
561
562
			if ( null !== $customer ) {
563
				// Connect to user.
564
				$user_id = $customer->get_user_id();
565
566
				if ( null !== $user_id ) {
567
					$user = \get_user_by( 'id', $user_id );
568
569
					if ( false !== $user ) {
570
						$this->customer_data_store->connect_mollie_customer_to_wp_user( $mollie_customer, $user );
571
					}
572
				}
573
			}
574
575
			// Subscription.
576
			$subscription = $payment->get_subscription();
577
578
			if ( null !== $subscription ) {
579
				$customer_id = $subscription->get_meta( 'mollie_customer_id' );
580
581
				if ( empty( $customer_id ) ) {
582
					$subscription->set_meta( 'mollie_customer_id', $mollie_customer->get_id() );
583
				}
584
585
				// Update mandate in subscription meta.
586
				// phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase -- Mollie.
587
				if ( isset( $mollie_payment->mandateId ) ) {
588
					$mandate_id = $subscription->get_meta( 'mollie_mandate_id' );
589
590
					// Only update if no mandate has been set yet or if payment succeeded.
591
					if ( empty( $mandate_id ) || PaymentStatus::SUCCESS === $payment->get_status() ) {
592
						$this->update_subscription_mandate( $subscription, $mollie_payment->mandateId, $payment->get_method() );
593
					}
594
				}
595
				// phpcs:enable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase -- Mollie.
596
			}
597
		}
598
599
		if ( isset( $mollie_payment->details ) ) {
600
			$consumer_bank_details = $payment->get_consumer_bank_details();
601
602
			if ( null === $consumer_bank_details ) {
603
				$consumer_bank_details = new BankAccountDetails();
604
605
				$payment->set_consumer_bank_details( $consumer_bank_details );
606
			}
607
608
			$details = $mollie_payment->details;
609
610
			/*
611
			 * @codingStandardsIgnoreStart
612
			 *
613
			 * Ignore coding standards because of sniff WordPress.NamingConventions.ValidVariableName.NotSnakeCaseMemberVar
614
			 */
615
			if ( isset( $details->consumerName ) ) {
616
				$consumer_bank_details->set_name( $details->consumerName );
617
			}
618
619
			if ( isset( $details->cardHolder ) ) {
620
				$consumer_bank_details->set_name( $details->cardHolder );
621
			}
622
623
			if ( isset( $details->cardNumber ) ) {
624
				// The last four digits of the card number.
625
				$consumer_bank_details->set_account_number( $details->cardNumber );
626
			}
627
628
			if ( isset( $details->cardCountryCode ) ) {
629
				// The ISO 3166-1 alpha-2 country code of the country the card was issued in.
630
				$consumer_bank_details->set_country( $details->cardCountryCode );
631
			}
632
633
			if ( isset( $details->consumerAccount ) ) {
634
				$mollie_method = \property_exists( $mollie_payment, 'method' ) ? $mollie_payment->method : null;
635
636
				switch ( $mollie_method ) {
637
					case Methods::BELFIUS:
638
					case Methods::DIRECT_DEBIT:
639
					case Methods::IDEAL:
640
					case Methods::KBC:
641
					case Methods::SOFORT:
642
						$consumer_bank_details->set_iban( $details->consumerAccount );
643
644
						break;
645
					case Methods::BANCONTACT:
646
					case Methods::BANKTRANSFER:
647
					case Methods::PAYPAL:
648
					default:
649
						$consumer_bank_details->set_account_number( $details->consumerAccount );
650
651
						break;
652
				}
653
			}
654
655
			if ( isset( $details->consumerBic ) ) {
656
				$consumer_bank_details->set_bic( $details->consumerBic );
657
			}
658
659
			/*
660
			 * Failure reason.
661
			 */
662
			$failure_reason = $payment->get_failure_reason();
663
664
			if ( null === $failure_reason ) {
665
				$failure_reason = new FailureReason();
666
667
				$payment->set_failure_reason( $failure_reason );
668
			}
669
670
			// SEPA Direct Debit.
671
			if ( isset( $details->bankReasonCode ) ) {
672
				$failure_reason->set_code( $details->bankReasonCode );
673
			}
674
675
			if ( isset( $details->bankReason ) ) {
676
				$failure_reason->set_message( $details->bankReason );
677
			}
678
679
			// Credit card.
680
			if ( isset( $details->failureReason ) ) {
681
				$failure_reason->set_code( $details->failureReason );
682
			}
683
684
			if ( isset( $details->failureMessage ) ) {
685
				$failure_reason->set_message( $details->failureMessage );
686
			}
687
			// @codingStandardsIgnoreEnd
688
		}
689
	}
690
691
	/**
692
	 * Update subscription mandate.
693
	 *
694
	 * @param Subscription $subscription   Subscription.
695
	 * @param string       $mandate_id     Mollie mandate ID.
696
	 * @param string|null  $payment_method Payment method.
697
	 * @return void
698
	 * @throws \Exception Throws exception if subscription note could not be added.
699
	 */
700
	public function update_subscription_mandate( Subscription $subscription, $mandate_id, $payment_method = null ) {
701
		$customer_id = (string) $subscription->get_meta( 'mollie_customer_id' );
702
703
		$mandate = $this->client->get_mandate( $mandate_id, $customer_id );
704
705
		if ( ! \is_object( $mandate ) ) {
706
			return;
707
		}
708
709
		// Update mandate.
710
		$old_mandate_id = $subscription->get_meta( 'mollie_mandate_id' );
711
712
		$subscription->set_meta( 'mollie_mandate_id', $mandate_id );
713
714
		if ( ! empty( $old_mandate_id ) && $old_mandate_id !== $mandate_id ) {
715
			$note = \sprintf(
716
			/* translators: 1: old mandate ID, 2: new mandate ID */
717
				\__( 'Mandate for subscription changed from "%1$s" to "%2$s".', 'pronamic_ideal' ),
718
				\esc_html( $old_mandate_id ),
719
				\esc_html( $mandate_id )
720
			);
721
722
			$subscription->add_note( $note );
723
		}
724
725
		// Update payment method.
726
		$old_method = $subscription->payment_method;
727
		$new_method = ( null === $payment_method && \property_exists( $mandate, 'method' ) ? Methods::transform_gateway_method( $mandate->method ) : $payment_method );
728
729
		// `Direct Debit` is not a recurring method, use `Direct Debit (mandate via ...)` instead.
730
		if ( PaymentMethods::DIRECT_DEBIT === $new_method ) {
731
			$new_method = PaymentMethods::DIRECT_DEBIT_IDEAL;
732
733
			// Use `Direct Debit (mandate via Bancontact)` if consumer account starts with `BE`.
734
			if ( \property_exists( $mandate, 'details' ) && 'BE' === \substr( $mandate->details->consumerAccount, 0, 2 ) ) {
735
				$new_method = PaymentMethods::DIRECT_DEBIT_BANCONTACT;
736
			}
737
		}
738
739
		if ( ! empty( $old_method ) && $old_method !== $new_method ) {
740
			$subscription->payment_method = $new_method;
741
742
			// Add note.
743
			$note = \sprintf(
744
				/* translators: 1: old payment method, 2: new payment method */
745
				\__( 'Payment method for subscription changed from "%1$s" to "%2$s".', 'pronamic_ideal' ),
746
				\esc_html( (string) PaymentMethods::get_name( $old_method ) ),
747
				\esc_html( (string) PaymentMethods::get_name( $new_method ) )
748
			);
749
750
			$subscription->add_note( $note );
751
		}
752
753
		$subscription->save();
754
	}
755
756
	/**
757
	 * Get Mollie customer ID for payment.
758
	 *
759
	 * @param Payment $payment Payment.
760
	 * @return string|null
761 10
	 */
762 10
	public function get_customer_id_for_payment( Payment $payment ) {
763
		$customer_ids = $this->get_customer_ids_for_payment( $payment );
764 10
765
		$customer_id = $this->get_first_existing_customer_id( $customer_ids );
766 10
767
		return $customer_id;
768
	}
769
770
	/**
771
	 * Get Mollie customers for the specified payment.
772
	 *
773
	 * @param Payment $payment Payment.
774
	 * @return array<string>
775 10
	 */
776 10
	private function get_customer_ids_for_payment( Payment $payment ) {
777
		$customer_ids = array();
778
779 10
		// Customer ID from subscription meta.
780
		$subscription = $payment->get_subscription();
781 10
782 10
		if ( null !== $subscription ) {
783
			$customer_id = $this->get_customer_id_for_subscription( $subscription );
784 10
785 4
			if ( null !== $customer_id ) {
786
				$customer_ids[] = $customer_id;
787
			}
788
		}
789
790 10
		// Customer ID from WordPress user.
791
		$customer = $payment->get_customer();
792 10
793 9
		if ( null !== $customer ) {
794
			$user_id = $customer->get_user_id();
795 9
796 7
			if ( ! empty( $user_id ) ) {
797
				$user_customer_ids = $this->get_customer_ids_for_user( $user_id );
798 7
799
				$customer_ids = \array_merge( $customer_ids, $user_customer_ids );
800
			}
801
		}
802 10
803
		return $customer_ids;
804
	}
805
806
	/**
807
	 * Get Mollie customers for the specified WordPress user ID.
808
	 *
809
	 * @param int $user_id WordPress user ID.
810
	 * @return array<string>
811 24
	 */
812 24
	public function get_customer_ids_for_user( $user_id ) {
813
		$customer_query = new CustomerQuery(
814 24
			array(
815
				'user_id' => $user_id,
816
			)
817
		);
818 24
819
		$customers = $customer_query->get_customers();
820 24
821
		$customer_ids = wp_list_pluck( $customers, 'mollie_id' );
822 24
823
		return $customer_ids;
824
	}
825
826
	/**
827
	 * Get customer ID for subscription.
828
	 *
829
	 * @param Subscription $subscription Subscription.
830
	 * @return string|null
831 10
	 */
832 10
	private function get_customer_id_for_subscription( Subscription $subscription ) {
833
		$customer_id = $subscription->get_meta( 'mollie_customer_id' );
834 10
835
		if ( empty( $customer_id ) ) {
836 7
			// Try to get (legacy) customer ID from first payment.
837
			$first_payment = $subscription->get_first_payment();
838 7
839 7
			if ( null !== $first_payment ) {
840
				$customer_id = $first_payment->get_meta( 'mollie_customer_id' );
841
			}
842
		}
843 10
844 6
		if ( empty( $customer_id ) ) {
845
			return null;
846
		}
847 4
848
		return $customer_id;
849
	}
850
851
	/**
852
	 * Get first existing customer from customers list.
853
	 *
854
	 * @param array<string> $customer_ids Customers.
855
	 * @return string|null
856
	 * @throws Error Throws error on Mollie error.
857 10
	 */
858 10
	private function get_first_existing_customer_id( $customer_ids ) {
859
		$customer_ids = \array_filter( $customer_ids );
860 10
861
		$customer_ids = \array_unique( $customer_ids );
862 10
863
		foreach ( $customer_ids as $customer_id ) {
864 4
			try {
865
				$customer = $this->client->get_customer( $customer_id );
866
			} catch ( Error $error ) {
867
				// Check for status 410 ("Gone - The customer is no longer available").
868
				if ( 410 === $error->get_status() ) {
869
					continue;
870
				}
871
872
				throw $error;
873
			}
874 4
875 4
			if ( null !== $customer ) {
876
				return $customer_id;
877
			}
878
		}
879 6
880
		return null;
881
	}
882
883
	/**
884
	 * Create customer for payment.
885
	 *
886
	 * @param Payment $payment Payment.
887
	 * @return string|null
888
	 * @throws Error Throws Error when Mollie error occurs.
889
	 * @throws \Exception Throws exception when error in customer data store occurs.
890
	 */
891
	private function create_customer_for_payment( Payment $payment ) {
892
		$mollie_customer = new Customer();
893
		$mollie_customer->set_mode( $this->config->is_test_mode() ? 'test' : 'live' );
894
		$mollie_customer->set_email( $payment->get_email() );
895
896
		$pronamic_customer = $payment->get_customer();
897
898
		if ( null !== $pronamic_customer ) {
899
			// Name.
900
			$name = (string) $pronamic_customer->get_name();
901
902
			if ( '' !== $name ) {
903
				$mollie_customer->set_name( $name );
904
			}
905
906
			// Locale.
907
			$locale = $pronamic_customer->get_locale();
908
909
			if ( null !== $locale ) {
910
				$mollie_customer->set_locale( LocaleHelper::transform( $locale ) );
911
			}
912
		}
913
914
		// Try to get name from consumer bank details.
915
		$consumer_bank_details = $payment->get_consumer_bank_details();
916
917
		if ( null === $mollie_customer->get_name() && null !== $consumer_bank_details ) {
918
			$name = $consumer_bank_details->get_name();
919
920
			if ( null !== $name ) {
921
				$mollie_customer->set_name( $name );
922
			}
923
		}
924
925
		// Create customer.
926
		$mollie_customer = $this->client->create_customer( $mollie_customer );
927
928
		$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...
929
930
		// Connect to user.
931
		if ( null !== $pronamic_customer ) {
932
			$user_id = $pronamic_customer->get_user_id();
933
934
			if ( null !== $user_id ) {
935
				$user = \get_user_by( 'id', $user_id );
936
937
				if ( false !== $user ) {
938
					$this->customer_data_store->connect_mollie_customer_to_wp_user( $mollie_customer, $user );
939
				}
940
			}
941
		}
942
943
		// Store customer ID in subscription meta.
944
		$subscription = $payment->get_subscription();
945
946
		if ( null !== $subscription ) {
947
			$subscription->set_meta( 'mollie_customer_id', $mollie_customer->get_id() );
948
		}
949
950
		return $mollie_customer->get_id();
951
	}
952
953
	/**
954
	 * Copy Mollie customer ID from subscription meta to WordPress user meta.
955
	 *
956
	 * @param Payment $payment Payment.
957
	 * @return void
958 27
	 */
959 27
	public function copy_customer_id_to_wp_user( Payment $payment ) {
960 1
		if ( $this->config->id !== $payment->config_id ) {
961
			return;
962
		}
963
964 26
		// Subscription.
965
		$subscription = $payment->get_subscription();
966
967 26
		// Customer.
968
		$customer = $payment->get_customer();
969 26
970 17
		if ( null === $customer && null !== $subscription ) {
971
			$customer = $subscription->get_customer();
972
		}
973 26
974 1
		if ( null === $customer ) {
975
			return;
976
		}
977
978 25
		// WordPress user.
979
		$user_id = $customer->get_user_id();
980 25
981 2
		if ( null === $user_id ) {
982
			return;
983
		}
984 23
985
		$user = \get_user_by( 'id', $user_id );
986 23
987 12
		if ( false === $user ) {
988
			return;
989
		}
990
991 11
		// Customer IDs.
992
		$customer_ids = array();
993
994 11
		// Payment.
995
		$customer_ids[] = $payment->get_meta( 'mollie_customer_id' );
996
997 11
		// Subscription.
998 11
		if ( null !== $subscription ) {
999
			$customer_ids[] = $subscription->get_meta( 'mollie_customer_id' );
1000
		}
1001
1002 11
		// Connect.
1003 11
		$customer_ids = \array_filter( $customer_ids );
1004
		$customer_ids = \array_unique( $customer_ids );
1005 11
1006 2
		foreach ( $customer_ids as $customer_id ) {
1007
			$customer = new Customer( $customer_id );
1008 2
1009
			$this->customer_data_store->get_or_insert_customer( $customer );
1010 2
1011
			$this->customer_data_store->connect_mollie_customer_to_wp_user( $customer, $user );
1012 11
		}
1013
	}
1014
}
1015