Test Failed
Push — master ( 417a33...505c40 )
by Reüel
16:46 queued 03:16
created

Gateway::update_subscription_mandate()   B

Complexity

Conditions 9
Paths 25

Size

Total Lines 54
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 20
CRAP Score 9

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 9
eloc 26
nc 25
nop 3
dl 0
loc 54
ccs 20
cts 20
cp 1
crap 9
rs 8.0555
c 1
b 0
f 0

How to fix   Long Method   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
/**
3
 * Mollie gateway.
4
 *
5
 * @author    Pronamic <[email protected]>
6
 * @copyright 2005-2020 Pronamic
7
 * @license   GPL-3.0-or-later
8
 * @package   Pronamic\WordPress\Pay
9
 */
10
11
namespace Pronamic\WordPress\Pay\Gateways\Mollie;
12
13
use Pronamic\WordPress\DateTime\DateTime;
14
use Pronamic\WordPress\Pay\Banks\BankAccountDetails;
15
use Pronamic\WordPress\Pay\Banks\BankTransferDetails;
16
use Pronamic\WordPress\Pay\Core\Gateway as Core_Gateway;
17
use Pronamic\WordPress\Pay\Core\PaymentMethods;
18
use Pronamic\WordPress\Pay\Payments\FailureReason;
19
use Pronamic\WordPress\Pay\Payments\Payment;
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 39
	 */
60 39
	public function __construct( Config $config ) {
61
		parent::__construct( $config );
62 39
63
		$this->set_method( self::METHOD_HTTP_REDIRECT );
64
65 39
		// Supported features.
66
		$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 39
		// Client.
77
		$this->client = new Client( \strval( $config->api_key ) );
78
79 39
		// Data Stores.
80 39
		$this->profile_data_store  = new ProfileDataStore();
81
		$this->customer_data_store = new CustomerDataStore();
82
83 39
		// Actions.
84 39
		add_action( 'pronamic_payment_status_update', array( $this, 'copy_customer_id_to_wp_user' ), 99, 1 );
85
	}
86
87
	/**
88
	 * Get issuers
89
	 *
90
	 * @see Core_Gateway::get_issuers()
91
	 * @return array<int, array<string, array<string>>>
92 3
	 */
93 3
	public function get_issuers() {
94
		$groups = array();
95
96 3
		try {
97
			$result = $this->client->get_issuers();
98
99
			$groups[] = array(
100
				'options' => $result,
101 3
			);
102
		} catch ( Error $e ) {
103 3
			// Catch Mollie error.
104 3
			$error = new \WP_Error(
105 3
				'mollie_error',
106
				sprintf( '%1$s (%2$s) - %3$s', $e->get_title(), $e->getCode(), $e->get_detail() )
107
			);
108 3
109
			$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 3
117
		return $groups;
118
	}
119
120
	/**
121
	 * Get available payment methods.
122
	 *
123
	 * @see Core_Gateway::get_available_payment_methods()
124
	 * @return array<string>
125 2
	 */
126 2
	public function get_available_payment_methods() {
127
		$payment_methods = array();
128
129 2
		// Set sequence types to get payment methods for.
130
		$sequence_types = array( Sequence::ONE_OFF, Sequence::RECURRING, Sequence::FIRST );
131 2
132
		$results = array();
133 2
134
		foreach ( $sequence_types as $sequence_type ) {
135
			// Get active payment methods for Mollie account.
136 2
			try {
137 2
				$result = $this->client->get_payment_methods( $sequence_type );
138
			} 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 2
				break;
148
			} catch ( \Exception $e ) {
149 2
				// Catch exceptions.
150
				$error = new \WP_Error( 'mollie_error', $e->getMessage() );
151 2
152
				$this->set_error( $error );
153 2
154
				break;
155
			}
156 2
157
			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 2
171 2
			if ( is_array( $result ) ) {
172
				$results = array_merge( $results, $result );
173
			}
174
		}
175
176 2
		// Transform to WordPress payment methods.
177 2
		foreach ( $results as $method => $title ) {
178
			if ( PaymentMethods::is_recurring_method( $method ) ) {
179
				$payment_method = $method;
180 2
			} else {
181
				$payment_method = Methods::transform_gateway_method( $method );
182
			}
183 2
184 2
			if ( $payment_method ) {
185
				$payment_methods[] = $payment_method;
186
			}
187
		}
188 2
189
		$payment_methods = array_unique( $payment_methods );
190 2
191
		return $payment_methods;
192
	}
193
194
	/**
195
	 * Get supported payment methods
196
	 *
197
	 * @see Pronamic_WP_Pay_Gateway::get_supported_payment_methods()
198
	 * @return array<string>
199 2
	 */
200
	public function get_supported_payment_methods() {
201 2
		return array(
202
			PaymentMethods::APPLE_PAY,
203
			PaymentMethods::BANCONTACT,
204
			PaymentMethods::BANK_TRANSFER,
205
			PaymentMethods::BELFIUS,
206
			PaymentMethods::CREDIT_CARD,
207
			PaymentMethods::DIRECT_DEBIT,
208
			PaymentMethods::DIRECT_DEBIT_BANCONTACT,
209
			PaymentMethods::DIRECT_DEBIT_IDEAL,
210
			PaymentMethods::DIRECT_DEBIT_SOFORT,
211
			PaymentMethods::EPS,
212
			PaymentMethods::GIROPAY,
213
			PaymentMethods::IDEAL,
214
			PaymentMethods::KBC,
215
			PaymentMethods::PAYPAL,
216
			PaymentMethods::SOFORT,
217
		);
218
	}
