Failed Conditions
Push — develop ( 2de1b1...49fae7 )
by Remco
12:57 queued 04:10
created

src/Gateway.php (2 issues)

1
<?php
2
3
namespace Pronamic\WordPress\Pay\Gateways\Buckaroo;
4
5
use Pronamic\WordPress\Money\Money;
6
use Pronamic\WordPress\Pay\Banks\BankAccountDetails;
7
use Pronamic\WordPress\Pay\Core\Gateway as Core_Gateway;
8
use Pronamic\WordPress\Pay\Core\PaymentMethods as Core_PaymentMethods;
9
use Pronamic\WordPress\Pay\Core\Server;
10
use Pronamic\WordPress\Pay\Payments\Payment;
11
12
/**
13
 * Title: Buckaroo gateway
14
 * Description:
15
 * Copyright: 2005-2021 Pronamic
16
 * Company: Pronamic
17
 *
18
 * @author Remco Tolsma
19
 * @version 2.0.4
20
 * @since 1.0.0
21
 */
22
class Gateway extends Core_Gateway {
23
	/**
24
	 * Client.
0 ignored issues
show
The type Pronamic\WordPress\Pay\Gateways\Buckaroo\Client was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
25
	 *
26
	 * @var Client
27
	 */
28
	protected $client;
29
30
	/**
31
	 * Constructs and initializes an Buckaroo gateway
32
	 *
33
	 * @param Config $config Config.
34
	 */
35
	public function __construct( Config $config ) {
36
		parent::__construct( $config );
37
38
		$this->set_method( self::METHOD_HTTP_REDIRECT );
39
40
		// Supported features.
41
		$this->supports = array(
42
			'payment_status_request',
43
			'refunds',
44
			'webhook',
45
			'webhook_log',
46
			'webhook_no_config',
47
		);
48
	}
49
50
	/**
51
	 * Get issuers.
52
	 *
53
	 * @since 1.2.4
54
	 * @see Pronamic_WP_Pay_Gateway::get_issuers()
55
	 */
56
	public function get_issuers() {
57
		$groups = array();
58
59
		$object = $this->request( 'GET', 'Transaction/Specification/ideal?serviceVersion=2' );
60
61
		foreach ( $object->Actions as $action ) {
62
			if ( 'Pay' === $action->Name ) {
63
				foreach ( $action->RequestParameters as $request_parameter ) {
64
					if ( 'issuer' === $request_parameter->Name ) {
65
						foreach ( $request_parameter->ListItemDescriptions as $item ) {
66
							if ( ! array_key_exists( $item->GroupName, $groups ) ) {
67
								$groups[ $item->GroupName ] = array(
68
									'name'    => $item->GroupName,
69
									'options' => array(),
70
								);
71
							}
72
73
							$groups[ $item->GroupName ]['options'][ $item->Value ] = $item->Description;
74
						}
75
					}
76
				}
77
			}
78
		}
79
80
		return $groups;
81
	}
82
83
	/**
84
	 * Get supported payment methods
85
	 *
86
	 * @see Pronamic_WP_Pay_Gateway::get_supported_payment_methods()
87
	 */
88
	public function get_supported_payment_methods() {
89
		return array(
90
			Core_PaymentMethods::BANK_TRANSFER,
91
			Core_PaymentMethods::BANCONTACT,
92
			Core_PaymentMethods::CREDIT_CARD,
93
			Core_PaymentMethods::GIROPAY,
94
			Core_PaymentMethods::IDEAL,
95
			Core_PaymentMethods::PAYPAL,
96
			Core_PaymentMethods::SOFORT,
97
		);
98
	}
99
100
	/**
101
	 * Start
102
	 *
103
	 * @param Payment $payment Payment.
104
	 *
105
	 * @see Core_Gateway::start()
106
	 */
107
	public function start( Payment $payment ) {
108
		/**
109
		 * Currency.
110
		 */
111
		$currency_code = $payment->get_total_amount()->get_currency()->get_alphabetic_code();
112
113
		if ( null === $currency_code ) {
114
			throw new \InvalidArgumentException( 'Can not start payment with empty currency code.' );
115
		}
116
117
		/**
118
		 * Push URL.
119
		 */
120
		$push_url = \rest_url( Integration::REST_ROUTE_NAMESPACE . '/push' );
121
122
		/**
123
		 * Filters the Buckaroo push URL.
124
		 *
125
		 * If you want to debug the Buckaroo report URL you can use this filter
126
		 * to override the push URL. You could for example use a service like
127
		 * https://webhook.site/ to inspect the push requests from Buckaroo.
128
		 *
129
		 * @param string $push_url Buckaroo push URL.
130
		 */
131
		$push_url = \apply_filters( 'pronamic_pay_buckaroo_push_url', $push_url );
132
133
		/**
134
		 * JSON Transaction.
135
		 *
136
		 * @link https://testcheckout.buckaroo.nl/json/Docs/Api/POST-json-Transaction
137
		 */
138
		$data = (object) array(
139
			'Currency'                  => $currency_code,
140
			'AmountDebit'               => $payment->get_total_amount()->get_value(),
141
			'Invoice'                   => Util::get_invoice_number( (string) $this->config->get_invoice_number(), $payment ),
142
			'ReturnURL'                 => $payment->get_return_url(),
143
			'ReturnURLCancel'           => \add_query_arg(
144
				'buckaroo_return_url_cancel',
145
				true,
146
				$payment->get_return_url()
147
			),
148
			'ReturnURLError'            => \add_query_arg(
149
				'buckaroo_return_url_error',
150
				true,
151
				$payment->get_return_url()
152
			),
153
			'ReturnURLReject'           => \add_query_arg(
154
				'buckaroo_return_url_reject',
155
				true,
156
				$payment->get_return_url()
157
			),
158
			/**
159
			 * Push URL.
160
			 *
161
			 * When provided, this push URL overrides all the push URLs as configured in the payment plaza under websites for the associated website key
162
			 *
163
			 * @link https://dev.buckaroo.nl/Apis
164
			 */
165
			'PushURL'                   => $push_url,
166
			/**
167
			 * Push URL Failure.
168
			 *
169
			 * When provided, this push URL overrides the push URL for failed transactions as configured in the payment plaza under websites for the associated website key.
170
			 *
171
			 * @link https://dev.buckaroo.nl/Apis
172
			 */
173
			'PushURLFailure'            => $push_url,
174
			/**
175
			 * Services.
176
			 *
177
			 * Specifies which service (can be a payment method and/or additional service) is being called upon in the request.
178
			 *
179
			 * @link https://dev.buckaroo.nl/Apis
180
			 */
181
			'Services'                  => (object) array(
182
				'ServiceList' => array(),
183
			),
184
			/**
185
			 * Continue On Incomplete.
186
			 *
187
			 * Specifies if a redirecturl to a payment form will be returned to
188
			 * which a customer should be sent if no paymentmethod is selected
189
			 * or if any required parameter which the customer may provide is
190
			 * missing or incorrect. Possible Values:
191
			 *
192
			 * · No: This is the default. The request will fail if not all the
193
			 * needed information is provided.
194
			 *
195
			 * · RedirectToHTML: A redirect to the HTML gateway is provided if
196
			 * a recoverable problems are detected in the request. The customer
197
			 * can then provide the needed information there.
198
			 *
199
			 * @link https://dev.buckaroo.nl/Apis
200
			 * @link https://testcheckout.buckaroo.nl/json/Docs/Api/POST-json-Transaction
201
			 * @link https://testcheckout.buckaroo.nl/json/Docs/ResourceModel?modelName=ContinueOnIncomplete
202
			 */
203
			'ContinueOnIncomplete'      => 'RedirectToHTML',
204
			/**
205
			 * Services Excluded For Client.
206
			 *
207
			 * If no primary service is provided and ContinueOnIncomplete is
208
			 * set, this list of comma separated servicescodes can be used to
209
			 * limit the number of services from which the customer may choose
210
			 * once he is redirected to the payment form. Services which are
211
			 * entered in this field are not selectable.
212
			 * This field is optional.
213
			 *
214
			 * @link https://dev.buckaroo.nl/Apis
215
			 * @link https://testcheckout.buckaroo.nl/json/Docs/Api/POST-json-Transaction
216
			 */
217
			'ServicesExcludedForClient' => $this->config->get_excluded_services(),
218
			/**
219
			 * Custom parameters.
220
			 *
221
			 * @link https://testcheckout.buckaroo.nl/json/Docs/Api/POST-json-Transaction
222
			 */
223
			'CustomParameters'          => array(
224
				(object) array(
225
					'Name'  => 'pronamic_payment_id',
226
					'Value' => $payment->get_id(),
227
				),
228
			),
229
		);
230
231
		/**
232
		 * Client IP.
233
		 *
234
		 * In this field the IP address of the customer (or employee) for which
235
		 * the action is being performed can be passed. Please note, If this
236
		 * field is not sent to our gateway, your server IP address will be
237
		 * used as the clientIP. This may result in unwanted behaviour for
238
		 * anti-fraud checks. Also, certain payment methods perform checks on
239
		 * the IP address, if an IP address is overused, the request could be
240
		 * blocked. This field is sent in the following format, where
241
		 * type 0 = IPv4 and type 1 = IPv6:
242
		 * "ClientIP": { "Type": 0, "Address": "0.0.0.0" },
243
		 *
244
		 * @link https://testcheckout.buckaroo.nl/json/Docs/Api/POST-json-Transaction
245
		 * @link https://stackoverflow.com/questions/1448871/how-to-know-which-version-of-the-internet-protocol-ip-a-client-is-using-when-c/1448901
246
		 */
247
		$customer = $payment->get_customer();
248
249
		if ( null !== $customer ) {
250
			$ip_address = $customer->get_ip_address();
251
252
			if ( null !== $ip_address ) {
253
				$data->ClientIP = (object) array(
254
					'Type'    => false === \strpos( $ip_address, ':' ) ? 0 : 1,
255
					'Address' => $ip_address,
256
				);
257
			}
258
		}
259
260
		/**
261
		 * Payment method.
262
		 *
263
		 * @link https://testcheckout.buckaroo.nl/json/Docs/Api/POST-json-Transaction
264
		 * @link https://testcheckout.buckaroo.nl/json/Docs/ResourceModel?modelName=ServicesRequest
265
		 * @link https://testcheckout.buckaroo.nl/json/Docs/ResourceModel?modelName=ServiceRequest
266
		 */
267
		$payment_method = $payment->get_method();
268
269
		switch ( $payment_method ) {
270
			/**
271
			 * Payment method creditcard.
272
			 *
273
			 * @link https://dev.buckaroo.nl/PaymentMethods/Description/creditcards#pay
274
			 */
275
			case Core_PaymentMethods::CREDIT_CARD:
276
				$data->Services->ServiceList[] = (object) array(
277
					'Action' => 'Pay',
278
					'Name'   => PaymentMethods::AMERICAN_EXPRESS,
279
				);
280
281
				$data->Services->ServiceList[] = (object) array(
282
					'Action' => 'Pay',
283
					'Name'   => PaymentMethods::MAESTRO,
284
				);
285
286
				$data->Services->ServiceList[] = (object) array(
287
					'Action' => 'Pay',
288
					'Name'   => PaymentMethods::MASTERCARD,
289
				);
290
291
				$data->Services->ServiceList[] = (object) array(
292
					'Action' => 'Pay',
293
					'Name'   => PaymentMethods::VISA,
294
				);
295
296
				break;
297
			/**
298
			 * Payment method iDEAL.
299
			 *
300
			 * @link https://dev.buckaroo.nl/PaymentMethods/Description/ideal#pay
301
			 */
302
			case Core_PaymentMethods::IDEAL:
303
				$data->Services->ServiceList[] = (object) array(
304
					'Action'     => 'Pay',
305
					'Name'       => 'ideal',
306
					'Parameters' => array(
307
						array(
308
							'Name'  => 'issuer',
309
							'Value' => $payment->get_issuer(),
310
						),
311
					),
312
				);
313
314
				break;
315
			/**
316
			 * Payment method transfer.
317
			 *
318
			 * @link https://dev.buckaroo.nl/PaymentMethods/Description/transfer#pay
319
			 */
320
			case Core_PaymentMethods::BANK_TRANSFER:
321
				$data->Services->ServiceList[] = (object) array(
322
					'Action' => 'Pay',
323
					'Name'   => 'transfer',
324
				);
325
326
				break;
327
			/**
328
			 * Payment method Bancontact.
329
			 *
330
			 * @link https://dev.buckaroo.nl/PaymentMethods/Description/bancontact#pay
331
			 */
332
			case Core_PaymentMethods::BANCONTACT:
333
			case Core_PaymentMethods::MISTER_CASH:
334
				$data->Services->ServiceList[] = (object) array(
335
					'Action' => 'Pay',
336
					'Name'   => 'bancontactmrcash',
337
				);
338
339
				break;
340
			/**
341
			 * Payment method Giropay.
342
			 *
343
			 * @link https://dev.buckaroo.nl/PaymentMethods/Description/giropay#pay
344
			 */
345
			case Core_PaymentMethods::GIROPAY:
346
				$data->Services->ServiceList[] = (object) array(
347
					'Action' => 'Pay',
348
					'Name'   => 'giropay',
349
				);
350
351
				break;
352
			/**
353
			 * Payment method PayPal.
354
			 *
355
			 * @link https://dev.buckaroo.nl/PaymentMethods/Description/paypal#pay
356
			 */
357
			case Core_PaymentMethods::PAYPAL:
358
				$data->Services->ServiceList[] = (object) array(
359
					'Action' => 'Pay',
360
					'Name'   => 'paypal',
361
				);
362
363
				break;
364
			/**
365
			 * Payment method Sofort.
366
			 *
367
			 * @link https://dev.buckaroo.nl/PaymentMethods/Description/sofort#pay
368
			 */
369
			case Core_PaymentMethods::SOFORT:
370
				$data->Services->ServiceList[] = (object) array(
371
					'Action' => 'Pay',
372
					'Name'   => 'sofortueberweisung',
373
				);
374
375
				break;
376
		}
377
378
		/**
379
		 * Request.
380
		 */
381
		$object = $this->request( 'POST', 'Transaction', $data );
382
383
		if ( 'Redirect' !== $object->RequiredAction->Name ) {
384
			throw new \Exception(
385
				\sprintf(
386
					'Unsupported Buckaroo action: %s',
387
					$object->RequiredAction->Name
388
				)
389
			);
390
		}
391
392
		$payment->set_action_url( $object->RequiredAction->RedirectURL );
393
394
		/**
395
		 * Buckaroo keys.
396
		 *
397
		 * @link https://testcheckout.buckaroo.nl/json/Docs/ResourceModel?modelName=TransactionResponse
398
		 */
399
		$payment->set_meta( 'buckaroo_transaction_key', $object->Key );
400
		$payment->set_meta( 'buckaroo_transaction_payment_key', $object->PaymentKey );
401
402
		/**
403
		 * Transaction ID.
404
		 *
405
		 * @link https://dev.buckaroo.nl/PaymentMethods/Description/ideal]
406
		 */
407
		foreach ( $object->Services as $service ) {
408
			foreach ( $service->Parameters as $parameter ) {
409
				if ( 'transactionId' === $parameter->Name ) {
410
					$payment->set_transaction_id( $parameter->Value );
411
				}
412
			}
413
		}
414
	}
415
416
	/**
417
	 * JSON API Request.
418
	 *
419
	 * @param string      $method   HTTP request method.
420
	 * @param string      $endpoint JSON API endpoint.
421
	 * @param object|null $data     Data.
422
	 */
423
	private function request( $method, $endpoint, $data = null ) {
424
		$host = 'checkout.buckaroo.nl';
425
426
		if ( self::MODE_TEST === $this->config->mode ) {
427
			$host = 'testcheckout.buckaroo.nl';
428
		}
429
430
		/**
431
		 * Authentication.
432
		 *
433
		 * The HMAC SHA256 is calculated over a concatenated string (as raw data/binary/bytes) of the following values: WebsiteKey, requestHttpMethod, requestUri, requestTimeStamp, nonce, requestContentBase64String. See the next table for more information about these values. Please note: the Base64 hash should be a string of 44 characters. If yours is longer, it is probably in hexadecimal format.
434
		 *
435
		 * @link https://dev.buckaroo.nl/Apis/Description/json
436
		 * @link https://testcheckout.buckaroo.nl/json/Docs/Authentication
437
		 */
438
		$website_key         = $this->config->website_key;
439
		$request_http_method = $method;
440
		$request_uri         = $host . '/json/' . $endpoint;
441
		$request_timestamp   = \strval( \time() );
442
		$nonce               = \wp_generate_password( 32 );
443
		$request_content     = null === $data ? '' : \wp_json_encode( $data );
444
445
		$values = \implode(
446
			'',
447
			array(
448
				$website_key,
449
				$request_http_method,
450
				\strtolower( \rawurlencode( $request_uri ) ),
451
				$request_timestamp,
452
				$nonce,
453
				\
454
				// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
455
				null === $data ? '' : \base64_encode( \md5( $request_content, true ) ),
0 ignored issues
show
It seems like $request_content can also be of type false; however, parameter $string of md5() 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

455
				null === $data ? '' : \base64_encode( \md5( /** @scrutinizer ignore-type */ $request_content, true ) ),
Loading history...
456
			)
457
		);
458
459
		$hash = \hash_hmac( 'sha256', $values, $this->config->secret_key, true );
460
461
		// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
462
		$hmac = \base64_encode( $hash );
463
464
		$authorization = \sprintf(
465
			'hmac %s:%s:%s:%s',
466
			$this->config->website_key,
467
			$hmac,
468
			$nonce,
469
			$request_timestamp
470
		);
471
472
		$response = \Pronamic\WordPress\Http\Facades\Http::request(
473
			'https://' . $request_uri,
474
			array(
475
				'method'  => $request_http_method,
476
				'headers' => array(
477
					'Authorization' => $authorization,
478
					'Content-Type'  => 'application/json',
479
				),
480
				'body'    => $request_content,
481
			)
482
		);
483
484
		$object = $response->json();
485
486
		/**
487
		 * Request Errors.
488
		 *
489
		 * @link https://testcheckout.buckaroo.nl/json/Docs/Api/POST-json-Transaction
490
		 */
491
		$exception = null;
492
493
		/**
494
		 * Channel errors.
495
		 *
496
		 * @link https://testcheckout.buckaroo.nl/json/Docs/ResourceModel?modelName=TransactionRequestResponseChannelError
497
		 */
498
		foreach ( $object->RequestErrors->ChannelErrors as $error ) {
499
			$exception = new \Exception( $error->ErrorMessage, 0, $exception );
500
		}
501
502
		/**
503
		 * Service errors.
504
		 *
505
		 * @link https://testcheckout.buckaroo.nl/json/Docs/ResourceModel?modelName=TransactionRequestResponseServiceError
506
		 */
507
		foreach ( $object->RequestErrors->ServiceErrors as $error ) {
508
			$exception = new \Exception( $error->ErrorMessage, 0, $exception );
509
		}
510
511
		/**
512
		 * Action errors.
513
		 *
514
		 * @link https://testcheckout.buckaroo.nl/json/Docs/ResourceModel?modelName=TransactionRequestResponseActionError
515
		 */
516
		foreach ( $object->RequestErrors->ActionErrors as $error ) {
517
			$exception = new \Exception( $error->ErrorMessage, 0, $exception );
518
		}
519
520
		/**
521
		 * Action errors.
522
		 *
523
		 * @link https://testcheckout.buckaroo.nl/json/Docs/ResourceModel?modelName=TransactionRequestResponseParameterError
524
		 */
525
		foreach ( $object->RequestErrors->ParameterErrors as $error ) {
526
			$exception = new \Exception( $error->ErrorMessage, 0, $exception );
527
		}
528
529
		/**
530
		 * Action errors.
531
		 *
532
		 * @link https://testcheckout.buckaroo.nl/json/Docs/ResourceModel?modelName=TransactionRequestResponseCustomParameterError
533
		 */
534
		foreach ( $object->RequestErrors->CustomParameterErrors as $error ) {
535
			$exception = new \Exception( $error->ErrorMessage, 0, $exception );
536
		}
537
538
		if ( null !== $exception ) {
539
			throw $exception;
540
		}
541
542
		/**
543
		 * OK.
544
		 */
545
		return $object;
546
	}
547
548
	/**
549
	 * Update status of the specified payment
550
	 *
551
	 * @link https://testcheckout.buckaroo.nl/json/Docs/Api/GET-json-Transaction-Status-transactionKey
552
	 * @param Payment $payment Payment.
553
	 */
554
	public function update_status( Payment $payment ) {
555
		$transaction_key = $payment->get_meta( 'buckaroo_transaction_key' );
556
557
		if ( empty( $transaction_key ) ) {
558
			return;
559
		}
560
561
		$result = $this->request( 'GET', 'Transaction/Status/' . $transaction_key );
562
563
		$payment->set_status( Statuses::transform( \strval( $result->Status->Code->Code ) ) );
564
565
		/**
566
		 * Consumer bank details.
567
		 */
568
		$consumer_bank_details = $payment->get_consumer_bank_details();
569
570
		if ( null === $consumer_bank_details ) {
571
			$consumer_bank_details = new BankAccountDetails();
572
573
			$payment->set_consumer_bank_details( $consumer_bank_details );
574
		}
575
576
		/**
577
		 * Services.
578
		 */
579
		foreach ( $result->Services as $service ) {
580
			foreach ( $service->Parameters as $parameter ) {
581
				if ( 'transactionId' === $parameter->Name ) {
582
					$payment->set_transaction_id( $parameter->Value );
583
				}
584
585
				if ( 'consumerName' === $parameter->Name ) {
586
					$consumer_bank_details->set_name( $parameter->Value );
587
				}
588
589
				if ( 'consumerIBAN' === $parameter->Name ) {
590
					$consumer_bank_details->set_iban( $parameter->Value );
591
				}
592
593
				if ( 'consumerBIC' === $parameter->Name ) {
594
					$consumer_bank_details->set_iban( $parameter->Value );
595
				}
596
			}
597
		}
598
599
		/**
600
		 * Refunds.
601
		 *
602
		 * @link https://testcheckout.buckaroo.nl/json/Docs/Api/GET-json-Transaction-RefundInfo-transactionKey
603
		 */
604
		$result = $this->request( 'GET', 'Transaction/RefundInfo/' . $transaction_key );
605
606
		$refunded_amount = new Money( $result->RefundedAmount, $result->RefundCurrency );
607
608
		$payment->set_refunded_amount( $refunded_amount );
609
	}
610
}
611