Test Failed
Push — develop ( b6437d...f2e903 )
by Reüel
03:57
created

Client   B

Complexity

Total Complexity 49

Size/Duplication

Total Lines 405
Duplicated Lines 0 %

Test Coverage

Coverage 38.79%

Importance

Changes 10
Bugs 0 Features 0
Metric Value
eloc 126
c 10
b 0
f 0
dl 0
loc 405
ccs 45
cts 116
cp 0.3879
rs 8.48
wmc 49

17 Methods

Rating   Name   Duplication   Size   Complexity  
A get_issuers() 0 15 3
A __construct() 0 2 1
A get_current_profile() 0 2 1
A get_payment_methods() 0 25 5
A get_payments() 0 2 1
B get_first_valid_mandate_datetime() 0 40 7
A get_payment() 0 6 2
A send_request_to_endpoint() 0 2 1
A create_customer() 0 10 1
A create_payment() 0 2 1
C has_valid_mandate() 0 39 12
A send_request() 0 52 5
A get_url() 0 4 1
A get_mandates() 0 6 2
A create_mandate() 0 12 1
A get_customer() 0 13 4
A get_profile() 0 2 1

How to fix   Complexity   

Complex Class

Complex classes like Client often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Client, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * Mollie client.
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\Core\XML\Security;
16
17
/**
18
 * Title: Mollie
19
 * Description:
20
 * Copyright: 2005-2020 Pronamic
21
 * Company: Pronamic
22
 *
23
 * @author  Remco Tolsma
24
 * @version 2.0.9
25
 * @since   1.0.0
26
 */