219
220
	/**
221
	 * Get webhook URL for Mollie.
222
	 *
223
	 * @return string|null
224 4
	 */
225 4
	public function get_webhook_url() {
226
		$url = \rest_url( Integration::REST_ROUTE_NAMESPACE . '/webhook' );
227 4
228
		$host = wp_parse_url( $url, PHP_URL_HOST );
229 4
230
		if ( is_array( $host ) ) {
231
			// Parsing failure.
232
			$host = '';
233
		}
234 4
235
		if ( 'localhost' === $host ) {
236 1
			// Mollie doesn't allow localhost.
237 3
			return null;
238
		} elseif ( '.dev' === substr( $host, -4 ) ) {
239 1
			// Mollie doesn't allow the .dev TLD.
240 2
			return null;
241
		} elseif ( '.local' === substr( $host, -6 ) ) {
242 1
			// Mollie doesn't allow the .local TLD.
243 1
			return null;
244
		} elseif ( '.test' === substr( $host, -5 ) ) {
245
			// Mollie doesn't allow the .test TLD.
246
			return null;
247
		}
248 1
249
		return $url;
250
	}
251
252
	/**
253
	 * Start
254
	 *
255
	 * @see Pronamic_WP_Pay_Gateway::start()
256
	 * @param Payment $payment Payment.
257
	 * @return void
258
	 */
259
	public function start( Payment $payment ) {
260
		$request = new PaymentRequest(
261
			AmountTransformer::transform( $payment->get_total_amount() ),
262
			\strval( $payment->get_description() )
263
		);
264
265
		$request->redirect_url = $payment->get_return_url();
266
		$request->webhook_url  = $this->get_webhook_url();
267
268
		// Locale.
269
		$customer = $payment->get_customer();
270
271
		if ( null !== $customer ) {
272
			$request->locale = LocaleHelper::transform( $customer->get_locale() );
273
		}
274
275
		// Customer ID.
276
		$customer_id = $this->get_customer_id_for_payment( $payment );
277
278
		if ( null === $customer_id ) {
279
			$customer_id = $this->create_customer_for_payment( $payment );
280
		}
281
282
		if ( null !== $customer_id ) {
283
			$request->customer_id = $customer_id;
284
		}
285
286
		// Payment method.
287
		$payment_method = $payment->get_method();
288
289
		// Recurring payment method.
290
		$subscription = $payment->get_subscription();
291
292
		$is_recurring_method = ( $subscription && PaymentMethods::is_recurring_method( $payment_method ) );
293
294
		// Consumer bank details.
295
		$consumer_bank_details = $payment->get_consumer_bank_details();
296
297
		if ( PaymentMethods::DIRECT_DEBIT === $payment_method && null !== $consumer_bank_details ) {
298
			$consumer_name = $consumer_bank_details->get_name();
299
			$consumer_iban = $consumer_bank_details->get_iban();
300
301
			$request->consumer_name    = $consumer_name;
302
			$request->consumer_account = $consumer_iban;
303
304
			// Check if one-off SEPA Direct Debit can be used, otherwise short circuit payment.
305
			if ( null !== $customer_id ) {
306
				// Find or create mandate.
307
				$mandate_id = $this->client->has_valid_mandate( $customer_id, PaymentMethods::DIRECT_DEBIT, $consumer_iban );
308
309
				if ( false === $mandate_id ) {
310
					$mandate = $this->client->create_mandate( $customer_id, $consumer_bank_details );
311
312
					$mandate_id = $mandate->id;
313
				}
314
315
				// Charge immediately on-demand.
316
				$request->set_sequence_type( Sequence::RECURRING );
317
				$request->set_mandate_id( $mandate_id );
318
319
				$is_recurring_method = true;
320
321
				$payment->recurring = true;
322
			}
323
		}
324
325
		if ( false === $is_recurring_method ) {
326
			// Always use 'direct debit mandate via iDEAL/Bancontact/Sofort' payment methods as recurring method.
327
			$is_recurring_method = PaymentMethods::is_direct_debit_method( $payment_method );
328
		}
329
330
		if ( $is_recurring_method ) {
331
			$request->sequence_type = $payment->get_recurring() ? Sequence::RECURRING : Sequence::FIRST;
332
333
			if ( Sequence::FIRST === $request->sequence_type ) {
334
				$payment_method = PaymentMethods::get_first_payment_method( $payment_method );
335
			}
336
337
			if ( Sequence::RECURRING === $request->sequence_type ) {
338
				// Use mandate from subscription.
339
				if ( $subscription && empty( $request->mandate_id ) ) {
340
					$request->set_mandate_id( $subscription->get_meta( 'mollie_mandate_id' ) );
0 ignored issues
show
Bug introduced by
It seems like $subscription->get_meta('mollie_mandate_id') can also be of type false; however, parameter $mandate_id of Pronamic\WordPress\Pay\G...quest::set_mandate_id() does only seem to accept null|string, maybe add an additional type check? ( Ignorable by Annotation )

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

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