Failed Conditions
Push — develop ( 158730...90e575 )
by Reüel
04:11
created

Gateway::copy_customer_id_to_wp_user()   B

Complexity

Conditions 9
Paths 15

Size

Total Lines 53
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 24
CRAP Score 9.0051

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 9
eloc 24
c 3
b 0
f 0
nc 15
nop 1
dl 0
loc 53
ccs 24
cts 25
cp 0.96
crap 9.0051
rs 8.0555

How to fix   Long Method   

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
		$links = $mollie_payment->get_links();
692
693
		// Change payment state URL.
694
		if ( isset( $links->changePaymentState->href ) ) {
695
			$payment->set_meta( 'mollie_change_payment_state_url', $links->changePaymentState->href );
696
		}
697
698
		// phpcs:enable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase -- Mollie JSON object.
699
700
		if ( $mollie_payment->has_chargebacks() ) {
701
			$mollie_chargebacks = $this->client->get_payment_chargebacks(
702
				$mollie_payment->get_id(),
703
				array( 'limit' => 1 )
704
			);
705
706
			$mollie_chargeback = \reset( $mollie_chargebacks );
707
708
			if ( false !== $mollie_chargeback ) {
709
				$subscriptions = array_filter(
710
					$payment->get_subscriptions(),
711
					function( $subscription ) {
712
						return SubscriptionStatus::ACTIVE === $subscription->get_status();
713
					}
714
				);
715
716
				foreach ( $subscriptions as $subscription ) {
717
					if ( $mollie_chargeback->get_created_at() > $subscription->get_activated_at() ) {
718
						$subscription->set_status( SubscriptionStatus::ON_HOLD );
719
720
						$subscription->add_note(
721
							\sprintf(
722
								/* translators: 1: Mollie chargeback ID, 2: Mollie payment ID */
723
								\__( 'Subscription put on hold due to chargeback `%1$s` of payment `%2$s`.', 'pronamic_ideal' ),
724
								\esc_html( $mollie_chargeback->get_id() ),
725
								\esc_html( $mollie_payment->get_id() )
726
							)
727
						);
728
729
						$subscription->save();
730
					}
731
				}
732
			}
733
		}
734
	}
735
736
	/**
737
	 * Update subscription mandate.
738
	 *
739
	 * @param Subscription $subscription   Subscription.
740
	 * @param string       $mandate_id     Mollie mandate ID.
741
	 * @param string|null  $payment_method Payment method.
742
	 * @return void
743
	 * @throws \Exception Throws exception if subscription note could not be added.
744
	 */
745
	public function update_subscription_mandate( Subscription $subscription, $mandate_id, $payment_method = null ) {
746
		$customer_id = (string) $subscription->get_meta( 'mollie_customer_id' );
747
748
		$mandate = $this->client->get_mandate( $mandate_id, $customer_id );
749
750
		if ( ! \is_object( $mandate ) ) {
751
			return;
752
		}
753
754
		// Update mandate.
755
		$old_mandate_id = $subscription->get_meta( 'mollie_mandate_id' );
756
757
		$subscription->set_meta( 'mollie_mandate_id', $mandate_id );
758
759
		if ( ! empty( $old_mandate_id ) && $old_mandate_id !== $mandate_id ) {
760
			$note = \sprintf(
761
			/* translators: 1: old mandate ID, 2: new mandate ID */
762
				\__( 'Mandate for subscription changed from "%1$s" to "%2$s".', 'pronamic_ideal' ),
763
				\esc_html( $old_mandate_id ),
764
				\esc_html( $mandate_id )
765
			);
766
767
			$subscription->add_note( $note );
768
		}
769
770
		// Update payment method.
771
		$old_method = $subscription->payment_method;
772
		$new_method = ( null === $payment_method && \property_exists( $mandate, 'method' ) ? Methods::transform_gateway_method( $mandate->method ) : $payment_method );
773
774
		// `Direct Debit` is not a recurring method, use `Direct Debit (mandate via ...)` instead.
775
		if ( PaymentMethods::DIRECT_DEBIT === $new_method ) {
776
			$new_method = PaymentMethods::DIRECT_DEBIT_IDEAL;
777
778
			// Use `Direct Debit (mandate via Bancontact)` if consumer account starts with `BE`.
779
			if ( \property_exists( $mandate, 'details' ) && 'BE' === \substr( $mandate->details->consumerAccount, 0, 2 ) ) {
780
				$new_method = PaymentMethods::DIRECT_DEBIT_BANCONTACT;
781
			}
782
		}
783
784
		if ( ! empty( $old_method ) && $old_method !== $new_method ) {
785
			$subscription->payment_method = $new_method;
786
787
			// Add note.
788
			$note = \sprintf(
789
				/* translators: 1: old payment method, 2: new payment method */
790
				\__( 'Payment method for subscription changed from "%1$s" to "%2$s".', 'pronamic_ideal' ),
791
				\esc_html( (string) PaymentMethods::get_name( $old_method ) ),
792
				\esc_html( (string) PaymentMethods::get_name( $new_method ) )
793
			);
794
795
			$subscription->add_note( $note );
796
		}
797
798
		$subscription->save();
799
	}
