Failed Conditions
Push — develop ( 838bc7...7dad48 )
by Reüel
16:09
created

DropInGateway   A

Complexity

Total Complexity 42

Size/Duplication

Total Lines 513
Duplicated Lines 0 %

Test Coverage

Coverage 3.09%

Importance

Changes 4
Bugs 0 Features 0
Metric Value
eloc 213
c 4
b 0
f 0
dl 0
loc 513
ccs 6
cts 194
cp 0.0309
rs 9.0399
wmc 42

9 Methods

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