Failed Conditions
Push — master ( 84f509...0a235e )
by Remco
14:29 queued 05:04
created

get_checkout_payment_methods_configuration()   B

Complexity

Conditions 8
Paths 30

Size

Total Lines 74
Code Lines 35

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 72

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 8
eloc 35
c 2
b 0
f 0
nc 30
nop 2
dl 0
loc 74
ccs 0
cts 30
cp 0
crap 72
rs 8.1155

How to fix   Long Method   

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