Failed Conditions
Push — develop ( b0c738...9276f5 )
by Remco
04:33 queued 11s
created

src/Gateway.php (3 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
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
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 Core_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 Core_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
			/**
142
			 * The debit amount for the request. This is in decimal format,
143
			 * with a point as the decimal separator. For example, if the
144
			 * currency is specified as EUR, sending “1” will mean that 1 euro
145
			 * will be paid. “1.00” is also 1 euro. “0.01” means 1 cent.
146
			 * Please note, a transaction must have either a debit amount or a
147
			 * credit amount and it cannot have both.
148
			 * 
149
			 * @link https://dev.buckaroo.nl/Apis
150
			 */
151
			'AmountDebit'               => $payment->get_total_amount()->number_format( null, '.', '' ),
0 ignored issues
show
The method number_format() does not exist on Pronamic\WordPress\Money\TaxedMoney. Did you maybe mean format()? ( Ignorable by Annotation )

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

151
			'AmountDebit'               => $payment->get_total_amount()->/** @scrutinizer ignore-call */ number_format( null, '.', '' ),

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
152
			'Description'               => $payment->get_description(),
153
			'Invoice'                   => Util::get_invoice_number( (string) $this->config->get_invoice_number(), $payment ),
154
			'ReturnURL'                 => $payment->get_return_url(),
155
			'ReturnURLCancel'           => \add_query_arg(
156
				'buckaroo_return_url_cancel',
157
				true,
158
				$payment->get_return_url()
159
			),
160
			'ReturnURLError'            => \add_query_arg(
161
				'buckaroo_return_url_error',
162
				true,
163
				$payment->get_return_url()
164
			),
165
			'ReturnURLReject'           => \add_query_arg(
166
				'buckaroo_return_url_reject',
167
				true,
168
				$payment->get_return_url()
169
			),
170
			/**
171
			 * Push URL.
172
			 *
173
			 * When provided, this push URL overrides all the push URLs as configured in the payment plaza under websites for the associated website key
174
			 *
175
			 * @link https://dev.buckaroo.nl/Apis
176
			 */
177
			'PushURL'                   => $push_url,
178
			/**
179
			 * Push URL Failure.
180
			 *
181
			 * 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.
182
			 *
183
			 * @link https://dev.buckaroo.nl/Apis
184
			 */
185
			'PushURLFailure'            => $push_url,
186
			/**
187
			 * Services.
188
			 *
189
			 * Specifies which service (can be a payment method and/or additional service) is being called upon in the request.
190
			 *
191
			 * @link https://dev.buckaroo.nl/Apis
192
			 */
193
			'Services'                  => (object) array(
194
				'ServiceList' => array(),
195
			),
196
			/**
197
			 * Continue On Incomplete.
198
			 *
199
			 * Specifies if a redirecturl to a payment form will be returned to
200
			 * which a customer should be sent if no paymentmethod is selected
201
			 * or if any required parameter which the customer may provide is
202
			 * missing or incorrect. Possible Values:
203
			 *
204
			 * · No: This is the default. The request will fail if not all the
205
			 * needed information is provided.
206
			 *
207
			 * · RedirectToHTML: A redirect to the HTML gateway is provided if
208
			 * a recoverable problems are detected in the request. The customer
209
			 * can then provide the needed information there.
210
			 *
211
			 * @link https://dev.buckaroo.nl/Apis
212
			 * @link https://testcheckout.buckaroo.nl/json/Docs/Api/POST-json-Transaction
213
			 * @link https://testcheckout.buckaroo.nl/json/Docs/ResourceModel?modelName=ContinueOnIncomplete
214
			 */
215
			'ContinueOnIncomplete'      => 'RedirectToHTML',
216
			/**
217
			 * Services Excluded For Client.
218
			 *
219
			 * If no primary service is provided and ContinueOnIncomplete is
220
			 * set, this list of comma separated servicescodes can be used to
221
			 * limit the number of services from which the customer may choose
222
			 * once he is redirected to the payment form. Services which are
223
			 * entered in this field are not selectable.
224
			 * This field is optional.
225
			 *
226
			 * @link https://dev.buckaroo.nl/Apis
227
			 * @link https://testcheckout.buckaroo.nl/json/Docs/Api/POST-json-Transaction
228
			 */
229
			'ServicesExcludedForClient' => $this->config->get_excluded_services(),
230
			/**
231
			 * Custom parameters.
232
			 *
233
			 * @link https://testcheckout.buckaroo.nl/json/Docs/Api/POST-json-Transaction
234
			 */
235
			'CustomParameters'          => array(
236
				(object) array(
237
					'Name'  => 'pronamic_payment_id',
238
					'Value' => $payment->get_id(),
239
				),
240
			),
241
		);
