Failed Conditions
Push — develop ( 29c8a0...4577f9 )
by Reüel
05:02
created

Gateway::get_available_payment_methods()   B

Complexity

Conditions 11
Paths 35

Size

Total Lines 68
Code Lines 34

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 22
CRAP Score 16.3179

Importance

Changes 6
Bugs 0 Features 0
Metric Value
cc 11
eloc 34
c 6
b 0
f 0
nc 35
nop 0
dl 0
loc 68
ccs 22
cts 34
cp 0.6471
crap 16.3179
rs 7.3166

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * Mollie gateway.
4
 *
5
 * @author    Pronamic <[email protected]>
6
 * @copyright 2005-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 Pronamic_WP_Pay_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 Pronamic_WP_Pay_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
		if ( false === $is_recurring_method && null !== $payment_method ) {
344
			// Always use 'direct debit mandate via iDEAL/Bancontact/Sofort' payment methods as recurring method.
345
			$is_recurring_method = PaymentMethods::is_direct_debit_method( $payment_method );
346
347
			// Check for non-recurring methods for subscription payments.
348
			if ( false === $is_recurring_method && null !== $payment->get_periods() ) {
349
				$direct_debit_methods = PaymentMethods::get_direct_debit_methods();
350
351
				$is_recurring_method = \in_array( $payment_method, $direct_debit_methods, true );
352
			}
353
		}
354
355
		if ( $is_recurring_method ) {
356
			$request->sequence_type = $payment->get_recurring() ? Sequence::RECURRING : Sequence::FIRST;
357
358
			if ( Sequence::FIRST === $request->sequence_type ) {
359
				$payment_method = PaymentMethods::get_first_payment_method( $payment_method );
360
			}
361
362
			if ( Sequence::RECURRING === $request->sequence_type ) {
363
				// Use mandate from subscription.
364
				if ( $subscription && empty( $request->mandate_id ) ) {
365
					$subscription_mandate_id = $subscription->get_meta( 'mollie_mandate_id' );
366
367
					if ( false !== $subscription_mandate_id ) {
368
						$request->set_mandate_id( $subscription_mandate_id );
369
					}
370
				}
371
372
				// Use credit card for recurring Apple Pay payments.
373
				if ( PaymentMethods::APPLE_PAY === $payment_method ) {
374
					$payment_method = PaymentMethods::CREDIT_CARD;
375
				}
376
377
				$direct_debit_methods = PaymentMethods::get_direct_debit_methods();
378
379
				$recurring_method = \array_search( $payment_method, $direct_debit_methods, true );
380
381
				if ( \is_string( $recurring_method ) ) {
382
					$payment_method = $recurring_method;
383
				}
384
385
				$payment->set_action_url( $payment->get_return_url() );
386
			}
387
		}
388
389
		// Leap of faith if the WordPress payment method could not transform to a Mollie method?
390
		$request->method = Methods::transform( $payment_method, $payment_method );
391
392
		/**
393
		 * Metadata.
394
		 *
395
		 * Provide any data you like, for example a string or a JSON object.
396
		 * We will save the data alongside the payment. Whenever you fetch
397
		 * the payment with our API, we’ll also include the metadata. You
398
		 * can use up to approximately 1kB.
399
		 *
400
		 * @link https://docs.mollie.com/reference/v2/payments-api/create-payment
401
		 * @link https://en.wikipedia.org/wiki/Metadata
402
		 */
403
		$metadata = null;
404
405
		/**
406
		 * Filters the Mollie metadata.
407
		 *
408
		 * @since 2.2.0
409
		 *
410
		 * @param mixed   $metadata Metadata.
411
		 * @param Payment $payment  Payment.
412
		 */
413
		$metadata = \apply_filters( 'pronamic_pay_mollie_payment_metadata', $metadata, $payment );
414
415
		$request->set_metadata( $metadata );
416
417
		// Issuer.
418
		if ( Methods::IDEAL === $request->method ) {
419
			$request->issuer = $payment->get_issuer();
420
		}
421
422
		// Billing email.
423
		$billing_email = $payment->get_email();
