Failed Conditions
Push — develop ( 0bf85e...ba0dec )
by Reüel
04:38
created

Gateway::create_refund()   C

Complexity

Conditions 13
Paths 30

Size

Total Lines 90
Code Lines 46

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 182

Importance

Changes 0
Metric Value
cc 13
eloc 46
nc 30
nop 3
dl 0
loc 90
ccs 0
cts 65
cp 0
crap 182
rs 6.6166
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
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
		 * Buckaroo keys.
387
		 *
388
		 * @link https://testcheckout.buckaroo.nl/json/Docs/ResourceModel?modelName=TransactionResponse
389
		 */
390
		if ( \property_exists( $object, 'Key' ) ) {
391
			$payment->set_transaction_id( $object->Key );
392
		}
393
394
		if ( \property_exists( $object, 'PaymentKey' ) ) {
395
			$payment->set_meta( 'buckaroo_transaction_payment_key', $object->PaymentKey );
396
		}
397
398
		/**
399
		 * Request Errors.
400
		 *
401
		 * @link https://testcheckout.buckaroo.nl/json/Docs/Api/POST-json-Transaction
402
		 */
403
		if ( \property_exists( $object, 'RequestErrors' ) && null !== $object->RequestErrors ) {
404
			$exception = null;
405
406
			foreach ( $object->RequestErrors as $errors ) {
407
				foreach ( $errors as $error ) {
408
					// Add exception.
409
					$exception = new \Exception( $error->ErrorMessage, 0, $exception );
410
				}
411
			}
412
413
			if ( null !== $exception ) {
414
				throw $exception;
415
			}
416
		}
417
418
		/**
419
		 * Required Action.
420
		 */
421
		if ( null !== $object->RequiredAction ) {
422
			if ( 'Redirect' !== $object->RequiredAction->Name ) {
423
				throw new \Exception(
424
					\sprintf(
425
						'Unsupported Buckaroo action: %s',
426
						$object->RequiredAction->Name
427
					)
428
				);
429
			}
430
431
			// Set action URL.
432
			if ( \property_exists( $object->RequiredAction, 'RedirectURL' ) ) {
433
				$payment->set_action_url( $object->RequiredAction->RedirectURL );
434
			}
435
		}
436
437
		// Failure.
438
		if ( \property_exists( $object, 'Status' ) && \property_exists( $object->Status, 'Code' ) ) {
439
			$status = Statuses::transform( (string) $object->Status->Code->Code );
440
441
			if ( PaymentStatus::FAILURE === $status ) {
442
				throw new \Exception(
443
					\sprintf(
444
						/* translators: 1: payment provider name, 2: status message, 3: status sub message*/
445
						__( 'Unable to create payment at gateway: %1$s%2$s', 'pronamic_ideal' ),
446
						$object->Status->Code->Description,
447
						\property_exists( $object->Status, 'SubCode' ) ? ' – ' . $object->Status->SubCode->Description : ''
448
					)
449
				);
450
			}
451
		}
452
	}
453
454
	/**
455
	 * JSON API Request.
456
	 *
457
	 * @param string      $method   HTTP request method.
458
	 * @param string      $endpoint JSON API endpoint.
459
	 * @param object|null $data     Data.
460
	 */
461
	public function request( $method, $endpoint, $data = null ) {
462
		$host = 'checkout.buckaroo.nl';
463
464
		if ( self::MODE_TEST === $this->config->mode ) {
465
			$host = 'testcheckout.buckaroo.nl';
466
		}
467
468
		/**
469
		 * Authentication.
470
		 *
471
		 * 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.
472
		 *
473
		 * @link https://dev.buckaroo.nl/Apis/Description/json
474
		 * @link https://testcheckout.buckaroo.nl/json/Docs/Authentication
475
		 */
476
		$website_key         = $this->config->website_key;
477
		$request_http_method = $method;
478
		$request_uri         = $host . '/json/' . $endpoint;
479
		$request_timestamp   = \strval( \time() );
480
		$nonce               = \wp_generate_password( 32 );
481
		$request_content     = null === $data ? '' : \wp_json_encode( $data );
482
483
		$values = \implode(
484
			'',
485
			array(
486
				$website_key,
487
				$request_http_method,
488
				\strtolower( \rawurlencode( $request_uri ) ),
489
				$request_timestamp,
490
				$nonce,
491
				\
492
				// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
493
				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

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

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