800
801
	/**
802
	 * Get Mollie customer ID for payment.
803
	 *
804
	 * @param Payment $payment Payment.
805
	 * @return string|null
806
	 */
807 10
	public function get_customer_id_for_payment( Payment $payment ) {
808 10
		$customer_ids = $this->get_customer_ids_for_payment( $payment );
809
810 10
		$customer_id = $this->get_first_existing_customer_id( $customer_ids );
811
812 10
		return $customer_id;
813
	}
814
815
	/**
816
	 * Get Mollie customers for the specified payment.
817
	 *
818
	 * @param Payment $payment Payment.
819
	 * @return array<string>
820
	 */
821 10
	private function get_customer_ids_for_payment( Payment $payment ) {
822 10
		$customer_ids = array();
823
824
		// Customer ID from subscription meta.
825 10
		$subscription = $payment->get_subscription();
826
827 10
		if ( null !== $subscription ) {
828 10
			$customer_id = $this->get_customer_id_for_subscription( $subscription );
829
830 10
			if ( null !== $customer_id ) {
831 4
				$customer_ids[] = $customer_id;
832
			}
833
		}
834
835
		// Customer ID from WordPress user.
836 10
		$customer = $payment->get_customer();
837
838 10
		if ( null !== $customer ) {
839 10
			$user_id = $customer->get_user_id();
840
841 10
			if ( ! empty( $user_id ) ) {
842 7
				$user_customer_ids = $this->get_customer_ids_for_user( $user_id );
843
844 7
				$customer_ids = \array_merge( $customer_ids, $user_customer_ids );
845
			}
846
		}
847
848 10
		return $customer_ids;
849
	}
850
851
	/**
852
	 * Get Mollie customers for the specified WordPress user ID.
853
	 *
854
	 * @param int $user_id WordPress user ID.
855
	 * @return array<string>
856
	 */
857 24
	public function get_customer_ids_for_user( $user_id ) {
858 24
		$customer_query = new CustomerQuery(
859
			array(
860 24
				'user_id' => $user_id,
861
			)
862
		);
863
864 24
		$customers = $customer_query->get_customers();
865
866 24
		$customer_ids = wp_list_pluck( $customers, 'mollie_id' );
867
868 24
		return $customer_ids;
869
	}
870
871
	/**
872
	 * Get customer ID for subscription.
873
	 *
874
	 * @param Subscription $subscription Subscription.
875
	 * @return string|null
876
	 */
877 10
	private function get_customer_id_for_subscription( Subscription $subscription ) {
878 10
		$customer_id = $subscription->get_meta( 'mollie_customer_id' );
879
880 10
		if ( empty( $customer_id ) ) {
881
			// Try to get (legacy) customer ID from first payment.
882 7
			$first_payment = $subscription->get_first_payment();
883
884 7
			if ( null !== $first_payment ) {
885 7
				$customer_id = $first_payment->get_meta( 'mollie_customer_id' );
886
			}
887
		}
888
889 10
		if ( empty( $customer_id ) ) {
890 6
			return null;
891
		}
892
893 4
		return $customer_id;
894
	}
895
896
	/**
897
	 * Get first existing customer from customers list.
898
	 *
899
	 * @param array<string> $customer_ids Customers.
900
	 * @return string|null
901
	 * @throws Error Throws error on Mollie error.
902
	 */
903 10
	private function get_first_existing_customer_id( $customer_ids ) {
904 10
		$customer_ids = \array_filter( $customer_ids );
905
906 10
		$customer_ids = \array_unique( $customer_ids );
907
908 10
		foreach ( $customer_ids as $customer_id ) {
909
			try {
910 4
				$customer = $this->client->get_customer( $customer_id );
911
			} catch ( Error $error ) {
912
				// Check for status 410 ("Gone - The customer is no longer available").
913
				if ( 410 === $error->get_status() ) {
914
					continue;
915
				}
916
917
				throw $error;
918
			}
919
920 4
			if ( null !== $customer ) {
921 4
				return $customer_id;
922
			}
923
		}
924
925 6
		return null;
926
	}
