Test Setup Failed
Push — master ( 81dce0...a438b2 )
by Reüel
07:10 queued 11s
created

Gateway::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 12
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 4
Bugs 0 Features 0
Metric Value
cc 1
eloc 8
c 4
b 0
f 0
nc 1
nop 1
dl 0
loc 12
ccs 0
cts 9
cp 0
crap 2
rs 10
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
use Pronamic\WordPress\Pay\Payments\PaymentStatus;
12
13
/**
14
 * Title: Buckaroo gateway
15
 * Description:
16
 * Copyright: 2005-2021 Pronamic
17
 * Company: Pronamic
18
 *
19
 * @author Remco Tolsma
20
 * @version 2.0.4
21
 * @since 1.0.0
22
 */
23
class Gateway extends Core_Gateway {
24
	/**
25
	 * Client.
0 ignored issues
show
Bug introduced by
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...
26
	 *
27
	 * @var Client
28
	 */
29
	protected $client;
30
31
	/**
32
	 * Constructs and initializes an Buckaroo gateway
33
	 *
34
	 * @param Config $config Config.
35
	 */
36
	public function __construct( Config $config ) {
37
		parent::__construct( $config );
38
39
		$this->set_method( self::METHOD_HTTP_REDIRECT );
40
41
		// Supported features.
42
		$this->supports = array(
43
			'payment_status_request',
44
			'refunds',
45
			'webhook',
46
			'webhook_log',
47
			'webhook_no_config',
48
		);
49
	}
50
51
	/**
52
	 * Get issuers.
53
	 *
54
	 * @since 1.2.4
55
	 * @see Pronamic_WP_Pay_Gateway::get_issuers()
56
	 */
57
	public function get_issuers() {
58
		$groups = array();
59
60
		$object = $this->request( 'GET', 'Transaction/Specification/ideal?serviceVersion=2' );
61
62
		foreach ( $object->Actions as $action ) {
63
			if ( 'Pay' === $action->Name ) {
64
				foreach ( $action->RequestParameters as $request_parameter ) {
65
					if ( 'issuer' === $request_parameter->Name ) {
66
						foreach ( $request_parameter->ListItemDescriptions as $item ) {
67
							if ( ! array_key_exists( $item->GroupName, $groups ) ) {
68
								$groups[ $item->GroupName ] = array(
69
									'name'    => $item->GroupName,
70
									'options' => array(),
71
								);
72
							}
73
74
							$groups[ $item->GroupName ]['options'][ $item->Value ] = $item->Description;
75
						}
76
					}
77
				}
78
			}
79
		}
80
81
		return $groups;
82
	}
83
84
	/**
85
	 * Get supported payment methods
86
	 *
87
	 * @see Pronamic_WP_Pay_Gateway::get_supported_payment_methods()
88
	 */
89
	public function get_supported_payment_methods() {
90
		return array(
91
			Core_PaymentMethods::BANK_TRANSFER,
92
			Core_PaymentMethods::BANCONTACT,
93
			Core_PaymentMethods::CREDIT_CARD,
94
			Core_PaymentMethods::GIROPAY,
95
			Core_PaymentMethods::IDEAL,
96
			Core_PaymentMethods::PAYPAL,
97
			Core_PaymentMethods::SOFORT,
98
		);
99
	}
100
101
	/**
102
	 * Start
103
	 *
104
	 * @param Payment $payment Payment.
105
	 *
106
	 * @see Core_Gateway::start()
107
	 */
108
	public function start( Payment $payment ) {
109
		/**
110
		 * Currency.
111
		 */
112
		$currency_code = $payment->get_total_amount()->get_currency()->get_alphabetic_code();
113
114
		if ( null === $currency_code ) {
115
			throw new \InvalidArgumentException( 'Can not start payment with empty currency code.' );
116
		}
117
118
		/**
119
		 * Push URL.
120
		 */
121
		$push_url = \rest_url( Integration::REST_ROUTE_NAMESPACE . '/push' );
122
123
		/**
124
		 * Filters the Buckaroo push URL.
125
		 *
126
		 * If you want to debug the Buckaroo report URL you can use this filter
127
		 * to override the push URL. You could for example use a service like
128
		 * https://webhook.site/ to inspect the push requests from Buckaroo.
129
		 *
130
		 * @param string $push_url Buckaroo push URL.
131
		 */
132
		$push_url = \apply_filters( 'pronamic_pay_buckaroo_push_url', $push_url );
133
134
		/**
135
		 * JSON Transaction.
136
		 *
137
		 * @link https://testcheckout.buckaroo.nl/json/Docs/Api/POST-json-Transaction
138
		 */
139
		$data = (object) array(
140
			'Currency'                  => $currency_code,
141
			'AmountDebit'               => $payment->get_total_amount()->get_value(),
142
			'Description'               => $payment->get_description(),
143
			'Invoice'                   => Util::get_invoice_number( (string) $this->config->get_invoice_number(), $payment ),
144
			'ReturnURL'                 => $payment->get_return_url(),
145
			'ReturnURLCancel'           => \add_query_arg(
146
				'buckaroo_return_url_cancel',
147
				true,
148
				$payment->get_return_url()
149
			),
150
			'ReturnURLError'            => \add_query_arg(
151
				'buckaroo_return_url_error',
152
				true,
153
				$payment->get_return_url()
154
			),
155
			'ReturnURLReject'           => \add_query_arg(
156
				'buckaroo_return_url_reject',
157
				true,
158
				$payment->get_return_url()
159
			),
160
			/**
161
			 * Push URL.
162
			 *
163
			 * When provided, this push URL overrides all the push URLs as configured in the payment plaza under websites for the associated website key
164
			 *
165
			 * @link https://dev.buckaroo.nl/Apis
166
			 */
167
			'PushURL'                   => $push_url,
168
			/**
169
			 * Push URL Failure.
170
			 *
171
			 * 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.
172
			 *
173
			 * @link https://dev.buckaroo.nl/Apis
174
			 */
175
			'PushURLFailure'            => $push_url,
176
			/**
177
			 * Services.
178
			 *
179
			 * Specifies which service (can be a payment method and/or additional service) is being called upon in the request.
180
			 *
181
			 * @link https://dev.buckaroo.nl/Apis
182
			 */
183
			'Services'                  => (object) array(
184
				'ServiceList' => array(),
185
			),
186
			/**
187
			 * Continue On Incomplete.
188
			 *
189
			 * Specifies if a redirecturl to a payment form will be returned to
190
			 * which a customer should be sent if no paymentmethod is selected
191
			 * or if any required parameter which the customer may provide is
192
			 * missing or incorrect. Possible Values:
193
			 *
194
			 * · No: This is the default. The request will fail if not all the
195
			 * needed information is provided.
196
			 *
197
			 * · RedirectToHTML: A redirect to the HTML gateway is provided if
198
			 * a recoverable problems are detected in the request. The customer
199
			 * can then provide the needed information there.
200
			 *
201
			 * @link https://dev.buckaroo.nl/Apis
202
			 * @link https://testcheckout.buckaroo.nl/json/Docs/Api/POST-json-Transaction
203
			 * @link https://testcheckout.buckaroo.nl/json/Docs/ResourceModel?modelName=ContinueOnIncomplete
204
			 */
205
			'ContinueOnIncomplete'      => 'RedirectToHTML',
206
			/**
207
			 * Services Excluded For Client.
208
			 *
209
			 * If no primary service is provided and ContinueOnIncomplete is
210
			 * set, this list of comma separated servicescodes can be used to
211
			 * limit the number of services from which the customer may choose
212
			 * once he is redirected to the payment form. Services which are
213
			 * entered in this field are not selectable.
214
			 * This field is optional.
215
			 *
216
			 * @link https://dev.buckaroo.nl/Apis
217
			 * @link https://testcheckout.buckaroo.nl/json/Docs/Api/POST-json-Transaction
218
			 */
219
			'ServicesExcludedForClient' => $this->config->get_excluded_services(),
220
			/**
221
			 * Custom parameters.
222
			 *
223
			 * @link https://testcheckout.buckaroo.nl/json/Docs/Api/POST-json-Transaction
224
			 */
225
			'CustomParameters'          => array(
226
				(object) array(
227
					'Name'  => 'pronamic_payment_id',
228
					'Value' => $payment->get_id(),
229
				),
230
			),
231
		);
232
233
		/**
234
		 * Client IP.
235
		 *
236
		 * In this field the IP address of the customer (or employee) for which
237
		 * the action is being performed can be passed. Please note, If this
238
		 * field is not sent to our gateway, your server IP address will be
239
		 * used as the clientIP. This may result in unwanted behaviour for
240
		 * anti-fraud checks. Also, certain payment methods perform checks on
241
		 * the IP address, if an IP address is overused, the request could be
242
		 * blocked. This field is sent in the following format, where
243
		 * type 0 = IPv4 and type 1 = IPv6:
244
		 * "ClientIP": { "Type": 0, "Address": "0.0.0.0" },
245
		 *
246
		 * @link https://testcheckout.buckaroo.nl/json/Docs/Api/POST-json-Transaction
247
		 * @link https://stackoverflow.com/questions/1448871/how-to-know-which-version-of-the-internet-protocol-ip-a-client-is-using-when-c/1448901
248
		 */
249
		$customer = $payment->get_customer();
250
251
		if ( null !== $customer ) {
252
			$ip_address = $customer->get_ip_address();
253
254
			if ( null !== $ip_address ) {
255
				$data->ClientIP = (object) array(
256
					'Type'    => false === \strpos( $ip_address, ':' ) ? 0 : 1,
257
					'Address' => $ip_address,
258
				);
259
			}
260
		}
261
262
		/**
263
		 * Payment method.
264
		 *
265
		 * @link https://testcheckout.buckaroo.nl/json/Docs/Api/POST-json-Transaction
266
		 * @link https://testcheckout.buckaroo.nl/json/Docs/ResourceModel?modelName=ServicesRequest
267
		 * @link https://testcheckout.buckaroo.nl/json/Docs/ResourceModel?modelName=ServiceRequest
268
		 */
269
		$payment_method = $payment->get_method();
270
271
		switch ( $payment_method ) {
272
			/**
273
			 * Payment method creditcard.
274
			 *
275
			 * @link https://dev.buckaroo.nl/PaymentMethods/Description/creditcards#pay
276
			 */
277
			case Core_PaymentMethods::CREDIT_CARD:
278
				$data->Services->ServiceList[] = (object) array(
279
					'Action' => 'Pay',
280
					'Name'   => PaymentMethods::AMERICAN_EXPRESS,
281
				);
282
283
				$data->Services->ServiceList[] = (object) array(
284
					'Action' => 'Pay',
285
					'Name'   => PaymentMethods::MAESTRO,
286
				);
287
288
				$data->Services->ServiceList[] = (object) array(
289
					'Action' => 'Pay',
290
					'Name'   => PaymentMethods::MASTERCARD,
291
				);
292
293
				$data->Services->ServiceList[] = (object) array(
294
					'Action' => 'Pay',
295
					'Name'   => PaymentMethods::VISA,
296
				);
297
298
				break;
299
			/**
300
			 * Payment method iDEAL.
301
			 *
302
			 * @link https://dev.buckaroo.nl/PaymentMethods/Description/ideal#pay
303
			 */
304
			case Core_PaymentMethods::IDEAL:
305
				$data->Services->ServiceList[] = (object) array(
306
					'Action'     => 'Pay',
307
					'Name'       => 'ideal',
308
					'Parameters' => array(
309
						array(
310
							'Name'  => 'issuer',
311
							'Value' => $payment->get_issuer(),
312
						),
313
					),
314
				);
315
316
				break;
317
			/**
318
			 * Payment method transfer.
319
			 *
320
			 * @link https://dev.buckaroo.nl/PaymentMethods/Description/transfer#pay
321
			 */
322
			case Core_PaymentMethods::BANK_TRANSFER:
323
				$data->Services->ServiceList[] = (object) array(
324
					'Action' => 'Pay',
325
					'Name'   => 'transfer',
326
				);
327
328
				break;
329
			/**
330
			 * Payment method Bancontact.
331
			 *
332
			 * @link https://dev.buckaroo.nl/PaymentMethods/Description/bancontact#pay
333
			 */
334
			case Core_PaymentMethods::BANCONTACT:
335
			case Core_PaymentMethods::MISTER_CASH:
0 ignored issues
show
Deprecated Code introduced by
The constant Pronamic\WordPress\Pay\C...entMethods::MISTER_CASH has been deprecated: "Bancontact/Mister Cash" was renamed to just "Bancontact". ( Ignorable by Annotation )

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

335
			case /** @scrutinizer ignore-deprecated */ Core_PaymentMethods::MISTER_CASH:

This class constant has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead.

Loading history...
336
				$data->Services->ServiceList[] = (object) array(
337
					'Action' => 'Pay',
338
					'Name'   => 'bancontactmrcash',
339
				);
340
341
				break;
342
			/**
343
			 * Payment method Giropay.
344
			 *
345
			 * @link https://dev.buckaroo.nl/PaymentMethods/Description/giropay#pay
346
			 */
347
			case Core_PaymentMethods::GIROPAY:
348
				$data->Services->ServiceList[] = (object) array(
349
					'Action' => 'Pay',
350
					'Name'   => 'giropay',
351
				);
352
353
				break;
354
			/**
355
			 * Payment method PayPal.
356
			 *
357
			 * @link https://dev.buckaroo.nl/PaymentMethods/Description/paypal#pay
358
			 */
359
			case Core_PaymentMethods::PAYPAL:
360
				$data->Services->ServiceList[] = (object) array(
361
					'Action' => 'Pay',
362
					'Name'   => 'paypal',
363
				);
364
365
				break;
366
			/**
367
			 * Payment method Sofort.
368
			 *
369
			 * @link https://dev.buckaroo.nl/PaymentMethods/Description/sofort#pay
370
			 */
371
			case Core_PaymentMethods::SOFORT:
372
				$data->Services->ServiceList[] = (object) array(
373
					'Action' => 'Pay',
374
					'Name'   => 'sofortueberweisung',
375
				);
376
377
				break;
378
		}
379
380
		/**
381
		 * Request.
382
		 */
383
		$object = $this->request( 'POST', 'Transaction', $data );
384
385
		/**
386
		 * Request Errors.
387
		 *
388
		 * @link https://testcheckout.buckaroo.nl/json/Docs/Api/POST-json-Transaction
389
		 */
390
		if ( \property_exists( $object, 'RequestErrors' ) && null !== $object->RequestErrors ) {
391
			$exception = null;
392
393
			foreach ( $object->RequestErrors as $errors ) {
394
				foreach ( $errors as $error ) {
395
					// Add exception.
396
					$exception = new \Exception( $error->ErrorMessage, 0, $exception );
397
				}
398
			}
399
400
			if ( null !== $exception ) {
401
				throw $exception;
402
			}
403
		}
404
405
		/**
406
		 * Required Action.
407
		 */
408
		if ( 'Redirect' !== $object->RequiredAction->Name ) {
409
			throw new \Exception(
410
				\sprintf(
411
					'Unsupported Buckaroo action: %s',
412
					$object->RequiredAction->Name
413
				)
414
			);
415
		}
416
417
		$payment->set_action_url( $object->RequiredAction->RedirectURL );
418
419
		/**
420
		 * Buckaroo keys.
421
		 *
422
		 * @link https://testcheckout.buckaroo.nl/json/Docs/ResourceModel?modelName=TransactionResponse
423
		 */
424
		if ( \property_exists( $object, 'Key' ) ) {
425
			$payment->set_transaction_id( $object->Key );
426
		}
427
428
		if ( \property_exists( $object, 'PaymentKey' ) ) {
429
			$payment->set_meta( 'buckaroo_transaction_payment_key', $object->PaymentKey );
430
		}
431
	}
432
433
	/**
434
	 * JSON API Request.
435
	 *
436
	 * @param string      $method   HTTP request method.
437
	 * @param string      $endpoint JSON API endpoint.
438
	 * @param object|null $data     Data.
439
	 */
440
	public function request( $method, $endpoint, $data = null ) {
441
		$host = 'checkout.buckaroo.nl';
442
443
		if ( self::MODE_TEST === $this->config->mode ) {
444
			$host = 'testcheckout.buckaroo.nl';
445
		}
446
447
		/**
448
		 * Authentication.
449
		 *
450
		 * 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.
451
		 *
452
		 * @link https://dev.buckaroo.nl/Apis/Description/json
453
		 * @link https://testcheckout.buckaroo.nl/json/Docs/Authentication
454
		 */
455
		$website_key         = $this->config->website_key;
456
		$request_http_method = $method;
457
		$request_uri         = $host . '/json/' . $endpoint;
458
		$request_timestamp   = \strval( \time() );
459
		$nonce               = \wp_generate_password( 32 );
460
		$request_content     = null === $data ? '' : \wp_json_encode( $data );
461
462
		$values = \implode(
463
			'',
464
			array(
465
				$website_key,
466
				$request_http_method,
467
				\strtolower( \rawurlencode( $request_uri ) ),
468
				$request_timestamp,
469
				$nonce,
470
				\
471
				// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
472
				null === $data ? '' : \base64_encode( \md5( $request_content, true ) ),
0 ignored issues
show
Bug introduced by
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

472
				null === $data ? '' : \base64_encode( \md5( /** @scrutinizer ignore-type */ $request_content, true ) ),
Loading history...
473
			)
474
		);
475
476
		$hash = \hash_hmac( 'sha256', $values, $this->config->secret_key, true );
477
478
		// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
479
		$hmac = \base64_encode( $hash );
480
481
		$authorization = \sprintf(
482
			'hmac %s:%s:%s:%s',
483
			$this->config->website_key,
484
			$hmac,
485
			$nonce,
486
			$request_timestamp
487
		);
488
489
		$response = \Pronamic\WordPress\Http\Facades\Http::request(
490
			'https://' . $request_uri,
491
			array(
492
				'method'  => $request_http_method,
493
				'headers' => array(
494
					'Authorization' => $authorization,
495
					'Content-Type'  => 'application/json',
496
				),
497
				'body'    => $request_content,
498
			)
499
		);
500
501
		$object = $response->json();
502
503
		/**
504
		 * OK.
505
		 */
506
		return $object;
507
	}
508
509
	/**
510
	 * Update status of the specified payment
511
	 *
512
	 * @link https://testcheckout.buckaroo.nl/json/Docs/Api/GET-json-Transaction-Status-transactionKey
513
	 * @param Payment $payment Payment.
514
	 */
515
	public function update_status( Payment $payment ) {
516
		$transaction_key = $payment->get_transaction_id();
517
518
		if ( empty( $transaction_key ) ) {
519
			return;
520
		}
521
522
		$result = $this->request( 'GET', 'Transaction/Status/' . $transaction_key );
523
524
		$payment->set_status( Statuses::transform( \strval( $result->Status->Code->Code ) ) );
525
526
		/**
527
		 * Consumer bank details.
528
		 */
529
		$consumer_bank_details = $payment->get_consumer_bank_details();
530
531
		if ( null === $consumer_bank_details ) {
532
			$consumer_bank_details = new BankAccountDetails();
533
534
			$payment->set_consumer_bank_details( $consumer_bank_details );
535
		}
536
537
		/**
538
		 * Services.
539
		 */
540
		foreach ( $result->Services as $service ) {
541
			foreach ( $service->Parameters as $parameter ) {
542
				if ( 'consumerName' === $parameter->Name ) {
543
					$consumer_bank_details->set_name( $parameter->Value );
544
				}
545
546
				if ( 'consumerIBAN' === $parameter->Name ) {
547
					$consumer_bank_details->set_iban( $parameter->Value );
548
				}
549
550
				if ( 'consumerBIC' === $parameter->Name ) {
551
					$consumer_bank_details->set_iban( $parameter->Value );
552
				}
553
			}
554
		}
555
556
		/**
557
		 * Refunds.
558
		 *
559
		 * @link https://testcheckout.buckaroo.nl/json/Docs/Api/GET-json-Transaction-RefundInfo-transactionKey
560
		 */
561
		$result = $this->request( 'GET', 'Transaction/RefundInfo/' . $transaction_key );
562
563
		if ( \property_exists( $result, 'RefundedAmount' ) && ! empty( $result->RefundedAmount ) ) {
564
			$refunded_amount = new Money( $result->RefundedAmount, $result->RefundCurrency );
565
566
			$payment->set_refunded_amount( $refunded_amount );
567
		}
568
	}
569
570
	/**
571
	 * Create refund.
572
	 *
573
	 * @param string $transaction_id Transaction ID.
574
	 * @param Money  $amount         Amount to refund.
575
	 * @param string $description    Refund reason.
576
	 * @return string
577
	 */
578
	public function create_refund( $transaction_id, Money $amount, $description = null ) {
0 ignored issues
show
Unused Code introduced by
The parameter $description 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

578
	public function create_refund( $transaction_id, Money $amount, /** @scrutinizer ignore-unused */ $description = null ) {

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...
579
		$original_transaction = $this->request( 'GET', 'Transaction/Status/' . $transaction_id );
580
581
		if ( ! \is_object( $original_transaction ) ) {
582
			throw new \Exception(
583
				sprintf(
584
					/* translators: %s: transaction key */
585
					__( 'Unable to create refund for transaction with transaction key: %s', 'pronamic_ideal' ),
586
					$transaction_id
587
				)
588
			);
589
		}
590
591
		$service_name = Util::get_transaction_service( $original_transaction );
592
593
		if ( null === $service_name ) {
594
			throw new \Exception(
595
				sprintf(
596
					/* translators: %s: transaction key */
597
					__( 'Unable to create refund for transaction without service name. Transaction key: %s', 'pronamic_ideal' ),
598
					$transaction_id
599
				)
600
			);
601
		}
602
603
		// Invoice.
604
		$payment = \get_pronamic_payment_by_transaction_id( $transaction_id );
605
606
		$invoice = null;
607
608
		if ( null !== $payment ) {
609
			$invoice = Util::get_invoice_number( (string) $this->config->get_invoice_number(), $payment );
610
		}
611
612
		// Refund request.
613
		$data = (object) array(
614
			'Channel'                => 'Web',
615
			'Currency'               => $amount->get_currency()->get_alphabetic_code(),
616
			'AmountCredit'           => (float) $amount->get_value(),
617
			'Invoice'                => $invoice,
618
			'OriginalTransactionKey' => $transaction_id,
619
			'Services'               => array(
620
				'ServiceList' => array(
621
					array(
622
						'Name'   => $service_name,
623
						'Action' => 'Refund',
624
					),
625
				),
626
			),
627
		);
628
629
		$refund = $this->request( 'POST', 'Transaction', $data );
630
631
		// Check refund object.
632
		if ( ! \is_object( $refund ) ) {
633
			return null;
634
		}
635
636
		// Check refund status.
637
		if ( \property_exists( $refund, 'Status' ) && \property_exists( $refund->Status, 'Code' ) ) {
638
			$status = Statuses::transform( (string) $refund->Status->Code->Code );
639
640
			if ( PaymentStatus::SUCCESS !== $status ) {
641
				throw new \Exception(
642
					\sprintf(
643
						/* translators: 1: payment provider name, 2: status message, 3: status sub message*/
644
						__( 'Unable to create refund at %1$s gateway: %2$s%3$s', 'pronamic_ideal' ),
645
						__( 'Buckaroo', 'pronamic_ideal' ),
646
						$refund->Status->Code->Description,
647
						\property_exists( $refund->Status, 'SubCode' ) ? ' – ' . $refund->Status->SubCode->Description : ''
648
					)
649
				);
650
			}
651
		}
652
653
		// Update payment refunded amount.
654
		if ( null !== $payment ) {
655
			$result = $this->request( 'GET', 'Transaction/RefundInfo/' . $transaction_id );
656
657
			if ( \property_exists( $result, 'RefundedAmount' ) && ! empty( $result->RefundedAmount ) ) {
658
				$refunded_amount = new Money( $result->RefundedAmount, $result->RefundCurrency );
659
660
				$payment->set_refunded_amount( $refunded_amount );
661
			}
662
		}
663
664
		// Return.
665
		$refund_id = \property_exists( $refund, 'Key' ) ? $refund->Key : null;
666
667
		return $refund_id;
668
	}
669
}
670