424
425
		/**
426
		 * Filters the Mollie payment billing email used for bank transfer payment instructions.
427
		 *
428
		 * @since 2.2.0
429
		 *
430
		 * @param string|null $billing_email Billing email.
431
		 * @param Payment     $payment       Payment.
432
		 */
433
		$billing_email = \apply_filters( 'pronamic_pay_mollie_payment_billing_email', $billing_email, $payment );
434
435
		$request->set_billing_email( $billing_email );
436
437
		// Due date.
438
		if ( ! empty( $this->config->due_date_days ) ) {
439
			try {
440
				$due_date = new DateTime( sprintf( '+%s days', $this->config->due_date_days ) );
441
			} catch ( \Exception $e ) {
442
				$due_date = null;
443
			}
444
445
			$request->set_due_date( $due_date );
446
		}
447
448
		// Create payment.
449
		$result = $this->client->create_payment( $request );
450
451
		// phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase -- Mollie JSON object.
452
453
		// Set transaction ID.
454
		if ( isset( $result->id ) ) {
455
			$payment->set_transaction_id( $result->id );
456
		}
457
458
		// Set expiry date.
459
		if ( isset( $result->expiresAt ) ) {
460
			try {
461
				$expires_at = new DateTime( $result->expiresAt );
462
			} catch ( \Exception $e ) {
463
				$expires_at = null;
464
			}
465
466
			$payment->set_expiry_date( $expires_at );
467
		}
468
469
		// Set status.
470
		if ( isset( $result->status ) ) {
471
			$payment->set_status( Statuses::transform( $result->status ) );
472
		}
473
474
		// Set bank transfer recipient details.
475
		if ( isset( $result->details ) ) {
476
			$bank_transfer_recipient_details = $payment->get_bank_transfer_recipient_details();
477
478
			if ( null === $bank_transfer_recipient_details ) {
479
				$bank_transfer_recipient_details = new BankTransferDetails();
480
481
				$payment->set_bank_transfer_recipient_details( $bank_transfer_recipient_details );
482
			}
483
484
			$bank_details = $bank_transfer_recipient_details->get_bank_account();
485
486
			if ( null === $bank_details ) {
487
				$bank_details = new BankAccountDetails();
488
489
				$bank_transfer_recipient_details->set_bank_account( $bank_details );
490
			}
491
492
			$details = $result->details;
493
494
			if ( isset( $details->bankName ) ) {
495
				/**
496
				 * Set `bankName` as bank details name, as result "Stichting Mollie Payments"
497
				 * is not the name of a bank, but the account holder name.
498
				 */
499
				$bank_details->set_name( $details->bankName );
500
			}
501
502
			if ( isset( $details->bankAccount ) ) {
503
				$bank_details->set_iban( $details->bankAccount );
504
			}
505
506
			if ( isset( $details->bankBic ) ) {
507
				$bank_details->set_bic( $details->bankBic );
508
			}
509
510
			if ( isset( $details->transferReference ) ) {
511
				$bank_transfer_recipient_details->set_reference( $details->transferReference );
512
			}
513
		}
514
515
		// Handle links.
516
		if ( isset( $result->_links ) ) {
517
			$links = $result->_links;
518
519
			// Action URL.
520
			if ( isset( $links->checkout->href ) ) {
521
				$payment->set_action_url( $links->checkout->href );
522
			}
523
524
			// Change payment state URL.
525
			if ( isset( $links->changePaymentState->href ) ) {
526
				$payment->set_meta( 'mollie_change_payment_state_url', $links->changePaymentState->href );
527
			}
528
		}
529
530
		// phpcs:enable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase -- Mollie JSON object.
531
	}
532
533
	/**
534
	 * Update status of the specified payment
535
	 *
536
	 * @param Payment $payment Payment.
537
	 * @return void
538
	 */
