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

Client::send_request()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 52
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 20
CRAP Score 5.2

Importance

Changes 5
Bugs 0 Features 0
Metric Value
cc 5
eloc 27
c 5
b 0
f 0
nc 5
nop 3
dl 0
loc 52
ccs 20
cts 25
cp 0.8
crap 5.2
rs 9.1768

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