Failed Conditions
Push — develop ( 89f61e...ae8897 )
by Reüel
06:06
created

src/PaymentsController.php (2 issues)

Labels
Severity
1
<?php
2
/**
3
 * Payments controller
4
 *
5
 * @author    Pronamic <[email protected]>
6
 * @copyright 2005-2020 Pronamic
7
 * @license   GPL-3.0-or-later
8
 * @package   Pronamic\WordPress\Pay\Gateways\Adyen
9
 */
10
11
namespace Pronamic\WordPress\Pay\Gateways\Adyen;
12
13
use JsonSchema\Exception\ValidationException;
14
use Pronamic\WordPress\Pay\Core\Server;
15
use Pronamic\WordPress\Pay\Plugin;
16
use WP_REST_Request;
17
18
/**
19
 * Payments result controller
20
 *
21
 * @link https://docs.adyen.com/developers/checkout/web-sdk/customization/logic#beforecomplete
22
 *
23
 * @author  Reüel van der Steege
24
 * @version 1.1.1
25
 * @since   1.1.0
26
 */
27
class PaymentsController {
28
	/**
29
	 * Setup.
30
	 *
31
	 * @return void
32
	 */
33 1
	public function setup() {
34 1
		add_action( 'rest_api_init', array( $this, 'rest_api_init' ) );
35 1
	}
36
37
	/**
38
	 * REST API init.
39
	 *
40
	 * @link https://developer.wordpress.org/rest-api/extending-the-rest-api/adding-custom-endpoints/
41
	 * @link https://developer.wordpress.org/reference/hooks/rest_api_init/
42
	 *
43
	 * @return void
44
	 */
45 10
	public function rest_api_init() {
46
		// Register REST route `/payments//{payment_id}`.
47 10
		register_rest_route(
48 10
			Integration::REST_ROUTE_NAMESPACE,
49 10
			'/payments/(?P<payment_id>\d+)',
50
			array(
51 10
				'methods'  => 'POST',
52 10
				'callback' => array( $this, 'rest_api_adyen_payments' ),
53
				'args'     => array(
54
					'payment_id' => array(
55 10
						'description' => __( 'Payment ID.', 'pronamic_ideal' ),
56 10
						'type'        => 'integer',
57
					),
58
				),
59
			)
60
		);
61
62
		// Register REST route `/payments/details/{payment_id}`.
63 10
		register_rest_route(
64 10
			Integration::REST_ROUTE_NAMESPACE,
65 10
			'/payments/details/(?P<payment_id>\d+)',
66
			array(
67 10
				'methods'  => 'POST',
68 10
				'callback' => array( $this, 'rest_api_adyen_payment_details' ),
69
				'args'     => array(
70
					'payment_id' => array(
71 10
						'description' => __( 'Payment ID.', 'pronamic_ideal' ),
72 10
						'type'        => 'integer',
73
					),
74
				),
75
			)
76
		);
77
78
		// Register REST route `/payments/applepay/merchant-validation/`.
79 10
		register_rest_route(
80 10
			Integration::REST_ROUTE_NAMESPACE,
81 10
			'/payments/applepay/merchant-validation/(?P<payment_id>\d+)',
82
			array(
83 10
				'methods'  => 'POST',
84 10
				'callback' => array( $this, 'rest_api_applepay_merchant_validation' ),
85
				'args'     => array(
86
					'payment_id' => array(
87 10
						'description' => __( 'Payment ID.', 'pronamic_ideal' ),
88 10
						'type'        => 'integer',
89
					),
90
				),
91
			)
92
		);
93 10
	}
94
95
	/**
96
	 * REST API Adyen payments handler.
97
	 *
98
	 * @param WP_REST_Request $request Request.
99
	 * @return object
100
	 * @throws \Exception Throws exception on Adyen service exception response.
101
	 */
102
	public function rest_api_adyen_payments( WP_REST_Request $request ) {
103
		$payment_id = $request->get_param( 'payment_id' );
104
105
		// Payment ID.
106
		if ( null === $payment_id ) {
107
			return new \WP_Error(
108
				'pronamic-pay-adyen-no-payment-id',
109
				__( 'No payment ID given in `payment_id` parameter.', 'pronamic_ideal' )
110
			);
111
		}
112
113
		$payment = \get_pronamic_payment( $payment_id );
114
115
		if ( null === $payment ) {
116
			return new \WP_Error(
117
				'pronamic-pay-adyen-payment-not-found',
118
				sprintf(
119
					/* translators: %s: payment ID */
120
					__( 'Could not find payment with ID `%s`.', 'pronamic_ideal' ),
121
					$payment_id
122
				),
123
				$payment_id
124
			);
125
		}
126
127
		// State data.
128
		$data = \json_decode( $request->get_body() );
129
130
		if ( null === $data ) {
131
			return new \WP_Error(
132
				'pronamic-pay-adyen-no-data',
133
				__( 'No data given in request body.', 'pronamic_ideal' )
134
			);
135
		}
136
137
		// Gateway.
138
		$config_id = $payment->get_config_id();
139
140
		if ( null === $config_id ) {
141
			return new \WP_Error(
142
				'pronamic-pay-adyen-no-config',
143
				__( 'No gateway configuration ID given in payment.', 'pronamic_ideal' )
144
			);
145
		}
146
147
		$gateway = Plugin::get_gateway( $config_id );
148
149
		if ( empty( $gateway ) ) {
150
			return new \WP_Error(
151
				'pronamic-pay-adyen-gateway-not-found',
152
				sprintf(
153
					/* translators: %s: Gateway configuration ID */
154
					__( 'Could not find gateway with ID `%s`.', 'pronamic_ideal' ),
155
					$config_id
156
				),
157
				$config_id
158
			);
159
		}
160
161
		if ( ! isset( $gateway->client ) ) {
162
			return new \WP_Error(
163
				'pronamic-pay-adyen-client-not-found',
164
				sprintf(
165
					/* translators: %s: Gateway configuration ID */
166
					__( 'Could not find client in gateway with ID `%s`.', 'pronamic_ideal' ),
167
					$config_id
168
				),
169
				$config_id
170
			);
171
		}
172
173
		// Create payment.
174
		// phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase -- Adyen JSON object.
175
		if ( ! isset( $data->paymentMethod->type ) ) {
176
			return new \WP_Error(
177
				'pronamic-pay-adyen-no-payment-method',
178
				__( 'No payment method given.', 'pronamic_ideal' )
179
			);
180
		}
181
182
		// phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase -- Adyen JSON object.
183
		$payment_method = PaymentMethod::from_object( $data->paymentMethod );
184
185
		try {
186
			if ( ! \is_callable( array( $gateway, 'create_payment' ) ) ) {
187
				return (object) array(
188
					'error' => __( 'Gateway does not support method to create payment.', 'pronamic_ideal' ),
189
				);
190
			}
191
192
			try {
193
				$response = $gateway->create_payment( $payment, $payment_method, $data );
194
			} catch ( \Pronamic\WordPress\Pay\Gateways\Adyen\ServiceException $service_exception ) {
195
				$message = $service_exception->getMessage();
196
197
				$error_code = $service_exception->get_error_code();
198
199
				if ( ! empty( $error_code ) ) {
200
					$message = sprintf(
201
						/* translators: 1: error message, 2: error code */
202
						__( '%1$s (error %2$s)', 'pronamic_ideal' ),
203
						$service_exception->getMessage(),
204
						$error_code
205
					);
206
				}
207
208
				throw new \Exception( $message );
209
			}
210
		} catch ( \Exception $e ) {
211
			$error = $e->getMessage();
212
213
			$error_code = $e->getCode();
214
215
			if ( ! empty( $error_code ) ) {
216
				$error = sprintf( '%s - %s', $error_code, $e->getMessage() );
217
			}
218
219
			return (object) array( 'error' => $error );
220
		}
221
222
		// Update payment status based on response.
223
		PaymentResponseHelper::update_payment( $payment, $response );
224
225
		$result = array(
226
			'resultCode' => $response->get_result_code(),
227
		);
228
229
		// Return action if available.
230
		$action = $response->get_action();
231
232
		if ( null !== $action ) {
233
			$result['action'] = $action->get_json();
234
		}
235
236
		// Return refusal reason if available.
237
		$refusal_reason = $response->get_refusal_reason();
238
239
		if ( null !== $refusal_reason ) {
240
			$result['refusalReason'] = $refusal_reason;
241
		}
242
243
		return (object) $result;
244
	}
245
246
	/**
247
	 * REST API Adyen payment details handler.
248
	 *
249
	 * @param WP_REST_Request $request Request.
250
	 * @return object
251
	 * @throws \Exception Throws exception on Adyen service exception response.
252
	 */
253
	public function rest_api_adyen_payment_details( WP_REST_Request $request ) {
254
		$payment_id = $request->get_param( 'payment_id' );
255
256
		// Payment ID.
257
		if ( null === $payment_id ) {
258
			return new \WP_Error(
259
				'pronamic-pay-adyen-no-payment-id',
260
				__( 'No payment ID given in `payment_id` parameter.', 'pronamic_ideal' )
261
			);
262
		}
263
264
		$payment = \get_pronamic_payment( $payment_id );
265
266
		if ( null === $payment ) {
267
			return new \WP_Error(
268
				'pronamic-pay-adyen-payment-not-found',
269
				sprintf(
270
					/* translators: %s: payment ID */
271
					__( 'Could not find payment with ID `%s`.', 'pronamic_ideal' ),
272
					$payment_id
273
				),
274
				$payment_id
275
			);
276
		}
277
278
		// State data.
279
		$data = \json_decode( $request->get_body() );
280
281
		if ( null === $data ) {
282
			return new \WP_Error(
283
				'pronamic-pay-adyen-no-data',
284
				__( 'No data given in request body.', 'pronamic_ideal' )
285
			);
286
		}
287
288
		// Gateway.
289
		$config_id = $payment->get_config_id();
290
291
		if ( null === $config_id ) {
292
			return new \WP_Error(
293
				'pronamic-pay-adyen-no-config',
294
				__( 'No gateway configuration ID given in payment.', 'pronamic_ideal' )
295
			);
296
		}
297
298
		$gateway = Plugin::get_gateway( $config_id );
0 ignored issues
show
Are you sure the assignment to $gateway is correct as Pronamic\WordPress\Pay\P...get_gateway($config_id) targeting Pronamic\WordPress\Pay\Plugin::get_gateway() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
299
300
		if ( empty( $gateway ) ) {
301
			return new \WP_Error(
302
				'pronamic-pay-adyen-gateway-not-found',
303
				sprintf(
304
					/* translators: %s: Gateway configuration ID */
305
					__( 'Could not find gateway with ID `%s`.', 'pronamic_ideal' ),
306
					$config_id
307
				),
308
				$config_id
309
			);
310
		}
311
312
		if ( ! isset( $gateway->client ) ) {
313
			return new \WP_Error(
314
				'pronamic-pay-adyen-client-not-found',
315
				sprintf(
316
					/* translators: %s: Gateway configuration ID */
317
					__( 'Could not find client in gateway with ID `%s`.', 'pronamic_ideal' ),
318
					$config_id
319
				),
320
				$config_id
321
			);
322
		}
323
324
		// phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase -- Adyen JSON object.
325
		if ( ! isset( $data->paymentMethod->type ) ) {
326
			return new \WP_Error(
327
				'pronamic-pay-adyen-no-payment-method',
328
				__( 'No payment method given.', 'pronamic_ideal' )
329
			);
330
		}
331
332
		// Send additional payment details.
333
		$payment_details_request = new PaymentDetailsRequest();
334
335
		// Set payment data from original payment response.
336
		$payment_response = $payment->get_meta( 'adyen_payment_response' );
337
338
		if ( is_string( $payment_response ) && '' !== $payment_response ) {
339
			$payment_response = \json_decode( $payment_response );
340
341
			$payment_response = PaymentResponse::from_object( $payment_response );
342
343
			$payment_data = $payment_response->get_payment_data();
344
345
			$payment_details_request->set_payment_data( $payment_data );
346
		}
347
348
		try {
349
			if ( ! \is_callable( array( $gateway, 'send_payment_details' ) ) ) {
350
				return (object) array(
351
					'error' => __( 'Gateway does not support sending additional payment details.', 'pronamic_ideal' ),
352
				);
353
			}
354
355
			try {
356
				$response = $gateway->send_payment_details( $payment_details_request );
357
			} catch ( \Pronamic\WordPress\Pay\Gateways\Adyen\ServiceException $service_exception ) {
358
				$message = $service_exception->getMessage();
359
360
				$error_code = $service_exception->get_error_code();
361
362
				if ( ! empty( $error_code ) ) {
363
					$message = sprintf(
364
					/* translators: 1: error message, 2: error code */
365
						__( '%1$s (error %2$s)', 'pronamic_ideal' ),
366
						$service_exception->getMessage(),
367
						$error_code
368
					);
369
				}
370
371
				throw new \Exception( $message );
372
			}
373
374
			// Update payment status based on response.
375
			PaymentResponseHelper::update_payment( $payment, $response );
376
		} catch ( \Exception $e ) {
377
			$error = $e->getMessage();
378
379
			$error_code = $e->getCode();
380
381
			if ( ! empty( $error_code ) ) {
382
				$error = sprintf( '%s - %s', $error_code, $e->getMessage() );
383
			}
384
385
			return (object) array( 'error' => $error );
386
		}
387
388
		$result = array(
389
			'resultCode' => $response->get_result_code(),
390
		);
391
392
		// Return action if available.
393
		$action = $response->get_action();
394
395
		if ( null !== $action ) {
396
			$result['action'] = $action->get_json();
397
		}
398
399
		// Return refusal reason if available.
400
		$refusal_reason = $response->get_refusal_reason();
401
402
		if ( null !== $refusal_reason ) {
403
			$result['refusalReason'] = $refusal_reason;
404
		}
405
406
		return (object) $result;
407
	}
408
409
	/**
410
	 * REST API Apple Pay merchant validation handler.
411
	 *
412
	 * @param WP_REST_Request $request Request.
413
	 * @return object
414
	 * @throws \Exception Throws exception on merchant identity files problems.
415
	 */
416
	public function rest_api_applepay_merchant_validation( WP_REST_Request $request ) {
417
		$payment_id = $request->get_param( 'payment_id' );
418
419
		// Payment ID.
420
		if ( null === $payment_id ) {
421
			return new \WP_Error(
422
				'pronamic-pay-adyen-no-payment-id',
423
				__( 'No payment ID given in `payment_id` parameter.', 'pronamic_ideal' )
424
			);
425
		}
426
427
		$payment = \get_pronamic_payment( $payment_id );
428
429
		if ( null === $payment ) {
430
			return new \WP_Error(
431
				'pronamic-pay-adyen-payment-not-found',
432
				sprintf(
433
					/* translators: %s: payment ID */
434
					__( 'Could not find payment with ID `%s`.', 'pronamic_ideal' ),
435
					$payment_id
436
				),
437
				$payment_id
438
			);
439
		}
440
441
		// State data.
442
		$data = \json_decode( $request->get_body() );
443
444
		if ( null === $data ) {
445
			return new \WP_Error(
446
				'pronamic-pay-adyen-no-data',
447
				__( 'No data given in request body.', 'pronamic_ideal' )
448
			);
449
		}
450
451
		// Gateway.
452
		$config_id = $payment->get_config_id();
453
454
		if ( null === $config_id ) {
455
			return new \WP_Error(
456
				'pronamic-pay-adyen-no-config',
457
				__( 'No gateway configuration ID given in payment.', 'pronamic_ideal' )
458
			);
459
		}
460
461
		$gateway = Plugin::get_gateway( $config_id );
0 ignored issues
show
Are you sure the assignment to $gateway is correct as Pronamic\WordPress\Pay\P...get_gateway($config_id) targeting Pronamic\WordPress\Pay\Plugin::get_gateway() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
462
463
		if ( empty( $gateway ) ) {
464
			return new \WP_Error(
465
				'pronamic-pay-adyen-gateway-not-found',
466
				sprintf(
467
					/* translators: %s: Gateway configuration ID */
468
					__( 'Could not find gateway with ID `%s`.', 'pronamic_ideal' ),
469
					$config_id
470
				),
471
				$config_id
472
			);
473
		}
474
475
		if ( ! isset( $gateway->client ) ) {
476
			return new \WP_Error(
477
				'pronamic-pay-adyen-client-not-found',
478
				sprintf(
479
					/* translators: %s: Gateway configuration ID */
480
					__( 'Could not find client in gateway with ID `%s`.', 'pronamic_ideal' ),
481
					$config_id
482
				),
483
				$config_id
484
			);
485
		}
486
487
		// Merchant identifier.
488
		$integration = new Integration();
489
490
		$config = $integration->get_config( $config_id );
491
492
		$merchant_identifier = $config->get_apple_pay_merchant_id();
493
494
		if ( empty( $merchant_identifier ) ) {
495
			return new \WP_Error(
496
				'pronamic-pay-adyen-applepay-no-merchant-identifier',
497
				__( 'Apple Pay merchant identifier not configured in gateway settings.', 'pronamic_ideal' )
498
			);
499
		}
500
501
		if ( ! isset( $data->validation_url ) ) {
502
			return new \WP_Error(
503
				'pronamic-pay-adyen-applepay-no-validation-url',
504
				__( 'No Apple Pay merchant validation URL given.', 'pronamic_ideal' )
505
			);
506
		}
507
508
		/*
509
		 * Request an Apple Pay payment session.
510
		 *
511
		 * @link https://developer.apple.com/documentation/apple_pay_on_the_web/applepaysession/1778021-onvalidatemerchant
512
		 * @link https://developer.apple.com/documentation/apple_pay_on_the_web/apple_pay_js_api/requesting_an_apple_pay_payment_session
513
		 * @link https://docs.adyen.com/payment-methods/apple-pay/web-drop-in#show-apple-pay-in-your-payment-form
514
		 */
515
		$request = array(
516
			'merchantIdentifier' => $merchant_identifier,
517
			'displayName'        => \get_bloginfo( 'name' ),
518
			'initiative'         => 'web',
519
			'initiativeContext'  => Server::get( 'HTTP_HOST', FILTER_SANITIZE_STRING ),
520
		);
521
522
		try {
523
			add_action( 'http_api_curl', array( $this, 'http_curl_applepay_merchant_identity' ), 10, 2 );
524
525
			$certificate = $config->get_apple_pay_merchant_id_certificate();
526
			$private_key = $config->get_apple_pay_merchant_id_private_key();
527
528
			if ( empty( $certificate ) || empty( $private_key ) ) {
529
				throw new \Exception( __( 'Invalid Apple Pay Merchant Identity configuration.', 'pronamic_ideal' ) );
530
			}
531
532
			// Create temporary files for merchant validation.
533
			$certificate_file = \tmpfile();
534
			$private_key_file = \tmpfile();
535
536
			if ( false === $certificate_file || false === $private_key_file ) {
537
				throw new \Exception( __( 'Error creating merchant identity files.', 'pronamic_ideal' ) );
538
			}
539
540
			// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_fwrite -- Temporary files.
541
			\fwrite( $certificate_file, $certificate );
542
			// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_fwrite -- Temporary files.
543
			\fwrite( $private_key_file, $private_key );
544
545
			// Validate merchant.
546
			$response = \wp_remote_request(
547
				$data->validation_url,
548
				array(
549
					'method'                           => 'POST',
550
					'headers'                          => array(
551
						'Content-Type' => 'application/json',
552
					),
553
					'body'                             => \wp_json_encode( (object) $request ),
554
					'adyen_applepay_merchant_identity' => array(
555
						'certificate_path'     => stream_get_meta_data( $certificate_file )['uri'],
556
						'private_key_path'     => stream_get_meta_data( $private_key_file )['uri'],
557
						'private_key_password' => $config->get_apple_pay_merchant_id_private_key_password(),
558
					),
559
				)
560
			);
561
562
			// Remove temporary files.
563
			// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_fclose -- Temporary files.
564
			\fclose( $certificate_file );
565
			// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_fclose -- Temporary files.
566
			\fclose( $private_key_file );
567
568
			$body = \wp_remote_retrieve_body( $response );
569
570
			$result = \json_decode( $body );
571
		} catch ( \Exception $e ) {
572
			$error = $e->getMessage();
573
574
			$error_code = $e->getCode();
575
576
			if ( ! empty( $error_code ) ) {
577
				$error = sprintf( '%s - %s', $error_code, $e->getMessage() );
578
			}
579
580
			return (object) array( 'error' => $error );
581
		}
582
583
		return (object) $result;
584
	}
585
586
	/**
587
	 * HTTP CURL options for Apple Pay merchant validation.
588
	 *
589
	 * @param resource             $handle      CURL handle.
590
	 * @param array<array<string>> $parsed_args Parsed arguments.
591
	 * @return void
592
	 * @throws \Exception Throws exception on error while reading temporary files.
593
	 */
594
	public function http_curl_applepay_merchant_identity( $handle, $parsed_args ) {
595
		if ( ! isset( $parsed_args['adyen_applepay_merchant_identity'] ) ) {
596
			return;
597
		}
598
599
		$merchant_identity = $parsed_args['adyen_applepay_merchant_identity'];
600
601
		$certificate_path     = $merchant_identity['certificate_path'];
602
		$private_key_path     = $merchant_identity['private_key_path'];
603
		$private_key_password = $merchant_identity['private_key_password'];
604
605
		// Check temporary files existence.
606
		if ( ! \is_readable( $certificate_path ) || ! \is_readable( $private_key_path ) ) {
607
			throw new \Exception( __( 'Error reading merchant identity files.', 'pronamic_ideal' ) );
608
		}
609
610
		// Set merchant identity certificate and private key SSL options.
611
		// phpcs:disable WordPress.WP.AlternativeFunctions.curl_curl_setopt
612
		\curl_setopt( $handle, CURLOPT_SSLCERT, $certificate_path );
613
		\curl_setopt( $handle, CURLOPT_SSLKEY, $private_key_path );
614
615
		// Set merchant identity private key password.
616
		if ( ! empty( $private_key_password ) ) {
617
			\curl_setopt( $handle, CURLOPT_SSLKEYPASSWD, $private_key_password );
618
		}
619
620
		// phpcs:enable WordPress.WP.AlternativeFunctions.curl_curl_setopt
621
	}
622
}
623