Test Failed
Push — master ( 505c40...c5b7ba )
by Reüel
10:20 queued 03:32
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 ) {
0 ignored issues
show
introduced by
$response is always a sub-type of WP_Error.
Loading history...
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
				if ( null === $id || null === $name ) {
211
					continue;
212
				}
213
214
				$issuers[ $id ] = $name;
215
			}
216
		}
217
218
		return $issuers;
219
	}
220
221
	/**
222
	 * Get payment methods
223
	 *
224
	 * @param string $sequence_type Sequence type.
225 2
	 *
226
	 * @return array<string>
227 2
	 * @throws \Exception Throws exception for methods on failed request or invalid response.
228
	 */
229
	public function get_payment_methods( $sequence_type = '' ) {
230 2
		$data = array(
231 2
			'includeWallets' => Methods::APPLE_PAY,
232
		);
233
234 2
		if ( '' !== $sequence_type ) {
235
			$data['sequenceType'] = $sequence_type;
236 2
		}
237
238 2
		$response = $this->send_request_to_endpoint( 'methods', 'GET', $data );
239
240
		$payment_methods = array();
241
242 2
		if ( ! isset( $response->_embedded ) ) {
243 2
			throw new \Exception( 'No embedded data in Mollie response.' );
244 2
		}
245 2
246
		if ( isset( $response->_embedded->methods ) ) {
247 2
			foreach ( $response->_embedded->methods as $payment_method ) {
248
				$id   = Security::filter( $payment_method->id );
249
				$name = Security::filter( $payment_method->description );
250
251 2
				if ( null === $id || null === $name ) {
252
					continue;
253
				}
254
255
				$payment_methods[ $id ] = $name;
256
			}
257
		}
258
259
		return $payment_methods;
260
	}
261
262
	/**
263
	 * Create customer.
264
	 *
265
	 * @param Customer $customer Customer.
266
	 * @return Customer
267
	 * @throws Error Throws Error when Mollie error occurs.
268
	 * @since 1.1.6
269
	 */
270
	public function create_customer( Customer $customer ) {
271
		$response = $this->send_request_to_endpoint(
272
			'customers',
273
			'POST',
274
			$customer->get_array()
275
		);
276
277
		if ( \property_exists( $response, 'id' ) ) {
278
			$customer->set_id( $response->id );
279
		}
280
281
		return $customer;
282
	}
283 4
284 4
	/**
285
	 * Get customer.
286
	 *
287
	 * @param string $customer_id Mollie customer ID.
288
	 *
289 4
	 * @return null|object
290
	 * @throws \InvalidArgumentException Throws exception on empty customer ID argument.
291
	 * @throws Error Throws Error when Mollie error occurs.
292
	 */
293
	public function get_customer( $customer_id ) {
294
		if ( empty( $customer_id ) ) {
295
			throw new \InvalidArgumentException( 'Mollie customer ID can not be empty string.' );
296
		}
297
298
		try {
299
			return $this->send_request_to_endpoint( 'customers/' . $customer_id, 'GET' );
300
		} catch ( Error $error ) {
301
			if ( 404 === $error->get_status() ) {
302
				return null;
303
			}
304
305
			throw $error;
306
		}
307
	}
308
309
	/**
310
	 * Create mandate.
311
	 *
312
	 * @param string             $customer_id           Customer ID.
313
	 * @param BankAccountDetails $consumer_bank_details Consumer bank details.
314
	 * @return object
315
	 * @throws Error Throws Error when Mollie error occurs.
316
	 * @since unreleased
317
	 */
318
	public function create_mandate( $customer_id, BankAccountDetails $consumer_bank_details ) {
319
		$response = $this->send_request_to_endpoint(
320
			'customers/' . $customer_id . '/mandates',
321
			'POST',
322
			array(
323
				'method'          => Methods::DIRECT_DEBIT,
324
				'consumerName'    => $consumer_bank_details->get_name(),
325
				'consumerAccount' => $consumer_bank_details->get_iban(),
326
			)
327
		);
328
329
		return $response;
330
	}
331
332
	/**
333
	 * Get mandate.
334
	 *
335
	 * @param string $mandate_id Mollie mandate ID.
336
	 * @param string $customer_id Mollie customer ID.
337
	 * @return object
338
	 * @throws \InvalidArgumentException Throws exception on empty mandate ID argument.
339
	 */