539
	public function update_status( Payment $payment ) {
540
		$transaction_id = $payment->get_transaction_id();
541
542
		if ( null === $transaction_id ) {
543
			return;
544
		}
545
546
		$mollie_payment = $this->client->get_payment( $transaction_id );
547
548
		$payment->set_status( Statuses::transform( $mollie_payment->get_status() ) );
549
550
		/**
551
		 * Mollie profile.
552
		 */
553
		$mollie_profile = new Profile();
554
555
		$mollie_profile->set_id( $mollie_payment->get_profile_id() );
556
557
		$profile_internal_id = $this->profile_data_store->get_or_insert_profile( $mollie_profile );
558
559
		/**
560
		 * If the Mollie payment contains a customer ID we will try to connect
561
		 * this Mollie customer ID the WordPress user and subscription.
562
		 * This can be usefull in case when a WordPress user is created after
563
		 * a succesfull payment.
564
		 *
565
		 * @link https://www.gravityforms.com/add-ons/user-registration/
566
		 */
567
		$mollie_customer_id = $mollie_payment->get_customer_id();
568
569
		if ( null !== $mollie_customer_id ) {
570
			$mollie_customer = new Customer( $mollie_customer_id );
571
572
			$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...
573
				$mollie_customer,
574
				array(
575
					'profile_id' => $profile_internal_id,
576
				),
577
				array(
578
					'profile_id' => '%s',
579
				)
580
			);
581
582
			// Meta.
583
			$customer_id = $payment->get_meta( 'mollie_customer_id' );
584
585
			if ( empty( $customer_id ) ) {
586
				$payment->set_meta( 'mollie_customer_id', $mollie_customer->get_id() );
587
			}
588
589
			// Customer.
590
			$customer = $payment->get_customer();
591
592
			if ( null !== $customer ) {
593
				// Connect to user.
594
				$user_id = $customer->get_user_id();
595
596
				if ( null !== $user_id ) {
597
					$user = \get_user_by( 'id', $user_id );
598
599
					if ( false !== $user ) {
600
						$this->customer_data_store->connect_mollie_customer_to_wp_user( $mollie_customer, $user );
601
					}
602
				}
603
			}
604
605
			// Subscription.
606
			$subscription = $payment->get_subscription();
607
608
			if ( null !== $subscription ) {
609
				$customer_id = $subscription->get_meta( 'mollie_customer_id' );
610
611
				if ( empty( $customer_id ) ) {
612
					$subscription->set_meta( 'mollie_customer_id', $mollie_customer->get_id() );
613
				}
614
615
				// Update mandate in subscription meta.
616
				$mollie_mandate_id = $mollie_payment->get_mandate_id();
617
618
				if ( null !== $mollie_mandate_id ) {
619
					$mandate_id = $subscription->get_meta( 'mollie_mandate_id' );
620
621
					// Only update if no mandate has been set yet or if payment succeeded.
622
					if ( empty( $mandate_id ) || PaymentStatus::SUCCESS === $payment->get_status() ) {
623
						$this->update_subscription_mandate( $subscription, $mollie_mandate_id, $payment->get_method() );
624
					}
625
				}
626
			}
627
		}
628
629
		// phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase -- Mollie JSON object.
630
		$mollie_payment_details = $mollie_payment->get_details();
