Issues (11)

src/Integration.php (7 issues)

1
<?php
2
/**
3
 * Integration
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 Pronamic\WordPress\Pay\Dependencies\PhpExtensionDependency;
14
use Pronamic\WordPress\Pay\AbstractGatewayIntegration;
15
use Pronamic\WordPress\Pay\Util as Pay_Util;
16
17
/**
18
 * Integration
19
 *
20
 * @author  Remco Tolsma
21
 * @version 1.1.2
22
 * @since   1.0.0
23
 */
24
class Integration extends AbstractGatewayIntegration {
25
	/**
26
	 * REST route namespace.
27
	 *
28
	 * @var string
29
	 */
30
	const REST_ROUTE_NAMESPACE = 'pronamic-pay/adyen/v1';
31
32
	/**
33
	 * Construct Adyen integration.
34
	 *
35
	 * @param array<string, array<string>> $args Arguments.
36
	 */
37 5
	public function __construct( $args = array() ) {
38 5
		$args = wp_parse_args(
39 5
			$args,
40
			array(
41 5
				'id'            => 'adyen',
42 5
				'name'          => 'Adyen',
43 5
				'provider'      => 'adyen',
44 5
				'url'           => \__( 'https://www.adyen.com/', 'pronamic_ideal' ),
45 5
				'product_url'   => \__( 'https://www.adyen.com/pricing', 'pronamic_ideal' ),
46
				'dashboard_url' => array(
47 5
					\__( 'test', 'pronamic_ideal' ) => 'https://ca-test.adyen.com/ca/ca/login.shtml',
48 5
					\__( 'live', 'pronamic_ideal' ) => 'https://ca-live.adyen.com/ca/ca/login.shtml',
49
				),
50 5
				'manual_url'    => \__( 'https://www.pronamic.eu/manuals/using-adyen-pronamic-pay/', 'pronamic_ideal' ),
51
				'supports'      => array(
52
					'webhook',
53
					'webhook_log',
54
				),
55
			)
56
		);
57
58 5
		parent::__construct( $args );
59
60
		// Dependencies.
61 5
		$dependencies = $this->get_dependencies();
62
63 5
		$dependencies->add( new PhpExtensionDependency( 'intl' ) );
64 5
	}
65
66
	/**
67
	 * Setup gateway integration.
68
	 *
69
	 * @return void
70
	 */
71 1
	public function setup() {
72
		// Check if dependencies are met and integration is active.
73 1
		if ( ! $this->is_active() ) {
74
			return;
75
		}
76
77
		// Notifications controller.
78 1
		$notifications_controller = new NotificationsController();
79
80 1
		$notifications_controller->setup();
81
82
		// Payments controller.
83 1
		$payments_controller = new PaymentsController();
84
85 1
		$payments_controller->setup();
86
87
		// Payments result controller.
88 1
		$payments_result_controller = new PaymentsResultController();
89
90 1
		$payments_result_controller->setup();
91
92
		// Site Health controller.
93 1
		$site_healht_controller = new SiteHealthController();
94
95 1
		$site_healht_controller->setup();
96
97
		// Settings.
98 1
		add_action( 'init', array( $this, 'init' ) );
99 1
		add_action( 'admin_init', array( $this, 'admin_init' ), 15 );
100
101
		// Actions.
102 1
		add_action( 'current_screen', array( $this, 'maybe_download_certificate_or_key' ) );
103
104 1
		\add_filter( 'pronamic_gateway_configuration_display_value_' . $this->get_id(), array( $this, 'gateway_configuration_display_value' ), 10, 2 );
105 1
	}
106
107
	/**
108
	 * Initialize.
109
	 *
110
	 * @return void
111
	 */
112 1
	public function init() {
113
		/*
114
		 * Authentication - User Name
115
		 */
116 1
		register_setting(
117 1
			'pronamic_pay',
118 1
			'pronamic_pay_adyen_notification_authentication_username',
119
			array(
120 1
				'type'              => 'string',
121
				'sanitize_callback' => 'sanitize_text_field',
122
			)
123
		);
124
125
		/*
126
		 * Authentication - Password
127
		 */
128 1
		register_setting(
129 1
			'pronamic_pay',
130 1
			'pronamic_pay_adyen_notification_authentication_password',
131
			array(
132 1
				'type'              => 'string',
133
				'sanitize_callback' => 'sanitize_text_field',
134
			)
135
		);
136 1
	}
137
138
	/**
139
	 * Admin initialize.
140
	 *
141
	 * @return void
142
	 */
143 1
	public function admin_init() {
144 1
		add_settings_section(
145 1
			'pronamic_pay_adyen_notification_authentication',
146
			/* translators: Translate 'notification' the same as in the Adyen dashboard. */
147 1
			_x( 'Adyen Notification Authentication', 'Adyen', 'pronamic_ideal' ),
148 1
			array( $this, 'settings_section_notification_authentication' ),
149 1
			'pronamic_pay'
150
		);
151
152 1
		add_settings_field(
153 1
			'pronamic_pay_adyen_notification_authentication_username',
154 1
			__( 'User Name', 'pronamic_ideal' ),
155 1
			array( __CLASS__, 'input_element' ),
156 1
			'pronamic_pay',
157 1
			'pronamic_pay_adyen_notification_authentication',
158
			array(
159 1
				'label_for' => 'pronamic_pay_adyen_notification_authentication_username',
160
			)
161
		);
162
163 1
		add_settings_field(
164 1
			'pronamic_pay_adyen_notification_authentication_password',
165 1
			__( 'Password', 'pronamic_ideal' ),
166 1
			array( __CLASS__, 'input_element' ),
167 1
			'pronamic_pay',
168 1
			'pronamic_pay_adyen_notification_authentication',
169
			array(
170 1
				'label_for' => 'pronamic_pay_adyen_notification_authentication_password',
171
			)
172
		);
173 1
	}
174
175
	/**
176
	 * Settings section notification authentication.
177
	 *
178
	 * @return void
179
	 */
180 1
	public function settings_section_notification_authentication() {
181 1
		printf(
182 1
			'<p>%s</p>',
183 1
			esc_html__(
184 1
				'Set the user name and password below and in the webhook authentication settings in the Adyen dashboard for increased security (recommended).',
185 1
				'pronamic_ideal'
186
			)
187
		);
188 1
	}
189
190
	/**
191
	 * Input text.
192
	 *
193
	 * @param array<string,string> $args Arguments.
194
	 * @return void
195
	 */
196 1
	public static function input_element( $args ) {
197 1
		$name = $args['label_for'];
198
199 1
		$value = get_option( $name );
200 1
		$value = strval( $value );
201
202 1
		printf(
203 1
			'<input name="%s" id="%s" value="%s" type="text" class="regular-text" />',
204 1
			esc_attr( $name ),
205 1
			esc_attr( $name ),
206 1
			esc_attr( $value )
207
		);
208 1
	}
209
210
	/**
211
	 * Get settings fields.
212
	 *
213
	 * @return array<int, array<string, callable|int|string|bool|array<int|string,int|string>>>
214
	 */
215 1
	public function get_settings_fields() {
216 1
		$fields = array();
217
218
		// Merchant Account.
219 1
		$fields[] = array(
220 1
			'section'  => 'general',
221 1
			'filter'   => FILTER_SANITIZE_STRING,
222 1
			'meta_key' => '_pronamic_gateway_adyen_merchant_account',
223 1
			'title'    => _x( 'Merchant Account', 'adyen', 'pronamic_ideal' ),
224 1
			'type'     => 'text',
225
			'classes'  => array( 'regular-text', 'code' ),
226 1
			'tooltip'  => __( 'The merchant account identifier, with which you want to process the transaction.', 'pronamic_ideal' ),
227
		);
228
229
		// API Key.
230 1
		$fields[] = array(
231 1
			'section'     => 'general',
232 1
			'filter'      => FILTER_SANITIZE_STRING,
233 1
			'meta_key'    => '_pronamic_gateway_adyen_api_key',
234 1
			'title'       => _x( 'API Key', 'adyen', 'pronamic_ideal' ),
235 1
			'type'        => 'textarea',
236
			'classes'     => array( 'code' ),
237 1
			'tooltip'     => __( 'API key as mentioned in the payment provider dashboard.', 'pronamic_ideal' ),
238 1
			'description' => sprintf(
239 1
				'<a href="%s" target="_blank">%s</a>',
240 1
				esc_url( 'https://docs.adyen.com/developers/user-management/how-to-get-the-api-key' ),
241 1
				esc_html__( 'Adyen documentation: "How to get the API key".', 'pronamic_ideal' )
242
			),
243
		);
244
245
		// Live API URL prefix.
246 1
		$fields[] = array(
247 1
			'section'     => 'general',
248 1
			'filter'      => FILTER_SANITIZE_STRING,
249 1
			'meta_key'    => '_pronamic_gateway_adyen_api_live_url_prefix',
250 1
			'title'       => _x( 'API Live URL Prefix', 'adyen', 'pronamic_ideal' ),
251 1
			'type'        => 'text',
252
			'classes'     => array( 'regular-text', 'code' ),
253 1
			'tooltip'     => __( 'The unique prefix for the live API URL, as mentioned at <strong>Account » API URLs</strong> in the Adyen dashboard.', 'pronamic_ideal' ),
254 1
			'description' => sprintf(
255 1
				'<a href="%s" target="_blank">%s</a>',
256 1
				esc_url( 'https://docs.adyen.com/developers/development-resources/live-endpoints#liveurlprefix' ),
257 1
				esc_html__( 'Adyen documentation: "Live URL prefix".', 'pronamic_ideal' )
258
			),
259
		);
260
261
		// Origin Key.
262 1
		$fields[] = array(
263 1
			'section'     => 'general',
264 1
			'filter'      => FILTER_SANITIZE_STRING,
265 1
			'meta_key'    => '_pronamic_gateway_adyen_origin_key',
266 1
			'title'       => _x( 'Origin Key', 'adyen', 'pronamic_ideal' ),
267 1
			'type'        => 'text',
268
			'classes'     => array(
269
				'regular-text',
270
				'code',
271
				'pronamic-pay-form-control-lg',
272
			),
273 1
			'tooltip'     => __( 'An origin key is a client-side key that is used to validate Adyen\'s JavaScript component library. It is required for the Drop-in and Component integrations.', 'pronamic_ideal' ),
274 1
			'description' => sprintf(
275 1
				'<a href="%s" target="_blank">%s</a>',
276 1
				esc_url( 'https://docs.adyen.com/user-management/how-to-get-an-origin-key' ),
277 1
				esc_html__( 'Adyen documentation: "How to get an origin key".', 'pronamic_ideal' )
278
			),
279
		);
280
281
		// Merchant Order Reference.
282 1
		$fields[] = array(
283 1
			'section'     => 'advanced',
284
			'filter'      => array(
285
				'filter' => \FILTER_SANITIZE_STRING,
286
				'flags'  => \FILTER_FLAG_NO_ENCODE_QUOTES,
287
			),
288 1
			'meta_key'    => '_pronamic_gateway_adyen_merchant_order_reference',
289 1
			'title'       => __( 'Merchant Order Reference', 'pronamic_ideal' ),
290 1
			'type'        => 'text',
291
			'classes'     => array( 'regular-text', 'code' ),
292 1
			'tooltip'     => \sprintf(
293
				/* translators: %s: <code>parameterName</code> */
294 1
				\__( 'The Adyen %s parameter.', 'pronamic_ideal' ),
295 1
				\sprintf( '<code>%s</code>', 'merchantOrderReference' )
296
			),
297 1
			'description' => \sprintf(
298 1
				'%s %s<br />%s',
299 1
				\__( 'Available tags:', 'pronamic_ideal' ),
300 1
				\sprintf(
301 1
					'<code>%s</code> <code>%s</code>',
302 1
					'{order_id}',
303 1
					'{payment_id}'
304
				),
305 1
				\sprintf(
306
					/* translators: %s: default code */
307 1
					\__( 'Default: <code>%s</code>', 'pronamic_ideal' ),
308 1
					'{payment_id}'
309
				)
310
			),
311
		);
312
313
		// Apple Pay - Merchant identifier.
314 1
		$fields[] = array(
315 1
			'section'     => 'advanced',
316
			'filter'      => \FILTER_SANITIZE_STRING,
317 1
			'meta_key'    => '_pronamic_gateway_adyen_apple_pay_merchant_id',
318 1
			'title'       => _x( 'Apple Pay Merchant ID', 'adyen', 'pronamic_ideal' ),
319 1
			'type'        => 'text',
320
			'classes'     => array( 'regular-text', 'code' ),
321 1
			'tooltip'     => __( 'Your Apple Pay Merchant ID. Required for accepting live payments.', 'pronamic_ideal' ),
322 1
			'description' => sprintf(
323 1
				'<a href="%s" target="_blank">%s</a><br /><a href="%s" target="_blank">%s</a>',
324 1
				esc_url( 'https://docs.adyen.com/payment-methods/apple-pay/web-drop-in#before-you-begin' ),
325 1
				esc_html__( 'Adyen documentation: "Apple Pay Drop-in - Before you begin".', 'pronamic_ideal' ),
326 1
				esc_url( 'https://developer.apple.com/documentation/apple_pay_on_the_web/configuring_your_environment' ),
327 1
				esc_html__( 'Apple documentation: "Configuring your environment".', 'pronamic_ideal' )
328
			),
329
		);
330
331
		// Apple Pay - Merchant Identity PKCS#12.
332 1
		$fields[] = array(
333 1
			'section'     => 'advanced',
334
			'filter'      => \FILTER_SANITIZE_STRING,
335 1
			'meta_key'    => '_pronamic_gateway_adyen_apple_pay_merchant_id_certificate',
336 1
			'title'       => __( 'Apple Pay Merchant Identity Certificate', 'pronamic_ideal' ),
337 1
			'type'        => 'textarea',
338 1
			'callback'    => array( $this, 'field_certificate' ),
339
			'classes'     => array( 'code' ),
340 1
			'tooltip'     => __( 'The Apple Pay Merchant Identity certificate required for secure communication with Apple.', 'pronamic_ideal' ),
341 1
			'description' => sprintf(
342 1
				'<a href="%s" target="_blank">%s</a>',
343 1
				esc_url( 'https://docs.adyen.com/payment-methods/apple-pay/enable-apple-pay#create-merchant-identity-certificate' ),
344 1
				esc_html__( 'Adyen documentation: "Enable Apple Pay - Create a merchant identity certificate".', 'pronamic_ideal' )
345
			),
346
		);
347
348
		// Apple Pay - Merchant Identity private key.
349 1
		$fields[] = array(
350 1
			'section'  => 'advanced',
351
			'filter'   => \FILTER_SANITIZE_STRING,
352 1
			'meta_key' => '_pronamic_gateway_adyen_apple_pay_merchant_id_private_key',
353 1
			'title'    => __( 'Apple Pay Merchant Identity Private Key', 'pronamic_ideal' ),
354 1
			'type'     => 'textarea',
355 1
			'callback' => array( $this, 'field_private_key' ),
356
			'classes'  => array( 'code' ),
357 1
			'tooltip'  => __( 'The private key of the Apple Pay Merchant Identity certificate for secure communication with Apple.', 'pronamic_ideal' ),
358
		);
359
360
		// Apple Pay - Merchant Identity certificate private key password.
361 1
		$fields[] = array(
362 1
			'section'  => 'advanced',
363
			'filter'   => \FILTER_SANITIZE_STRING,
364 1
			'meta_key' => '_pronamic_gateway_adyen_apple_pay_merchant_id_private_key_password',
365 1
			'title'    => _x( 'Apple Pay Merchant Identity Private Key Password', 'adyen', 'pronamic_ideal' ),
366 1
			'type'     => 'text',
367
			'classes'  => array( 'regular-text', 'code' ),
368 1
			'tooltip'  => __( 'Your Apple Pay Merchant Identity Certificate private key password.', 'pronamic_ideal' ),
369
		);
370
371
		// Google Pay - Merchant identifier.
372 1
		$fields[] = array(
373 1
			'section'     => 'advanced',
374
			'filter'      => \FILTER_SANITIZE_STRING,
375 1
			'meta_key'    => '_pronamic_gateway_adyen_google_pay_merchant_identifier',
376 1
			'title'       => _x( 'Google Pay Merchant ID', 'adyen', 'pronamic_ideal' ),
377 1
			'type'        => 'text',
378
			'classes'     => array( 'regular-text', 'code' ),
379 1
			'tooltip'     => __( 'Your Google Merchant ID. Required for accepting live payments.', 'pronamic_ideal' ),
380 1
			'description' => sprintf(
381 1
				'<a href="%s" target="_blank">%s</a><br /><a href="%s" target="_blank">%s</a>',
382 1
				esc_url( 'https://docs.adyen.com/payment-methods/google-pay/web-drop-in#test-and-go-live' ),
383 1
				esc_html__( 'Adyen documentation: "Google Pay Drop-in - Test and go live".', 'pronamic_ideal' ),
384 1
				esc_url( 'https://developers.google.com/pay/api/web/guides/test-and-deploy/deploy-production-environment' ),
385 1
				esc_html__( 'Google documentation: "Deploy production environment".', 'pronamic_ideal' )
386
			),
387
		);
388
389
		// Webhook URL.
390 1
		$fields[] = array(
391 1
			'section'  => 'feedback',
392 1
			'title'    => __( 'Webhook URL', 'pronamic_ideal' ),
393 1
			'type'     => 'text',
394
			'classes'  => array( 'large-text', 'code' ),
395 1
			'value'    => rest_url( self::REST_ROUTE_NAMESPACE . '/notifications' ),
396
			'readonly' => true,
397 1
			'tooltip'  => sprintf(
398
				/* translators: %s: payment provider name */
399 1
				__( 'Copy the Webhook URL to the %s dashboard to receive automatic transaction status updates.', 'pronamic_ideal' ),
400 1
				__( 'Adyen', 'pronamic_ideal' )
401
			),
402
		);
403
404
		/**
405
		 * SSL Version.
406
		 *
407
		 * @link https://docs.adyen.com/developers/development-resources/notifications/set-up-notifications#step3configurenotificationsinthecustomerarea
408
		 * @link https://www.howsmyssl.com/a/check
409
		 */
410 1
		$fields[] = array(
411 1
			'section' => 'feedback',
412 1
			'title'   => __( 'SSL Version', 'pronamic_ideal' ),
413 1
			'type'    => 'description',
414 1
			'html'    => __( 'Choose the SSL Version of your server on the Adyen Customer Area.', 'pronamic_ideal' ),
415
		);
416
417
		/**
418
		 * Method.
419
		 *
420
		 * @link https://docs.adyen.com/developers/development-resources/notifications/set-up-notifications#step3configurenotificationsinthecustomerarea
421
		 * @link https://www.howsmyssl.com/a/check
422
		 */
423 1
		$fields[] = array(
424 1
			'section' => 'feedback',
425 1
			'title'   => _x( 'Method', 'adyen notification', 'pronamic_ideal' ),
426 1
			'type'    => 'description',
427 1
			'html'    => __( 'JSON', 'pronamic_ideal' ),
428
		);
429
430
		// Webhook authentication settings.
431 1
		$fields[] = array(
432 1
			'section' => 'feedback',
433 1
			'title'   => __( 'Authentication', 'pronamic_ideal' ),
434 1
			'type'    => 'description',
435 1
			'html'    => \sprintf(
436
				/* translators: %s: Pronamic Pay settings page URL. */
437 1
				__( 'Go to the <a href="%s">Pronamic Pay settings page</a> for webhook authentication settings.', 'pronamic_ideal' ),
438 1
				\esc_url(
439 1
					\add_query_arg(
440
						array(
441 1
							'page' => 'pronamic_pay_settings',
442
						),
443 1
						\admin_url( 'admin.php' )
444
					)
445
				)
446
			),
447
		);
448
449
		// Return fields.
450 1
		return $fields;
451
	}
452
453
	/**
454
	 * Field certificate.
455
	 *
456
	 * @param array<string> $field Field.
457
	 * @return void
458
	 */
459
	public function field_certificate( $field ) {
460
		if ( ! \array_key_exists( 'meta_key', $field ) ) {
461
			return;
462
		}
463
464
		$post_id = \get_the_ID();
465
466
		if ( false === $post_id ) {
467
			return;
468
		}
469
470
		$certificate = \get_post_meta( $post_id, $field['meta_key'], true );
471
472
		if ( ! empty( $certificate ) ) {
473
			$fingerprint = Security::get_sha_fingerprint( $certificate );
474
475
			echo '<dl>';
476
477
			if ( null !== $fingerprint ) {
478
				$fingerprint = \str_split( $fingerprint, 2 );
479
				$fingerprint = \implode( ':', $fingerprint );
0 ignored issues
show
It seems like $fingerprint can also be of type true; however, parameter $pieces of implode() does only seem to accept array, 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

479
				$fingerprint = \implode( ':', /** @scrutinizer ignore-type */ $fingerprint );
Loading history...
480
481
				echo '<dt>', \esc_html__( 'SHA Fingerprint', 'pronamic_ideal' ), '</dt>';
482
				echo '<dd>', \esc_html( $fingerprint ), '</dd>';
483
			}
484
485
			$info = \openssl_x509_parse( $certificate );
486
487
			if ( $info ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $info 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...
488
				$date_format = __( 'M j, Y @ G:i', 'pronamic_ideal' );
489
490
				if ( isset( $info['validFrom_time_t'] ) ) {
491
					echo '<dt>', \esc_html__( 'Valid From', 'pronamic_ideal' ), '</dt>';
492
					echo '<dd>', \esc_html( \date_i18n( $date_format, $info['validFrom_time_t'] ) ), '</dd>';
493
				}
494
495
				if ( isset( $info['validTo_time_t'] ) ) {
496
					echo '<dt>', \esc_html__( 'Valid To', 'pronamic_ideal' ), '</dt>';
497
					echo '<dd>', \esc_html( \date_i18n( $date_format, $info['validTo_time_t'] ) ), '</dd>';
498
				}
499
			}
500
501
			echo '</dl>';
502
		} elseif ( false !== \strpos( $field['meta_key'], 'apple_pay' ) ) {
503
			\printf(
504
				'<p class="pronamic-pay-description description">%s</p><p>&nbsp;</p>',
505
				\esc_html__( 'Upload an Apple Pay Merchant Identity certificate, which can be exported from Keychain Access on Mac as a PKCS#12 (*.p12) file.', 'pronamic_ideal' )
506
			);
507
		}
508
509
		?>
510
		<p>
511
			<?php
512
513
			if ( ! empty( $certificate ) ) {
514
				\submit_button(
515
					__( 'Download', 'pronamic_ideal' ),
516
					'secondary',
517
					'download' . $field['meta_key'],
518
					false
519
				);
520
521
				echo ' ';
522
			}
523
524
			\printf(
525
				'<label class="pronamic-pay-form-control-file-button button">%s <input type="file" name="%s" /></label>',
526
				\esc_html__( 'Upload', 'pronamic_ideal' ),
527
				\esc_attr( $field['meta_key'] . '_file' )
528
			);
529
530
			?>
531
		</p>
532
		<?php
533
	}
534
535
	/**
536
	 * Field private key.
537
	 *
538
	 * @param array<string> $field Field.
539
	 * @return void
540
	 */
541
	public function field_private_key( $field ) {
542
		if ( ! \array_key_exists( 'meta_key', $field ) ) {
543
			return;
544
		}
545
546
		$post_id = \get_the_ID();
547
548
		if ( false === $post_id ) {
549
			return;
550
		}
551
552
		$private_key = \get_post_meta( $post_id, $field['meta_key'], true );
553
554
		?>
555
		<p>
556
			<?php
557
558
			if ( ! empty( $private_key ) ) {
559
				\submit_button(
560
					__( 'Download', 'pronamic_ideal' ),
561
					'secondary',
562
					'download' . $field['meta_key'],
563
					false
564
				);
565
566
				echo ' ';
567
			}
568
569
			if ( empty( $private_key ) && false !== \strpos( $field['meta_key'], 'apple_pay' ) ) {
570
				\printf(
571
					'<p class="pronamic-pay-description description">%s</p><p>&nbsp;</p>',
572
					\esc_html__( 'Leave empty to auto fill when uploading an Apple Pay Merchant Identity PKCS#12 certificate file.', 'pronamic_ideal' )
573
				);
574
			}
575
576
			\printf(
577
				'<label class="pronamic-pay-form-control-file-button button">%s <input type="file" name="%s" /></label>',
578
				\esc_html__( 'Upload', 'pronamic_ideal' ),
579
				\esc_attr( $field['meta_key'] . '_file' )
580
			);
581
582
			?>
583
		</p>
584
		<?php
585
	}
586
587
	/**
588
	 * Download certificate or key in Privacy Enhanced Mail (PEM) format.
589
	 *
590
	 * @return void
591
	 */
592
	public function maybe_download_certificate_or_key() {
593
		// Certificate fields and download filename.
594
		$fields = array(
595
			'_pronamic_gateway_adyen_apple_pay_merchant_id_certificate' => 'apple-pay-merchant-identity-certificate-%s.pem',
596
			'_pronamic_gateway_adyen_apple_pay_merchant_id_private_key' => 'apple-pay-merchant-identity-private-key-%s.pem',
597
		);
598
599
		// Check download actions.
600
		$is_download_action = false;
601
602
		foreach ( $fields as $meta_key => $filename ) {
603
			if ( \filter_has_var( \INPUT_POST, 'download' . $meta_key ) ) {
604
				$is_download_action = true;
605
606
				break;
607
			}
608
		}
609
610
		// No valid download action found.
611
		if ( false === $is_download_action ) {
612
			return;
613
		}
614
615
		$post_id = filter_input( \INPUT_POST, 'post_ID', \FILTER_SANITIZE_STRING );
616
617
		$filename = sprintf( $filename, $post_id );
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $filename seems to be defined by a foreach iteration on line 602. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
618
619
		header( 'Content-Description: File Transfer' );
620
		header( 'Content-Disposition: attachment; filename=' . $filename );
621
		header( 'Content-Type: application/x-pem-file; charset=' . get_option( 'blog_charset' ), true );
622
623
		// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
624
		echo get_post_meta( $post_id, $meta_key, true );
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $meta_key seems to be defined by a foreach iteration on line 602. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
625
626
		exit;
0 ignored issues
show
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
627
	}
628
629
	/**
630
	 * Gateway configuration display value.
631
	 *
632
	 * @param string $display_value Display value.
633
	 * @param int    $post_id       Gateway configuration post ID.
634
	 * @return string
635
	 */
636
	public function gateway_configuration_display_value( $display_value, $post_id ) {
637
		$config = $this->get_config( $post_id );
638
639
		return $config->get_merchant_account();
640
	}
641
642
	/**
643
	 * Save post.
644
	 *
645
	 * @param int $post_id Post ID.
646
	 * @return void
647
	 */
648
	public function save_post( $post_id ) {
649
		// Files.
650
		$files = array(
651
			'_pronamic_gateway_adyen_apple_pay_merchant_id_certificate_file' => '_pronamic_gateway_adyen_apple_pay_merchant_id_certificate',
652
			'_pronamic_gateway_adyen_apple_pay_merchant_id_private_key_file' => '_pronamic_gateway_adyen_apple_pay_merchant_id_private_key',
653
		);
654
655
		foreach ( $files as $name => $meta_key ) {
656
			// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated
657
			if ( isset( $_FILES[ $name ] ) && \UPLOAD_ERR_OK === $_FILES[ $name ]['error'] ) {
658
				// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated, WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
659
				$value = file_get_contents( $_FILES[ $name ]['tmp_name'], true );
660
661
				update_post_meta( $post_id, $meta_key, $value );
662
			}
663
		}
664
665
		// Update Apple Pay Merchant Identity certificate and private key from uploaded PKCS#12 file.
666
		$apple_pay_merchant_id_pkcs12 = get_post_meta( $post_id, '_pronamic_gateway_adyen_apple_pay_merchant_id_certificate', true );
667
668
		if ( ! empty( $apple_pay_merchant_id_pkcs12 ) ) {
669
			// Try to read file without using password.
670
			$pkcs12_read = \openssl_pkcs12_read( $apple_pay_merchant_id_pkcs12, $certs, '' );
671
672
			$password = \get_post_meta( $post_id, '_pronamic_gateway_adyen_apple_pay_merchant_id_private_key_password', true );
673
674
			// Try to read file with private key password.
675
			if ( false === $pkcs12_read ) {
676
				$pkcs12_read = \openssl_pkcs12_read( $apple_pay_merchant_id_pkcs12, $certs, $password );
0 ignored issues
show
It seems like $password can also be of type false and null; however, parameter $passphrase of openssl_pkcs12_read() 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

676
				$pkcs12_read = \openssl_pkcs12_read( $apple_pay_merchant_id_pkcs12, $certs, /** @scrutinizer ignore-type */ $password );
Loading history...
677
			}
678
679
			if ( true === $pkcs12_read ) {
680
				if ( isset( $certs['cert'] ) ) {
681
					\update_post_meta( $post_id, '_pronamic_gateway_adyen_apple_pay_merchant_id_certificate', $certs['cert'] );
682
				}
683
684
				if ( isset( $certs['pkey'] ) ) {
685
					$private_key = $certs['pkey'];
686
687
					$cipher = null;
688
689
					// Try to export the private key encrypted.
690
					if ( defined( 'OPENSSL_CIPHER_AES_128_CBC' ) ) {
691
						$cipher = \OPENSSL_CIPHER_AES_128_CBC;
692
					} elseif ( defined( 'OPENSSL_CIPHER_3DES' ) ) {
693
						$cipher = \OPENSSL_CIPHER_3DES;
694
					}
695
696
					if ( null !== $cipher && '' !== $password ) {
697
						$args = array(
698
							'digest_alg'             => 'SHA256',
699
							'private_key_bits'       => 2048,
700
							'private_key_type'       => \OPENSSL_KEYTYPE_RSA,
701
							'encrypt_key'            => true,
702
							'encrypt_key_cipher'     => $cipher,
703
							'subjectKeyIdentifier'   => 'hash',
704
							'authorityKeyIdentifier' => 'keyid:always,issuer:always',
705
							'basicConstraints'       => 'CA:true',
706
						);
707
708
						\openssl_pkey_export( $certs['pkey'], $private_key, $password, $args );
0 ignored issues
show
It seems like $password can also be of type false; however, parameter $passphrase of openssl_pkey_export() does only seem to accept null|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

708
						\openssl_pkey_export( $certs['pkey'], $private_key, /** @scrutinizer ignore-type */ $password, $args );
Loading history...
709
					}
710
711
					\update_post_meta( $post_id, '_pronamic_gateway_adyen_apple_pay_merchant_id_private_key', $private_key );
712
				}
713
			}
714
		}
715
	}
716
717
	/**
718
	 * Get configuration by post ID.
719
	 *
720
	 * @param int $post_id Post ID.
721
	 * @return Config
722
	 */
723 1
	public function get_config( $post_id ) {
724 1
		$config = new Config();
725
726 1
		$config->mode                                       = $this->get_meta( $post_id, 'mode' );
727 1
		$config->api_key                                    = $this->get_meta( $post_id, 'adyen_api_key' );
728 1
		$config->api_live_url_prefix                        = $this->get_meta( $post_id, 'adyen_api_live_url_prefix' );
729 1
		$config->merchant_account                           = $this->get_meta( $post_id, 'adyen_merchant_account' );
730 1
		$config->origin_key                                 = $this->get_meta( $post_id, 'adyen_origin_key' );
731 1
		$config->merchant_order_reference                   = $this->get_meta( $post_id, 'adyen_merchant_order_reference' );
732 1
		$config->apple_pay_merchant_id                      = $this->get_meta( $post_id, 'adyen_apple_pay_merchant_id' );
733 1
		$config->apple_pay_merchant_id_certificate          = $this->get_meta( $post_id, 'adyen_apple_pay_merchant_id_certificate' );
734 1
		$config->apple_pay_merchant_id_private_key          = $this->get_meta( $post_id, 'adyen_apple_pay_merchant_id_private_key' );
735 1
		$config->apple_pay_merchant_id_private_key_password = $this->get_meta( $post_id, 'adyen_apple_pay_merchant_id_private_key_password' );
736 1
		$config->google_pay_merchant_identifier             = $this->get_meta( $post_id, 'adyen_google_pay_merchant_identifier' );
737
738 1
		return $config;
739
	}
740
741
	/**
742
	 * Get gateway.
743
	 *
744
	 * @param int $post_id Post ID.
745
	 * @return AbstractGateway
746
	 */
747 1
	public function get_gateway( $post_id ) {
748 1
		$config = $this->get_config( $post_id );
749
750 1
		if ( empty( $config->origin_key ) ) {
751 1
			return new WebSdkGateway( $config );
752
		}
753
754
		return new DropInGateway( $config );
755
	}
756
}
757