Failed Conditions
Push — develop ( 26b9bd...e03969 )
by Remco
08:58 queued 03:52
created

Gateway::update_subscription_mandate()   B

Complexity

Conditions 11
Paths 49

Size

Total Lines 54
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 132

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 11
eloc 26
c 1
b 0
f 0
nc 49
nop 3
dl 0
loc 54
ccs 0
cts 27
cp 0
crap 132
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 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
		$description = (string) $payment->get_description();
275
276
		/**
277
		 * Filters the Mollie payment description.
278
		 * 
279
		 * The maximum length of the description field differs per payment
280
		 * method, with the absolute maximum being 255 characters.
281
		 *
282
		 * @link https://docs.mollie.com/reference/v2/payments-api/create-payment#parameters
283
		 * @since 3.0.1
284
		 * @param string  $description Description.
285
		 * @param Payment $payment     Payment.
286
		 */
287
		$description = \apply_filters( 'pronamic_pay_mollie_payment_description', $description, $payment );
288
289
		$request = new PaymentRequest(
290
			AmountTransformer::transform( $payment->get_total_amount() ),
291
			$description
292
		);
293
294
		$request->redirect_url = $payment->get_return_url();
295
		$request->webhook_url  = $this->get_webhook_url();
296
297
		// Locale.
298
		$customer = $payment->get_customer();
299
300
		if ( null !== $customer ) {
301
			$request->locale = LocaleHelper::transform( $customer->get_locale() );
302
		}
303
304
		// Customer ID.
305
		$customer_id = $this->get_customer_id_for_payment( $payment );
306
307
		if ( null === $customer_id ) {
308
			$customer_id = $this->create_customer_for_payment( $payment );
309
		}
310
311
		if ( null !== $customer_id ) {
312
			$request->customer_id = $customer_id;
313
		}
314
315
		// Payment method.
316
		$payment_method = $payment->get_payment_method();
0 ignored issues
show
Bug introduced by
The method get_payment_method() 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

316
		/** @scrutinizer ignore-call */ 
317
  $payment_method = $payment->get_payment_method();

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...
317
318
		// Recurring payment method.
319
		$subscription = $payment->get_subscription();
320
321
		$is_recurring_method = ( $subscription && PaymentMethods::is_recurring_method( (string) $payment_method ) );
0 ignored issues
show
Unused Code introduced by
The assignment to $is_recurring_method is dead and can be removed.
Loading history...
322
323
		// Consumer bank details.
324
		$consumer_bank_details = $payment->get_consumer_bank_details();
325
326
		if ( PaymentMethods::DIRECT_DEBIT === $payment_method && null !== $consumer_bank_details ) {
327
			$consumer_name = $consumer_bank_details->get_name();
328
			$consumer_iban = $consumer_bank_details->get_iban();
329
330
			$request->consumer_name    = $consumer_name;
331
			$request->consumer_account = $consumer_iban;
332
333
			// Check if one-off SEPA Direct Debit can be used, otherwise short circuit payment.
334
			if ( null !== $customer_id ) {
335
				// Find or create mandate.
336
				$mandate_id = $this->client->has_valid_mandate( $customer_id, PaymentMethods::DIRECT_DEBIT, $consumer_iban );
337
338
				if ( false === $mandate_id ) {
339
					$mandate = $this->client->create_mandate( $customer_id, $consumer_bank_details );
340
341
					if ( ! \property_exists( $mandate, 'id' ) ) {
342
						throw new \Exception( 'Missing mandate ID.' );
343
					}
344
345
					$mandate_id = $mandate->id;
346
				}
347
348
				// Charge immediately on-demand.
349
				$request->set_sequence_type( Sequence::RECURRING );
350
				$request->set_mandate_id( (string) $mandate_id );
351
352
				$is_recurring_method = true;
353
354
				$payment->recurring = true;
355
			}
356
		}
357
358
		/**
359
		 * Payment method.
360
		 *
361
		 * Leap of faith if the WordPress payment method could not transform to a Mollie method?
362
		 */
363
		$request->method = Methods::transform( $payment_method, $payment_method );
364
365
		/**
366
		 * Sequence type.
367
		 *
368
		 * Recurring payments are created through the Payments API by providing a `sequenceType`.
369
		 */
370
		$subscriptions = $payment->get_subscriptions();
