Failed Conditions
Push — develop ( 1980e7...158730 )
by Reüel
05:02
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;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, Pronamic\WordPress\Pay\Gateways\Mollie\Payment. Consider defining an alias.

Let?s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let?s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
20
use Pronamic\WordPress\Pay\Payments\PaymentStatus;
21
use Pronamic\WordPress\Pay\Subscriptions\Subscription;
22
use Pronamic\WordPress\Pay\Subscriptions\SubscriptionStatus;
23
24
/**
25
 * Title: Mollie
26
 * Description:
27
 * Copyright: 2005-2020 Pronamic
28
 * Company: Pronamic
29
 *
30
 * @author  Remco Tolsma
31
 * @version 2.1.4
32
 * @since   1.1.0
33
 */
34
class Gateway extends Core_Gateway {
35
	/**
36
	 * Client.
37
	 *
38
	 * @var Client
39
	 */
40
	protected $client;
41
42
	/**
43
	 * Config
44
	 *
45
	 * @var Config
46
	 */
47
	protected $config;
48
49
	/**
50
	 * Profile data store.
51
	 *
52
	 * @var ProfileDataStore
53
	 */
54
	private $profile_data_store;
55
56
	/**
57
	 * Customer data store.
58
	 *
59
	 * @var CustomerDataStore
60
	 */
61
	private $customer_data_store;
62
63
	/**
64
	 * Constructs and initializes an Mollie gateway
65
	 *
66
	 * @param Config $config Config.
67
	 */
68 39
	public function __construct( Config $config ) {
69 39
		parent::__construct( $config );
70
71 39
		$this->set_method( self::METHOD_HTTP_REDIRECT );
72
73
		// Supported features.
74 39
		$this->supports = array(
75
			'payment_status_request',
76
			'recurring_direct_debit',
77
			'recurring_credit_card',
78
			'recurring',
79
			'webhook',
80
			'webhook_log',
81
			'webhook_no_config',
82
		);
83
84
		// Client.
85 39
		$this->client = new Client( (string) $config->api_key );
86
87
		// Data Stores.
88 39
		$this->profile_data_store  = new ProfileDataStore();
89 39
		$this->customer_data_store = new CustomerDataStore();
90
91
		// Actions.
92 39
		add_action( 'pronamic_payment_status_update', array( $this, 'copy_customer_id_to_wp_user' ), 99, 1 );
93 39
	}
94
95
	/**
96
	 * Get issuers
97
	 *
98
	 * @see Core_Gateway::get_issuers()
99
	 * @return array<int, array<string, array<string>>>
100
	 */
101 3
	public function get_issuers() {
102 3
		$groups = array();
103
104
		try {
105 3
			$result = $this->client->get_issuers();
106
107
			$groups[] = array(
108
				'options' => $result,
109
			);
110 3
		} catch ( Error $e ) {
111
			// Catch Mollie error.
112 3
			$error = new \WP_Error(
113 3
				'mollie_error',
114 3
				sprintf( '%1$s (%2$s) - %3$s', $e->get_title(), $e->getCode(), $e->get_detail() )
115
			);
116
117 3
			$this->set_error( $error );
118
		} catch ( \Exception $e ) {
119
			// Catch exceptions.
120
			$error = new \WP_Error( 'mollie_error', $e->getMessage() );
121
122
			$this->set_error( $error );
123
		}
124
125 3
		return $groups;
126
	}
127
128
	/**
129
	 * Get available payment methods.
130
	 *
131
	 * @see Core_Gateway::get_available_payment_methods()
132
	 * @return array<int, string>
133
	 */
134 2
	public function get_available_payment_methods() {
135 2
		$payment_methods = array();
136
137
		// Set sequence types to get payment methods for.
138 2
		$sequence_types = array( Sequence::ONE_OFF, Sequence::RECURRING, Sequence::FIRST );
139
140 2
		$results = array();
141
142 2
		foreach ( $sequence_types as $sequence_type ) {
143
			// Get active payment methods for Mollie account.
144
			try {
145 2
				$result = $this->client->get_payment_methods( $sequence_type );
146 2
			} catch ( Error $e ) {
147
				// Catch Mollie error.
148
				$error = new \WP_Error(
149
					'mollie_error',
150
					sprintf( '%1$s (%2$s) - %3$s', $e->get_title(), $e->getCode(), $e->get_detail() )
151
				);
152
153
				$this->set_error( $error );
154
155
				break;
156 2
			} catch ( \Exception $e ) {
157
				// Catch exceptions.
158 2
				$error = new \WP_Error( 'mollie_error', $e->getMessage() );
159
160 2
				$this->set_error( $error );
161
162 2
				break;
163
			}
164
165 2
			if ( Sequence::FIRST === $sequence_type ) {
166
				foreach ( $result as $method => $title ) {
167
					unset( $result[ $method ] );
168
169
					// Get WordPress payment method for direct debit method.
170
					$method         = Methods::transform_gateway_method( $method );
171
					$payment_method = array_search( $method, PaymentMethods::get_recurring_methods(), true );
172
173
					if ( $payment_method ) {
174
						$results[ $payment_method ] = $title;
175
					}
176
				}
177
			}
178
179 2
			if ( is_array( $result ) ) {
180 2
				$results = array_merge( $results, $result );
181
			}
182
		}
183
184
		// Transform to WordPress payment methods.
185 2
		foreach ( $results as $method => $title ) {
186 2
			$method = (string) $method;
187
188 2
			$payment_method = Methods::transform_gateway_method( $method );
189
190 2
			if ( PaymentMethods::is_recurring_method( $method ) ) {
191
				$payment_method = $method;
192
			}
193
194 2
			if ( null !== $payment_method ) {
195 2
				$payment_methods[] = (string) $payment_method;
196
			}
197
		}
198
199 2
		$payment_methods = array_unique( $payment_methods );
200
201 2
		return $payment_methods;
202
	}
203
204
	/**
205
	 * Get supported payment methods
206
	 *
207
	 * @see Pronamic_WP_Pay_Gateway::get_supported_payment_methods()
208
	 * @return array<string>
209
	 */
210 2
	public function get_supported_payment_methods() {
211
		return array(
212 2
			PaymentMethods::APPLE_PAY,
213
			PaymentMethods::BANCONTACT,
214
			PaymentMethods::BANK_TRANSFER,
215
			PaymentMethods::BELFIUS,
216
			PaymentMethods::CREDIT_CARD,
217
			PaymentMethods::DIRECT_DEBIT,
218
			PaymentMethods::DIRECT_DEBIT_BANCONTACT,
219
			PaymentMethods::DIRECT_DEBIT_IDEAL,
220
			PaymentMethods::DIRECT_DEBIT_SOFORT,
221
			PaymentMethods::EPS,
222
			PaymentMethods::GIROPAY,
223
			PaymentMethods::IDEAL,
224
			PaymentMethods::KBC,
225
			PaymentMethods::PAYPAL,
226
			PaymentMethods::PRZELEWY24,
227
			PaymentMethods::SOFORT,
228
		);
229
	}
230
231
	/**
232
	 * Get webhook URL for Mollie.
233
	 *
234
	 * @return string|null
235
	 */
236 4
	public function get_webhook_url() {
237 4
		$url = \rest_url( Integration::REST_ROUTE_NAMESPACE . '/webhook' );
238
239 4
		$host = wp_parse_url( $url, PHP_URL_HOST );
240
241 4
		if ( is_array( $host ) ) {
242
			// Parsing failure.
243
			$host = '';
244
		}
245
246 4
		if ( 'localhost' === $host ) {
247
			// Mollie doesn't allow localhost.
248 1
			return null;
249 3
		} elseif ( '.dev' === substr( $host, -4 ) ) {
250
			// Mollie doesn't allow the .dev TLD.
251 1
			return null;
252 2
		} elseif ( '.local' === substr( $host, -6 ) ) {
253
			// Mollie doesn't allow the .local TLD.
254 1
			return null;
255 1
		} elseif ( '.test' === substr( $host, -5 ) ) {
256
			// Mollie doesn't allow the .test TLD.
257
			return null;
258
		}
259
260 1
		return $url;
261
	}
262
263
	/**
264
	 * Start
265
	 *
266
	 * @see Pronamic_WP_Pay_Gateway::start()
267
	 * @param Payment $payment Payment.
268
	 * @return void
269
	 * @throws \Exception Throws exception on error creating Mollie customer for payment.
270
	 */
271
	public function start( Payment $payment ) {
272
		$request = new PaymentRequest(
273
			AmountTransformer::transform( $payment->get_total_amount() ),
274
			(string) $payment->get_description()
275
		);
276
277
		$request->redirect_url = $payment->get_return_url();
278
		$request->webhook_url  = $this->get_webhook_url();
279
280
		// Locale.
281
		$customer = $payment->get_customer();
282
283
		if ( null !== $customer ) {
284
			$request->locale = LocaleHelper::transform( $customer->get_locale() );
285
		}
286
287
		// Customer ID.
288
		$customer_id = $this->get_customer_id_for_payment( $payment );
289
290
		if ( null === $customer_id ) {
291
			$customer_id = $this->create_customer_for_payment( $payment );
292
		}
293
294
		if ( null !== $customer_id ) {
295
			$request->customer_id = $customer_id;
296
		}
297
298
		// Payment method.
299
		$payment_method = $payment->get_method();
300
301
		// Recurring payment method.
302
		$subscription = $payment->get_subscription();
303
304
		$is_recurring_method = ( $subscription && PaymentMethods::is_recurring_method( (string) $payment_method ) );
305
306
		// Consumer bank details.
307
		$consumer_bank_details = $payment->get_consumer_bank_details();
308
309
		if ( PaymentMethods::DIRECT_DEBIT === $payment_method && null !== $consumer_bank_details ) {
310
			$consumer_name = $consumer_bank_details->get_name();
311
			$consumer_iban = $consumer_bank_details->get_iban();
312
313
			$request->consumer_name    = $consumer_name;
314
			$request->consumer_account = $consumer_iban;
315
316
			// Check if one-off SEPA Direct Debit can be used, otherwise short circuit payment.
317
			if ( null !== $customer_id ) {
318
				// Find or create mandate.
319
				$mandate_id = $this->client->has_valid_mandate( $customer_id, PaymentMethods::DIRECT_DEBIT, $consumer_iban );
320
321
				if ( false === $mandate_id ) {
322
					$mandate = $this->client->create_mandate( $customer_id, $consumer_bank_details );
323
324
					if ( ! \property_exists( $mandate, 'id' ) ) {
325
						throw new \Exception( 'Missing mandate ID.' );
326
					}
327
328
					$mandate_id = $mandate->id;
329
				}
330
331
				// Charge immediately on-demand.
332
				$request->set_sequence_type( Sequence::RECURRING );
333
				$request->set_mandate_id( (string) $mandate_id );
334
335
				$is_recurring_method = true;
336
337
				$payment->recurring = true;
338
			}
339
		}
340
341
		if ( false === $is_recurring_method && null !== $payment_method ) {
342
			// Always use 'direct debit mandate via iDEAL/Bancontact/Sofort' payment methods as recurring method.
343
			$is_recurring_method = PaymentMethods::is_direct_debit_method( $payment_method );
344
		}
345
346
		if ( $is_recurring_method ) {
347
			$request->sequence_type = $payment->get_recurring() ? Sequence::RECURRING : Sequence::FIRST;
348
349
			if ( Sequence::FIRST === $request->sequence_type ) {
350
				$payment_method = PaymentMethods::get_first_payment_method( $payment_method );
351
			}
352
353
			if ( Sequence::RECURRING === $request->sequence_type ) {
354
				// Use mandate from subscription.
355
				if ( $subscription && empty( $request->mandate_id ) ) {
356
					$subscription_mandate_id = $subscription->get_meta( 'mollie_mandate_id' );
357
358
					if ( false !== $subscription_mandate_id ) {
359
						$request->set_mandate_id( $subscription_mandate_id );
360
					}
361
				}
362
363
				$payment->set_action_url( $payment->get_return_url() );
364
			}
365
		}
366
367
		// Leap of faith if the WordPress payment method could not transform to a Mollie method?
368
		$request->method = Methods::transform( $payment_method, $payment_method );
369
370
		/**
371
		 * Metadata.
372
		 *
373
		 * Provide any data you like, for example a string or a JSON object.
374
		 * We will save the data alongside the payment. Whenever you fetch
375
		 * the payment with our API, we’ll also include the metadata. You
376
		 * can use up to approximately 1kB.
377
		 *
378
		 * @link https://docs.mollie.com/reference/v2/payments-api/create-payment
379
		 * @link https://en.wikipedia.org/wiki/Metadata
380
		 */
381
		$metadata = null;
382
383
		/**
384
		 * Filters the Mollie metadata.
385
		 *
386
		 * @since 2.2.0
387
		 *
388
		 * @param mixed   $metadata Metadata.
389
		 * @param Payment $payment  Payment.
390
		 */
391
		$metadata = \apply_filters( 'pronamic_pay_mollie_payment_metadata', $metadata, $payment );
392
393
		$request->set_metadata( $metadata );
394
395
		// Issuer.
396
		if ( Methods::IDEAL === $request->method ) {
397
			$request->issuer = $payment->get_issuer();
398
		}
399
400
		// Billing email.
401
		$billing_email = $payment->get_email();
402
403
		/**
404
		 * Filters the Mollie payment billing email used for bank transfer payment instructions.
405
		 *
406
		 * @since 2.2.0
407
		 *
408
		 * @param string|null $billing_email Billing email.
409
		 * @param Payment     $payment       Payment.
410
		 */
411
		$billing_email = \apply_filters( 'pronamic_pay_mollie_payment_billing_email', $billing_email, $payment );
412
413
		$request->set_billing_email( $billing_email );
414
415
		// Due date.
416
		if ( ! empty( $this->config->due_date_days ) ) {
417
			try {
418
				$due_date = new DateTime( sprintf( '+%s days', $this->config->due_date_days ) );
419
			} catch ( \Exception $e ) {
420
				$due_date = null;
421
			}
422
423
			$request->set_due_date( $due_date );
424
		}
425
426
		// Create payment.
427
		$result = $this->client->create_payment( $request );
428
429
		// phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase -- Mollie JSON object.
430
431
		// Set transaction ID.
432
		if ( isset( $result->id ) ) {
433
			$payment->set_transaction_id( $result->id );
434
		}
435
436
		// Set expiry date.
437
		if ( isset( $result->expiresAt ) ) {
438
			try {
439
				$expires_at = new DateTime( $result->expiresAt );
440
			} catch ( \Exception $e ) {
441
				$expires_at = null;
442
			}
443
444
			$payment->set_expiry_date( $expires_at );
445
		}
446
447
		// Set status.
448
		if ( isset( $result->status ) ) {
449
			$payment->set_status( Statuses::transform( $result->status ) );
450
		}
451
452
		// Set bank transfer recipient details.
453
		if ( isset( $result->details ) ) {
454
			$bank_transfer_recipient_details = $payment->get_bank_transfer_recipient_details();
455
456
			if ( null === $bank_transfer_recipient_details ) {
457
				$bank_transfer_recipient_details = new BankTransferDetails();
458
459
				$payment->set_bank_transfer_recipient_details( $bank_transfer_recipient_details );
460
			}
461
462
			$bank_details = $bank_transfer_recipient_details->get_bank_account();
463
464
			if ( null === $bank_details ) {
465
				$bank_details = new BankAccountDetails();
466
467
				$bank_transfer_recipient_details->set_bank_account( $bank_details );
468
			}
469
470
			$details = $result->details;
471
472
			if ( isset( $details->bankName ) ) {
473
				/**
474
				 * Set `bankName` as bank details name, as result "Stichting Mollie Payments"
475
				 * is not the name of a bank, but the account holder name.
476
				 */
477
				$bank_details->set_name( $details->bankName );
478
			}
479
480
			if ( isset( $details->bankAccount ) ) {
481
				$bank_details->set_iban( $details->bankAccount );
482
			}
483
484
			if ( isset( $details->bankBic ) ) {
485
				$bank_details->set_bic( $details->bankBic );
486
			}
487
488
			if ( isset( $details->transferReference ) ) {
489
				$bank_transfer_recipient_details->set_reference( $details->transferReference );
490
			}
491
		}
492
493
		// Handle links.
494
		if ( isset( $result->_links ) ) {
495
			$links = $result->_links;
496
497
			// Action URL.
498
			if ( isset( $links->checkout->href ) ) {
499
				$payment->set_action_url( $links->checkout->href );
500
			}
501
502
			// Change payment state URL.
503
			if ( isset( $links->changePaymentState->href ) ) {
504
				$payment->set_meta( 'mollie_change_payment_state_url', $links->changePaymentState->href );
505
			}
506
		}
507
508
		// phpcs:enable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase -- Mollie JSON object.
509
	}
510
511
	/**
512
	 * Update status of the specified payment
513
	 *
514
	 * @param Payment $payment Payment.
515
	 * @return void
516
	 */
517
	public function update_status( Payment $payment ) {
518
		$transaction_id = $payment->get_transaction_id();
519
520
		if ( null === $transaction_id ) {
521
			return;
522
		}
523
524
		$mollie_payment = $this->client->get_payment( $transaction_id );
525
526
		$payment->set_status( Statuses::transform( $mollie_payment->get_status() ) );
527
528
		/**
529
		 * Mollie profile.
530
		 */
531
		$mollie_profile = new Profile();
532
533
		$mollie_profile->set_id( $mollie_payment->get_profile_id() );
534
535
		$profile_internal_id = $this->profile_data_store->get_or_insert_profile( $mollie_profile );
536
537
		/**
538
		 * If the Mollie payment contains a customer ID we will try to connect
539
		 * this Mollie customer ID the WordPress user and subscription.
540
		 * This can be usefull in case when a WordPress user is created after
541
		 * a succesfull payment.
542
		 *
543
		 * @link https://www.gravityforms.com/add-ons/user-registration/
544
		 */
545
		$mollie_customer_id = $mollie_payment->get_customer_id();
546
547
		if ( null !== $mollie_customer_id ) {
548
			$mollie_customer = new Customer( $mollie_customer_id );
549
550
			$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...
551
				$mollie_customer,
552
				array(
553
					'profile_id' => $profile_internal_id,
554
				),
555
				array(
556
					'profile_id' => '%s',
557
				)
558
			);
559
560
			// Meta.
561
			$customer_id = $payment->get_meta( 'mollie_customer_id' );
562
563
			if ( empty( $customer_id ) ) {
564
				$payment->set_meta( 'mollie_customer_id', $mollie_customer->get_id() );
565
			}
566
567
			// Customer.
568
			$customer = $payment->get_customer();
569
570
			if ( null !== $customer ) {
571
				// Connect to user.
572
				$user_id = $customer->get_user_id();
573
574
				if ( null !== $user_id ) {
575
					$user = \get_user_by( 'id', $user_id );
576
577
					if ( false !== $user ) {
578
						$this->customer_data_store->connect_mollie_customer_to_wp_user( $mollie_customer, $user );
579
					}
580
				}
581
			}
582
583
			// Subscription.
584
			$subscription = $payment->get_subscription();
585
586
			if ( null !== $subscription ) {
587
				$customer_id = $subscription->get_meta( 'mollie_customer_id' );
588
589
				if ( empty( $customer_id ) ) {
590
					$subscription->set_meta( 'mollie_customer_id', $mollie_customer->get_id() );
591
				}
592
593
				// Update mandate in subscription meta.
594
				$mollie_mandate_id = $mollie_payment->get_mandate_id();
595
596
				if ( null !== $mollie_mandate_id ) {
597
					$mandate_id = $subscription->get_meta( 'mollie_mandate_id' );
598
599
					// Only update if no mandate has been set yet or if payment succeeded.
600
					if ( empty( $mandate_id ) || PaymentStatus::SUCCESS === $payment->get_status() ) {
601
						$this->update_subscription_mandate( $subscription, $mollie_mandate_id, $payment->get_method() );
602
					}
603
				}
604
			}
605
		}
606
607
		// phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase -- Mollie JSON object.
608
		$mollie_payment_details = $mollie_payment->get_details();
609
610
		if ( null !== $mollie_payment_details ) {
611
			$consumer_bank_details = $payment->get_consumer_bank_details();
612
613
			if ( null === $consumer_bank_details ) {
614
				$consumer_bank_details = new BankAccountDetails();
615
616
				$payment->set_consumer_bank_details( $consumer_bank_details );
617
			}
618
619
			if ( isset( $mollie_payment_details->consumerName ) ) {
620
				$consumer_bank_details->set_name( $mollie_payment_details->consumerName );
621
			}
622
623
			if ( isset( $mollie_payment_details->cardHolder ) ) {
624
				$consumer_bank_details->set_name( $mollie_payment_details->cardHolder );
625
			}
626
627
			if ( isset( $mollie_payment_details->cardNumber ) ) {
628
				// The last four digits of the card number.
629
				$consumer_bank_details->set_account_number( $mollie_payment_details->cardNumber );
630
			}
631
632
			if ( isset( $mollie_payment_details->cardCountryCode ) ) {
633
				// The ISO 3166-1 alpha-2 country code of the country the card was issued in.
634
				$consumer_bank_details->set_country( $mollie_payment_details->cardCountryCode );
635
			}
636
637
			if ( isset( $mollie_payment_details->consumerAccount ) ) {
638
				switch ( $mollie_payment->get_method() ) {
639
					case Methods::BELFIUS:
640
					case Methods::DIRECT_DEBIT:
641
					case Methods::IDEAL:
642
					case Methods::KBC:
643
					case Methods::SOFORT:
644
						$consumer_bank_details->set_iban( $mollie_payment_details->consumerAccount );
645
646
						break;
647
					case Methods::BANCONTACT:
648
					case Methods::BANKTRANSFER:
649
					case Methods::PAYPAL:
650
					default:
651
						$consumer_bank_details->set_account_number( $mollie_payment_details->consumerAccount );
652
653
						break;
654
				}
655
			}
656
657
			if ( isset( $mollie_payment_details->consumerBic ) ) {
658
				$consumer_bank_details->set_bic( $mollie_payment_details->consumerBic );
659
			}
660
661
			/*
662
			 * Failure reason.
663
			 */
664
			$failure_reason = $payment->get_failure_reason();
665
666
			if ( null === $failure_reason ) {
667
				$failure_reason = new FailureReason();
668
669
				$payment->set_failure_reason( $failure_reason );
670
			}
671
672
			// SEPA Direct Debit.
673
			if ( isset( $mollie_payment_details->bankReasonCode ) ) {
674
				$failure_reason->set_code( $mollie_payment_details->bankReasonCode );
675
			}
676
677
			if ( isset( $mollie_payment_details->bankReason ) ) {
678
				$failure_reason->set_message( $mollie_payment_details->bankReason );
679
			}
680
681
			// Credit card.
682
			if ( isset( $mollie_payment_details->failureReason ) ) {
683
				$failure_reason->set_code( $mollie_payment_details->failureReason );
684
			}
685
686
			if ( isset( $mollie_payment_details->failureMessage ) ) {
687
				$failure_reason->set_message( $mollie_payment_details->failureMessage );
688
			}
689
		}
690
691
		if ( isset( $mollie_payment->_links ) ) {
0 ignored issues
show
Bug introduced by
The property _links does not exist on Pronamic\WordPress\Pay\Gateways\Mollie\Payment. Did you mean links?
Loading history...
692
			$links = $mollie_payment->_links;
693
694
			// Change payment state URL.
695
			if ( isset( $links->changePaymentState->href ) ) {
696
				$payment->set_meta( 'mollie_change_payment_state_url', $links->changePaymentState->href );
697
			}
698
		}
699
700
		// phpcs:enable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase -- Mollie JSON object.
701
702
		if ( $mollie_payment->has_chargebacks() ) {
703
			$mollie_chargebacks = $this->client->get_payment_chargebacks(
704
				$mollie_payment->get_id(),
705
				array( 'limit' => 1 )
706
			);
707
708
			$mollie_chargeback = \reset( $mollie_chargebacks );
709
710
			if ( false !== $mollie_chargeback ) {
711
				$subscriptions = array_filter(
712
					$payment->get_subscriptions(),
713
					function( $subscription ) {
714
						return SubscriptionStatus::ACTIVE === $subscription->get_status();
715
					}
716
				);
717
718
				foreach ( $subscriptions as $subscription ) {
719
					if ( $mollie_chargeback->get_created_at() > $subscription->get_activated_at() ) {
720
						$subscription->set_status( SubscriptionStatus::ON_HOLD );
721
722
						$subscription->add_note(
723
							\sprintf(
724
								/* translators: 1: Mollie chargeback ID, 2: Mollie payment ID */
725
								\__( 'Subscription put on hold due to chargeback `%1$s` of payment `%2$s`.', 'pronamic_ideal' ),
726
								\esc_html( $mollie_chargeback->get_id() ),
727
								\esc_html( $mollie_payment->get_id() )
728
							)
729
						);
730
731
						$subscription->save();
732
					}
733
				}
734
			}
735
		}
736
	}