631
632
		if ( null !== $mollie_payment_details ) {
633
			$consumer_bank_details = $payment->get_consumer_bank_details();
634
635
			if ( null === $consumer_bank_details ) {
636
				$consumer_bank_details = new BankAccountDetails();
637
638
				$payment->set_consumer_bank_details( $consumer_bank_details );
639
			}
640
641
			if ( isset( $mollie_payment_details->consumerName ) ) {
642
				$consumer_bank_details->set_name( $mollie_payment_details->consumerName );
643
			}
644
645
			if ( isset( $mollie_payment_details->cardHolder ) ) {
646
				$consumer_bank_details->set_name( $mollie_payment_details->cardHolder );
647
			}
648
649
			if ( isset( $mollie_payment_details->cardNumber ) ) {
650
				// The last four digits of the card number.
651
				$consumer_bank_details->set_account_number( $mollie_payment_details->cardNumber );
652
			}
653
654
			if ( isset( $mollie_payment_details->cardCountryCode ) ) {
655
				// The ISO 3166-1 alpha-2 country code of the country the card was issued in.
656
				$consumer_bank_details->set_country( $mollie_payment_details->cardCountryCode );
657
			}
658
659
			if ( isset( $mollie_payment_details->consumerAccount ) ) {
660
				switch ( $mollie_payment->get_method() ) {
661
					case Methods::BELFIUS:
662
					case Methods::DIRECT_DEBIT:
663
					case Methods::IDEAL:
664
					case Methods::KBC:
665
					case Methods::SOFORT:
666
						$consumer_bank_details->set_iban( $mollie_payment_details->consumerAccount );
667
668
						break;
669
					case Methods::BANCONTACT:
670
					case Methods::BANKTRANSFER:
671
					case Methods::PAYPAL:
672
					default:
673
						$consumer_bank_details->set_account_number( $mollie_payment_details->consumerAccount );
674
675
						break;
676
				}
677
			}
678
679
			if ( isset( $mollie_payment_details->consumerBic ) ) {
680
				$consumer_bank_details->set_bic( $mollie_payment_details->consumerBic );
681
			}
682
683
			/*
684
			 * Failure reason.
685
			 */
686
			$failure_reason = $payment->get_failure_reason();
687
688
			if ( null === $failure_reason ) {
689
				$failure_reason = new FailureReason();
690
691
				$payment->set_failure_reason( $failure_reason );
692
			}
693
694
			// SEPA Direct Debit.
695
			if ( isset( $mollie_payment_details->bankReasonCode ) ) {
696
				$failure_reason->set_code( $mollie_payment_details->bankReasonCode );
697
			}
698
699
			if ( isset( $mollie_payment_details->bankReason ) ) {
700
				$failure_reason->set_message( $mollie_payment_details->bankReason );
701
			}
702
703
			// Credit card.
704
			if ( isset( $mollie_payment_details->failureReason ) ) {
705
				$failure_reason->set_code( $mollie_payment_details->failureReason );
706
			}
707
708
			if ( isset( $mollie_payment_details->failureMessage ) ) {
709
				$failure_reason->set_message( $mollie_payment_details->failureMessage );
710
			}
711
		}
712
713
		$links = $mollie_payment->get_links();
714
715
		// Change payment state URL.
716
		if ( \property_exists( $links, 'changePaymentState' ) ) {
717
			$payment->set_meta( 'mollie_change_payment_state_url', $links->changePaymentState->href );
718
		}
719
720
		// phpcs:enable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase -- Mollie JSON object.
721
722
		if ( $mollie_payment->has_chargebacks() ) {
723
			$mollie_chargebacks = $this->client->get_payment_chargebacks(
724
				$mollie_payment->get_id(),
725
				array( 'limit' => 1 )
726
			);
727
728
			$mollie_chargeback = \reset( $mollie_chargebacks );
729
730
			if ( false !== $mollie_chargeback ) {
731
				$subscriptions = array_filter(
732
					$payment->get_subscriptions(),
733
					function( $subscription ) {
734
						return SubscriptionStatus::ACTIVE === $subscription->get_status();
735
					}
736
				);
737
738
				foreach ( $subscriptions as $subscription ) {
739
					if ( $mollie_chargeback->get_created_at() > $subscription->get_activated_at() ) {
740
						$subscription->set_status( SubscriptionStatus::ON_HOLD );
741
742
						$subscription->add_note(
743
							\sprintf(
744
								/* translators: 1: Mollie chargeback ID, 2: Mollie payment ID */
745
								\__( 'Subscription put on hold due to chargeback `%1$s` of payment `%2$s`.', 'pronamic_ideal' ),
746
								\esc_html( $mollie_chargeback->get_id() ),
747
								\esc_html( $mollie_payment->get_id() )
748
							)
749
						);
750
751
						$subscription->save();
752
					}
753
				}
754
			}
755
		}
756
757
		// Refunds.
758
		$amount_refunded = $mollie_payment->get_amount_refunded();
759
760
		if ( null !== $amount_refunded ) {
761
			$refunded_amount = new Money( $amount_refunded->get_value(), $amount_refunded->get_currency() );
762
763
			$payment->set_refunded_amount( $refunded_amount );
0 ignored issues
show
Bug introduced by
The method set_refunded_amount() does not exist on Pronamic\WordPress\Pay\Payments\Payment. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

763
			$payment->/** @scrutinizer ignore-call */ 
764
             set_refunded_amount( $refunded_amount );

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
764
		}
765
	}
