Passed
Push — master ( d64864...1c1fa7 )
by Remco
04:12 queued 02:11
created

Gateway::copy_customer_id_to_wp_user()   A

Complexity

Conditions 5
Paths 4

Size

Total Lines 19
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 9
dl 0
loc 19
rs 9.6111
c 0
b 0
f 0
cc 5
nc 4
nop 1
1
<?php
2
3
namespace Pronamic\WordPress\Pay\Gateways\Mollie;
4
5
use Pronamic\WordPress\Pay\Core\Gateway as Core_Gateway;
6
use Pronamic\WordPress\Pay\Core\PaymentMethods;
7
use Pronamic\WordPress\Pay\Core\Recurring as Core_Recurring;
8
use Pronamic\WordPress\Pay\Core\Statuses as Core_Statuses;
9
use Pronamic\WordPress\Pay\Payments\Payment;
10
11
/**
12
 * Title: Mollie
13
 * Description:
14
 * Copyright: Copyright (c) 2005 - 2018
15
 * Company: Pronamic
16
 *
17
 * @author  Remco Tolsma
18
 * @version 2.0.4
19
 * @since   1.1.0
20
 */
21
class Gateway extends Core_Gateway {
22
	/**
23
	 * Slug of this gateway
24
	 *
25
	 * @var string
26
	 */
27
	const SLUG = 'mollie';
28
29
	/**
30
	 * Meta key for customer ID.
31
	 *
32
	 * @var string
33
	 */
34
	private $meta_key_customer_id = '_pronamic_pay_mollie_customer_id';
35
36
	/**
37
	 * Constructs and initializes an Mollie gateway
38
	 *
39
	 * @param Config $config
40
	 */
41
	public function __construct( Config $config ) {
42
		parent::__construct( $config );
43
44
		$this->supports = array(
45
			'payment_status_request',
46
			'recurring_direct_debit',
47
			'recurring_credit_card',
48
			'recurring',
49
		);
50
51
		$this->set_method( Core_Gateway::METHOD_HTTP_REDIRECT );
52
		$this->set_has_feedback( true );
53
		$this->set_amount_minimum( 1.20 );
54
		$this->set_slug( self::SLUG );
55
56
		$this->client = new Client( $config->api_key );
0 ignored issues
show
Bug Best Practice introduced by
The property client does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
57
		$this->client->set_mode( $config->mode );
58
59
		if ( 'test' === $config->mode ) {
60
			$this->meta_key_customer_id = '_pronamic_pay_mollie_customer_id_test';
61
		}
62
63
		// Actions.
64
		add_action( 'pronamic_payment_status_update', array( $this, 'copy_customer_id_to_wp_user' ), 99, 1 );
0 ignored issues
show
Bug introduced by
The function add_action was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

64
		/** @scrutinizer ignore-call */ 
65
  add_action( 'pronamic_payment_status_update', array( $this, 'copy_customer_id_to_wp_user' ), 99, 1 );
Loading history...
65
	}
66
67
	/**
68
	 * Get issuers
69
	 *
70
	 * @see Pronamic_WP_Pay_Gateway::get_issuers()
71
	 */
72
	public function get_issuers() {
73
		$groups = array();
74
75
		$result = $this->client->get_issuers();
76
77
		if ( ! $result ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $result of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
78
			$this->error = $this->client->get_error();
79
80
			return $groups;
81
		}
82
83
		$groups[] = array(
84
			'options' => $result,
85
		);
86
87
		return $groups;
88
	}
89
90
	/**
91
	 * Get available payment methods.
92
	 *
93
	 * @see Core_Gateway::get_available_payment_methods()
94
	 */
95
	public function get_available_payment_methods() {
96
		$payment_methods = array();
97
98
		// Set recurring types to get payment methods for.
99
		$recurring_types = array( null, Recurring::RECURRING, Recurring::FIRST );
100
101
		$results = array();
102
103
		foreach ( $recurring_types as $recurring_type ) {
104
			// Get active payment methods for Mollie account.
105
			$result = $this->client->get_payment_methods( $recurring_type );
106
107
			if ( ! $result ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $result of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
108
				$this->error = $this->client->get_error();
109
110
				break;
111
			}
112
113
			if ( Recurring::FIRST === $recurring_type ) {
114
				foreach ( $result as $method => $title ) {
115
					unset( $result[ $method ] );
116
117
					// Get WordPress payment method for direct debit method.
118
					$method         = Methods::transform_gateway_method( $method );
119
					$payment_method = array_search( $method, PaymentMethods::get_recurring_methods(), true );
120
121
					if ( $payment_method ) {
122
						$results[ $payment_method ] = $title;
123
					}
124
				}
125
			}
126
127
			$results = array_merge( $results, $result );
128
		}
129
130
		// Transform to WordPress payment methods.
131
		foreach ( $results as $method => $title ) {
132
			if ( PaymentMethods::is_recurring_method( $method ) ) {
133
				$payment_method = $method;
134
			} else {
135
				$payment_method = Methods::transform_gateway_method( $method );
136
			}
137
138
			if ( $payment_method ) {
139
				$payment_methods[] = $payment_method;
140
			}
141
		}
142
143
		$payment_methods = array_unique( $payment_methods );
144
145
		return $payment_methods;
146
	}
147
148
	/**
149
	 * Get supported payment methods
150
	 *
151
	 * @see Pronamic_WP_Pay_Gateway::get_supported_payment_methods()
152
	 */
153
	public function get_supported_payment_methods() {
154
		return array(
155
			PaymentMethods::BANCONTACT,
156
			PaymentMethods::BANK_TRANSFER,
157
			PaymentMethods::BELFIUS,
158
			PaymentMethods::BITCOIN,
159
			PaymentMethods::CREDIT_CARD,
160
			PaymentMethods::DIRECT_DEBIT,
161
			PaymentMethods::DIRECT_DEBIT_BANCONTACT,
162
			PaymentMethods::DIRECT_DEBIT_IDEAL,
163
			PaymentMethods::DIRECT_DEBIT_SOFORT,
164
			PaymentMethods::IDEAL,
165
			PaymentMethods::KBC,
166
			PaymentMethods::PAYPAL,
167
			PaymentMethods::SOFORT,
168
		);
169
	}
170
171
	/**
172
	 * Get webhook URL for Mollie.
173
	 *
174
	 * @return string
175
	 */
176
	private function get_webhook_url() {
177
		$url = home_url( '/' );
0 ignored issues
show
Bug introduced by
The function home_url was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

177
		$url = /** @scrutinizer ignore-call */ home_url( '/' );
Loading history...
178
179
		$host = wp_parse_url( $url, PHP_URL_HOST );
0 ignored issues
show
Bug introduced by
The function wp_parse_url was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

179
		$host = /** @scrutinizer ignore-call */ wp_parse_url( $url, PHP_URL_HOST );
Loading history...
180
181
		if ( 'localhost' === $host ) {
182
			// Mollie doesn't allow localhost.
183
			return null;
184
		} elseif ( '.dev' === substr( $host, -4 ) ) {
185
			// Mollie doesn't allow the .dev TLD.
186
			return null;
187
		} elseif ( '.local' === substr( $host, -6 ) ) {
188
			// Mollie doesn't allow the .local TLD.
189
			return null;
190
		}
191
192
		$url = add_query_arg( 'mollie_webhook', '', $url );
0 ignored issues
show
Bug introduced by
The function add_query_arg was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

192
		$url = /** @scrutinizer ignore-call */ add_query_arg( 'mollie_webhook', '', $url );
Loading history...
193
194
		return $url;
195
	}
196
197
	/**
198
	 * Start
199
	 *
200
	 * @see Pronamic_WP_Pay_Gateway::start()
201
	 */
202
	public function start( Payment $payment ) {
203
		$request = new PaymentRequest();
204
205
		$request->amount       = $payment->get_amount()->get_amount();
206
		$request->description  = $payment->get_description();
207
		$request->redirect_url = $payment->get_return_url();
208
		$request->webhook_url  = $this->get_webhook_url();
209
		$request->locale       = LocaleHelper::transform( $payment->get_language() );
210
211
		// Customer ID.
212
		$customer_id = $this->get_customer_id_for_payment( $payment );
213
214
		if ( ! empty( $customer_id ) ) {
215
			$request->customer_id = $customer_id;
0 ignored issues
show
Documentation Bug introduced by
It seems like $customer_id can also be of type boolean. However, the property $customer_id is declared as type 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...
216
		}
217
218
		// Payment method.
219
		$payment_method = $payment->get_method();
220
221
		// Subscription.
222
		$subscription = $payment->get_subscription();
223
224
		if ( $subscription && PaymentMethods::is_recurring_method( $payment_method ) ) {
225
			$request->recurring_type = $payment->get_recurring() ? Recurring::RECURRING : Recurring::FIRST;
226
227
			if ( Recurring::FIRST === $request->recurring_type ) {
228
				$payment_method = PaymentMethods::get_first_payment_method( $payment_method );
229
			}
230
231
			if ( Recurring::RECURRING === $request->recurring_type ) {
232
				$payment->set_action_url( $payment->get_return_url() );
233
			}
234
		}
235
236
		// Leap of faith if the WordPress payment method could not transform to a Mollie method?
237
		$request->method = Methods::transform( $payment_method, $payment_method );
238
239
		// Issuer.
240
		if ( Methods::IDEAL === $request->method ) {
241
			// If payment method is iDEAL we set the user chosen issuer ID.
242
			$request->issuer = $payment->get_issuer();
243
		}
244
245
		// Create payment.
246
		$result = $this->client->create_payment( $request );
247
248
		if ( ! $result ) {
249
			$this->error = $this->client->get_error();
250
251
			return false;
252
		}
253
254
		// Set transaction ID.
255
		if ( isset( $result->id ) ) {
256
			$payment->set_transaction_id( $result->id );
257
		}
258
259
		// Set status
260
		if ( isset( $result->status ) ) {
261
			$payment->set_status( Statuses::transform( $result->status ) );
262
		}
263
264
		// Set action URL.
265
		if ( isset( $result->links, $result->links->paymentUrl ) ) {
266
			$payment->set_action_url( $result->links->paymentUrl );
267
		}
268
	}
269
270
	/**
271
	 * Update status of the specified payment
272
	 *
273
	 * @param Payment $payment
274
	 */
275
	public function update_status( Payment $payment ) {
276
		$mollie_payment = $this->client->get_payment( $payment->get_transaction_id() );
277
278
		if ( ! $mollie_payment ) {
279
			$payment->set_status( Core_Statuses::FAILURE );
280
281
			$this->error = $this->client->get_error();
282
283
			return;
284
		}
285
286
		$payment->set_status( Statuses::transform( $mollie_payment->status ) );
287
288
		if ( isset( $mollie_payment->details ) ) {
289
			$details = $mollie_payment->details;
290
291
			if ( isset( $details->consumerName ) ) {
292
				$payment->set_consumer_name( $details->consumerName );
293
			}
294
295
			if ( isset( $details->cardHolder ) ) {
296
				$payment->set_consumer_name( $details->cardHolder );
297
			}
298
299
			if ( isset( $details->consumerAccount ) ) {
300
				$payment->set_consumer_iban( $details->consumerAccount );
301
			}
302
303
			if ( isset( $details->consumerBic ) ) {
304
				$payment->set_consumer_bic( $details->consumerBic );
305
			}
306
		}
307
	}
308
309
	/**
310
	 * Get Mollie customer ID for payment.
311
	 *
312
	 * @param Payment $payment Payment.
313
	 *
314
	 * @return bool|string
315
	 */
316
	private function get_customer_id_for_payment( Payment $payment ) {
317
		// Get Mollie customer ID from user meta.
318
		$customer_id = $this->get_customer_id_by_wp_user_id( $payment->user_id );
319
320
		$subscription = $payment->get_subscription();
321
322
		// Get customer ID from subscription meta.
323
		if ( $subscription ) {
0 ignored issues
show
introduced by
$subscription is of type Pronamic\WordPress\Pay\Subscriptions\Subscription, thus it always evaluated to true.
Loading history...
324
			$subscription_customer_id = $subscription->get_meta( 'mollie_customer_id' );
325
326
			// Try to get (legacy) customer ID from first payment.
327
			if ( empty( $subscription_customer_id ) && $subscription->get_first_payment() ) {
328
				$first_payment = $subscription->get_first_payment();
329
330
				$subscription_customer_id = $first_payment->get_meta( 'mollie_customer_id' );
331
			}
332
333
			if ( ! empty( $subscription_customer_id ) ) {
334
				$customer_id = $subscription_customer_id;
335
			}
336
		}
337
338
		// Create new customer if the customer does not exist at Mollie.
339
		if ( ( empty( $customer_id ) || ! $this->client->get_customer( $customer_id ) ) && Core_Recurring::RECURRING !== $payment->recurring_type ) {
340
			$customer_id = $this->client->create_customer( $payment->get_email(), $payment->get_customer_name() );
341
342
			$this->update_wp_user_customer_id( $payment->user_id, $customer_id );
0 ignored issues
show
Bug introduced by
It seems like $customer_id can also be of type array; however, parameter $customer_id of Pronamic\WordPress\Pay\G...e_wp_user_customer_id() 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

342
			$this->update_wp_user_customer_id( $payment->user_id, /** @scrutinizer ignore-type */ $customer_id );
Loading history...
343
		}
344
345
		// Store customer ID in subscription meta.
346
		if ( $subscription && empty( $subscription_customer_id ) && ! empty( $customer_id ) ) {
347
			$subscription->set_meta( 'mollie_customer_id', $customer_id );
348
		}
349
350
		// Copy customer ID from subscription to user meta.
351
		$this->copy_customer_id_to_wp_user( $payment );
352
353
		return $customer_id;
354
	}
355
356
	/**
357
	 * Get Mollie customer ID by the specified WordPress user ID.
358
	 *
359
	 * @param int $user_id WordPress user ID.
360
	 *
361
	 * @return string
362
	 */
363
	private function get_customer_id_by_wp_user_id( $user_id ) {
364
		if ( empty( $user_id ) ) {
365
			return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type string.
Loading history...
366
		}
367
368
		return get_user_meta( $user_id, $this->meta_key_customer_id, true );
0 ignored issues
show
Bug introduced by
The function get_user_meta was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

368
		return /** @scrutinizer ignore-call */ get_user_meta( $user_id, $this->meta_key_customer_id, true );
Loading history...
369
	}
370
371
	/**
372
	 * Update Mollie customer ID meta for WordPress user.
373
	 *
374
	 * @param int    $user_id     WordPress user ID.
375
	 * @param string $customer_id Mollie Customer ID.
376
	 *
377
	 * @return bool
378
	 */
379
	private function update_wp_user_customer_id( $user_id, $customer_id ) {
380
		if ( empty( $user_id ) || empty( $customer_id ) ) {
381
			return false;
382
		}
383
384
		update_user_meta( $user_id, $this->meta_key_customer_id, $customer_id );
0 ignored issues
show
Bug introduced by
The function update_user_meta was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

384
		/** @scrutinizer ignore-call */ 
385
  update_user_meta( $user_id, $this->meta_key_customer_id, $customer_id );
Loading history...
385
	}
386
387
	/**
388
	 * Copy Mollie customer ID from subscription meta to WordPress user meta.
389
	 *
390
	 * @param Payment $payment Payment.
391
	 *
392
	 * @return void
393
	 */
394
	public function copy_customer_id_to_wp_user( Payment $payment ) {
395
		if ( $this->config->id !== $payment->config_id ) {
396
			return;
397
		}
398
399
		$subscription = $payment->get_subscription();
400
401
		if ( ! $subscription || empty( $subscription->user_id ) ) {
0 ignored issues
show
introduced by
$subscription is of type Pronamic\WordPress\Pay\Subscriptions\Subscription, thus it always evaluated to true.
Loading history...
402
			return;
403
		}
404
405
		// Get customer ID from subscription meta.
406
		$customer_id = $subscription->get_meta( 'mollie_customer_id' );
407
408
		$user_customer_id = $this->get_customer_id_by_wp_user_id( $subscription->user_id );
0 ignored issues
show
Bug introduced by
The property user_id does not seem to exist on Pronamic\WordPress\Pay\Subscriptions\Subscription.
Loading history...
409
410
		if ( empty( $user_customer_id ) ) {
411
			// Set customer ID as user meta.
412
			$this->update_wp_user_customer_id( $subscription->user_id, $customer_id );
413
		}
414
	}
415
}
416