242
243
		/**
244
		 * Client IP.
245
		 *
246
		 * In this field the IP address of the customer (or employee) for which
247
		 * the action is being performed can be passed. Please note, If this
248
		 * field is not sent to our gateway, your server IP address will be
249
		 * used as the clientIP. This may result in unwanted behaviour for
250
		 * anti-fraud checks. Also, certain payment methods perform checks on
251
		 * the IP address, if an IP address is overused, the request could be
252
		 * blocked. This field is sent in the following format, where
253
		 * type 0 = IPv4 and type 1 = IPv6:
254
		 * "ClientIP": { "Type": 0, "Address": "0.0.0.0" },
255
		 *
256
		 * @link https://testcheckout.buckaroo.nl/json/Docs/Api/POST-json-Transaction
257
		 * @link https://stackoverflow.com/questions/1448871/how-to-know-which-version-of-the-internet-protocol-ip-a-client-is-using-when-c/1448901
258
		 */
259
		$customer = $payment->get_customer();
260
261
		if ( null !== $customer ) {
262
			$ip_address = $customer->get_ip_address();
263
264
			if ( null !== $ip_address ) {
265
				$data->ClientIP = (object) array(
266
					'Type'    => false === \strpos( $ip_address, ':' ) ? 0 : 1,
267
					'Address' => $ip_address,
268
				);
269
			}
270
		}
271
272
		/**
273
		 * Payment method.
274
		 *
275
		 * @link https://testcheckout.buckaroo.nl/json/Docs/Api/POST-json-Transaction
276
		 * @link https://testcheckout.buckaroo.nl/json/Docs/ResourceModel?modelName=ServicesRequest
277
		 * @link https://testcheckout.buckaroo.nl/json/Docs/ResourceModel?modelName=ServiceRequest
278
		 */
279
		$payment_method = $payment->get_method();
280
281
		switch ( $payment_method ) {
282
			/**
283
			 * Payment method creditcard.
284
			 *
285
			 * @link https://dev.buckaroo.nl/PaymentMethods/Description/creditcards#pay
286
			 */
287
			case Core_PaymentMethods::CREDIT_CARD:
288
				$data->Services->ServiceList[] = (object) array(
289
					'Action' => 'Pay',
290
					'Name'   => PaymentMethods::AMERICAN_EXPRESS,
291
				);
292
293
				$data->Services->ServiceList[] = (object) array(
294
					'Action' => 'Pay',
295
					'Name'   => PaymentMethods::MAESTRO,
296
				);
297
298
				$data->Services->ServiceList[] = (object) array(
299
					'Action' => 'Pay',
300
					'Name'   => PaymentMethods::MASTERCARD,
301
				);
302
303
				$data->Services->ServiceList[] = (object) array(
304
					'Action' => 'Pay',
305
					'Name'   => PaymentMethods::VISA,
306
				);
307
308
				break;
309
			/**
310
			 * Payment method iDEAL.
311
			 *
312
			 * @link https://dev.buckaroo.nl/PaymentMethods/Description/ideal#pay
313
			 */
314
			case Core_PaymentMethods::IDEAL:
315
				$data->Services->ServiceList[] = (object) array(
316
					'Action'     => 'Pay',
317
					'Name'       => 'ideal',
318
					'Parameters' => array(
319
						array(
320
							'Name'  => 'issuer',
321
							'Value' => $payment->get_issuer(),
322
						),
323
					),
324
				);
325
326
				break;
327
			/**
328
			 * Payment method transfer.
329
			 *
330
			 * @link https://dev.buckaroo.nl/PaymentMethods/Description/transfer#pay
331
			 */
332
			case Core_PaymentMethods::BANK_TRANSFER:
333
				$data->Services->ServiceList[] = (object) array(
334
					'Action' => 'Pay',
335
					'Name'   => 'transfer',
336
				);
337
338
				break;
339
			/**
340
			 * Payment method Bancontact.
341
			 *
342
			 * @link https://dev.buckaroo.nl/PaymentMethods/Description/bancontact#pay
343
			 */
344
			case Core_PaymentMethods::BANCONTACT:
345
			case Core_PaymentMethods::MISTER_CASH:
346
				$data->Services->ServiceList[] = (object) array(
347
					'Action' => 'Pay',
348
					'Name'   => 'bancontactmrcash',
349
				);
350
351
				break;
352
			/**
353
			 * Payment method Giropay.
354
			 *
355
			 * @link https://dev.buckaroo.nl/PaymentMethods/Description/giropay#pay
356
			 */
357
			case Core_PaymentMethods::GIROPAY:
358
				$data->Services->ServiceList[] = (object) array(
359
					'Action' => 'Pay',
360
					'Name'   => 'giropay',
361
				);
362
363
				break;
364
			/**
365
			 * Payment method PayPal.
366
			 *
367
			 * @link https://dev.buckaroo.nl/PaymentMethods/Description/paypal#pay
368
			 */
369
			case Core_PaymentMethods::PAYPAL:
370
				$data->Services->ServiceList[] = (object) array(
371
					'Action' => 'Pay',
372
					'Name'   => 'paypal',
373
				);
374
375
				break;
376
			/**
377
			 * Payment method Sofort.
378
			 *
379
			 * @link https://dev.buckaroo.nl/PaymentMethods/Description/sofort#pay
380
			 */
381
			case Core_PaymentMethods::SOFORT:
382
				$data->Services->ServiceList[] = (object) array(
383
					'Action' => 'Pay',
384
					'Name'   => 'sofortueberweisung',
385
				);
386
387
				break;
388
		}
