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