Failed Conditions
Push — master ( 0a235e...cf5794 )
by Reüel
09:07 queued 11s
created

DropInGateway   B

Complexity

Total Complexity 47

Size/Duplication

Total Lines 573
Duplicated Lines 0 %

Test Coverage

Coverage 2.74%

Importance

Changes 9
Bugs 0 Features 0
Metric Value
eloc 239
dl 0
loc 573
ccs 6
cts 219
cp 0.0274
rs 8.64
c 9
b 0
f 0
wmc 47

9 Methods

Rating   Name   Duplication   Size   Complexity  
A start() 0 41 4
F payment_redirect() 0 170 14
A __construct() 0 7 1
B get_checkout_payment_methods_configuration() 0 74 8
A send_payment_details() 0 4 1
C update_status() 0 82 12
A create_payment() 0 81 5
A get_supported_payment_methods() 0 12 1
A checkout_head() 0 6 1

How to fix   Complexity   

Complex Class

Complex classes like DropInGateway 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 DropInGateway, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * Drop-in gateway
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\Core\Gateway as Core_Gateway;
14
use Pronamic\WordPress\Pay\Core\PaymentMethods;
15
use Pronamic\WordPress\Pay\Core\Server;
16
use Pronamic\WordPress\Pay\Core\Util as Core_Util;
17
use Pronamic\WordPress\Pay\Payments\Payment;
18
use Pronamic\WordPress\Pay\Plugin;
19
20
/**
21
 * Drop-in gateway
22
 *
23
 * @link https://github.com/adyenpayments/php/blob/master/generatepaymentform.php
24
 *
25
 * @author  Remco Tolsma
26
 * @version 1.1.1
27
 * @since   1.0.0
28
 */
