Failed Conditions
Push — develop ( 9abb35...58f301 )
by Remco
05:08
created

DropInGateway::send_payment_details()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 2
c 0
b 0
f 0
nc 1
nop 2
dl 0
loc 4
ccs 0
cts 3
cp 0
crap 2
rs 10
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
	 */
63 1
	public function get_supported_payment_methods() {
64
		return array(
65 1
			PaymentMethods::ALIPAY,
66
			PaymentMethods::BANCONTACT,
67
			PaymentMethods::CREDIT_CARD,
68
			PaymentMethods::DIRECT_DEBIT,
69
			PaymentMethods::EPS,
70
			PaymentMethods::GIROPAY,
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
	 */
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
		if ( $payment_response instanceof \WP_Error ) {
122
			$this->error = $payment_response;
123
124
			return;
125
		}
126
127
		// Set payment action URL.
128
		$redirect = $payment_response->get_redirect();
129
130
		if ( null !== $redirect ) {
131
			$payment->set_action_url( $redirect->get_url() );
132
		}
133
	}
134
135
	/**
136
	 * Payment redirect.
137
	 *
138
	 * @param Payment $payment Payment.
139
	 *
140
	 * @return void
141
	 */
142
	public function payment_redirect( Payment $payment ) {
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 ( '' !== $payment_response ) {
147
			$payment_response = PaymentResponse::from_object( $payment_response );
0 ignored issues
show
Bug introduced by
It seems like $payment_response can also be of type false and string; however, parameter $object of Pronamic\WordPress\Pay\G...Response::from_object() does only seem to accept object, 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

147
			$payment_response = PaymentResponse::from_object( /** @scrutinizer ignore-type */ $payment_response );
Loading history...
148
149
			$redirect = $payment_response->get_redirect();
150
151
			if ( null !== $redirect ) {
152
				\wp_redirect( $redirect->get_url() );
153
			}
154
		}
155
156
		$url_script = sprintf(
157
			'https://checkoutshopper-%s.adyen.com/checkoutshopper/sdk/%s/adyen.js',
158
			( self::MODE_TEST === $payment->get_mode() ? 'test' : 'live' ),
159
			self::SDK_VERSION
160
		);
161
162
		// phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion -- Version is part of URL.
163
		wp_register_script(
164
			'pronamic-pay-adyen-checkout',
165
			$url_script,
166
			array(),
167
			null,
168
			false
169
		);
170
171
		wp_register_script(
172
			'pronamic-pay-adyen-checkout-drop-in',
173
			plugins_url( '../js/dist/checkout-drop-in.js', __FILE__ ),
174
			array ( 'pronamic-pay-adyen-checkout' ),
175
			null,
176
			true
177
		);
178
179
		$url_stylesheet = sprintf(
180
			'https://checkoutshopper-%s.adyen.com/checkoutshopper/sdk/%s/adyen.css',
181
			( self::MODE_TEST === $payment->get_mode() ? 'test' : 'live' ),
182
			self::SDK_VERSION
183
		);
184
185
		// phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion -- Version is part of URL.
186
		wp_register_style(
187
			'pronamic-pay-adyen-checkout',
188
			$url_stylesheet,
189
			array(),
190
			null
191
		);
192
193
		/**
194
		 * Payment methods.
195
		 */
196
		$request = new PaymentMethodsRequest( $this->config->get_merchant_account() );
197
198
		if ( null !== $payment->get_method() ) {
199
			// Payment method type.
200
			$payment_method_type = PaymentMethodType::transform( $payment->get_method() );
201
202
			$request->set_allowed_payment_methods( array( $payment_method_type ) );
203
		}
204
205
		$locale = Util::get_payment_locale( $payment );
206
207
		$country_code = Locale::getRegion( $locale );
208
209
		$request->set_country_code( $country_code );
210
		$request->set_amount( AmountTransformer::transform( $payment->get_total_amount() ) );
211
212
		try {
213
			$payment_methods = $this->client->get_payment_methods( $request );
214
		} catch ( \Exception $e ) {
215
			Plugin::render_exception( $e );
216
217
			exit;
1 ignored issue
show
Best Practice introduced by
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...
218
		}
219
220
		/**
221
		 * Adyen checkout configuration.
222
		 *
223
		 * @link https://docs.adyen.com/checkout/drop-in-web
224
		 * @link https://docs.adyen.com/checkout/components-web
225
		 */
226
		$configuration = (object) array(
227
			'locale'                      => Util::get_payment_locale( $payment ),
228
			'environment'                 => ( self::MODE_TEST === $payment->get_mode() ? 'test' : 'live' ),
229
			'originKey'                   => $this->config->origin_key,
230
			'paymentMethodsResponse'      => $payment_methods->get_original_object(),
231
			'paymentMethodsConfiguration' => $this->get_checkout_payment_methods_configuration( $payment ),
232
			'amount'                      => AmountTransformer::transform( $payment->get_total_amount() )->get_json(),
233
		);
234
235
		/**
236
		 * Filters the Adyen checkout configuration.
237
		 *
238
		 * @param object $configuration Adyen checkout configuration.
239
		 * @since 1.2.0
240
		 */
241
		$configuration = apply_filters( 'pronamic_pay_adyen_checkout_configuration', $configuration );
242
243
		wp_localize_script(
244
			'pronamic-pay-adyen-checkout',
245
			'pronamicPayAdyenCheckout',
246
			array(
247
				'paymentsUrl'        => rest_url( Integration::REST_ROUTE_NAMESPACE . '/payments/' . $payment->get_id() ),
248
				'paymentsDetailsUrl' => rest_url( Integration::REST_ROUTE_NAMESPACE . '/payments/details/' ),
249
				'paymentReturnUrl'   => $payment->get_return_url(),
250
				'configuration'      => $configuration,
251
				'paymentAuthorised'  => __( 'Payment completed successfully.', 'pronamic_ideal' ),
252
				'paymentReceived'    => __( 'The order has been received and we are waiting for the payment to clear.', 'pronamic_ideal' ),
253
				'paymentRefused'     => __( 'The payment has been refused. Please try again using a different method or card.', 'pronamic_ideal' ),
254
			)
255
		);
256
257
		// Add checkout head action.
258
		add_action( 'pronamic_pay_adyen_checkout_head', array( $this, 'checkout_head' ) );
259
260
		// No cache.
261
		Core_Util::no_cache();
262
263
		require __DIR__ . '/../views/checkout-drop-in.php';
264
265
		exit;
1 ignored issue
show
Best Practice introduced by
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...
266
	}
267
268
	/**
269
	 * Checkout head.
270
	 *
271
	 * @return void
272
	 */
273
	public function checkout_head() {
274
		wp_print_styles( 'pronamic-pay-redirect' );
275
276
		wp_print_scripts( 'pronamic-pay-adyen-checkout' );
277
278
		wp_print_styles( 'pronamic-pay-adyen-checkout' );
279
	}
280
281
	/**
282
	 * Update status of the specified payment.
283
	 *
284
	 * @param Payment $payment Payment.
285
	 *
286
	 * @return void
287
	 */
288
	public function update_status( Payment $payment ) {
289
		// Process payload on return.
290
		if ( filter_has_var( INPUT_GET, 'payload' ) ) {
291
			$payload = filter_input( INPUT_GET, 'payload', FILTER_SANITIZE_STRING );
292
293
			$payment_result_request = new PaymentResultRequest( $payload );
294
295
			try {
296
				$payment_result_response = $this->client->get_payment_result( $payment_result_request );
297
298
				PaymentResultHelper::update_payment( $payment, $payment_result_response );
299
			} catch ( \Exception $e ) {
300
				$note = sprintf(
301
				/* translators: %s: exception message */
302
					__( 'Error getting payment result: %s', 'pronamic_ideal' ),
303
					$e->getMessage()
304
				);
305
306
				$payment->add_note( $note );
307
			}
308
309
			return;
310
		}
311
312
		// Retrieve status from payment details.
313
		$payment_response = $payment->get_meta( 'adyen_payment_response' );
314
315
		if ( '' !== $payment_response ) {
316
			$payment_response = PaymentResponse::from_object( $payment_response );
0 ignored issues
show
Bug introduced by
It seems like $payment_response can also be of type false and string; however, parameter $object of Pronamic\WordPress\Pay\G...Response::from_object() does only seem to accept object, 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

316
			$payment_response = PaymentResponse::from_object( /** @scrutinizer ignore-type */ $payment_response );
Loading history...
317
318
			$details_result = $payment->get_meta( 'adyen_details_result' );
319
320
			// JSON decode details result meta.
321
			if ( '' !== $details_result ) {
322
				$details_result = \json_decode( $details_result );
0 ignored issues
show
Bug introduced by
It seems like $details_result can also be of type false; however, parameter $json of json_decode() 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

322
				$details_result = \json_decode( /** @scrutinizer ignore-type */ $details_result );
Loading history...
323
			}
324
325
			// Set details result meta from GET or POST request parameters.
326
			if ( '' === $details_result ) {
327
				$details_result = array();
328
329
				$details = $payment_response->get_details();
330
331
				if ( null !== $details ) {
332
					$input_type = ( 'POST' === Server::get( 'REQUEST_METHOD' ) ? INPUT_POST : INPUT_GET );
333
334
					foreach ( $details as $detail ) {
335
						$key = $detail->get_key();
336
337
						$details_result[ $key ] = \filter_input( $input_type, $key, FILTER_SANITIZE_STRING );
338
					}
339
340
					$details_result = Util::filter_null( $details_result );
341
342
					$details_result = (object) $details_result;
343
344
					if ( ! empty( $details_result ) ) {
345
						$payment->set_meta( 'adyen_details_result', \wp_json_encode( $details_result ) );
346
					}
347
				}
348
			}
349
350
			$payment_data = $payment_response->get_payment_data();
351
352
			// Do not attempt to retrieve status without any request data,
353
			// payment status already updated when additional details were submitted (i.e. cards).
354
			if ( empty( $details_result ) && empty( $payment_data ) ) {
355
				return;
356
			}
357
358
			// Update payment status from payment details.
359
			$payment_details_request = new PaymentDetailsRequest( $details_result );
360
361
			$payment_details_request->set_payment_data( $payment_data );
362
363
			try {
364
				$payment_details_response = $this->client->request_payment_details( $payment_details_request );
365
366
				PaymentResponseHelper::update_payment( $payment, $payment_details_response );
367
			} catch ( \Exception $e ) {
368
				$note = sprintf(
369
					/* translators: %s: exception message */
370
					__( 'Error getting payment details: %s', 'pronamic_ideal' ),
371
					$e->getMessage()
372
				);
373
374
				$payment->add_note( $note );
375
			}
376
		}
377
	}
378
379
	/**
380
	 * Create payment.
381
	 *
382
	 * @param Payment       $payment        Payment.
383
	 * @param PaymentMethod $payment_method Payment method.
384
	 *
385
	 * @return \WP_Error|PaymentResponse
386
	 */
387
	public function create_payment( Payment $payment, PaymentMethod $payment_method ) {
388
		// Amount.
389
		try {
390
			$amount = AmountTransformer::transform( $payment->get_total_amount() );
391
		} catch ( \InvalidArgumentException $e ) {
392
			return new \WP_Error( 'adyen_error', $e->getMessage() );
393
		}
394
395
		// Payment request.
396
		$payment_request = new PaymentRequest(
397
			$amount,
398
			$this->config->get_merchant_account(),
399
			strval( $payment->get_id() ),
400
			$payment->get_return_url(),
401
			$payment_method
402
		);
403
404
		/**
405
		 * Application info.
406
		 *
407
		 * @link https://docs.adyen.com/api-explorer/#/PaymentSetupAndVerificationService/v51/payments__reqParam_applicationInfo
408
		 * @link https://docs.adyen.com/development-resources/building-adyen-solutions
409
		 */
410
		$application_info = new ApplicationInfo();
411
412
		$application_info->merchant_application = (object) array(
413
			'name'    => 'Pronamic Pay',
414
			'version' => \pronamic_pay_plugin()->get_version(),
415
		);
416
417
		$application_info->external_platform = (object) array(
418
			'integrator' => 'Pronamic',
419
			'name'       => 'WordPress',
420
			'version'    => \get_bloginfo( 'version' ),
421
		);
422
423
		$payment_request->set_application_info( $application_info );
424
425
		// Set country code.
426
		$locale = Util::get_payment_locale( $payment );
427
428
		$country_code = \Locale::getRegion( $locale );
429
430
		$billing_address = $payment->get_billing_address();
431
432
		if ( null !== $billing_address ) {
433
			$country = $billing_address->get_country_code();
434
435
			if ( ! empty( $country ) ) {
436
				$country_code = $country;
437
			}
438
		}
439
440
		$payment_request->set_country_code( $country_code );
441
442
		// Complement payment request.
443
		PaymentRequestHelper::complement( $payment, $payment_request );
444
445
		// Create payment.
446
		try {
447
			$payment_response = $this->client->create_payment( $payment_request );
448
		} catch ( \Exception $e ) {
449
			return new \WP_Error( 'adyen_error', $e->getMessage() );
450
		}
451
452
		/*
453
		 * Store payment response for later requests to `/payments/details`.
454
		 *
455
		 * @link https://docs.adyen.com/api-explorer/#/PaymentSetupAndVerificationService/v51/payments/details
456
		 */
457
		$payment->set_meta( 'adyen_payment_response', $payment_response->get_json() );
458
459
		// Update payment status based on response.
460
		PaymentResponseHelper::update_payment( $payment, $payment_response );
461
462
		return $payment_response;
463
	}
464
465
	/**
466
	 * Send payment details.
467
	 *
468
	 * @param Payment               $payment                 Payment.
469
	 * @param PaymentDetailsRequest $payment_details_request Payment details request.
470
	 *
471
	 * @throws \Exception
472
	 */
473
	public function send_payment_details( Payment $payment, PaymentDetailsRequest $payment_details_request ) {
0 ignored issues
show
Unused Code introduced by
The parameter $payment is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

473
	public function send_payment_details( /** @scrutinizer ignore-unused */ Payment $payment, PaymentDetailsRequest $payment_details_request ) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
474
		$payment_response = $this->client->request_payment_details( $payment_details_request );
475
476
		return $payment_response;
477
	}
478
479
	/**
480
	 * Get checkout payment methods configuration.
481
	 *
482
	 * @param Payment $payment Payment.
483
	 *
484
	 * @return object
485
	 */
486
	public function get_checkout_payment_methods_configuration( Payment $payment ) {
1 ignored issue
show
Unused Code introduced by
The parameter $payment is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

486
	public function get_checkout_payment_methods_configuration( /** @scrutinizer ignore-unused */ Payment $payment ) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
487
		$configuration = array();
488
489
		// Cards.
490
		$configuration[ 'card' ] = array(
491
			'enableStoreDetails' => true,
492
			'hasHolderName'      => true,
493
			'holderNameRequired' => true,
494
			'hideCVC'            => false,
495
			'name'               => __( 'Credit or debit card', 'pronamic_ideal' ),
496
		);
497
498
		// Apple Pay.
499
		$configuration[ 'applepay' ] = array(
500
			'configuration' => array(
501
				// Name to be displayed on the form
502
				'merchantName'       => 'Adyen Test merchant',
503
504
				// Your Apple merchant identifier as described in https://developer.apple.com/documentation/apple_pay_on_the_web/applepayrequest/2951611-merchantidentifier
505
				'merchantIdentifier' => 'adyen.test.merchant',
506
			),
507
508
			/*
509
			onValidateMerchant: ( resolve, reject, validationURL ) => {
510
				// Call the validation endpoint with validationURL.
511
				// Call resolve(MERCHANTSESSION) or reject() to complete merchant validation.
512
			}
513
			*/
514
		);
515
516
		// Google Pay.
517
		$configuration[ 'googlepay' ] = array(
518
			// Change this to PRODUCTION when you're ready to accept live Google Pay payments
519
			'environment' => 'TEST',
520
521
			'configuration' => array(
522
				// Your Adyen merchant or company account name. Remove this field in TEST.
523
				'gatewayMerchantId'  => 'YourCompanyOrMerchantAccount',
524
525
				// Required for PRODUCTION. Remove this field in TEST. Your Google Merchant ID as described in https://developers.google.com/pay/api/web/guides/test-and-deploy/deploy-production-environment#obtain-your-merchantID
526
				'merchantIdentifier' => '12345678910111213141',
527
			),
528
		);
529
530
		// Boleto Bancário.
531
		$configuration[ 'boletobancario ' ] = array(
532
			// Turn personal details section on/off.
533
			'personalDetailsRequired' => true,
534
535
			// Turn billing address section on/off.
536
			'billingAddressRequired'  => true,
537
538
			// Allow shopper to specify their email address.
539
			'showEmailAddress'        => true,
540
541
			// Optionally pre-fill some fields, here all fields are filled:
542
			/*
543
			'data'                    => array(
544
				'socialSecurityNumber' => '56861752509',
545
				'shopperName'          => array(
546
					'firstName' => $payment->get_customer()->get_name()->get_first_name(),
547
					'lastName'  => $payment->get_customer()->get_name()->get_last_name(),
548
				),
549
				'billingAddress'       => array(
550
					'street'            => 'Rua Funcionarios',
551
					'houseNumberOrName' => '952',
552
					'city'              => 'São Paulo',
553
					'postalCode'        => '04386040',
554
					'stateOrProvince'   => 'SP',
555
					'country'           => 'BR'
556
				),
557
				'shopperEmail'         => $payment->get_customer()->get_email(),
558
			),
559
			*/
560
		);
561
562
		return (object) $configuration;
563
	}
564
}
565