766
767
	/**
768
	 * Update subscription mandate.
769
	 *
770
	 * @param Subscription $subscription   Subscription.
771
	 * @param string       $mandate_id     Mollie mandate ID.
772
	 * @param string|null  $payment_method Payment method.
773
	 * @return void
774
	 * @throws \Exception Throws exception if subscription note could not be added.
775
	 */
776
	public function update_subscription_mandate( Subscription $subscription, $mandate_id, $payment_method = null ) {
777
		$customer_id = (string) $subscription->get_meta( 'mollie_customer_id' );
778
779
		$mandate = $this->client->get_mandate( $mandate_id, $customer_id );
780
781
		if ( ! \is_object( $mandate ) ) {
782
			return;
783
		}
784
785
		// Update mandate.
786
		$old_mandate_id = $subscription->get_meta( 'mollie_mandate_id' );
787
788
		$subscription->set_meta( 'mollie_mandate_id', $mandate_id );
789
790
		if ( ! empty( $old_mandate_id ) && $old_mandate_id !== $mandate_id ) {
791
			$note = \sprintf(
792
			/* translators: 1: old mandate ID, 2: new mandate ID */
793
				\__( 'Mandate for subscription changed from "%1$s" to "%2$s".', 'pronamic_ideal' ),
794
				\esc_html( $old_mandate_id ),
795
				\esc_html( $mandate_id )
796
			);
797
798
			$subscription->add_note( $note );
799
		}
800
801
		// Update payment method.
802
		$old_method = $subscription->payment_method;
803
		$new_method = ( null === $payment_method && \property_exists( $mandate, 'method' ) ? Methods::transform_gateway_method( $mandate->method ) : $payment_method );
804
805
		// `Direct Debit` is not a recurring method, use `Direct Debit (mandate via ...)` instead.
806
		if ( PaymentMethods::DIRECT_DEBIT === $new_method ) {
807
			$new_method = PaymentMethods::DIRECT_DEBIT_IDEAL;
808
809
			// Use `Direct Debit (mandate via Bancontact)` if consumer account starts with `BE`.
810
			if ( \property_exists( $mandate, 'details' ) && 'BE' === \substr( $mandate->details->consumerAccount, 0, 2 ) ) {
811
				$new_method = PaymentMethods::DIRECT_DEBIT_BANCONTACT;
812
			}
813
		}
814
815
		if ( ! empty( $old_method ) && $old_method !== $new_method ) {
816
			$subscription->payment_method = $new_method;
817
818
			// Add note.
819
			$note = \sprintf(
820
				/* translators: 1: old payment method, 2: new payment method */
821
				\__( 'Payment method for subscription changed from "%1$s" to "%2$s".', 'pronamic_ideal' ),
822
				\esc_html( (string) PaymentMethods::get_name( $old_method ) ),
823
				\esc_html( (string) PaymentMethods::get_name( $new_method ) )
824
			);
825
826
			$subscription->add_note( $note );
827
		}
828
829
		$subscription->save();
830
	}
831
832
	/**
833
	 * Create refund.
834
	 *
835
	 * @param string $transaction_id Transaction ID.
836
	 * @param Money  $amount         Amount to refund.
837
	 * @param string $description    Refund reason.
838
	 * @return string
839
	 */
840
	public function create_refund( $transaction_id, Money $amount, $description = null ) {
841
		$request = new RefundRequest( AmountTransformer::transform( $amount ) );
842
843
		// Metadata payment ID.
844
		$payment = \get_pronamic_payment_by_transaction_id( $transaction_id );
845
846
		$payment_id = null;
847
848
		if ( null !== $payment ) {
849
			$payment_id = $payment->get_id();
850
		}
851
852
		$request->set_metadata(
853
			array(
854
				'pronamic_payment_id' => $payment_id,
855
			)
856
		);
857
858
		// Description.
859
		if ( ! empty( $description ) ) {
860
			$request->set_description( $description );
861
		}
862
863
		$refund = $this->client->create_refund( $transaction_id, $request );
864
865
		return $refund->get_id();
866
	}
867
868
	/**
869
	 * Get Mollie customer ID for payment.
870
	 *
871
	 * @param Payment $payment Payment.
872
	 * @return string|null
873
	 */