737
738
	/**
739
	 * Update subscription mandate.
740
	 *
741
	 * @param Subscription $subscription   Subscription.
742
	 * @param string       $mandate_id     Mollie mandate ID.
743
	 * @param string|null  $payment_method Payment method.
744
	 * @return void
745
	 * @throws \Exception Throws exception if subscription note could not be added.
746
	 */
747
	public function update_subscription_mandate( Subscription $subscription, $mandate_id, $payment_method = null ) {
748
		$customer_id = (string) $subscription->get_meta( 'mollie_customer_id' );
749
750
		$mandate = $this->client->get_mandate( $mandate_id, $customer_id );
751
752
		if ( ! \is_object( $mandate ) ) {
753
			return;
754
		}
755
756
		// Update mandate.
757
		$old_mandate_id = $subscription->get_meta( 'mollie_mandate_id' );
758
759
		$subscription->set_meta( 'mollie_mandate_id', $mandate_id );
760
761
		if ( ! empty( $old_mandate_id ) && $old_mandate_id !== $mandate_id ) {
762
			$note = \sprintf(
763
			/* translators: 1: old mandate ID, 2: new mandate ID */
764
				\__( 'Mandate for subscription changed from "%1$s" to "%2$s".', 'pronamic_ideal' ),
765
				\esc_html( $old_mandate_id ),
766
				\esc_html( $mandate_id )
767
			);
768
769
			$subscription->add_note( $note );
770
		}
771
772
		// Update payment method.
773
		$old_method = $subscription->payment_method;
774
		$new_method = ( null === $payment_method && \property_exists( $mandate, 'method' ) ? Methods::transform_gateway_method( $mandate->method ) : $payment_method );
775
776
		// `Direct Debit` is not a recurring method, use `Direct Debit (mandate via ...)` instead.
777
		if ( PaymentMethods::DIRECT_DEBIT === $new_method ) {
778
			$new_method = PaymentMethods::DIRECT_DEBIT_IDEAL;
779
780
			// Use `Direct Debit (mandate via Bancontact)` if consumer account starts with `BE`.
781
			if ( \property_exists( $mandate, 'details' ) && 'BE' === \substr( $mandate->details->consumerAccount, 0, 2 ) ) {
782
				$new_method = PaymentMethods::DIRECT_DEBIT_BANCONTACT;
783
			}
784
		}
785
786
		if ( ! empty( $old_method ) && $old_method !== $new_method ) {
787
			$subscription->payment_method = $new_method;
788
789
			// Add note.
790
			$note = \sprintf(
791
				/* translators: 1: old payment method, 2: new payment method */
792
				\__( 'Payment method for subscription changed from "%1$s" to "%2$s".', 'pronamic_ideal' ),
793
				\esc_html( (string) PaymentMethods::get_name( $old_method ) ),
794
				\esc_html( (string) PaymentMethods::get_name( $new_method ) )
795
			);
796
797
			$subscription->add_note( $note );
798
		}
799
800
		$subscription->save();
801
	}