371
372
		if ( \count( $subscriptions ) > 0 ) {
373
			$request->method        = PaymentMethods::get_first_payment_method( $payment_method );
374
			$request->sequence_type = 'first';
375
376
			foreach ( $subscriptions as $subscription ) {
377
				$mandate_id = $subscription->get_meta( 'mollie_mandate_id' );
378
379
				if ( ! empty( $mandate_id ) ) {
380
					$request->method        = null;
381
					$request->sequence_type = 'recurring';
382
383
					$request->set_mandate_id( $mandate_id );
384
				}
385
			}
386
		}
387
388
		/**
389
		 * Metadata.
390
		 *
391
		 * Provide any data you like, for example a string or a JSON object.
392
		 * We will save the data alongside the payment. Whenever you fetch
393
		 * the payment with our API, we’ll also include the metadata. You
394
		 * can use up to approximately 1kB.
395
		 *
396
		 * @link https://docs.mollie.com/reference/v2/payments-api/create-payment
397
		 * @link https://en.wikipedia.org/wiki/Metadata
398
		 */
399
		$metadata = null;
400
401
		/**
402
		 * Filters the Mollie metadata.
403
		 *
404
		 * @since 2.2.0
405
		 *
406
		 * @param mixed   $metadata Metadata.
407
		 * @param Payment $payment  Payment.
408
		 */
409
		$metadata = \apply_filters( 'pronamic_pay_mollie_payment_metadata', $metadata, $payment );
410
411
		$request->set_metadata( $metadata );
412
413
		// Issuer.
414
		if ( Methods::IDEAL === $request->method ) {
415
			$request->issuer = $payment->get_meta( 'issuer' );
0 ignored issues
show
Documentation Bug introduced by
It seems like $payment->get_meta('issuer') can also be of type false. However, the property $issuer is declared as type null|string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
416
		}
417
418
		// Billing email.
419
		$billing_email = $payment->get_email();
420
421
		/**
422
		 * Filters the Mollie payment billing email used for bank transfer payment instructions.
423
		 *
424
		 * @since 2.2.0
425
		 *
426
		 * @param string|null $billing_email Billing email.
427
		 * @param Payment     $payment       Payment.
428
		 */
429
		$billing_email = \apply_filters( 'pronamic_pay_mollie_payment_billing_email', $billing_email, $payment );
430
431
		$request->set_billing_email( $billing_email );
432
433
		// Due date.
434
		if ( ! empty( $this->config->due_date_days ) ) {
435
			try {
436
				$due_date = new DateTime( sprintf( '+%s days', $this->config->due_date_days ) );
437
			} catch ( \Exception $e ) {
438
				$due_date = null;
439
			}
440
441
			$request->set_due_date( $due_date );
442
		}
443
444
		// Create payment.
445
		$result = $this->client->create_payment( $request );
446
447
		// phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase -- Mollie JSON object.
448
449
		// Set transaction ID.
450
		if ( isset( $result->id ) ) {
451
			$payment->set_transaction_id( $result->id );
452
		}
453
454
		// Set expiry date.
455
		if ( isset( $result->expiresAt ) ) {
456
			try {
457
				$expires_at = new DateTime( $result->expiresAt );
458
			} catch ( \Exception $e ) {
459
				$expires_at = null;
460
			}
461
462
			$payment->set_expiry_date( $expires_at );
463
		}
464
465
		// Set status.
466
		if ( isset( $result->status ) ) {
467
			$payment->set_status( Statuses::transform( $result->status ) );
468
		}
469
470
		// Set bank transfer recipient details.
471
		if ( isset( $result->details ) ) {
472
			$bank_transfer_recipient_details = $payment->get_bank_transfer_recipient_details();
473
474
			if ( null === $bank_transfer_recipient_details ) {
475
				$bank_transfer_recipient_details = new BankTransferDetails();
476
477
				$payment->set_bank_transfer_recipient_details( $bank_transfer_recipient_details );
478
			}
479
480
			$bank_details = $bank_transfer_recipient_details->get_bank_account();
481
482
			if ( null === $bank_details ) {
483
				$bank_details = new BankAccountDetails();
484
485
				$bank_transfer_recipient_details->set_bank_account( $bank_details );
486
			}
487
488
			$details = $result->details;
489
490
			if ( isset( $details->bankName ) ) {
491
				/**
492
				 * Set `bankName` as bank details name, as result "Stichting Mollie Payments"
493
				 * is not the name of a bank, but the account holder name.
494
				 */
495
				$bank_details->set_name( $details->bankName );
496
			}
497
498
			if ( isset( $details->bankAccount ) ) {
499
				$bank_details->set_iban( $details->bankAccount );
500
			}
501
502
			if ( isset( $details->bankBic ) ) {
503
				$bank_details->set_bic( $details->bankBic );
504
			}
505
506
			if ( isset( $details->transferReference ) ) {
507
				$bank_transfer_recipient_details->set_reference( $details->transferReference );
508
			}
509
		}
