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