802
803
	/**
804
	 * Get Mollie customer ID for payment.
805
	 *
806
	 * @param Payment $payment Payment.
807
	 * @return string|null
808
	 */
809 10
	public function get_customer_id_for_payment( Payment $payment ) {
810 10
		$customer_ids = $this->get_customer_ids_for_payment( $payment );
811
812 10
		$customer_id = $this->get_first_existing_customer_id( $customer_ids );
813
814 10
		return $customer_id;
815
	}
816
817
	/**
818
	 * Get Mollie customers for the specified payment.
819
	 *
820
	 * @param Payment $payment Payment.
821
	 * @return array<string>
822
	 */
823 10
	private function get_customer_ids_for_payment( Payment $payment ) {
824 10
		$customer_ids = array();
825
826
		// Customer ID from subscription meta.
827 10
		$subscription = $payment->get_subscription();
828
829 10
		if ( null !== $subscription ) {
830 10
			$customer_id = $this->get_customer_id_for_subscription( $subscription );
831
832 10
			if ( null !== $customer_id ) {
833 4
				$customer_ids[] = $customer_id;
834
			}
835
		}
836
837
		// Customer ID from WordPress user.
838 10
		$customer = $payment->get_customer();
839
840 10
		if ( null !== $customer ) {
841 10
			$user_id = $customer->get_user_id();
842
843 10
			if ( ! empty( $user_id ) ) {
844 7
				$user_customer_ids = $this->get_customer_ids_for_user( $user_id );
845
846 7
				$customer_ids = \array_merge( $customer_ids, $user_customer_ids );
847
			}
848
		}
849
850 10
		return $customer_ids;
851
	}