389
390
		/**
391
		 * Request.
392
		 */
393
		$object = $this->request( 'POST', 'Transaction', $data );
394
395
		/**
396
		 * Buckaroo keys.
397
		 *
398
		 * @link https://testcheckout.buckaroo.nl/json/Docs/ResourceModel?modelName=TransactionResponse
399
		 */
400
		if ( \property_exists( $object, 'Key' ) ) {
401
			$payment->set_transaction_id( $object->Key );
402
		}
403
404
		if ( \property_exists( $object, 'PaymentKey' ) ) {
405
			$payment->set_meta( 'buckaroo_transaction_payment_key', $object->PaymentKey );
406
		}
407
408
		/**
409
		 * Request Errors.
410
		 *
411
		 * @link https://testcheckout.buckaroo.nl/json/Docs/Api/POST-json-Transaction
412
		 */
413
		if ( \property_exists( $object, 'RequestErrors' ) && null !== $object->RequestErrors ) {
414
			$exception = null;
415
416
			foreach ( $object->RequestErrors as $errors ) {
417
				foreach ( $errors as $error ) {
418
					// Add exception.
419
					$exception = new \Exception( $error->ErrorMessage, 0, $exception );
420
				}
421
			}
422
423
			if ( null !== $exception ) {
424
				throw $exception;
425
			}
426
		}
427
428
		/**
429
		 * Required Action.
430
		 */
431
		if ( null !== $object->RequiredAction ) {
432
			if ( 'Redirect' !== $object->RequiredAction->Name ) {
433
				throw new \Exception(
434
					\sprintf(
435
						'Unsupported Buckaroo action: %s',
436
						$object->RequiredAction->Name
437
					)
438
				);
439
			}
440
441
			// Set action URL.
442
			if ( \property_exists( $object->RequiredAction, 'RedirectURL' ) ) {
443
				$payment->set_action_url( $object->RequiredAction->RedirectURL );
444
			}
445
		}
446
447
		// Failure.
448
		if ( \property_exists( $object, 'Status' ) && \property_exists( $object->Status, 'Code' ) ) {
449
			$status = Statuses::transform( (string) $object->Status->Code->Code );
450
451
			if ( PaymentStatus::FAILURE === $status ) {
452
				throw new \Exception(
453
					\sprintf(
454
						/* translators: 1: payment provider name, 2: status message, 3: status sub message*/
455
						__( 'Unable to create payment at gateway: %1$s%2$s', 'pronamic_ideal' ),
456
						$object->Status->Code->Description,
457
						\property_exists( $object->Status, 'SubCode' ) ? ' – ' . $object->Status->SubCode->Description : ''
458
					)
459
				);
460
			}
461
		}
462
	}
463
464
	/**
465
	 * JSON API Request.
466
	 *
467
	 * @param string      $method   HTTP request method.
468
	 * @param string      $endpoint JSON API endpoint.
469
	 * @param object|null $data     Data.
470
	 */