510
511
		// Handle links.
512
		if ( isset( $result->_links ) ) {
513
			$links = $result->_links;
514
515
			// Action URL.
516
			if ( isset( $links->checkout->href ) ) {
517
				$payment->set_action_url( $links->checkout->href );
518
			}
519
520
			// Change payment state URL.
521
			if ( isset( $links->changePaymentState->href ) ) {
522
				$payment->set_meta( 'mollie_change_payment_state_url', $links->changePaymentState->href );
523
			}
524
		}
525
526
		// phpcs:enable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase -- Mollie JSON object.
527
	}
528
529
	/**
530
	 * Update status of the specified payment
531
	 *
532
	 * @param Payment $payment Payment.
533
	 * @return void
534
	 */
535
	public function update_status( Payment $payment ) {
536
		$transaction_id = $payment->get_transaction_id();
537
538
		if ( null === $transaction_id ) {
539
			return;
540
		}
541
542
		$mollie_payment = $this->client->get_payment( $transaction_id );
543
544
		$payment->set_status( Statuses::transform( $mollie_payment->get_status() ) );
545
546
		/**
547
		 * Mollie profile.
548
		 */
549
		$mollie_profile = new Profile();
550
551
		$mollie_profile->set_id( $mollie_payment->get_profile_id() );
552
553
		$profile_internal_id = $this->profile_data_store->get_or_insert_profile( $mollie_profile );
554
555
		/**
556
		 * If the Mollie payment contains a customer ID we will try to connect
557
		 * this Mollie customer ID the WordPress user and subscription.
558
		 * This can be useful in case when a WordPress user is created after
559
		 * a successful payment.
560
		 *
561
		 * @link https://www.gravityforms.com/add-ons/user-registration/
562
		 */
563
		$mollie_customer_id = $mollie_payment->get_customer_id();
564
565
		if ( null !== $mollie_customer_id ) {
566
			$mollie_customer = new Customer( $mollie_customer_id );
567
568
			$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...
569
				$mollie_customer,
570
				array(
571
					'profile_id' => $profile_internal_id,
572
				),
573
				array(
574
					'profile_id' => '%s',
575
				)
576
			);
577
578
			// Meta.
579
			$customer_id = $payment->get_meta( 'mollie_customer_id' );
580
581
			if ( empty( $customer_id ) ) {
582
				$payment->set_meta( 'mollie_customer_id', $mollie_customer->get_id() );
583
			}
584
585
			// Customer.
586
			$customer = $payment->get_customer();
587
588
			if ( null !== $customer ) {
589
				// Connect to user.
590
				$user_id = $customer->get_user_id();
591
592
				if ( null !== $user_id ) {
593
					$user = \get_user_by( 'id', $user_id );
594
595
					if ( false !== $user ) {
596
						$this->customer_data_store->connect_mollie_customer_to_wp_user( $mollie_customer, $user );
597
					}
598
				}
599
			}
600
601
			// Subscription.
602
			$subscription = $payment->get_subscription();
603
604
			if ( null !== $subscription ) {
605
				$customer_id = $subscription->get_meta( 'mollie_customer_id' );
606
607
				if ( empty( $customer_id ) ) {
608
					$subscription->set_meta( 'mollie_customer_id', $mollie_customer->get_id() );
609
				}
610
611
				// Update mandate in subscription meta.
612
				$mollie_mandate_id = $mollie_payment->get_mandate_id();
613
614
				if ( null !== $mollie_mandate_id ) {
615
					$mandate_id = $subscription->get_meta( 'mollie_mandate_id' );
616
617
					// Only update if no mandate has been set yet or if payment succeeded.
618
					if ( empty( $mandate_id ) || PaymentStatus::SUCCESS === $payment->get_status() ) {
619
						$this->update_subscription_mandate( $subscription, $mollie_mandate_id, $payment->get_payment_method() );
620
					}
621
				}
622
			}
623
		}
