Failed Conditions
Push — develop ( 1045a8...04bbf4 )
by Reüel
03:59
created

Gateway::update_subscription_mandate()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 17
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 8
nc 1
nop 2
dl 0
loc 17
ccs 0
cts 9
cp 0
crap 2
rs 10
c 0
b 0
f 0
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.3
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( \strval( $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<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
			if ( PaymentMethods::is_recurring_method( $method ) ) {
179
				$payment_method = $method;
180
			} else {
181 2
				$payment_method = Methods::transform_gateway_method( $method );
182
			}
183
184 2
			if ( $payment_method ) {
185 2
				$payment_methods[] = $payment_method;
186
			}
187
		}
188
189 2
		$payment_methods = array_unique( $payment_methods );
190
191 2
		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
	 */
200 2
	public function get_supported_payment_methods() {
201
		return array(
202 2
			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
	 */
225 4
	public function get_webhook_url() {
226 4
		$url = \rest_url( Integration::REST_ROUTE_NAMESPACE . '/webhook' );
227
228 4
		$host = wp_parse_url( $url, PHP_URL_HOST );
229
230 4
		if ( is_array( $host ) ) {
231
			// Parsing failure.
232
			$host = '';
233
		}
234
235 4
		if ( 'localhost' === $host ) {
236
			// Mollie doesn't allow localhost.
237 1
			return null;
238 3
		} elseif ( '.dev' === substr( $host, -4 ) ) {
239
			// Mollie doesn't allow the .dev TLD.
240 1
			return null;
241 2
		} elseif ( '.local' === substr( $host, -6 ) ) {
242
			// Mollie doesn't allow the .local TLD.
243 1
			return null;
244 1
		} elseif ( '.test' === substr( $host, -5 ) ) {
245
			// Mollie doesn't allow the .test TLD.
246
			return null;
247
		}
248
249 1
		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->sequence_type = Sequence::RECURRING;
317
				$request->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->mandate_id = $subscription->get_meta( 'mollie_mandate_id' );
0 ignored issues
show
Documentation Bug introduced by
It seems like $subscription->get_meta('mollie_mandate_id') can also be of type false. However, the property $mandate_id 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...
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
				if ( isset( $mollie_payment->mandateId ) ) {
573
					$mandate_id = $subscription->get_meta( 'mollie_mandate_id' );
574
575
					// Only update if no mandate has been set yet or if payment succeeded.
576
					if ( empty( $mandate_id ) || PaymentStatus::SUCCESS === $payment->get_status() ) {
577
						$this->update_subscription_mandate( $subscription, $mollie_payment->mandateId );
578
					}
579
				}
580
			}
581
		}
582
583
		if ( isset( $mollie_payment->details ) ) {
584
			$consumer_bank_details = $payment->get_consumer_bank_details();
585
586
			if ( null === $consumer_bank_details ) {
587
				$consumer_bank_details = new BankAccountDetails();
588
589
				$payment->set_consumer_bank_details( $consumer_bank_details );
590
			}
591
592
			$details = $mollie_payment->details;
593
594
			/*
595
			 * @codingStandardsIgnoreStart
596
			 *
597
			 * Ignore coding standards because of sniff WordPress.NamingConventions.ValidVariableName.NotSnakeCaseMemberVar
598
			 */
599
			if ( isset( $details->consumerName ) ) {
600
				$consumer_bank_details->set_name( $details->consumerName );
601
			}
602
603
			if ( isset( $details->cardHolder ) ) {
604
				$consumer_bank_details->set_name( $details->cardHolder );
605
			}
606
607
			if ( isset( $details->cardNumber ) ) {
608
				// The last four digits of the card number.
609
				$consumer_bank_details->set_account_number( $details->cardNumber );
610
			}
611
612
			if ( isset( $details->cardCountryCode ) ) {
613
				// The ISO 3166-1 alpha-2 country code of the country the card was issued in.
614
				$consumer_bank_details->set_country( $details->cardCountryCode );
615
			}
616
617
			if ( isset( $details->consumerAccount ) ) {
618
				switch ( $mollie_payment->method ) {
619
					case Methods::BELFIUS:
620
					case Methods::DIRECT_DEBIT:
621
					case Methods::IDEAL:
622
					case Methods::KBC:
623
					case Methods::SOFORT:
624
						$consumer_bank_details->set_iban( $details->consumerAccount );
625
626
						break;
627
					case Methods::BANCONTACT:
628
					case Methods::BANKTRANSFER:
629
					case Methods::PAYPAL:
630
					default:
631
						$consumer_bank_details->set_account_number( $details->consumerAccount );
632
633
						break;
634
				}
635
			}
636
637
			if ( isset( $details->consumerBic ) ) {
638
				$consumer_bank_details->set_bic( $details->consumerBic );
639
			}
640
641
			/*
642
			 * Failure reason.
643
			 */
644
			$failure_reason = $payment->get_failure_reason();
645
646
			if ( null === $failure_reason ) {
647
				$failure_reason = new FailureReason();
648
649
				$payment->set_failure_reason( $failure_reason );
650
			}
651
652
			// SEPA Direct Debit.
653
			if ( isset( $details->bankReasonCode ) ) {
654
				$failure_reason->set_code( $details->bankReasonCode );
655
			}
656
657
			if ( isset( $details->bankReasonCode ) ) {
658
				$failure_reason->set_message( $details->bankReason );
659
			}
660
661
			// Credit card.
662
			if ( isset( $details->failureReason ) ) {
663
				$failure_reason->set_code( $details->failureReason );
664
			}
665
666
			if ( isset( $details->failureMessage ) ) {
667
				$failure_reason->set_message( $details->failureMessage );
668
			}
669
			// @codingStandardsIgnoreEnd
670
		}
671
	}
672
673
	/**
674
	 * Update subscription mandate.
675
	 *
676
	 * @param Subscription $subscrption Subscription.
677
	 * @param string       $mandate_id  Mollie mandate ID.
678
	 * @return void
679
	 */
680
	public function update_subscription_mandate( Subscription $subscription, $mandate_id ) {
681
		$old_mandate_id = $subscription->get_meta( 'mollie_mandate_id' );
682
683
		// Update meta.
684
		$subscription->set_meta( 'mollie_mandate_id', $mandate_id );
685
686
		$subscription->save();
687
688
		// Add note.
689
		$note = \sprintf(
690
			/* translators: 1: old mandate ID, 2: new mandate ID */
691
			\__( 'Mandate for subscription changed from "%1$s" to "%2$s".', 'pronamic_ideal' ),
692
			\esc_html( $old_mandate_id ),
0 ignored issues
show
Bug introduced by
It seems like $old_mandate_id can also be of type false; however, parameter $text of esc_html() does only seem to accept 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

692
			\esc_html( /** @scrutinizer ignore-type */ $old_mandate_id ),
Loading history...
693
			\esc_html( $mandate_id )
694
		);
695
696
		$subscription->add_note( $note );
697
	}
698
699
	/**
700
	 * Get Mollie customer ID for payment.
701
	 *
702
	 * @param Payment $payment Payment.
703
	 * @return string|null
704
	 */
705 10
	public function get_customer_id_for_payment( Payment $payment ) {
706 10
		$customer_ids = $this->get_customer_ids_for_payment( $payment );
707
708 10
		$customer_id = $this->get_first_existing_customer_id( $customer_ids );
709
710 10
		return $customer_id;
711
	}
712
713
	/**
714
	 * Get Mollie customers for the specified payment.
715
	 *
716
	 * @param Payment $payment Payment.
717
	 * @return array<string>
718
	 */
719 10
	private function get_customer_ids_for_payment( Payment $payment ) {
720 10
		$customer_ids = array();
721
722
		// Customer ID from subscription meta.
723 10
		$subscription = $payment->get_subscription();
724
725 10
		if ( null !== $subscription ) {
726 10
			$customer_id = $this->get_customer_id_for_subscription( $subscription );
727
728 10
			if ( null !== $customer_id ) {
729 4
				$customer_ids[] = $customer_id;
730
			}
731
		}
732
733
		// Customer ID from WordPress user.
734 10
		$customer = $payment->get_customer();
735
736 10
		if ( null !== $customer ) {
737 9
			$user_id = $customer->get_user_id();
738
739 9
			if ( ! empty( $user_id ) ) {
740 7
				$user_customer_ids = $this->get_customer_ids_for_user( $user_id );
741
742 7
				$customer_ids = \array_merge( $customer_ids, $user_customer_ids );
743
			}
744
		}
745
746 10
		return $customer_ids;
747
	}
748
749
	/**
750
	 * Get Mollie customers for the specified WordPress user ID.
751
	 *
752
	 * @param int $user_id WordPress user ID.
753
	 * @return array<string>
754
	 */
755 24
	public function get_customer_ids_for_user( $user_id ) {
756 24
		$customer_query = new CustomerQuery(
757
			array(
758 24
				'user_id' => $user_id,
759
			)
760
		);
761
762 24
		$customers = $customer_query->get_customers();
763
764 24
		$customer_ids = wp_list_pluck( $customers, 'mollie_id' );
765
766 24
		return $customer_ids;
767
	}
768
769
	/**
770
	 * Get customer ID for subscription.
771
	 *
772
	 * @param Subscription $subscription Subscription.
773
	 * @return string|null
774
	 */
775 10
	private function get_customer_id_for_subscription( Subscription $subscription ) {
776 10
		$customer_id = $subscription->get_meta( 'mollie_customer_id' );
777
778 10
		if ( empty( $customer_id ) ) {
779
			// Try to get (legacy) customer ID from first payment.
780 7
			$first_payment = $subscription->get_first_payment();
781
782 7
			if ( null !== $first_payment ) {
783 7
				$customer_id = $first_payment->get_meta( 'mollie_customer_id' );
784
			}
785
		}
786
787 10
		if ( empty( $customer_id ) ) {
788 6
			return null;
789
		}
790
791 4
		return $customer_id;
792
	}
793
794
	/**
795
	 * Get first existing customer from customers list.
796
	 *
797
	 * @param array<string> $customer_ids Customers.
798
	 * @return string|null
799
	 */
800 10
	private function get_first_existing_customer_id( $customer_ids ) {
801 10
		$customer_ids = \array_filter( $customer_ids );
802
803 10
		$customer_ids = \array_unique( $customer_ids );
804
805 10
		foreach ( $customer_ids as $customer_id ) {
806 4
			$customer = $this->client->get_customer( $customer_id );
807
808 4
			if ( null !== $customer ) {
809 4
				return $customer_id;
810
			}
811
		}
812
813 6
		return null;
814
	}
815
816
	/**
817
	 * Create customer for payment.
818
	 *
819
	 * @param Payment $payment Payment.
820
	 * @return string|null
821
	 * @throws Error Throws Error when Mollie error occurs.
822
	 */
823
	private function create_customer_for_payment( Payment $payment ) {
824
		$mollie_customer = new Customer();
825
		$mollie_customer->set_mode( $this->config->is_test_mode() ? 'test' : 'live' );
826
		$mollie_customer->set_email( $payment->get_email() );
827
828
		$pronamic_customer = $payment->get_customer();
829
830
		if ( null !== $pronamic_customer ) {
831
			// Name.
832
			$name = \strval( $pronamic_customer->get_name() );
833
834
			if ( '' !== $name ) {
835
				$mollie_customer->set_name( $name );
836
			}
837
838
			// Locale.
839
			$locale = $pronamic_customer->get_locale();
840
841
			if ( null !== $locale ) {
842
				$mollie_customer->set_locale( LocaleHelper::transform( $locale ) );
843
			}
844
		}
845
846
		// Try to get name from consumer bank details.
847
		$consumer_bank_details = $payment->get_consumer_bank_details();
848
849
		if ( null === $mollie_customer->get_name() && null !== $consumer_bank_details ) {
850
			$name = $consumer_bank_details->get_name();
851
852
			if ( null !== $name ) {
853
				$mollie_customer->set_name( $name );
854
			}
855
		}
856
857
		// Create customer.
858
		$mollie_customer = $this->client->create_customer( $mollie_customer );
859
860
		$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...
861
862
		// Connect to user.
863
		if ( null !== $pronamic_customer ) {
864
			$user_id = $pronamic_customer->get_user_id();
865
866
			if ( null !== $user_id ) {
867
				$user = \get_user_by( 'id', $user_id );
868
869
				if ( false !== $user ) {
870
					$this->customer_data_store->connect_mollie_customer_to_wp_user( $mollie_customer, $user );
871
				}
872
			}
873
		}
874
875
		// Store customer ID in subscription meta.
876
		$subscription = $payment->get_subscription();
877
878
		if ( null !== $subscription ) {
879
			$subscription->set_meta( 'mollie_customer_id', $mollie_customer->get_id() );
880
		}
881
882
		return $mollie_customer->get_id();
883
	}
884
885
	/**
886
	 * Copy Mollie customer ID from subscription meta to WordPress user meta.
887
	 *
888
	 * @param Payment $payment Payment.
889
	 * @return void
890
	 */
891 27
	public function copy_customer_id_to_wp_user( Payment $payment ) {
892 27
		if ( $this->config->id !== $payment->config_id ) {
893 1
			return;
894
		}
895
896
		// Subscription.
897 26
		$subscription = $payment->get_subscription();
898
899
		// Customer.
900 26
		$customer = $payment->get_customer();
901
902 26
		if ( null === $customer && null !== $subscription ) {
903 17
			$customer = $subscription->get_customer();
904
		}
905
906 26
		if ( null === $customer ) {
907 1
			return;
908
		}
909
910
		// WordPress user.
911 25
		$user_id = $customer->get_user_id();
912
913 25
		if ( null === $user_id ) {
914 2
			return;
915
		}
916
917 23
		$user = \get_user_by( 'id', $user_id );
918
919 23
		if ( false === $user ) {
920 12
			return;
921
		}
922
923
		// Customer IDs.
924 11
		$customer_ids = array();
925
926
		// Payment.
927 11
		$customer_ids[] = $payment->get_meta( 'mollie_customer_id' );
928
929
		// Subscription.
930 11
		if ( null !== $subscription ) {
931 11
			$customer_ids[] = $subscription->get_meta( 'mollie_customer_id' );
932
		}
933
934
		// Connect.
935 11
		$customer_ids = \array_filter( $customer_ids );
936 11
		$customer_ids = \array_unique( $customer_ids );
937
938 11
		foreach ( $customer_ids as $customer_id ) {
939 2
			$customer = new Customer( $customer_id );
940
941 2
			$this->customer_data_store->get_or_insert_customer( $customer );
942
943 2
			$this->customer_data_store->connect_mollie_customer_to_wp_user( $customer, $user );
944
		}
945 11
	}
946
}
947