471
	public function request( $method, $endpoint, $data = null ) {
472
		$host = 'checkout.buckaroo.nl';
473
474
		if ( self::MODE_TEST === $this->config->mode ) {
475
			$host = 'testcheckout.buckaroo.nl';
476
		}
477
478
		/**
479
		 * Authentication.
480
		 *
481
		 * 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.
482
		 *
483
		 * @link https://dev.buckaroo.nl/Apis/Description/json
484
		 * @link https://testcheckout.buckaroo.nl/json/Docs/Authentication
485
		 */
486
		$website_key         = $this->config->website_key;
487
		$request_http_method = $method;
488
		$request_uri         = $host . '/json/' . $endpoint;
489
		$request_timestamp   = \strval( \time() );
490
		$nonce               = \wp_generate_password( 32 );
491
		$request_content     = null === $data ? '' : \wp_json_encode( $data );
492
493
		$values = \implode(
494
			'',
495
			array(
496
				$website_key,
497
				$request_http_method,
498
				\strtolower( \rawurlencode( $request_uri ) ),
499
				$request_timestamp,
500
				$nonce,
501
				\
502
				// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
503
				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

503
				null === $data ? '' : \base64_encode( \md5( /** @scrutinizer ignore-type */ $request_content, true ) ),
Loading history...
504
			)
505
		);
506
507
		$hash = \hash_hmac( 'sha256', $values, $this->config->secret_key, true );
508
509
		// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
510
		$hmac = \base64_encode( $hash );
511
512
		$authorization = \sprintf(
513
			'hmac %s:%s:%s:%s',
514
			$this->config->website_key,
515
			$hmac,
516
			$nonce,
517
			$request_timestamp
518
		);
519
520
		$response = \Pronamic\WordPress\Http\Facades\Http::request(
521
			'https://' . $request_uri,
522
			array(
523
				'method'  => $request_http_method,
524
				'headers' => array(
525
					'Authorization' => $authorization,
526
					'Content-Type'  => 'application/json',
527
				),
528
				'body'    => $request_content,
529
			)
530
		);
531
532
		$object = $response->json();
533
534
		/**
535
		 * OK.
536
		 */
537
		return $object;
538
	}
539
540
	/**
541
	 * Update status of the specified payment
542
	 *
543
	 * @link https://testcheckout.buckaroo.nl/json/Docs/Api/GET-json-Transaction-Status-transactionKey
544
	 * @param Payment $payment Payment.
545
	 */
546
	public function update_status( Payment $payment ) {
547
		$transaction_key = $payment->get_transaction_id();
548
549
		if ( empty( $transaction_key ) ) {
550
			return;
551
		}
552
553
		$result = $this->request( 'GET', 'Transaction/Status/' . $transaction_key );
554
555
		$payment->set_status( Statuses::transform( \strval( $result->Status->Code->Code ) ) );
556
557
		/**
558
		 * Consumer bank details.
559
		 */
560
		$consumer_bank_details = $payment->get_consumer_bank_details();
561
562
		if ( null === $consumer_bank_details ) {
563
			$consumer_bank_details = new BankAccountDetails();
564
565
			$payment->set_consumer_bank_details( $consumer_bank_details );
566
		}
567
568
		/**
569
		 * Services.
570
		 */
571
		foreach ( $result->Services as $service ) {
572
			foreach ( $service->Parameters as $parameter ) {
573
				if ( 'consumerName' === $parameter->Name ) {
574
					$consumer_bank_details->set_name( $parameter->Value );
575
				}
576
577
				if ( 'consumerIBAN' === $parameter->Name ) {
578
					$consumer_bank_details->set_iban( $parameter->Value );
579
				}
580
581
				if ( 'consumerBIC' === $parameter->Name ) {
582
					$consumer_bank_details->set_bic( $parameter->Value );
583
				}
584
			}
585
		}
586
587
		/**
588
		 * Refunds.
589
		 *
590
		 * @link https://testcheckout.buckaroo.nl/json/Docs/Api/GET-json-Transaction-RefundInfo-transactionKey
591
		 */
592
		$result = $this->request( 'GET', 'Transaction/RefundInfo/' . $transaction_key );
593
594
		if ( \property_exists( $result, 'RefundedAmount' ) && ! empty( $result->RefundedAmount ) ) {
595
			$refunded_amount = new Money( $result->RefundedAmount, $result->RefundCurrency );
596
597
			$payment->set_refunded_amount( $refunded_amount );
598
		}
599
	}