340
	public function get_mandate( $mandate_id, $customer_id ) {
341
		if ( '' === $mandate_id ) {
342
			throw new \InvalidArgumentException( 'Mollie mandate ID can not be empty string.' );
343
		}
344
345
		if ( '' === $customer_id ) {
346
			throw new \InvalidArgumentException( 'Mollie customer ID can not be empty string.' );
347
		}
348
349
		return $this->send_request_to_endpoint( 'customers/' . $customer_id . '/mandates/' . $mandate_id, 'GET' );
350
	}
351
352
	/**
353
	 * Get mandates for customer.
354
	 *
355
	 * @param string $customer_id Mollie customer ID.
356
	 * @return object
357
	 * @throws \InvalidArgumentException Throws exception on empty customer ID argument.
358
	 */
359
	public function get_mandates( $customer_id ) {
360
		if ( '' === $customer_id ) {
361
			throw new \InvalidArgumentException( 'Mollie customer ID can not be empty string.' );
362
		}
363
364
		return $this->send_request_to_endpoint( 'customers/' . $customer_id . '/mandates?limit=250', 'GET' );
365
	}
366
367
	/**
368
	 * Is there a valid mandate for customer?
369
	 *
370
	 * @param string      $customer_id    Mollie customer ID.
371
	 * @param string|null $payment_method Payment method to find mandates for.
372
	 * @param string|null $search         Search.
373
	 *
374
	 * @return string|bool
375
	 * @throws \Exception Throws exception for mandates on failed request or invalid response.
376
	 */
377
	public function has_valid_mandate( $customer_id, $payment_method = null, $search = null ) {
378
		$mandates = $this->get_mandates( $customer_id );
379
380
		$mollie_method = Methods::transform( $payment_method );
381
382
		if ( ! isset( $mandates->_embedded ) ) {
383
			throw new \Exception( 'No embedded data in Mollie response.' );
384
		}
385
386
		foreach ( $mandates->_embedded->mandates as $mandate ) {
387
			if ( null !== $mollie_method && $mollie_method !== $mandate->method ) {
388
				continue;
389
			}
390
391
			// Search consumer account or card number.
392
			if ( null !== $search ) {
393
				switch ( $mollie_method ) {
394
					case Methods::DIRECT_DEBIT:
395
					case Methods::PAYPAL:
396
						if ( $search !== $mandate->details->consumerAccount ) {
397
							continue 2;
398
						}
399
400
						break;
401
					case Methods::CREDITCARD:
402
						if ( $search !== $mandate->details->cardNumber ) {
403
							continue 2;
404
						}
405
406
						break;
407
				}
408
			}
409
410
			if ( 'valid' === $mandate->status ) {
411
				return $mandate->id;
412
			}
413
		}
414
415
		return false;
416
	}
417
418
	/**
419
	 * Get formatted date and time of first valid mandate.
420
	 *
421
	 * @param string $customer_id    Mollie customer ID.
422
	 * @param string $payment_method Payment method.
423
	 *
424
	 * @return null|DateTime
425
	 * @throws \Exception Throws exception for mandates on failed request or invalid response.
426
	 */
427
	public function get_first_valid_mandate_datetime( $customer_id, $payment_method = null ) {
428
		$mandates = $this->get_mandates( $customer_id );
429
430
		$mollie_method = Methods::transform( $payment_method );
431
432
		if ( ! isset( $mandates->_embedded ) ) {
433
			throw new \Exception( 'No embedded data in Mollie response.' );
434
		}
435
436
		foreach ( $mandates->_embedded->mandates as $mandate ) {
437
			if ( $mollie_method !== $mandate->method ) {
438
				continue;
439
			}
440
441
			if ( 'valid' !== $mandate->status ) {
442
				continue;
443
			}
444
445
			if ( ! isset( $valid_mandates ) ) {
446
				$valid_mandates = array();
447
			}
448
449
			// @codingStandardsIgnoreStart
450
			$valid_mandates[ $mandate->createdAt ] = $mandate;
451
			// @codingStandardsIgnoreEnd
452
		}
453
454
		if ( isset( $valid_mandates ) ) {
455
			ksort( $valid_mandates );
456
457
			$mandate = array_shift( $valid_mandates );
458
459
			// @codingStandardsIgnoreStart
460
			$create_date = new DateTime( $mandate->createdAt );
461
			// @codingStandardsIgnoreEnd
462
463
			return $create_date;
464
		}
465
466
		return null;
467
	}
468
}
469