Failed Conditions
Push — develop ( 8acda5...89cf9b )
by Remco
07:14
created

src/Gateway.php (9 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
	 * Constructs and initializes an Buckaroo gateway
26
	 *
27
	 * @param Config $config Config.
28
	 */
29
	public function __construct( Config $config ) {
30
		parent::__construct( $config );
31
32
		$this->set_method( self::METHOD_HTTP_REDIRECT );
33
34
		// Supported features.
35
		$this->supports = array(
36
			'payment_status_request',
37
			'refunds',
38
			'webhook',
39
			'webhook_log',
40
			'webhook_no_config',
41
		);
42
	}
43
44
	/**
45
	 * Get issuers.
46
	 *
47
	 * @since 1.2.4
48
	 * @see Core_Gateway::get_issuers()
49
	 */
50
	public function get_issuers() {
51
		$groups = array();
52
53
		$object = $this->request( 'GET', 'Transaction/Specification/ideal?serviceVersion=2' );
54
55
		foreach ( $object->Actions as $action ) {
56
			if ( 'Pay' === $action->Name ) {
57
				foreach ( $action->RequestParameters as $request_parameter ) {
58
					if ( 'issuer' === $request_parameter->Name ) {
59
						foreach ( $request_parameter->ListItemDescriptions as $item ) {
60
							if ( ! array_key_exists( $item->GroupName, $groups ) ) {
61
								$groups[ $item->GroupName ] = array(
62
									'name'    => $item->GroupName,
63
									'options' => array(),
64
								);
65
							}
66
67
							$groups[ $item->GroupName ]['options'][ $item->Value ] = $item->Description;
68
						}
69
					}
70
				}
71
			}
72
		}
73
74
		return $groups;
75
	}
76
77
	/**
78
	 * Get supported payment methods
79
	 *
80
	 * @see Core_Gateway::get_supported_payment_methods()
81
	 */
82
	public function get_supported_payment_methods() {
83
		return array(
84
			Core_PaymentMethods::AMERICAN_EXPRESS,
0 ignored issues
show
The constant Pronamic\WordPress\Pay\C...thods::AMERICAN_EXPRESS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
85
			Core_PaymentMethods::BANK_TRANSFER,
86
			Core_PaymentMethods::BANCONTACT,
87
			Core_PaymentMethods::CREDIT_CARD,
88
			Core_PaymentMethods::GIROPAY,
89
			Core_PaymentMethods::IDEAL,
90
			Core_PaymentMethods::MAESTRO,
91
			Core_PaymentMethods::MASTERCARD,
0 ignored issues
show
The constant Pronamic\WordPress\Pay\C...mentMethods::MASTERCARD was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
92
			Core_PaymentMethods::PAYPAL,
93
			Core_PaymentMethods::SOFORT,
94
			Core_PaymentMethods::V_PAY,
0 ignored issues
show
The constant Pronamic\WordPress\Pay\Core\PaymentMethods::V_PAY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
95
			Core_PaymentMethods::VISA,
0 ignored issues
show
The constant Pronamic\WordPress\Pay\Core\PaymentMethods::VISA was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
96
		);
97
	}
98
99
	/**
100
	 * Start
101
	 *
102
	 * @param Payment $payment Payment.
103
	 *
104
	 * @see Core_Gateway::start()
105
	 */
106
	public function start( Payment $payment ) {
107
		/**
108
		 * Currency.
109
		 */
110
		$currency_code = $payment->get_total_amount()->get_currency()->get_alphabetic_code();
111
112
		/**
113
		 * Push URL.
114
		 */
115
		$push_url = \rest_url( Integration::REST_ROUTE_NAMESPACE . '/push' );
116
117
		/**
118
		 * Filters the Buckaroo push URL.
119
		 *
120
		 * If you want to debug the Buckaroo report URL you can use this filter
121
		 * to override the push URL. You could for example use a service like
122
		 * https://webhook.site/ to inspect the push requests from Buckaroo.
123
		 *
124
		 * @param string $push_url Buckaroo push URL.
125
		 */
126
		$push_url = \apply_filters( 'pronamic_pay_buckaroo_push_url', $push_url );
127
128
		/**
129
		 * JSON Transaction.
130
		 *
131
		 * @link https://testcheckout.buckaroo.nl/json/Docs/Api/POST-json-Transaction
132
		 */
133
		$data = (object) array(
134
			'Currency'                  => $currency_code,
135
			/**
136
			 * The debit amount for the request. This is in decimal format,
137
			 * with a point as the decimal separator. For example, if the
138
			 * currency is specified as EUR, sending “1” will mean that 1 euro
139
			 * will be paid. “1.00” is also 1 euro. “0.01” means 1 cent.
140
			 * Please note, a transaction must have either a debit amount or a
141
			 * credit amount and it cannot have both.
142
			 *
143
			 * @link https://dev.buckaroo.nl/Apis
144
			 */
145
			'AmountDebit'               => $payment->get_total_amount()->number_format( null, '.', '' ),
146
			'Description'               => $payment->get_description(),
147
			'Invoice'                   => Util::get_invoice_number( (string) $this->config->get_invoice_number(), $payment ),
148
			'ReturnURL'                 => $payment->get_return_url(),
149
			'ReturnURLCancel'           => \add_query_arg(
150
				'buckaroo_return_url_cancel',
151
				true,
152
				$payment->get_return_url()
153
			),
154
			'ReturnURLError'            => \add_query_arg(
155
				'buckaroo_return_url_error',
156
				true,
157
				$payment->get_return_url()
158
			),
159
			'ReturnURLReject'           => \add_query_arg(
160
				'buckaroo_return_url_reject',
161
				true,
162
				$payment->get_return_url()
163
			),
164
			/**
165
			 * Push URL.
166
			 *
167
			 * When provided, this push URL overrides all the push URLs as configured in the payment plaza under websites for the associated website key
168
			 *
169
			 * @link https://dev.buckaroo.nl/Apis
170
			 */
171
			'PushURL'                   => $push_url,
172
			/**
173
			 * Push URL Failure.
174
			 *
175
			 * 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.
176
			 *
177
			 * @link https://dev.buckaroo.nl/Apis
178
			 */
179
			'PushURLFailure'            => $push_url,
180
			/**
181
			 * Services.
182
			 *
183
			 * Specifies which service (can be a payment method and/or additional service) is being called upon in the request.
184
			 *
185
			 * @link https://dev.buckaroo.nl/Apis
186
			 */
187
			'Services'                  => (object) array(
188
				'ServiceList' => array(),
189
			),
190
			/**
191
			 * Continue On Incomplete.
192
			 *
193
			 * Specifies if a redirecturl to a payment form will be returned to
194
			 * which a customer should be sent if no paymentmethod is selected
195
			 * or if any required parameter which the customer may provide is
196
			 * missing or incorrect. Possible Values:
197
			 *
198
			 * · No: This is the default. The request will fail if not all the
199
			 * needed information is provided.
200
			 *
201
			 * · RedirectToHTML: A redirect to the HTML gateway is provided if
202
			 * a recoverable problems are detected in the request. The customer
203
			 * can then provide the needed information there.
204
			 *
205
			 * @link https://dev.buckaroo.nl/Apis
206
			 * @link https://testcheckout.buckaroo.nl/json/Docs/Api/POST-json-Transaction
207
			 * @link https://testcheckout.buckaroo.nl/json/Docs/ResourceModel?modelName=ContinueOnIncomplete
208
			 */
209
			'ContinueOnIncomplete'      => 'RedirectToHTML',
210
			/**
211
			 * Services Excluded For Client.
212
			 *
213
			 * If no primary service is provided and ContinueOnIncomplete is
214
			 * set, this list of comma separated servicescodes can be used to
215
			 * limit the number of services from which the customer may choose
216
			 * once he is redirected to the payment form. Services which are
217
			 * entered in this field are not selectable.
218
			 * This field is optional.
219
			 *
220
			 * @link https://dev.buckaroo.nl/Apis
221
			 * @link https://testcheckout.buckaroo.nl/json/Docs/Api/POST-json-Transaction
222
			 */
223
			'ServicesExcludedForClient' => $this->config->get_excluded_services(),
224
			/**
225
			 * Custom parameters.
226
			 *
227
			 * @link https://testcheckout.buckaroo.nl/json/Docs/Api/POST-json-Transaction
228
			 */
229
			'CustomParameters'          => array(
230
				(object) array(
231
					'Name'  => 'pronamic_payment_id',
232
					'Value' => $payment->get_id(),
233
				),
234
			),
235
		);
236
237
		/**
238
		 * Client IP.
239
		 *
240
		 * In this field the IP address of the customer (or employee) for which
241
		 * the action is being performed can be passed. Please note, If this
242
		 * field is not sent to our gateway, your server IP address will be
243
		 * used as the clientIP. This may result in unwanted behaviour for
244
		 * anti-fraud checks. Also, certain payment methods perform checks on
245
		 * the IP address, if an IP address is overused, the request could be
246
		 * blocked. This field is sent in the following format, where
247
		 * type 0 = IPv4 and type 1 = IPv6:
248
		 * "ClientIP": { "Type": 0, "Address": "0.0.0.0" },
249
		 *
250
		 * @link https://testcheckout.buckaroo.nl/json/Docs/Api/POST-json-Transaction
251
		 * @link https://stackoverflow.com/questions/1448871/how-to-know-which-version-of-the-internet-protocol-ip-a-client-is-using-when-c/1448901
252
		 */
253
		$customer = $payment->get_customer();
254
255
		if ( null !== $customer ) {
256
			$ip_address = $customer->get_ip_address();
257
258
			if ( null !== $ip_address ) {
259
				$data->ClientIP = (object) array(
260
					'Type'    => false === \strpos( $ip_address, ':' ) ? 0 : 1,
261
					'Address' => $ip_address,
262
				);
263
			}
264
		}
265
266
		/**
267
		 * Payment method.
268
		 *
269
		 * @link https://testcheckout.buckaroo.nl/json/Docs/Api/POST-json-Transaction
270
		 * @link https://testcheckout.buckaroo.nl/json/Docs/ResourceModel?modelName=ServicesRequest
271
		 * @link https://testcheckout.buckaroo.nl/json/Docs/ResourceModel?modelName=ServiceRequest
272
		 */
273
		$payment_method = $payment->get_method();
274
275
		switch ( $payment_method ) {
276
			/**
277
			 * Paymet method American Express.
278
			 * 
279
			 * @link 
280
			 */
281
			case Core_PaymentMethods::AMERICAN_EXPRESS:
0 ignored issues
show
The constant Pronamic\WordPress\Pay\C...thods::AMERICAN_EXPRESS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
282
				$data->Services->ServiceList[] = (object) array(
283
					'Action' => 'Pay',
284
					'Name'   => PaymentMethods::AMERICAN_EXPRESS,
285
				);
286
287
				break;
288
			/**
289
			 * Payment method creditcard.
290
			 *
291
			 * @link https://dev.buckaroo.nl/PaymentMethods/Description/creditcards#pay
292
			 */
293
			case Core_PaymentMethods::CREDIT_CARD:
294
				$data->Services->ServiceList[] = (object) array(
295
					'Action' => 'Pay',
296
					'Name'   => PaymentMethods::AMERICAN_EXPRESS,
297
				);
298
299
				$data->Services->ServiceList[] = (object) array(
300
					'Action' => 'Pay',
301
					'Name'   => PaymentMethods::MAESTRO,
302
				);
303
304
				$data->Services->ServiceList[] = (object) array(
305
					'Action' => 'Pay',
306
					'Name'   => PaymentMethods::MASTERCARD,
307
				);
308
309
				$data->Services->ServiceList[] = (object) array(
310
					'Action' => 'Pay',
311
					'Name'   => PaymentMethods::VISA,
312
				);
313
314
				break;
315
			/**
316
			 * Payment method iDEAL.
317
			 *
318
			 * @link https://dev.buckaroo.nl/PaymentMethods/Description/ideal#pay
319
			 */
320
			case Core_PaymentMethods::IDEAL:
321
				$data->Services->ServiceList[] = (object) array(
322
					'Action'     => 'Pay',
323
					'Name'       => 'ideal',
324
					'Parameters' => array(
325
						array(
326
							'Name'  => 'issuer',
327
							'Value' => $payment->get_issuer(),
328
						),
329
					),
330
				);
331
332
				break;
333
			/**
334
			 * Payment method transfer.
335
			 *
336
			 * @link https://dev.buckaroo.nl/PaymentMethods/Description/transfer#pay
337
			 */
338
			case Core_PaymentMethods::BANK_TRANSFER:
339
				$data->Services->ServiceList[] = (object) array(
340
					'Action' => 'Pay',
341
					'Name'   => 'transfer',
342
				);
343
344
				break;
345
			/**
346
			 * Payment method Bancontact.
347
			 *
348
			 * @link https://dev.buckaroo.nl/PaymentMethods/Description/bancontact#pay
349
			 */
350
			case Core_PaymentMethods::BANCONTACT:
351
			case Core_PaymentMethods::MISTER_CASH:
352
				$data->Services->ServiceList[] = (object) array(
353
					'Action' => 'Pay',
354
					'Name'   => 'bancontactmrcash',
355
				);
356
357
				break;
358
			/**
359
			 * Paymet method Maestro.
360
			 * 
361
			 * @link 
362
			 */
363
			case Core_PaymentMethods::MAESTRO:
364
				$data->Services->ServiceList[] = (object) array(
365
					'Action' => 'Pay',
366
					'Name'   => PaymentMethods::MAESTRO,
367
				);
368
369
				break;
370
			/**
371
			 * Paymet method Mastercard.
372
			 * 
373
			 * @link 
374
			 */
375
			case Core_PaymentMethods::MASTERCARD:
0 ignored issues
show
The constant Pronamic\WordPress\Pay\C...mentMethods::MASTERCARD was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
376
				$data->Services->ServiceList[] = (object) array(
377
					'Action' => 'Pay',
378
					'Name'   => PaymentMethods::MASTERCARD,
379
				);
380
381
				break;
382
			/**
383
			 * Payment method Giropay.
384
			 *
385
			 * @link https://dev.buckaroo.nl/PaymentMethods/Description/giropay#pay
386
			 */
387
			case Core_PaymentMethods::GIROPAY:
388
				$data->Services->ServiceList[] = (object) array(
389
					'Action' => 'Pay',
390
					'Name'   => 'giropay',
391
				);
392
393
				break;
394
			/**
395
			 * Payment method PayPal.
396
			 *
397
			 * @link https://dev.buckaroo.nl/PaymentMethods/Description/paypal#pay
398
			 */
399
			case Core_PaymentMethods::PAYPAL:
400
				$data->Services->ServiceList[] = (object) array(
401
					'Action' => 'Pay',
402
					'Name'   => 'paypal',
403
				);
404
405
				break;
406
			/**
407
			 * Payment method Sofort.
408
			 *
409
			 * @link https://dev.buckaroo.nl/PaymentMethods/Description/sofort#pay
410
			 */
411
			case Core_PaymentMethods::SOFORT:
412
				$data->Services->ServiceList[] = (object) array(
413
					'Action' => 'Pay',
414
					'Name'   => 'sofortueberweisung',
415
				);
416
417
				break;
418
			/**
419
			 * Paymet method V PAY.
420
			 * 
421
			 * @link https://dev.buckaroo.nl/PaymentMethods/Description/creditcards#top
422
			 */
423
			case Core_PaymentMethods::V_PAY:
0 ignored issues
show
The constant Pronamic\WordPress\Pay\Core\PaymentMethods::V_PAY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
424
				$data->Services->ServiceList[] = (object) array(
425
					'Action' => 'Pay',
426
					'Name'   => PaymentMethods::V_PAY,
0 ignored issues
show
The constant Pronamic\WordPress\Pay\G...o\PaymentMethods::V_PAY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
427
				);
428
429
				break;
430
			/**
431
			 * Paymet method Visa.
432
			 * 
433
			 * @link https://dev.buckaroo.nl/PaymentMethods/Description/creditcards#top
434
			 */
435
			case Core_PaymentMethods::VISA:
0 ignored issues
show
The constant Pronamic\WordPress\Pay\Core\PaymentMethods::VISA was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
436
				$data->Services->ServiceList[] = (object) array(
437
					'Action' => 'Pay',
438
					'Name'   => PaymentMethods::VISA,
439
				);
440
441
				break;
442
		}
443
444
		/**
445
		 * Request.
446
		 */
447
		$object = $this->request( 'POST', 'Transaction', $data );
448
449
		/**
450
		 * Buckaroo keys.
451
		 *
452
		 * @link https://testcheckout.buckaroo.nl/json/Docs/ResourceModel?modelName=TransactionResponse
453
		 */
454
		if ( \property_exists( $object, 'Key' ) ) {
455
			$payment->set_transaction_id( $object->Key );
456
		}
457
458
		if ( \property_exists( $object, 'PaymentKey' ) ) {
459
			$payment->set_meta( 'buckaroo_transaction_payment_key', $object->PaymentKey );
460
		}
461
462
		/**
463
		 * Request Errors.
464
		 *
465
		 * @link https://testcheckout.buckaroo.nl/json/Docs/Api/POST-json-Transaction
466
		 */
467
		if ( \property_exists( $object, 'RequestErrors' ) && null !== $object->RequestErrors ) {
468
			$exception = null;
469
470
			foreach ( $object->RequestErrors as $errors ) {
471
				foreach ( $errors as $error ) {
472
					// Add exception.
473
					$exception = new \Exception( $error->ErrorMessage, 0, $exception );
474
				}
475
			}
476
477
			if ( null !== $exception ) {
478
				throw $exception;
479
			}
480
		}
481
482
		/**
483
		 * Required Action.
484
		 */
485
		if ( null !== $object->RequiredAction ) {
486
			if ( 'Redirect' !== $object->RequiredAction->Name ) {
487
				throw new \Exception(
488
					\sprintf(
489
						'Unsupported Buckaroo action: %s',
490
						$object->RequiredAction->Name
491
					)
492
				);
493
			}
494
495
			// Set action URL.
496
			if ( \property_exists( $object->RequiredAction, 'RedirectURL' ) ) {
497
				$payment->set_action_url( $object->RequiredAction->RedirectURL );
498
			}
499
		}
500
501
		// Failure.
502
		if ( \property_exists( $object, 'Status' ) && \property_exists( $object->Status, 'Code' ) ) {
503
			$status = Statuses::transform( (string) $object->Status->Code->Code );
504
505
			if ( PaymentStatus::FAILURE === $status ) {
506
				throw new \Exception(
507
					\sprintf(
508
						/* translators: 1: payment provider name, 2: status message, 3: status sub message*/
509
						__( 'Unable to create payment at gateway: %1$s%2$s', 'pronamic_ideal' ),
510
						$object->Status->Code->Description,
511
						\property_exists( $object->Status, 'SubCode' ) ? ' – ' . $object->Status->SubCode->Description : ''
512
					)
513
				);
514
			}
515
		}
516
	}
517
518
	/**
519
	 * JSON API Request.
520
	 *
521
	 * @param string      $method   HTTP request method.
522
	 * @param string      $endpoint JSON API endpoint.
523
	 * @param object|null $data     Data.
524
	 */
525
	public function request( $method, $endpoint, $data = null ) {
526
		$host = 'checkout.buckaroo.nl';
527
528
		if ( self::MODE_TEST === $this->config->mode ) {
529
			$host = 'testcheckout.buckaroo.nl';
530
		}
531
532
		/**
533
		 * Authentication.
534
		 *
535
		 * 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.
536
		 *
537
		 * @link https://dev.buckaroo.nl/Apis/Description/json
538
		 * @link https://testcheckout.buckaroo.nl/json/Docs/Authentication
539
		 */
540
		$website_key         = $this->config->website_key;
541
		$request_http_method = $method;
542
		$request_uri         = $host . '/json/' . $endpoint;
543
		$request_timestamp   = \strval( \time() );
544
		$nonce               = \wp_generate_password( 32 );
545
		$request_content     = null === $data ? '' : \wp_json_encode( $data );
546
547
		$values = \implode(
548
			'',
549
			array(
550
				$website_key,
551
				$request_http_method,
552
				\strtolower( \rawurlencode( $request_uri ) ),
553
				$request_timestamp,
554
				$nonce,
555
				// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
556
				null === $data ? '' : \base64_encode( \md5( (string) $request_content, true ) ),
557
			)
558
		);
559
560
		$hash = \hash_hmac( 'sha256', $values, $this->config->secret_key, true );
561
562
		// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
563
		$hmac = \base64_encode( $hash );
564
565
		$authorization = \sprintf(
566
			'hmac %s:%s:%s:%s',
567
			$this->config->website_key,
568
			$hmac,
569
			$nonce,
570
			$request_timestamp
571
		);
572
573
		$response = \Pronamic\WordPress\Http\Facades\Http::request(
574
			'https://' . $request_uri,
575
			array(
576
				'method'  => $request_http_method,
577
				'headers' => array(
578
					'Authorization' => $authorization,
579
					'Content-Type'  => 'application/json',
580
				),
581
				'body'    => $request_content,
582
			)
583
		);
584
585
		$object = $response->json();
586
587
		/**
588
		 * OK.
589
		 */
590
		return $object;
591
	}
592
593
	/**
594
	 * Update status of the specified payment
595
	 *
596
	 * @link https://testcheckout.buckaroo.nl/json/Docs/Api/GET-json-Transaction-Status-transactionKey
597
	 * @param Payment $payment Payment.
598
	 */
599
	public function update_status( Payment $payment ) {
600
		$transaction_key = $payment->get_transaction_id();
601
602
		if ( empty( $transaction_key ) ) {
603
			return;
604
		}
605
606
		$result = $this->request( 'GET', 'Transaction/Status/' . $transaction_key );
607
608
		$payment->set_status( Statuses::transform( \strval( $result->Status->Code->Code ) ) );
609
610
		/**
611
		 * Consumer bank details.
612
		 */
613
		$consumer_bank_details = $payment->get_consumer_bank_details();
614
615
		if ( null === $consumer_bank_details ) {
616
			$consumer_bank_details = new BankAccountDetails();
617
618
			$payment->set_consumer_bank_details( $consumer_bank_details );
619
		}
620
621
		/**
622
		 * Services.
623
		 */
624
		foreach ( $result->Services as $service ) {
625
			foreach ( $service->Parameters as $parameter ) {
626
				if ( 'consumerName' === $parameter->Name ) {
627
					$consumer_bank_details->set_name( $parameter->Value );
628
				}
629
630
				if ( 'consumerIBAN' === $parameter->Name ) {
631
					$consumer_bank_details->set_iban( $parameter->Value );
632
				}
633
634
				if ( 'consumerBIC' === $parameter->Name ) {
635
					$consumer_bank_details->set_bic( $parameter->Value );
636
				}
637
			}
638
		}
639
640
		/**
641
		 * Refunds.
642
		 *
643
		 * @link https://testcheckout.buckaroo.nl/json/Docs/Api/GET-json-Transaction-RefundInfo-transactionKey
644
		 */
645
		$result = $this->request( 'GET', 'Transaction/RefundInfo/' . $transaction_key );
646
647
		if ( \property_exists( $result, 'RefundedAmount' ) && ! empty( $result->RefundedAmount ) ) {
648
			$refunded_amount = new Money( $result->RefundedAmount, $result->RefundCurrency );
649
650
			$payment->set_refunded_amount( $refunded_amount );
651
		}
652
	}
653
654
	/**
655
	 * Create refund.
656
	 *
657
	 * @param string $transaction_id Transaction ID.
658
	 * @param Money  $amount         Amount to refund.
659
	 * @param string $description    Refund reason.
660
	 * @return null|string
661
	 */
662
	public function create_refund( $transaction_id, Money $amount, $description = null ) {
663
		$original_transaction = $this->request( 'GET', 'Transaction/Status/' . $transaction_id );
664
665
		if ( ! \is_object( $original_transaction ) ) {
666
			throw new \Exception(
667
				sprintf(
668
					/* translators: %s: transaction key */
669
					__( 'Unable to create refund for transaction with transaction key: %s', 'pronamic_ideal' ),
670
					$transaction_id
671
				)
672
			);
673
		}
674
675
		$service_name = Util::get_transaction_service( $original_transaction );
676
677
		if ( null === $service_name ) {
678
			throw new \Exception(
679
				sprintf(
680
					/* translators: %s: transaction key */
681
					__( 'Unable to create refund for transaction without service name. Transaction key: %s', 'pronamic_ideal' ),
682
					$transaction_id
683
				)
684
			);
685
		}
686
687
		// Invoice.
688
		$payment = \get_pronamic_payment_by_transaction_id( $transaction_id );
689
690
		$invoice = null;
691
692
		if ( null !== $payment ) {
693
			$invoice = Util::get_invoice_number( (string) $this->config->get_invoice_number(), $payment );
694
		}
695
696
		// Refund request.
697
		$data = (object) array(
698
			'Channel'                => 'Web',
699
			'Currency'               => $amount->get_currency()->get_alphabetic_code(),
700
			/**
701
			 * The credit amount for the request. This is in decimal format,
702
			 * with a point as the decimal separator. For example, if the
703
			 * currency is specified as EUR, sending “1” will mean that 1 euro
704
			 * will be paid. “1.00” is also 1 euro. “0.01” means 1 cent.
705
			 * Please note, a transaction must have either a debit amount or a
706
			 * credit amount and it cannot have both.
707
			 *
708
			 * @link https://dev.buckaroo.nl/Apis
709
			 */
710
			'AmountCredit'           => $amount->format( null, '.', '' ),
711
			'Invoice'                => $invoice,
712
			'OriginalTransactionKey' => $transaction_id,
713
			'Services'               => array(
714
				'ServiceList' => array(
715
					array(
716
						'Name'   => $service_name,
717
						'Action' => 'Refund',
718
					),
719
				),
720
			),
721
		);
722
723
		$refund = $this->request( 'POST', 'Transaction', $data );
724
725
		// Check refund object.
726
		if ( ! \is_object( $refund ) ) {
727
			return null;
728
		}
729
730
		// Check refund status.
731
		if ( \property_exists( $refund, 'Status' ) && \property_exists( $refund->Status, 'Code' ) ) {
732
			$status = Statuses::transform( (string) $refund->Status->Code->Code );
733
734
			if ( PaymentStatus::SUCCESS !== $status ) {
735
				throw new \Exception(
736
					\sprintf(
737
						/* translators: 1: payment provider name, 2: status message, 3: status sub message*/
738
						__( 'Unable to create refund at %1$s gateway: %2$s%3$s', 'pronamic_ideal' ),
739
						__( 'Buckaroo', 'pronamic_ideal' ),
740
						$refund->Status->Code->Description,
741
						\property_exists( $refund->Status, 'SubCode' ) ? ' – ' . $refund->Status->SubCode->Description : ''
742
					)
743
				);
744
			}
745
		}
746
747
		// Update payment refunded amount.
748
		if ( null !== $payment ) {
749
			$result = $this->request( 'GET', 'Transaction/RefundInfo/' . $transaction_id );
750
751
			if ( \property_exists( $result, 'RefundedAmount' ) && ! empty( $result->RefundedAmount ) ) {
752
				$refunded_amount = new Money( $result->RefundedAmount, $result->RefundCurrency );
753
754
				$payment->set_refunded_amount( $refunded_amount );
755
			}
756
		}
757
758
		// Return.
759
		$refund_id = \property_exists( $refund, 'Key' ) ? $refund->Key : null;
760
761
		return $refund_id;
762
	}
763
}
764