852
853
	/**
854
	 * Get Mollie customers for the specified WordPress user ID.
855
	 *
856
	 * @param int $user_id WordPress user ID.
857
	 * @return array<string>
858
	 */
859 24
	public function get_customer_ids_for_user( $user_id ) {
860 24
		$customer_query = new CustomerQuery(
861
			array(
862 24
				'user_id' => $user_id,
863
			)
864
		);
865
866 24
		$customers = $customer_query->get_customers();
867
868 24
		$customer_ids = wp_list_pluck( $customers, 'mollie_id' );
869
870 24
		return $customer_ids;
871
	}
872
873
	/**
874
	 * Get customer ID for subscription.
875
	 *
876
	 * @param Subscription $subscription Subscription.
877
	 * @return string|null
878
	 */
879 10
	private function get_customer_id_for_subscription( Subscription $subscription ) {
880 10
		$customer_id = $subscription->get_meta( 'mollie_customer_id' );
881
882 10
		if ( empty( $customer_id ) ) {
883
			// Try to get (legacy) customer ID from first payment.
884 7
			$first_payment = $subscription->get_first_payment();
885
886 7
			if ( null !== $first_payment ) {
887 7
				$customer_id = $first_payment->get_meta( 'mollie_customer_id' );
888
			}
889
		}
890
891 10
		if ( empty( $customer_id ) ) {
892 6
			return null;
893
		}
894
895 4
		return $customer_id;
896
	}
