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