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