897
898
	/**
899
	 * Get first existing customer from customers list.
900
	 *
901
	 * @param array<string> $customer_ids Customers.
902
	 * @return string|null
903
	 * @throws Error Throws error on Mollie error.
904
	 */
905 10
	private function get_first_existing_customer_id( $customer_ids ) {
906 10
		$customer_ids = \array_filter( $customer_ids );
907
908 10
		$customer_ids = \array_unique( $customer_ids );
909
910 10
		foreach ( $customer_ids as $customer_id ) {
911
			try {
912 4
				$customer = $this->client->get_customer( $customer_id );
913
			} catch ( Error $error ) {
914
				// Check for status 410 ("Gone - The customer is no longer available").
915
				if ( 410 === $error->get_status() ) {
916
					continue;
917
				}
918
919
				throw $error;
920
			}
921
922 4
			if ( null !== $customer ) {
923 4
				return $customer_id;
924
			}
925
		}
926
927 6
		return null;
928
	}
929
930
	/**
931
	 * Create customer for payment.
932
	 *
933
	 * @param Payment $payment Payment.
934
	 * @return string|null
935
	 * @throws Error Throws Error when Mollie error occurs.
936
	 * @throws \Exception Throws exception when error in customer data store occurs.
937
	 */
938
	private function create_customer_for_payment( Payment $payment ) {
939
		$mollie_customer = new Customer();
940
		$mollie_customer->set_mode( $this->config->is_test_mode() ? 'test' : 'live' );
941
		$mollie_customer->set_email( $payment->get_email() );
942
943
		$pronamic_customer = $payment->get_customer();
944
945
		if ( null !== $pronamic_customer ) {
946
			// Name.
947
			$name = (string) $pronamic_customer->get_name();
948
949
			if ( '' !== $name ) {
950
				$mollie_customer->set_name( $name );
951
			}
952
953
			// Locale.
954
			$locale = $pronamic_customer->get_locale();
955
956
			if ( null !== $locale ) {
957
				$mollie_customer->set_locale( LocaleHelper::transform( $locale ) );
958
			}
959
		}
960
961
		// Try to get name from consumer bank details.
962
		$consumer_bank_details = $payment->get_consumer_bank_details();
963
964
		if ( null === $mollie_customer->get_name() && null !== $consumer_bank_details ) {
965
			$name = $consumer_bank_details->get_name();
966
967
			if ( null !== $name ) {
968
				$mollie_customer->set_name( $name );
969
			}
970
		}
971
972
		// Create customer.
973
		$mollie_customer = $this->client->create_customer( $mollie_customer );
974
975
		$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...
976
977
		// Connect to user.
978
		if ( null !== $pronamic_customer ) {
979
			$user_id = $pronamic_customer->get_user_id();
980
981
			if ( null !== $user_id ) {
982
				$user = \get_user_by( 'id', $user_id );
983
984
				if ( false !== $user ) {
985
					$this->customer_data_store->connect_mollie_customer_to_wp_user( $mollie_customer, $user );
986
				}
987
			}
988
		}
989
990
		// Store customer ID in subscription meta.
991
		$subscription = $payment->get_subscription();
992
993
		if ( null !== $subscription ) {
994
			$subscription->set_meta( 'mollie_customer_id', $mollie_customer->get_id() );
995
		}
996
997
		return $mollie_customer->get_id();
998
	}