27
class Client {
28
	/**
29
	 * Mollie API endpoint URL
30
	 *
31
	 * @var string
32
	 */
33
	const API_URL = 'https://api.mollie.com/v2/';
34
35
	/**
36
	 * Mollie API Key ID
37
	 *
38
	 * @var string
39
	 */
40
	private $api_key;
41
42
	/**
43
	 * Constructs and initializes an Mollie client object
44
	 *
45
	 * @param string $api_key Mollie API key.
46
	 */
47
	public function __construct( $api_key ) {
48
		$this->api_key = $api_key;
49
	}
50
51
	/**
52
	 * Send request with the specified action and parameters
53
	 *
54 39
	 * @param string                            $url    URL.
55 39
	 * @param string                            $method HTTP method to use.
56 39
	 * @param array<string, string|object|null> $data   Request data.
57
	 * @return object
58
	 * @throws Error Throws Error when Mollie error occurs.
59
	 * @throws \Exception Throws exception when error occurs.
60
	 */
61
	public function send_request( $url, $method = 'GET', array $data = array() ) {
62
		// Request.
63
		$response = wp_remote_request(
64
			$url,
65 39
			array(
66 39
				'method'  => $method,
67 39
				'headers' => array(
68
					'Authorization' => 'Bearer ' . $this->api_key,
69
				),
70
				'body'    => $data,
71
			)
72
		);
73
74
		if ( $response instanceof \WP_Error ) {
75
			throw new \Exception( $response->get_error_message() );
76
		}
77
78
		// Body.
79 9
		$body = wp_remote_retrieve_body( $response );
80
81 9
		$data = json_decode( $body );
82
83 9
		// JSON error.
84 9
		$json_error = \json_last_error();
85
86 9
		if ( \JSON_ERROR_NONE !== $json_error ) {
87
			throw new \Exception(
88 9
				\sprintf( 'JSON: %s', \json_last_error_msg() ),
89
				$json_error
90 9
			);
91
		}
92
93
		// Object.
94 9
		if ( ! \is_object( $data ) ) {
95
			$code = \wp_remote_retrieve_response_code( $response );
96
97
			throw new \Exception(
98
				\sprintf( 'Could not JSON decode Mollie response to an object (HTTP Status Code: %s).', $code ),
99 9
				\intval( $code )
100
			);
101 9
		}
102
103
		// Mollie error from JSON response.
104 9
		if ( isset( $data->status, $data->title, $data->detail ) ) {
105
			throw new Error(
106 9
				$data->status,
107 2
				$data->title,
108 2
				$data->detail
109
			);
110
		}
111
112
		return $data;
113
	}
114 9
115
	/**
116
	 * Get URL.
117
	 *
118
	 * @return string $endpoint URL endpoint.
119
	 */
120
	public function get_url( $endpoint ) {
121
		$url = self::API_URL . $endpoint;
122
123
		return $url;
124 9
	}
125 3
126 3
	/**
127 3
	 * Send request to endpoint.
128 3
	 *
129
	 * @param string                            $url    URL.
130
	 * @param string                            $method HTTP method to use.
131
	 * @param array<string, string|object|null> $data   Request data.
132 6
	 * @return object
133
	 */
134
	public function send_request_to_endpoint( $endpoint, $method = 'GET', array $data = array() ) {
135
		return $this->send_request( $this->get_url( $endpoint ), $method, $data );
136
	}
137
138
	/**
139
	 * Get profile.
140
	 *
141
	 * @param string $profile Mollie profile ID.
142
	 * @return object
143
	 * @throws Error
144
	 */
145
	public function get_profile( $profile ) {
146
		return $this->send_request_to_endpoint( 'profiles/' . $profile, 'GET' );
147
	}
148
149
	/**
150
	 * Get current profile.
151
	 *
152
	 * @return object
153
	 * @throws Error
154
	 */
155
	public function get_current_profile() {
156
		return $this->get_profile( 'me' );
157
	}
158
159
	/**
160
	 * Create payment.
161
	 *
162
	 * @param PaymentRequest $request Payment request.
163
	 * @return object
164
	 */
165
	public function create_payment( PaymentRequest $request ) {
166
		return $this->send_request_to_endpoint( 'payments', 'POST', $request->get_array() );
167
	}
168
169
	/**
170
	 * Get payments.
171
	 *
172
	 * @return bool|object
173
	 */
174
	public function get_payments() {
175 3
		return $this->send_request_to_endpoint( 'payments', 'GET' );
176 3
	}
177
178
	/**
179
	 * Get payment.
180
	 *
181
	 * @param string $payment_id Payment ID.
182
	 *
183
	 * @return object
184
	 * @throws \InvalidArgumentException Throws exception on empty payment ID argument.
185
	 */
186
	public function get_payment( $payment_id ) {
187
		if ( empty( $payment_id ) ) {
188
			throw new \InvalidArgumentException( 'Mollie payment ID can not be empty string.' );
189
		}
190
191
		return $this->send_request_to_endpoint( 'payments/' . $payment_id, 'GET' );
192
	}
193
194
	/**
195
	 * Get issuers
196
	 *
197
	 * @return array<string>
198
	 */
199
	public function get_issuers() {
200 2
		$response = $this->send_request_to_endpoint( 'methods/ideal?include=issuers', 'GET' );
201 2
202
		$issuers = array();
203 2
204 2
		if ( isset( $response->issuers ) ) {
205
			foreach ( $response->issuers as $issuer ) {
206
				$id   = Security::filter( $issuer->id );
207 2
				$name = Security::filter( $issuer->name );
208
209 2
				$issuers[ $id ] = $name;
210
			}
211 2
		}
212
213
		return $issuers;
214
	}
215 2
216 2
	/**
217 2
	 * Get payment methods
218 2
	 *
219
	 * @param string $sequence_type Sequence type.
220 2
	 *
221
	 * @return array<string>
222
	 * @throws \Exception Throws exception for methods on failed request or invalid response.
223
	 */
224 2
	public function get_payment_methods( $sequence_type = '' ) {
225
		$data = array();
226
227
		if ( '' !== $sequence_type ) {
228
			$data['sequenceType'] = $sequence_type;
229
		}
230
231
		$response = $this->send_request_to_endpoint( 'methods', 'GET', $data );
232
233
		$payment_methods = array();
234
235
		if ( ! isset( $response->_embedded ) ) {
236
			throw new \Exception( 'No embedded data in Mollie response.' );
237
		}
238
239
		if ( isset( $response->_embedded->methods ) ) {
240
			foreach ( $response->_embedded->methods as $payment_method ) {
241
				$id   = Security::filter( $payment_method->id );
242
				$name = Security::filter( $payment_method->description );
243
244
				$payment_methods[ $id ] = $name;
245
			}
246
		}
247
248
		return $payment_methods;
249
	}
250
251
	/**
252
	 * Create customer.
253
	 *
254
	 * @param Customer $customer Customer.
255
	 * @return Customer
256
	 * @throws Error Throws Error when Mollie error occurs.
257
	 * @since 1.1.6
258
	 */
259
	public function create_customer( Customer $customer ) {
260
		$response = $this->send_request_to_endpoint(
261
			'customers',
262
			'POST',
263
			$customer->get_array()
264
		);
265
266 4
		$customer->set_id( $response->id );
267 4
268
		return $customer;
269
	}
270
271
	/**
272 4
	 * Get customer.
273
	 *
274
	 * @param string $customer_id Mollie customer ID.
275
	 *
276
	 * @return null|object
277
	 * @throws \InvalidArgumentException Throws exception on empty customer ID argument.
278
	 * @throws Error Throws Error when Mollie error occurs.
279
	 */
280
	public function get_customer( $customer_id ) {
281
		if ( empty( $customer_id ) ) {
282
			throw new \InvalidArgumentException( 'Mollie customer ID can not be empty string.' );
283
		}
284
285
		try {
286
			return $this->send_request_to_endpoint( 'customers/' . $customer_id, 'GET' );
287
		} catch ( Error $error ) {
288
			if ( 404 === $error->get_status() ) {
289
				return null;
290
			}
291
292
			throw $error;
293
		}
294
	}
295
296
	/**
297
	 * Create mandate.
298
	 *
299
	 * @param Customer $customer Customer.
300
	 * @return object
301
	 * @throws Error Throws Error when Mollie error occurs.
302
	 * @since unreleased
303
	 */
304
	public function create_mandate( $customer_id, BankAccountDetails $consumer_bank_details ) {
305
		$response = $this->send_request_to_endpoint(
306
			'customers/' . $customer_id . '/mandates',
307
			'POST',
308
			array(
309
				'method'          => Methods::DIRECT_DEBIT,
310
				'consumerName'    => $consumer_bank_details->get_name(),
311
				'consumerAccount' => $consumer_bank_details->get_iban(),
312
			)
313
		);
314
315
		return $response;
316
	}
317
318
	/**
319
	 * Get mandates for customer.
320
	 *
321
	 * @param string $customer_id Mollie customer ID.
322
	 * @return object
323
	 * @throws \InvalidArgumentException Throws exception on empty customer ID argument.
324
	 */
325
	public function get_mandates( $customer_id ) {
326
		if ( '' === $customer_id ) {
327
			throw new \InvalidArgumentException( 'Mollie customer ID can not be empty string.' );
328
		}
329
330
		return $this->send_request_to_endpoint( 'customers/' . $customer_id . '/mandates?limit=250', 'GET' );
331
	}
332
333
	/**
334
	 * Is there a valid mandate for customer?
335
	 *
336
	 * @param string      $customer_id    Mollie customer ID.
337
	 * @param string|null $payment_method Payment method to find mandates for.
338
	 *
339
	 * @return boolean
340
	 * @throws \Exception Throws exception for mandates on failed request or invalid response.
341
	 */
342
	public function has_valid_mandate( $customer_id, $payment_method = null, $search = null ) {
343
		$mandates = $this->get_mandates( $customer_id );
344
345
		$mollie_method = Methods::transform( $payment_method );
346
347
		if ( ! isset( $mandates->_embedded ) ) {
348
			throw new \Exception( 'No embedded data in Mollie response.' );
349
		}
350
351
		foreach ( $mandates->_embedded->mandates as $mandate ) {
352
			if ( null !== $mollie_method && $mollie_method !== $mandate->method ) {
353
				continue;
354
			}
355
356
			// Search consumer account or card number.
357
			if ( null !== $search ) {
358
				switch ( $mollie_method ) {
359
					case Methods::DIRECT_DEBIT:
360
					case Methods::PAYPAL:
361
						if ( $search !== $mandate->details->consumerAccount ) {
362
							continue 2;
363
						}
364
365
						break;
366
					case Methods::CREDITCARD:
367
						if ( $search !== $mandate->details->cardNumber ) {
368
							continue 2;
369
						}
370
371
						break;
372
				}
373
			}
374
375
			if ( 'valid' === $mandate->status ) {
376
				return $mandate->id;
377
			}
378
		}
379
380
		return false;
381
	}
382
383
	/**
384
	 * Get formatted date and time of first valid mandate.
385
	 *
386
	 * @param string $customer_id    Mollie customer ID.
387
	 * @param string $payment_method Payment method.
388
	 *
389
	 * @return null|DateTime
390
	 * @throws \Exception Throws exception for mandates on failed request or invalid response.
391
	 */
392
	public function get_first_valid_mandate_datetime( $customer_id, $payment_method = null ) {
393
		$mandates = $this->get_mandates( $customer_id );
394
395
		$mollie_method = Methods::transform( $payment_method );
396
397
		if ( ! isset( $mandates->_embedded ) ) {
398
			throw new \Exception( 'No embedded data in Mollie response.' );
399
		}
400
401
		foreach ( $mandates->_embedded->mandates as $mandate ) {
402
			if ( $mollie_method !== $mandate->method ) {
403
				continue;
404
			}
405
406
			if ( 'valid' !== $mandate->status ) {
407
				continue;
408
			}
409
410
			if ( ! isset( $valid_mandates ) ) {
411
				$valid_mandates = array();
412
			}
413
414
			// @codingStandardsIgnoreStart
415
			$valid_mandates[ $mandate->createdAt ] = $mandate;
416
			// @codingStandardsIgnoreEnd
417
		}
418
419
		if ( isset( $valid_mandates ) ) {
420
			ksort( $valid_mandates );
421
422
			$mandate = array_shift( $valid_mandates );
423
424
			// @codingStandardsIgnoreStart
425
			$create_date = new DateTime( $mandate->createdAt );
426
			// @codingStandardsIgnoreEnd
427
428
			return $create_date;
429
		}
430
431
		return null;
432
	}
433
}
434