29
class DropInGateway extends AbstractGateway {
30
	/**
31
	 * Web SDK version.
32
	 *
33
	 * @link https://docs.adyen.com/developers/checkout/web-sdk/release-notes-web-sdk
34
	 *
35
	 * @var string
36
	 */
37
	const SDK_VERSION = '3.4.0';
38
39
	/**
40
	 * Constructs and initializes an Adyen gateway.
41
	 *
42
	 * @param Config $config Config.
43
	 */
44 1
	public function __construct( Config $config ) {
45 1
		parent::__construct( $config );
46
47
		// Supported features.
48 1
		$this->supports = array(
49
			'webhook_log',
50
			'webhook',
51
		);
52 1
	}
53
54
	/**
55
	 * Get supported payment methods
56
	 *
57
	 * @return array<string>
58
	 * @see Core_Gateway::get_supported_payment_methods()
59
	 */
60 1
	public function get_supported_payment_methods() {
61
		return array(
62 1
			PaymentMethods::ALIPAY,
63
			PaymentMethods::APPLE_PAY,
64
			PaymentMethods::BANCONTACT,
65
			PaymentMethods::CREDIT_CARD,
66
			PaymentMethods::DIRECT_DEBIT,
67
			PaymentMethods::EPS,
68
			PaymentMethods::GIROPAY,
69
			PaymentMethods::GOOGLE_PAY,
70
			PaymentMethods::IDEAL,
71
			PaymentMethods::SOFORT,
72
		);
73
	}
74
75
	/**
76
	 * Start.
77
	 *
78
	 * @param Payment $payment Payment.
79
	 *
80
	 * @return void
81
	 * @see Plugin::start()
82
	 */
83
	public function start( Payment $payment ) {
84
		$payment->set_meta( 'adyen_sdk_version', self::SDK_VERSION );
85
		$payment->set_action_url( $payment->get_pay_redirect_url() );
86
87
		/*
88
		 * API Integration
89
		 *
90
		 * @link https://docs.adyen.com/api-explorer/#/PaymentSetupAndVerificationService/v41/payments
91
		 */
92
		$api_integration_payment_method_types = array(
93
			PaymentMethodType::ALIPAY,
94
			PaymentMethodType::IDEAL,
95
			PaymentMethodType::DIRECT_EBANKING,
96
		);
97
98
		// Return early if API integration is not being used.
99
		$payment_method_type = PaymentMethodType::transform( $payment->get_method() );
100
101
		if ( ! in_array( $payment_method_type, $api_integration_payment_method_types, true ) ) {
102
			return;
103
		}
104
105
		// Payment method.
106
		$payment_method = array(
107
			'type' => $payment_method_type,
108
		);
109
110
		if ( PaymentMethodType::IDEAL === $payment_method_type ) {
111
			$payment_method['issuer'] = (string) $payment->get_issuer();
112
		}
113
114
		$payment_method = new PaymentMethod( (object) $payment_method );
115
116
		// Create payment.
117
		$payment_response = $this->create_payment( $payment, $payment_method );
118
119
		// Set payment action URL.
120
		$redirect = $payment_response->get_redirect();
121
122
		if ( null !== $redirect ) {
123
			$payment->set_action_url( $redirect->get_url() );
124
		}
125
	}
126
127
	/**
128
	 * Payment redirect.
129
	 *
130
	 * @param Payment $payment Payment.
131
	 *
132
	 * @return void
133
	 */
134
	public function payment_redirect( Payment $payment ) {
135
		// Check payment ID.
136
		$payment_id = $payment->get_id();
137
138
		if ( null === $payment_id ) {
139
			return;
140
		}
141
142
		$payment_response = $payment->get_meta( 'adyen_payment_response' );
143
144
		// Only show drop-in checkout page if payment method does not redirect.
145
		if ( is_object( $payment_response ) ) {
146
			$payment_response = PaymentResponse::from_object( $payment_response );
147
148
			$redirect = $payment_response->get_redirect();
149
150
			if ( null !== $redirect ) {
151
				\wp_redirect( $redirect->get_url() );
152
			}
153
		}
154
155
		/**
156
		 * Payment methods.
157
		 */
158
		$request = new PaymentMethodsRequest( $this->config->get_merchant_account() );
159
160
		if ( null !== $payment->get_method() ) {
161
			// Payment method type.
162
			$payment_method_type = PaymentMethodType::transform( $payment->get_method() );
163
164
			if ( null !== $payment_method_type ) {
165
				$request->set_allowed_payment_methods( array( $payment_method_type ) );
166
			}
167
		}
168
169
		// Prevent Apple Pay if no merchant identifier has been configured.
170
		$apple_pay_merchant_id = $this->config->get_apple_pay_merchant_id();
171
172
		if ( empty( $apple_pay_merchant_id ) ) {
173
			$request->set_blocked_payment_methods( array( PaymentMethodType::APPLE_PAY ) );
174
		}
175
176
		// Set country code.
177
		$locale = Util::get_payment_locale( $payment );
178
179
		$country_code = \Locale::getRegion( $locale );
180
181
		$billing_address = $payment->get_billing_address();
182
183
		if ( null !== $billing_address ) {
184
			$country = $billing_address->get_country_code();
185
186
			if ( null !== $country ) {
187
				$country_code = $country;
188
			}
189
		}
190
191
		$request->set_country_code( $country_code );
192
		$request->set_amount( AmountTransformer::transform( $payment->get_total_amount() ) );
193
194
		try {
195
			$payment_methods = $this->client->get_payment_methods( $request );
196
		} catch ( \Exception $e ) {
197
			Plugin::render_exception( $e );
198
199
			exit;
200
		}
201
202
		$payment_method_types = $payment_methods->get_payment_method_types();
203
204
		// Register scripts.
205
		$url_script = sprintf(
206
			'https://checkoutshopper-%s.adyen.com/checkoutshopper/sdk/%s/adyen.js',
207
			( self::MODE_TEST === $payment->get_mode() ? 'test' : 'live' ),
208
			self::SDK_VERSION
209
		);
210
211
		// phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion -- Version is part of URL.
212
		wp_register_script(
213
			'pronamic-pay-adyen-checkout',
214
			$url_script,
215
			array(),
216
			self::SDK_VERSION,
217
			false
218
		);
219
220
		wp_register_script(
221
			'pronamic-pay-adyen-google-pay',
222
			'https://pay.google.com/gp/p/js/pay.js',
223
			array(),
224
			\pronamic_pay_plugin()->get_version(),
225
			false
226
		);
227
228
		$dependencies = array( 'pronamic-pay-adyen-checkout' );
229
230
		if ( \in_array( PaymentMethodType::GOOGLE_PAY, $payment_method_types, true ) ) {
231
			$dependencies[] = 'pronamic-pay-adyen-google-pay';
232
		}
233
234
		wp_register_script(
235
			'pronamic-pay-adyen-checkout-drop-in',
236
			plugins_url( '../js/dist/checkout-drop-in.js', __FILE__ ),
237
			$dependencies,
238
			\pronamic_pay_plugin()->get_version(),
239
			true
240
		);
241
242
		// Register styles.
243
		$url_stylesheet = sprintf(
244
			'https://checkoutshopper-%s.adyen.com/checkoutshopper/sdk/%s/adyen.css',
245
			( self::MODE_TEST === $payment->get_mode() ? 'test' : 'live' ),
246
			self::SDK_VERSION
247
		);
248
249
		// phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion -- Version is part of URL.
250
		wp_register_style(
251
			'pronamic-pay-adyen-checkout',
252
			$url_stylesheet,
253
			array(),
254
			null
255
		);
256
257
		/**
258
		 * Adyen checkout configuration.
259
		 *
260
		 * @link https://docs.adyen.com/checkout/drop-in-web
261
		 * @link https://docs.adyen.com/checkout/components-web
262
		 */
263
		$configuration = (object) array(
264
			'locale'                 => Util::get_payment_locale( $payment ),
265
			'environment'            => ( self::MODE_TEST === $payment->get_mode() ? 'test' : 'live' ),
266
			'originKey'              => $this->config->origin_key,
267
			'paymentMethodsResponse' => $payment_methods->get_original_object(),
268
			'amount'                 => AmountTransformer::transform( $payment->get_total_amount() )->get_json(),
269
		);
270
271
		/**
272
		 * Filters the Adyen checkout configuration.
273
		 *
274
		 * @param object $configuration Adyen checkout configuration.
275
		 * @since 1.2.0
276
		 */
277
		$configuration = apply_filters( 'pronamic_pay_adyen_checkout_configuration', $configuration );
278
279
		wp_localize_script(
280
			'pronamic-pay-adyen-checkout',
281
			'pronamicPayAdyenCheckout',
282
			array(
283
				'paymentMethodsConfiguration'   => $this->get_checkout_payment_methods_configuration( $payment_method_types, $payment ),
284
				'paymentsUrl'                   => rest_url( Integration::REST_ROUTE_NAMESPACE . '/payments/' . $payment_id ),
285
				'paymentsDetailsUrl'            => rest_url( Integration::REST_ROUTE_NAMESPACE . '/payments/details/' ),
286
				'applePayMerchantValidationUrl' => rest_url( Integration::REST_ROUTE_NAMESPACE . '/payments/applepay/merchant-validation/' . $payment_id ),
287
				'paymentReturnUrl'              => $payment->get_return_url(),
288
				'configuration'                 => $configuration,
289
				'paymentAuthorised'             => __( 'Payment completed successfully.', 'pronamic_ideal' ),
290
				'paymentReceived'               => __( 'The order has been received and we are waiting for the payment to clear.', 'pronamic_ideal' ),
291
				'paymentRefused'                => __( 'The payment has been refused. Please try again using a different method or card.', 'pronamic_ideal' ),
292
			)
293
		);
294
295
		// Add checkout head action.
296
		add_action( 'pronamic_pay_adyen_checkout_head', array( $this, 'checkout_head' ) );
297
298
		// No cache.
299
		Core_Util::no_cache();
300
301
		require __DIR__ . '/../views/checkout-drop-in.php';
302
303
		exit;
304
	}
305
306
	/**
307
	 * Checkout head.
308
	 *
309
	 * @return void
310
	 */
311
	public function checkout_head() {
312
		wp_print_styles( 'pronamic-pay-redirect' );
313
314
		wp_print_scripts( 'pronamic-pay-adyen-checkout' );
315
316
		wp_print_styles( 'pronamic-pay-adyen-checkout' );
317
	}
318
319
	/**
320
	 * Update status of the specified payment.
321
	 *
322
	 * @param Payment $payment Payment.
323
	 *
324
	 * @return void
325
	 */
326
	public function update_status( Payment $payment ) {
327
		// Process payload on return.
328
		if ( filter_has_var( INPUT_GET, 'payload' ) ) {
329
			$payload = filter_input( INPUT_GET, 'payload', FILTER_SANITIZE_STRING );
330
331
			$payment_result_request = new PaymentResultRequest( $payload );
332
333
			try {
334
				$payment_result_response = $this->client->get_payment_result( $payment_result_request );
335
336
				PaymentResultHelper::update_payment( $payment, $payment_result_response );
337
			} catch ( \Exception $e ) {
338
				$note = sprintf(
339
				/* translators: %s: exception message */
340
					__( 'Error getting payment result: %s', 'pronamic_ideal' ),
341
					$e->getMessage()
342
				);
343
344
				$payment->add_note( $note );
345
			}
346
347
			return;
348
		}
349
350
		// Retrieve status from payment details.
351
		$payment_response = $payment->get_meta( 'adyen_payment_response' );
352
353
		if ( is_object( $payment_response ) ) {
354
			$payment_response = PaymentResponse::from_object( $payment_response );
355
356
			$details_result = $payment->get_meta( 'adyen_details_result' );
357
358
			// Set details result meta from GET or POST request parameters.
359
			if ( '' === $details_result ) {
360
				$details_result = array();
361
362
				$details = $payment_response->get_details();
363
364
				if ( null !== $details ) {
365
					$input_type = ( 'POST' === Server::get( 'REQUEST_METHOD' ) ? INPUT_POST : INPUT_GET );
366
367
					foreach ( $details as $detail ) {
368
						$key = (string) $detail->get_key();
369
370
						$details_result[ $key ] = \filter_input( $input_type, $key, FILTER_SANITIZE_STRING );
371
					}
372
373
					$details_result = Util::filter_null( $details_result );
374
				}
375
376
				if ( ! empty( $details_result ) ) {
377
					$payment->set_meta( 'adyen_details_result', \wp_json_encode( (object) $details_result ) );
378
				}
379
			}
380
381
			$payment_data = $payment_response->get_payment_data();
382
383
			// Do not attempt to retrieve status without any request data,
384
			// payment status already updated when additional details were submitted (i.e. cards).
385
			if ( empty( $details_result ) && empty( $payment_data ) ) {
386
				return;
387
			}
388
389
			// Update payment status from payment details.
390
			$payment_details_request = new PaymentDetailsRequest();
391
392
			$payment_details_request->set_details( (object) $details_result );
393
394
			$payment_details_request->set_payment_data( $payment_data );
395
396
			try {
397
				$payment_details_response = $this->client->request_payment_details( $payment_details_request );
398
399
				PaymentResponseHelper::update_payment( $payment, $payment_details_response );
400
			} catch ( \Exception $e ) {
401
				$note = sprintf(
402
					/* translators: %s: exception message */
403
					__( 'Error getting payment details: %s', 'pronamic_ideal' ),
404
					$e->getMessage()
405
				);
406
407
				$payment->add_note( $note );
408
			}
409
		}
410
	}
411
412
	/**
413
	 * Create payment.
414
	 *
415
	 * @param Payment       $payment        Payment.
416
	 * @param PaymentMethod $payment_method Payment method.
417
	 * @param object        $data           Adyen `state.data` object from drop-in.
418
	 *
419
	 * @return PaymentResponse
420
	 * @throws \InvalidArgumentException Throws exception on invalid amount.
421
	 * @throws \Exception Throws exception if payment creation request fails.
422
	 */
423
	public function create_payment( Payment $payment, PaymentMethod $payment_method, $data = null ) {
424
		$amount = AmountTransformer::transform( $payment->get_total_amount() );
425
426
		// Payment request.
427
		$payment_request = new PaymentRequest(
428
			$amount,
429
			$this->config->get_merchant_account(),
430
			strval( $payment->get_id() ),
431
			$payment->get_return_url(),
432
			$payment_method
433
		);
434
435
		// phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase -- Adyen JSON object.
436
437
		// Set browser info.
438
		if ( \is_object( $data ) && isset( $data->browserInfo ) ) {
439
			$browser_info = BrowserInformation::from_object( $data->browserInfo );
440
441
			$payment_request->set_browser_info( $browser_info );
442
		}
443
444
		// phpcs:enable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase -- Adyen JSON object.
445
446
		// Merchant order reference.
447
		$payment_request->set_merchant_order_reference( $payment->format_string( $this->config->get_merchant_order_reference() ) );
448
449
		/**
450
		 * Application info.
451
		 *
452
		 * @link https://docs.adyen.com/api-explorer/#/PaymentSetupAndVerificationService/v51/payments__reqParam_applicationInfo
453
		 * @link https://docs.adyen.com/development-resources/building-adyen-solutions
454
		 */
455
		$application_info = new ApplicationInfo();
456
457
		$application_info->merchant_application = (object) array(
458
			'name'    => 'Pronamic Pay',
459
			'version' => \pronamic_pay_plugin()->get_version(),
460
		);
461
462
		$application_info->external_platform = (object) array(
463
			'integrator' => 'Pronamic',
464
			'name'       => 'WordPress',
465
			'version'    => \get_bloginfo( 'version' ),
466
		);
467
468
		$payment_request->set_application_info( $application_info );
469
470
		// Set country code.
471
		$locale = Util::get_payment_locale( $payment );
472
473
		$country_code = \Locale::getRegion( $locale );
474
475
		$billing_address = $payment->get_billing_address();
476
477
		if ( null !== $billing_address ) {
478
			$country = $billing_address->get_country_code();
479
480
			if ( null !== $country ) {
481
				$country_code = $country;
482
			}
483
		}
484
485
		$payment_request->set_country_code( $country_code );
486
487
		// Complement payment request.
488
		PaymentRequestHelper::complement( $payment, $payment_request );
489
490
		// Create payment.
491
		$payment_response = $this->client->create_payment( $payment_request );
492
493
		/*
494
		 * Store payment response for later requests to `/payments/details`.
495
		 *
496
		 * @link https://docs.adyen.com/api-explorer/#/PaymentSetupAndVerificationService/v51/payments/details
497
		 */
498
		$payment->set_meta( 'adyen_payment_response', $payment_response->get_json() );
499
500
		// Update payment status based on response.
501
		PaymentResponseHelper::update_payment( $payment, $payment_response );
502
503
		return $payment_response;
504
	}
505
506
	/**
507
	 * Send payment details.
508
	 *
509
	 * @param PaymentDetailsRequest $payment_details_request Payment details request.
510
	 *
511
	 * @return PaymentResponse
512
	 * @throws \Exception Throws error if request fails.
513
	 */
514
	public function send_payment_details( PaymentDetailsRequest $payment_details_request ) {
515
		$payment_response = $this->client->request_payment_details( $payment_details_request );
516
517
		return $payment_response;
518
	}
519
520
	/**
521
	 * Get checkout payment methods configuration.
522
	 *
523
	 * @param array<int, string> $payment_method_types Payment method types.
524
	 * @param Payment            $payment              Payment.
525
	 *
526
	 * @return object
527
	 */
528
	public function get_checkout_payment_methods_configuration( $payment_method_types, Payment $payment ) {
529
		$configuration = array();
530
531
		/*
532
		 * Apple Pay.
533
		 *
534
		 * @link https://docs.adyen.com/payment-methods/apple-pay/web-drop-in#show-apple-pay-in-your-payment-form
535
		 */
536
		if ( \in_array( PaymentMethodType::APPLE_PAY, $payment_method_types, true ) ) {
537
			$configuration['applepay'] = array(
538
				'amount'        => $payment->get_total_amount()->get_minor_units(),
539
				'currencyCode'  => $payment->get_total_amount()->get_currency()->get_alphabetic_code(),
540
				'configuration' => array(
541
					'merchantName'       => \get_bloginfo( 'name' ),
542
					'merchantIdentifier' => $this->config->get_apple_pay_merchant_id(),
543
				),
544
			);
545
546
			// Line items.
547
			$lines = $payment->get_lines();
548
549
			if ( null !== $lines ) {
550
				$line_items = array();
551
552
				foreach ( $lines as $line ) {
553
					$line_items[] = array(
554
						'label'  => $line->get_name(),
555
						'amount' => (string) $line->get_total_amount()->get_value(),
556
						'type'   => 'final',
557
					);
558
				}
559
560
				$configuration['applepay']['lineItems'] = $line_items;
561
			}
562
		}
563
564
		/*
565
		 * Cards.
566
		 *
567
		 * @link https://docs.adyen.com/payment-methods/cards/web-drop-in#show-the-available-cards-in-your-payment-form
568
		 */
569
		if ( \in_array( PaymentMethodType::SCHEME, $payment_method_types, true ) ) {
570
			$configuration['card'] = array(
571
				'enableStoreDetails' => true,
572
				'hasHolderName'      => true,
573
				'holderNameRequired' => true,
574
				'hideCVC'            => false,
575
				'name'               => __( 'Credit or debit card', 'pronamic_ideal' ),
576
			);
577
		}
578
579
		/*
580
		 * Google Pay.
581
		 *
582
		 * @link https://docs.adyen.com/payment-methods/google-pay/web-drop-in#show-google-pay-in-your-payment-form
583
		 */
584
		if ( \in_array( PaymentMethodType::GOOGLE_PAY, $payment_method_types, true ) ) {
585
			$configuration['paywithgoogle'] = array(
586
				'environment'   => ( self::MODE_TEST === $this->config->mode ? 'TEST' : 'PRODUCTION' ),
587
				'amount'        => array(
588
					'currency' => $payment->get_total_amount()->get_currency()->get_alphabetic_code(),
589
					'value'    => $payment->get_total_amount()->get_minor_units(),
590
				),
591
				'configuration' => array(
592
					'gatewayMerchantId' => $this->config->merchant_account,
593
				),
594
			);
595
596
			if ( self::MODE_LIVE === $this->config->mode ) {
597
				$configuration['paywithgoogle']['configuration']['merchantIdentifier'] = $this->config->get_google_pay_merchant_identifier();
598
			}
599
		}
600
601
		return (object) $configuration;
602
	}
603
}
604