999
1000
	/**
1001
	 * Copy Mollie customer ID from subscription meta to WordPress user meta.
1002
	 *
1003
	 * @param Payment $payment Payment.
1004
	 * @return void
1005
	 */
1006 27
	public function copy_customer_id_to_wp_user( Payment $payment ) {
1007 27
		if ( $this->config->id !== $payment->config_id ) {
1008 1
			return;
1009
		}
1010
1011
		// Subscription.
1012 26
		$subscription = $payment->get_subscription();
1013
1014
		// Customer.
1015 26
		$customer = $payment->get_customer();
1016
1017 26
		if ( null === $customer && null !== $subscription ) {
1018 16
			$customer = $subscription->get_customer();
1019
		}
1020
1021 26
		if ( null === $customer ) {
1022
			return;
1023
		}
1024
1025
		// WordPress user.
1026 26
		$user_id = $customer->get_user_id();
1027
1028 26
		if ( null === $user_id ) {
1029 3
			return;
1030
		}
1031
1032 23
		$user = \get_user_by( 'id', $user_id );
1033
1034 23
		if ( false === $user ) {
1035 12
			return;
1036
		}
1037
1038
		// Customer IDs.
1039 11
		$customer_ids = array();
1040
1041
		// Payment.
1042 11
		$customer_ids[] = $payment->get_meta( 'mollie_customer_id' );
1043
1044
		// Subscription.
1045 11
		if ( null !== $subscription ) {
1046 11
			$customer_ids[] = $subscription->get_meta( 'mollie_customer_id' );
1047
		}
1048
1049
		// Connect.
1050 11
		$customer_ids = \array_filter( $customer_ids );
1051 11
		$customer_ids = \array_unique( $customer_ids );
1052
1053 11
		foreach ( $customer_ids as $customer_id ) {
1054 2
			$customer = new Customer( $customer_id );
1055
1056 2
			$this->customer_data_store->get_or_insert_customer( $customer );
1057
1058 2
			$this->customer_data_store->connect_mollie_customer_to_wp_user( $customer, $user );
1059
		}
1060 11
	}
1061
}
1062