600
601
	/**
602
	 * Create refund.
603
	 *
604
	 * @param string $transaction_id Transaction ID.
605
	 * @param Money  $amount         Amount to refund.
606
	 * @param string $description    Refund reason.
607
	 * @return string
608
	 */
609
	public function create_refund( $transaction_id, Money $amount, $description = null ) {
610
		$original_transaction = $this->request( 'GET', 'Transaction/Status/' . $transaction_id );
611
612
		if ( ! \is_object( $original_transaction ) ) {
613
			throw new \Exception(
614
				sprintf(
615
					/* translators: %s: transaction key */
616
					__( 'Unable to create refund for transaction with transaction key: %s', 'pronamic_ideal' ),
617
					$transaction_id
618
				)
619
			);
620
		}
621
622
		$service_name = Util::get_transaction_service( $original_transaction );
623
624
		if ( null === $service_name ) {
625
			throw new \Exception(
626
				sprintf(
627
					/* translators: %s: transaction key */
628
					__( 'Unable to create refund for transaction without service name. Transaction key: %s', 'pronamic_ideal' ),
629
					$transaction_id
630
				)
631
			);
632
		}
633
634
		// Invoice.
635
		$payment = \get_pronamic_payment_by_transaction_id( $transaction_id );
636
637
		$invoice = null;
638
639
		if ( null !== $payment ) {
640
			$invoice = Util::get_invoice_number( (string) $this->config->get_invoice_number(), $payment );
641
		}
642
643
		// Refund request.
644
		$data = (object) array(
645
			'Channel'                => 'Web',
646
			'Currency'               => $amount->get_currency()->get_alphabetic_code(),
647
			/**
648
			 * The credit amount for the request. This is in decimal format,
649
			 * with a point as the decimal separator. For example, if the
650
			 * currency is specified as EUR, sending “1” will mean that 1 euro
651
			 * will be paid. “1.00” is also 1 euro. “0.01” means 1 cent.
652
			 * Please note, a transaction must have either a debit amount or a
653
			 * credit amount and it cannot have both.
654
			 * 
655
			 * @link https://dev.buckaroo.nl/Apis
656
			 */
657
			'AmountCredit'           => $amount->format( null, '.', '' ),
658
			'Invoice'                => $invoice,
659
			'OriginalTransactionKey' => $transaction_id,
660
			'Services'               => array(
661
				'ServiceList' => array(
662
					array(
663
						'Name'   => $service_name,
664
						'Action' => 'Refund',
665
					),
666
				),
667
			),
668
		);
669
670
		$refund = $this->request( 'POST', 'Transaction', $data );
671
672
		// Check refund object.
673
		if ( ! \is_object( $refund ) ) {
674
			return null;
675
		}
676
677
		// Check refund status.
678
		if ( \property_exists( $refund, 'Status' ) && \property_exists( $refund->Status, 'Code' ) ) {
679
			$status = Statuses::transform( (string) $refund->Status->Code->Code );
680
681
			if ( PaymentStatus::SUCCESS !== $status ) {
682
				throw new \Exception(
683
					\sprintf(
684
						/* translators: 1: payment provider name, 2: status message, 3: status sub message*/
685
						__( 'Unable to create refund at %1$s gateway: %2$s%3$s', 'pronamic_ideal' ),
686
						__( 'Buckaroo', 'pronamic_ideal' ),
687
						$refund->Status->Code->Description,
688
						\property_exists( $refund->Status, 'SubCode' ) ? ' – ' . $refund->Status->SubCode->Description : ''
689
					)
690
				);
691
			}
692
		}
693
694
		// Update payment refunded amount.
695
		if ( null !== $payment ) {
696
			$result = $this->request( 'GET', 'Transaction/RefundInfo/' . $transaction_id );
697
698
			if ( \property_exists( $result, 'RefundedAmount' ) && ! empty( $result->RefundedAmount ) ) {
699
				$refunded_amount = new Money( $result->RefundedAmount, $result->RefundCurrency );
700
701
				$payment->set_refunded_amount( $refunded_amount );
702
			}
703
		}
704
705
		// Return.
706
		$refund_id = \property_exists( $refund, 'Key' ) ? $refund->Key : null;
707
708
		return $refund_id;
709
	}
710
}
711