624
625
		// phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase -- Mollie JSON object.
626
		$mollie_payment_details = $mollie_payment->get_details();
627
628
		if ( null !== $mollie_payment_details ) {
629
			$consumer_bank_details = $payment->get_consumer_bank_details();
630
631
			if ( null === $consumer_bank_details ) {
632
				$consumer_bank_details = new BankAccountDetails();
633
634
				$payment->set_consumer_bank_details( $consumer_bank_details );
635
			}
636
637
			if ( isset( $mollie_payment_details->consumerName ) ) {
638
				$consumer_bank_details->set_name( $mollie_payment_details->consumerName );
639
			}
640
641
			if ( isset( $mollie_payment_details->cardHolder ) ) {
642
				$consumer_bank_details->set_name( $mollie_payment_details->cardHolder );
643
			}
644
645
			if ( isset( $mollie_payment_details->cardNumber ) ) {
646
				// The last four digits of the card number.
647
				$consumer_bank_details->set_account_number( $mollie_payment_details->cardNumber );
648
			}
649
650
			if ( isset( $mollie_payment_details->cardCountryCode ) ) {
651
				// The ISO 3166-1 alpha-2 country code of the country the card was issued in.
652
				$consumer_bank_details->set_country( $mollie_payment_details->cardCountryCode );
653
			}
654
655
			if ( isset( $mollie_payment_details->consumerAccount ) ) {
656
				switch ( $mollie_payment->get_method() ) {
657
					case Methods::BELFIUS:
658
					case Methods::DIRECT_DEBIT:
659
					case Methods::IDEAL:
660
					case Methods::KBC:
661
					case Methods::SOFORT:
662
						$consumer_bank_details->set_iban( $mollie_payment_details->consumerAccount );
663
664
						break;
665
					case Methods::BANCONTACT:
666
					case Methods::BANKTRANSFER:
667
					case Methods::PAYPAL:
668
					default:
669
						$consumer_bank_details->set_account_number( $mollie_payment_details->consumerAccount );
670
671
						break;
672
				}
673
			}
674
675
			if ( isset( $mollie_payment_details->consumerBic ) ) {
676
				$consumer_bank_details->set_bic( $mollie_payment_details->consumerBic );
677
			}
678
679
			/*
680
			 * Failure reason.
681
			 */
682
			$failure_reason = $payment->get_failure_reason();
683
684
			if ( null === $failure_reason ) {
685
				$failure_reason = new FailureReason();
686
			}
687
688
			// SEPA Direct Debit.
689
			if ( isset( $mollie_payment_details->bankReasonCode ) ) {
690
				$failure_reason->set_code( $mollie_payment_details->bankReasonCode );
691
			}
692
693
			if ( isset( $mollie_payment_details->bankReason ) ) {
694
				$failure_reason->set_message( $mollie_payment_details->bankReason );
695
			}
696
697
			// Credit card.
698
			if ( isset( $mollie_payment_details->failureReason ) ) {
699
				$failure_reason->set_code( $mollie_payment_details->failureReason );
700
			}
701
702
			if ( isset( $mollie_payment_details->failureMessage ) ) {
703
				$failure_reason->set_message( $mollie_payment_details->failureMessage );
704
			}
705
706
			$failure_code    = $failure_reason->get_code();
707
			$failure_message = $failure_reason->get_message();
708
709
			if ( ! empty( $failure_code ) || ! empty( $failure_message ) ) {
710
				$payment->set_failure_reason( $failure_reason );
711
			}
712
		}
713
714
		$links = $mollie_payment->get_links();
715
716
		// Change payment state URL.
717
		if ( \property_exists( $links, 'changePaymentState' ) ) {
718
			$payment->set_meta( 'mollie_change_payment_state_url', $links->changePaymentState->href );
719
		}
720
721
		// phpcs:enable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase -- Mollie JSON object.
