DropInGateway::payment_redirect()   F
last analyzed

Complexity

Conditions 14
Paths 271

Size

Total Lines 170
Code Lines 90

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 210

Importance

Changes 6
Bugs 0 Features 0
Metric Value
cc 14
eloc 90
c 6
b 0
f 0
nc 271
nop 1
dl 0
loc 170
ccs 0
cts 85
cp 0
crap 210
rs 3.8548

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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