874 10
	public function get_customer_id_for_payment( Payment $payment ) {
875 10
		$customer_ids = $this->get_customer_ids_for_payment( $payment );
876
877 10
		$customer_id = $this->get_first_existing_customer_id( $customer_ids );
878
879 10
		return $customer_id;
880
	}
881
882
	/**
883
	 * Get Mollie customers for the specified payment.
884
	 *
885
	 * @param Payment $payment Payment.
886
	 * @return array<string>
887
	 */
888 10
	private function get_customer_ids_for_payment( Payment $payment ) {
889 10
		$customer_ids = array();
890
891
		// Customer ID from subscription meta.
892 10
		$subscription = $payment->get_subscription();
893
894 10
		if ( null !== $subscription ) {
895 10
			$customer_id = $this->get_customer_id_for_subscription( $subscription );
896
897 10
			if ( null !== $customer_id ) {
898 4
				$customer_ids[] = $customer_id;
899
			}
900
		}
901
902
		// Customer ID from WordPress user.
903 10
		$customer = $payment->get_customer();
904
905 10
		if ( null !== $customer ) {
906 10
			$user_id = $customer->get_user_id();
907
908 10
			if ( ! empty( $user_id ) ) {
909 7
				$user_customer_ids = $this->get_customer_ids_for_user( $user_id );
910
911 7
				$customer_ids = \array_merge( $customer_ids, $user_customer_ids );
912
			}
913
		}
914
915 10
		return $customer_ids;
916
	}
917
918
	/**
919
	 * Get Mollie customers for the specified WordPress user ID.
920
	 *
921
	 * @param int $user_id WordPress user ID.
922
	 * @return array<string>
923
	 */
924 24
	public function get_customer_ids_for_user( $user_id ) {
925 24
		$customer_query = new CustomerQuery(
926
			array(
927 24
				'user_id' => $user_id,
928
			)
929
		);
930
931 24
		$customers = $customer_query->get_customers();
932
933 24
		$customer_ids = wp_list_pluck( $customers, 'mollie_id' );
934
935 24
		return $customer_ids;
936
	}
937
938
	/**
939
	 * Get customer ID for subscription.
940
	 *
941
	 * @param Subscription $subscription Subscription.
942
	 * @return string|null
943
	 */
944 10
	private function get_customer_id_for_subscription( Subscription $subscription ) {
945 10
		$customer_id = $subscription->get_meta( 'mollie_customer_id' );
946
947 10
		if ( empty( $customer_id ) ) {
948
			// Try to get (legacy) customer ID from first payment.
949 7
			$first_payment = $subscription->get_first_payment();
950
951 7
			if ( null !== $first_payment ) {
952 7
				$customer_id = $first_payment->get_meta( 'mollie_customer_id' );
953
			}
954
		}
955
956 10
		if ( empty( $customer_id ) ) {
957 6
			return null;
958
		}
959
960 4
		return $customer_id;
961
	}
962
963
	/**
964
	 * Get first existing customer from customers list.
965
	 *
966
	 * @param array<string> $customer_ids Customers.
967
	 * @return string|null
968
	 * @throws Error Throws error on Mollie error.
969
	 */
970 10
	private function get_first_existing_customer_id( $customer_ids ) {
971 10
		$customer_ids = \array_filter( $customer_ids );
972
973 10
		$customer_ids = \array_unique( $customer_ids );
974
975 10
		foreach ( $customer_ids as $customer_id ) {
976
			try {
977 4
				$customer = $this->client->get_customer( $customer_id );
978
			} catch ( Error $error ) {
979
				// Check for status 410 ("Gone - The customer is no longer available").
980
				if ( 410 === $error->get_status() ) {
981
					continue;
982
				}
983
984
				throw $error;
985
			}
986
987 4
			if ( null !== $customer ) {
988 4
				return $customer_id;
989
			}
990
		}
991
992 6
		return null;
993
	}
994
995
	/**
996
	 * Create customer for payment.
997
	 *
998
	 * @param Payment $payment Payment.
999
	 * @return string|null
1000
	 * @throws Error Throws Error when Mollie error occurs.
1001
	 * @throws \Exception Throws exception when error in customer data store occurs.
1002
	 */