927
928
	/**
929
	 * Create customer for payment.
930
	 *
931
	 * @param Payment $payment Payment.
932
	 * @return string|null
933
	 * @throws Error Throws Error when Mollie error occurs.
934
	 * @throws \Exception Throws exception when error in customer data store occurs.
935
	 */
936
	private function create_customer_for_payment( Payment $payment ) {
937
		$mollie_customer = new Customer();
938
		$mollie_customer->set_mode( $this->config->is_test_mode() ? 'test' : 'live' );
939
		$mollie_customer->set_email( $payment->get_email() );
940
941
		$pronamic_customer = $payment->get_customer();
942
943
		if ( null !== $pronamic_customer ) {
944
			// Name.
945
			$name = (string) $pronamic_customer->get_name();
946
947
			if ( '' !== $name ) {
948
				$mollie_customer->set_name( $name );
949
			}
950
951
			// Locale.
952
			$locale = $pronamic_customer->get_locale();
953
954
			if ( null !== $locale ) {
955
				$mollie_customer->set_locale( LocaleHelper::transform( $locale ) );
956
			}
957
		}
958
959
		// Try to get name from consumer bank details.
960
		$consumer_bank_details = $payment->get_consumer_bank_details();
961
962
		if ( null === $mollie_customer->get_name() && null !== $consumer_bank_details ) {
963
			$name = $consumer_bank_details->get_name();
964
965
			if ( null !== $name ) {
966
				$mollie_customer->set_name( $name );
967
			}
968
		}
969
970
		// Create customer.
971
		$mollie_customer = $this->client->create_customer( $mollie_customer );
972
973
		$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...
974
975
		// Connect to user.
976
		if ( null !== $pronamic_customer ) {
977
			$user_id = $pronamic_customer->get_user_id();
978
979
			if ( null !== $user_id ) {
980
				$user = \get_user_by( 'id', $user_id );
981
982
				if ( false !== $user ) {
983
					$this->customer_data_store->connect_mollie_customer_to_wp_user( $mollie_customer, $user );
984
				}
985
			}
986
		}
987
988
		// Store customer ID in subscription meta.
989
		$subscription = $payment->get_subscription();
990
991
		if ( null !== $subscription ) {
992
			$subscription->set_meta( 'mollie_customer_id', $mollie_customer->get_id() );
993
		}
994
995
		return $mollie_customer->get_id();
996
	}
997
998
	/**
999
	 * Copy Mollie customer ID from subscription meta to WordPress user meta.
1000
	 *
1001
	 * @param Payment $payment Payment.
1002
	 * @return void
1003
	 */
1004 27
	public function copy_customer_id_to_wp_user( Payment $payment ) {
1005 27
		if ( $this->config->id !== $payment->config_id ) {
1006 1
			return;
1007
		}
1008
1009
		// Subscription.
1010 26
		$subscription = $payment->get_subscription();
1011
1012
		// Customer.
1013 26
		$customer = $payment->get_customer();
1014
1015 26
		if ( null === $customer && null !== $subscription ) {
1016 16
			$customer = $subscription->get_customer();
1017
		}
1018
1019 26
		if ( null === $customer ) {
1020
			return;
1021
		}
1022
1023
		// WordPress user.
1024 26
		$user_id = $customer->get_user_id();
1025
1026 26
		if ( null === $user_id ) {
1027 3
			return;
1028
		}
1029
1030 23
		$user = \get_user_by( 'id', $user_id );
1031
1032 23
		if ( false === $user ) {
1033 12
			return;
1034
		}
1035
1036
		// Customer IDs.
1037 11
		$customer_ids = array();
1038
1039
		// Payment.
1040 11
		$customer_ids[] = $payment->get_meta( 'mollie_customer_id' );
1041
1042
		// Subscription.
1043 11
		if ( null !== $subscription ) {
1044 11
			$customer_ids[] = $subscription->get_meta( 'mollie_customer_id' );
1045
		}
1046
1047
		// Connect.
1048 11
		$customer_ids = \array_filter( $customer_ids );
1049 11
		$customer_ids = \array_unique( $customer_ids );
1050
1051 11
		foreach ( $customer_ids as $customer_id ) {
1052 2
			$customer = new Customer( $customer_id );
1053
1054 2
			$this->customer_data_store->get_or_insert_customer( $customer );
1055
1056 2
			$this->customer_data_store->connect_mollie_customer_to_wp_user( $customer, $user );
1057
		}
1058 11
	}
1059
}
1060