722
723
		if ( $mollie_payment->has_chargebacks() ) {
724
			$mollie_chargebacks = $this->client->get_payment_chargebacks(
725
				$mollie_payment->get_id(),
726
				array( 'limit' => 1 )
727
			);
728
729
			$mollie_chargeback = \reset( $mollie_chargebacks );
730
731
			if ( false !== $mollie_chargeback ) {
732
				$subscriptions = array_filter(
733
					$payment->get_subscriptions(),
734
					function( $subscription ) {
735
						return SubscriptionStatus::ACTIVE === $subscription->get_status();
736
					}
737
				);
738
739
				foreach ( $subscriptions as $subscription ) {
740
					if ( $mollie_chargeback->get_created_at() > $subscription->get_activated_at() ) {
741
						$subscription->set_status( SubscriptionStatus::ON_HOLD );
742
743
						$subscription->add_note(
744
							\sprintf(
745
								/* translators: 1: Mollie chargeback ID, 2: Mollie payment ID */
746
								\__( 'Subscription put on hold due to chargeback `%1$s` of payment `%2$s`.', 'pronamic_ideal' ),
747
								\esc_html( $mollie_chargeback->get_id() ),
748
								\esc_html( $mollie_payment->get_id() )
749
							)
750
						);
751
752
						$subscription->save();
753
					}
754
				}
755
			}
756
		}
757
758
		// Refunds.
759
		$amount_refunded = $mollie_payment->get_amount_refunded();
760
761
		if ( null !== $amount_refunded ) {
762
			$refunded_amount = new Money( $amount_refunded->get_value(), $amount_refunded->get_currency() );
763
764
			$payment->set_refunded_amount( $refunded_amount->get_value() > 0 ? $refunded_amount : null );
765
		}
766
	}
767
768
	/**
769
	 * Update subscription mandate.
770
	 *
771
	 * @param Subscription $subscription   Subscription.
772
	 * @param string       $mandate_id     Mollie mandate ID.
773
	 * @param string|null  $payment_method Payment method.
774
	 * @return void
775
	 * @throws \Exception Throws exception if subscription note could not be added.
776
	 */
777
	public function update_subscription_mandate( Subscription $subscription, $mandate_id, $payment_method = null ) {
778
		$customer_id = (string) $subscription->get_meta( 'mollie_customer_id' );
779
780
		$mandate = $this->client->get_mandate( $mandate_id, $customer_id );
781
782
		if ( ! \is_object( $mandate ) ) {
783
			return;
784
		}
785
786
		// Update mandate.
787
		$old_mandate_id = $subscription->get_meta( 'mollie_mandate_id' );
788
789
		$subscription->set_meta( 'mollie_mandate_id', $mandate_id );
790
791
		if ( ! empty( $old_mandate_id ) && $old_mandate_id !== $mandate_id ) {
792
			$note = \sprintf(
793
			/* translators: 1: old mandate ID, 2: new mandate ID */
794
				\__( 'Mandate for subscription changed from "%1$s" to "%2$s".', 'pronamic_ideal' ),
795
				\esc_html( $old_mandate_id ),
796
				\esc_html( $mandate_id )
797
			);
798
799
			$subscription->add_note( $note );
800
		}
801
802
		// Update payment method.
803
		$old_method = $subscription->get_payment_method();
0 ignored issues
show
Bug introduced by
The method get_payment_method() does not exist on Pronamic\WordPress\Pay\Subscriptions\Subscription. Did you maybe mean get_payments()? ( Ignorable by Annotation )

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

803
		/** @scrutinizer ignore-call */ 
804
  $old_method = $subscription->get_payment_method();

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...
804
		$new_method = ( null === $payment_method && \property_exists( $mandate, 'method' ) ? Methods::transform_gateway_method( $mandate->method ) : $payment_method );
805
806
		// `Direct Debit` is not a recurring method, use `Direct Debit (mandate via ...)` instead.
807
		if ( PaymentMethods::DIRECT_DEBIT === $new_method ) {
808
			$new_method = PaymentMethods::DIRECT_DEBIT_IDEAL;
809
810
			// Use `Direct Debit (mandate via Bancontact)` if consumer account starts with `BE`.
811
			if ( \property_exists( $mandate, 'details' ) && 'BE' === \substr( $mandate->details->consumerAccount, 0, 2 ) ) {
812
				$new_method = PaymentMethods::DIRECT_DEBIT_BANCONTACT;
813
			}
814
		}
815
816
		if ( ! empty( $old_method ) && $old_method !== $new_method ) {
817
			$subscription->set_payment_method( $new_method );
0 ignored issues
show
Bug introduced by
The method set_payment_method() does not exist on Pronamic\WordPress\Pay\Subscriptions\Subscription. ( Ignorable by Annotation )

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

817
			$subscription->/** @scrutinizer ignore-call */ 
818
                  set_payment_method( $new_method );

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