1003
	private function create_customer_for_payment( Payment $payment ) {
1004
		$mollie_customer = new Customer();
1005
		$mollie_customer->set_mode( $this->config->is_test_mode() ? 'test' : 'live' );
1006
		$mollie_customer->set_email( $payment->get_email() );
1007
1008
		$pronamic_customer = $payment->get_customer();
1009
1010
		if ( null !== $pronamic_customer ) {
1011
			// Name.
1012
			$name = (string) $pronamic_customer->get_name();
1013
1014
			if ( '' !== $name ) {
1015
				$mollie_customer->set_name( $name );
1016
			}
1017
1018
			// Locale.
1019
			$locale = $pronamic_customer->get_locale();
1020
1021
			if ( null !== $locale ) {
1022
				$mollie_customer->set_locale( LocaleHelper::transform( $locale ) );
1023
			}
1024
		}
1025
1026
		// Try to get name from consumer bank details.
1027
		$consumer_bank_details = $payment->get_consumer_bank_details();
1028
1029
		if ( null === $mollie_customer->get_name() && null !== $consumer_bank_details ) {
1030
			$name = $consumer_bank_details->get_name();
1031
1032
			if ( null !== $name ) {
1033
				$mollie_customer->set_name( $name );
1034
			}
1035
		}
1036
1037
		// Create customer.
1038
		$mollie_customer = $this->client->create_customer( $mollie_customer );
1039
1040
		$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...
1041
1042
		// Connect to user.
1043
		if ( null !== $pronamic_customer ) {
1044
			$user_id = $pronamic_customer->get_user_id();
1045
1046
			if ( null !== $user_id ) {
1047
				$user = \get_user_by( 'id', $user_id );
1048
1049
				if ( false !== $user ) {
1050
					$this->customer_data_store->connect_mollie_customer_to_wp_user( $mollie_customer, $user );
1051
				}
1052
			}
1053
		}
1054
1055
		// Store customer ID in subscription meta.
1056
		$subscription = $payment->get_subscription();
1057
1058
		if ( null !== $subscription ) {
1059
			$subscription->set_meta( 'mollie_customer_id', $mollie_customer->get_id() );
1060
		}
1061
1062
		return $mollie_customer->get_id();
1063
	}
1064
1065
	/**
1066
	 * Copy Mollie customer ID from subscription meta to WordPress user meta.
1067
	 *
1068
	 * @param Payment $payment Payment.
1069
	 * @return void
1070
	 */
1071 27
	public function copy_customer_id_to_wp_user( Payment $payment ) {
1072 27
		if ( $this->config->id !== $payment->config_id ) {
1073 1
			return;
1074
		}
1075
1076
		// Subscription.
1077 26
		$subscription = $payment->get_subscription();
1078
1079
		// Customer.
1080 26
		$customer = $payment->get_customer();
1081
1082 26
		if ( null === $customer && null !== $subscription ) {
1083 16
			$customer = $subscription->get_customer();
1084
		}
1085
1086 26
		if ( null === $customer ) {
1087
			return;
1088
		}
1089
1090
		// WordPress user.
1091 26
		$user_id = $customer->get_user_id();
1092
1093 26
		if ( null === $user_id ) {
1094 3
			return;
1095
		}
1096
1097 23
		$user = \get_user_by( 'id', $user_id );
1098
1099 23
		if ( false === $user ) {
1100 12
			return;
1101
		}
1102
1103
		// Customer IDs.
1104 11
		$customer_ids = array();
1105
1106
		// Payment.
1107 11
		$customer_ids[] = $payment->get_meta( 'mollie_customer_id' );
1108
1109
		// Subscription.
1110 11
		if ( null !== $subscription ) {
1111 11
			$customer_ids[] = $subscription->get_meta( 'mollie_customer_id' );
1112
		}
1113
1114
		// Connect.
1115 11
		$customer_ids = \array_filter( $customer_ids );
1116 11
		$customer_ids = \array_unique( $customer_ids );
1117
1118 11
		foreach ( $customer_ids as $customer_id ) {
1119 2
			$customer = new Customer( $customer_id );
1120
1121 2
			$this->customer_data_store->get_or_insert_customer( $customer );
1122
1123 2
			$this->customer_data_store->connect_mollie_customer_to_wp_user( $customer, $user );
1124
		}
1125 11
	}
1126
}
1127