Failed Conditions
Push — develop ( d9bf11...21613b )
by Reüel
04:11
created

src/Gateway.php (1 issue)

Labels
Severity
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\Payments\Payment;
10
use Pronamic\WordPress\Pay\Payments\PaymentStatus;
11
use WP_Error;
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
		// Check non-empty keys in configuration.
54
		if ( empty( $this->config->website_key ) || empty( $this->config->secret_key ) ) {
55
			return $groups;
56
		}
57
58
		// Get iDEAL issuers.
59
		try {
60
			$object = $this->request( 'GET', 'Transaction/Specification/ideal?serviceVersion=2' );
61
		} catch ( \Exception $e ) {
62
			$this->set_error( new WP_Error( 'buckaroo_error', $e->getMessage() ) );
63
64
			return $groups;
65
		}
66
67
		foreach ( $object->Actions as $action ) {
68
			// Check action name.
69
			if ( 'Pay' !== $action->Name ) {
70
				continue;
71
			}
72
73
			foreach ( $action->RequestParameters as $request_parameter ) {
74
				// Check request parameter name.
75
				if ( 'issuer' !== $request_parameter->Name ) {
76
					continue;
77
				}
78
79
				foreach ( $request_parameter->ListItemDescriptions as $item ) {
80
					// Make sure to add group.
81
					if ( ! array_key_exists( $item->GroupName, $groups ) ) {
82
						$groups[ $item->GroupName ] = array(
83
							'name'    => $item->GroupName,
84
							'options' => array(),
85
						);
86
					}
87
88
					// Add issuer to group.
89
					$groups[ $item->GroupName ]['options'][ $item->Value ] = $item->Description;
90
				}
91
			}
92
		}
93
94
		return $groups;
95
	}
96
97
	/**
98
	 * Get supported payment methods
99
	 *
100
	 * @see Core_Gateway::get_supported_payment_methods()
101
	 */
102
	public function get_supported_payment_methods() {
103
		return array(
104
			Core_PaymentMethods::AMERICAN_EXPRESS,
105
			Core_PaymentMethods::BANK_TRANSFER,
106
			Core_PaymentMethods::BANCONTACT,
107
			Core_PaymentMethods::CREDIT_CARD,
108
			Core_PaymentMethods::GIROPAY,
109
			Core_PaymentMethods::IDEAL,
110
			Core_PaymentMethods::MAESTRO,
111
			Core_PaymentMethods::MASTERCARD,
112
			Core_PaymentMethods::PAYPAL,
113
			Core_PaymentMethods::SOFORT,
114
			Core_PaymentMethods::V_PAY,
115
			Core_PaymentMethods::VISA,
116
		);
117
	}
118
119
	/**
120
	 * Start
121
	 *
122
	 * @param Payment $payment Payment.
123
	 *
124
	 * @see Core_Gateway::start()
125
	 */
126
	public function start( Payment $payment ) {
127
		/**
128
		 * Currency.
129
		 */
130
		$currency_code = $payment->get_total_amount()->get_currency()->get_alphabetic_code();
131
132
		/**
133
		 * Push URL.
134
		 */
135
		$push_url = \rest_url( Integration::REST_ROUTE_NAMESPACE . '/push' );
136
137
		/**
138
		 * Filters the Buckaroo push URL.
139
		 *
140
		 * If you want to debug the Buckaroo report URL you can use this filter
141
		 * to override the push URL. You could for example use a service like
142
		 * https://webhook.site/ to inspect the push requests from Buckaroo.
143
		 *
144
		 * @param string $push_url Buckaroo push URL.
145
		 */
146
		$push_url = \apply_filters( 'pronamic_pay_buckaroo_push_url', $push_url );
147
148
		/**
149
		 * JSON Transaction.
150
		 *
151
		 * @link https://testcheckout.buckaroo.nl/json/Docs/Api/POST-json-Transaction
152
		 */
153
		$data = (object) array(
154
			'Currency'                  => $currency_code,
155
			/**
156
			 * The debit amount for the request. This is in decimal format,
157
			 * with a point as the decimal separator. For example, if the
158
			 * currency is specified as EUR, sending “1” will mean that 1 euro
159
			 * will be paid. “1.00” is also 1 euro. “0.01” means 1 cent.
160
			 * Please note, a transaction must have either a debit amount or a
161
			 * credit amount and it cannot have both.
162
			 *
163
			 * @link https://dev.buckaroo.nl/Apis
164
			 */
165
			'AmountDebit'               => $payment->get_total_amount()->number_format( null, '.', '' ),
166
			'Description'               => $payment->get_description(),
167
			'Invoice'                   => Util::get_invoice_number( (string) $this->config->get_invoice_number(), $payment ),
168
			'ReturnURL'                 => $payment->get_return_url(),
169
			'ReturnURLCancel'           => \add_query_arg(
170
				'buckaroo_return_url_cancel',
171
				true,
172
				$payment->get_return_url()
173
			),
174
			'ReturnURLError'            => \add_query_arg(
175
				'buckaroo_return_url_error',
176
				true,
177
				$payment->get_return_url()
178
			),
179
			'ReturnURLReject'           => \add_query_arg(
180
				'buckaroo_return_url_reject',
181
				true,
182
				$payment->get_return_url()
183
			),
184
			/**
185
			 * Push URL.
186
			 *
187
			 * When provided, this push URL overrides all the push URLs as configured in the payment plaza under websites for the associated website key
188
			 *
189
			 * @link https://dev.buckaroo.nl/Apis
190
			 */
191
			'PushURL'                   => $push_url,
192
			/**
193
			 * Push URL Failure.
194
			 *
195
			 * 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.
196
			 *
197
			 * @link https://dev.buckaroo.nl/Apis
198
			 */
199
			'PushURLFailure'            => $push_url,
200
			/**
201
			 * Services.
202
			 *
203
			 * Specifies which service (can be a payment method and/or additional service) is being called upon in the request.
204
			 *
205
			 * @link https://dev.buckaroo.nl/Apis
206
			 */
207
			'Services'                  => (object) array(
208
				'ServiceList' => array(),
209
			),
210
			/**
211
			 * Continue On Incomplete.
212
			 *
213
			 * Specifies if a redirecturl to a payment form will be returned to
214
			 * which a customer should be sent if no paymentmethod is selected
215
			 * or if any required parameter which the customer may provide is
216
			 * missing or incorrect. Possible Values:
217
			 *
218
			 * · No: This is the default. The request will fail if not all the
219
			 * needed information is provided.
220
			 *
221
			 * · RedirectToHTML: A redirect to the HTML gateway is provided if
222
			 * a recoverable problems are detected in the request. The customer
223
			 * can then provide the needed information there.
224
			 *
225
			 * @link https://dev.buckaroo.nl/Apis
226
			 * @link https://testcheckout.buckaroo.nl/json/Docs/Api/POST-json-Transaction
227
			 * @link https://testcheckout.buckaroo.nl/json/Docs/ResourceModel?modelName=ContinueOnIncomplete
228
			 */
229
			'ContinueOnIncomplete'      => 'RedirectToHTML',
230
			/**
231
			 * Services Excluded For Client.
232
			 *
233
			 * If no primary service is provided and ContinueOnIncomplete is
234
			 * set, this list of comma separated servicescodes can be used to
235
			 * limit the number of services from which the customer may choose
236
			 * once he is redirected to the payment form. Services which are
237
			 * entered in this field are not selectable.
238
			 * This field is optional.
239
			 *
240
			 * @link https://dev.buckaroo.nl/Apis
241
			 * @link https://testcheckout.buckaroo.nl/json/Docs/Api/POST-json-Transaction
242
			 */
243
			'ServicesExcludedForClient' => $this->config->get_excluded_services(),
244
			/**
245
			 * Custom parameters.
246
			 *
247
			 * @link https://testcheckout.buckaroo.nl/json/Docs/Api/POST-json-Transaction
248
			 */
249
			'CustomParameters'          => array(
250
				(object) array(
251
					'Name'  => 'pronamic_payment_id',
252
					'Value' => $payment->get_id(),
253
				),
254
			),
255
		);
256
257
		/**
258
		 * Client IP.
259
		 *
260
		 * In this field the IP address of the customer (or employee) for which
261
		 * the action is being performed can be passed. Please note, If this
262
		 * field is not sent to our gateway, your server IP address will be
263
		 * used as the clientIP. This may result in unwanted behaviour for
264
		 * anti-fraud checks. Also, certain payment methods perform checks on
265
		 * the IP address, if an IP address is overused, the request could be
266
		 * blocked. This field is sent in the following format, where
267
		 * type 0 = IPv4 and type 1 = IPv6:
268
		 * "ClientIP": { "Type": 0, "Address": "0.0.0.0" },
269
		 *
270
		 * @link https://testcheckout.buckaroo.nl/json/Docs/Api/POST-json-Transaction
271
		 * @link https://stackoverflow.com/questions/1448871/how-to-know-which-version-of-the-internet-protocol-ip-a-client-is-using-when-c/1448901
272
		 */
273
		$customer = $payment->get_customer();
274
275
		if ( null !== $customer ) {
276
			$ip_address = $customer->get_ip_address();
277
278
			if ( null !== $ip_address ) {
279
				$data->ClientIP = (object) array(
280
					'Type'    => false === \strpos( $ip_address, ':' ) ? 0 : 1,
281
					'Address' => $ip_address,
282
				);
283
			}
284
		}
285
286
		/**
287
		 * Payment method.
288
		 *
289
		 * @link https://testcheckout.buckaroo.nl/json/Docs/Api/POST-json-Transaction
290
		 * @link https://testcheckout.buckaroo.nl/json/Docs/ResourceModel?modelName=ServicesRequest
291
		 * @link https://testcheckout.buckaroo.nl/json/Docs/ResourceModel?modelName=ServiceRequest
292
		 */
293
		switch ( $payment->get_payment_method() ) {
0 ignored issues
show
The method get_payment_method() does not exist on Pronamic\WordPress\Pay\Payments\Payment. ( Ignorable by Annotation )

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

293
		switch ( $payment->/** @scrutinizer ignore-call */ get_payment_method() ) {

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...
294
			/**
295
			 * Payment method American Express.
296
			 * 
297
			 * @link 
298
			 */
299
			case Core_PaymentMethods::AMERICAN_EXPRESS:
300
				$data->Services->ServiceList[] = (object) array(
301
					'Action' => 'Pay',
302
					'Name'   => PaymentMethods::AMERICAN_EXPRESS,
303
				);
304
305
				break;
306
			/**
307
			 * Payment method creditcard.
308
			 *
309
			 * @link https://dev.buckaroo.nl/PaymentMethods/Description/creditcards#pay
310
			 */
311
			case Core_PaymentMethods::CREDIT_CARD:
312
				$data->Services->ServiceList[] = (object) array(
313
					'Action' => 'Pay',
314
					'Name'   => PaymentMethods::AMERICAN_EXPRESS,
315
				);
316
317
				$data->Services->ServiceList[] = (object) array(
318
					'Action' => 'Pay',
319
					'Name'   => PaymentMethods::MAESTRO,
320
				);
321
322
				$data->Services->ServiceList[] = (object) array(
323
					'Action' => 'Pay',
324
					'Name'   => PaymentMethods::MASTERCARD,
325
				);
326
327
				$data->Services->ServiceList[] = (object) array(
328
					'Action' => 'Pay',
329
					'Name'   => PaymentMethods::VISA,
330
				);
331
332
				break;
333
			/**
334
			 * Payment method iDEAL.
335
			 *
336
			 * @link https://dev.buckaroo.nl/PaymentMethods/Description/ideal#pay
337
			 */
338
			case Core_PaymentMethods::IDEAL:
339
				$data->Services->ServiceList[] = (object) array(
340
					'Action'     => 'Pay',
341
					'Name'       => 'ideal',
342
					'Parameters' => array(
343
						array(
344
							'Name'  => 'issuer',
345
							'Value' => $payment->get_meta( 'issuer' ),
346
						),
347
					),
348
				);
349
350
				break;
351
			/**
352
			 * Payment method transfer.
353
			 *
354
			 * @link https://dev.buckaroo.nl/PaymentMethods/Description/transfer#pay
355
			 */
356
			case Core_PaymentMethods::BANK_TRANSFER:
357
				$data->Services->ServiceList[] = (object) array(
358
					'Action' => 'Pay',
359
					'Name'   => 'transfer',
360
				);
361
362
				break;
363
			/**
364
			 * Payment method Bancontact.
365
			 *
366
			 * @link https://dev.buckaroo.nl/PaymentMethods/Description/bancontact#pay
367
			 */
368
			case Core_PaymentMethods::BANCONTACT:
369
			case Core_PaymentMethods::MISTER_CASH:
370
				$data->Services->ServiceList[] = (object) array(
371
					'Action' => 'Pay',
372
					'Name'   => 'bancontactmrcash',
373
				);
374
375
				break;
376
			/**
377
			 * Payment method Maestro.
378
			 * 
379
			 * @link 
380
			 */
381
			case Core_PaymentMethods::MAESTRO:
382
				$data->Services->ServiceList[] = (object) array(
383
					'Action' => 'Pay',
384
					'Name'   => PaymentMethods::MAESTRO,
385
				);
386
387
				break;
388
			/**
389
			 * Payment method Mastercard.
390
			 * 
391
			 * @link 
392
			 */
393
			case Core_PaymentMethods::MASTERCARD:
394
				$data->Services->ServiceList[] = (object) array(
395
					'Action' => 'Pay',
396
					'Name'   => PaymentMethods::MASTERCARD,
397
				);
398
399
				break;
400
			/**
401
			 * Payment method Giropay.
402
			 *
403
			 * @link https://dev.buckaroo.nl/PaymentMethods/Description/giropay#pay
404
			 */
405
			case Core_PaymentMethods::GIROPAY:
406
				$data->Services->ServiceList[] = (object) array(
407
					'Action' => 'Pay',
408
					'Name'   => 'giropay',
409
				);
410
411
				break;
412
			/**
413
			 * Payment method PayPal.
414
			 *
415
			 * @link https://dev.buckaroo.nl/PaymentMethods/Description/paypal#pay
416
			 */
417
			case Core_PaymentMethods::PAYPAL:
418
				$data->Services->ServiceList[] = (object) array(
419
					'Action' => 'Pay',
420
					'Name'   => 'paypal',
421
				);
422
423
				break;
424
			/**
425
			 * Payment method Sofort.
426
			 *
427
			 * @link https://dev.buckaroo.nl/PaymentMethods/Description/sofort#pay
428
			 */
429
			case Core_PaymentMethods::SOFORT:
430
				$data->Services->ServiceList[] = (object) array(
431
					'Action' => 'Pay',
432
					'Name'   => 'sofortueberweisung',
433
				);
434
435
				break;
436
			/**
437
			 * Payment method V PAY.
438
			 * 
439
			 * @link https://dev.buckaroo.nl/PaymentMethods/Description/creditcards#top
440
			 */
441
			case Core_PaymentMethods::V_PAY:
442
				$data->Services->ServiceList[] = (object) array(
443
					'Action' => 'Pay',
444
					'Name'   => PaymentMethods::V_PAY,
445
				);
446
447
				break;
448
			/**
449
			 * Payment method Visa.
450
			 * 
451
			 * @link https://dev.buckaroo.nl/PaymentMethods/Description/creditcards#top
452
			 */
453
			case Core_PaymentMethods::VISA:
454
				$data->Services->ServiceList[] = (object) array(
455
					'Action' => 'Pay',
456
					'Name'   => PaymentMethods::VISA,
457
				);
458
459
				break;
460
		}
461
462
		/**
463
		 * Request.
464
		 */
465
		$object = $this->request( 'POST', 'Transaction', $data );
466
467
		/**
468
		 * Buckaroo keys.
469
		 *
470
		 * @link https://testcheckout.buckaroo.nl/json/Docs/ResourceModel?modelName=TransactionResponse
471
		 */
472
		if ( \property_exists( $object, 'Key' ) ) {
473
			$payment->set_transaction_id( $object->Key );
474
		}
475
476
		if ( \property_exists( $object, 'PaymentKey' ) ) {
477
			$payment->set_meta( 'buckaroo_transaction_payment_key', $object->PaymentKey );
478
		}
479
480
		/**
481
		 * Request Errors.
482
		 *
483
		 * @link https://testcheckout.buckaroo.nl/json/Docs/Api/POST-json-Transaction
484
		 */
485
		if ( \property_exists( $object, 'RequestErrors' ) && null !== $object->RequestErrors ) {
486
			$exception = null;
487
488
			foreach ( $object->RequestErrors as $errors ) {
489
				foreach ( $errors as $error ) {
490
					// Add exception.
491
					$exception = new \Exception( $error->ErrorMessage, 0, $exception );
492
				}
493
			}
494
495
			if ( null !== $exception ) {
496
				throw $exception;
497
			}
498
		}
499
500
		/**
501
		 * Required Action.
502
		 */
503
		if ( null !== $object->RequiredAction ) {
504
			if ( 'Redirect' !== $object->RequiredAction->Name ) {
505
				throw new \Exception(
506
					\sprintf(
507
						'Unsupported Buckaroo action: %s',
508
						$object->RequiredAction->Name
509
					)
510
				);
511
			}
512
513
			// Set action URL.
514
			if ( \property_exists( $object->RequiredAction, 'RedirectURL' ) ) {
515
				$payment->set_action_url( $object->RequiredAction->RedirectURL );
516
			}
517
		}
518
519
		// Failure.
520
		if ( \property_exists( $object, 'Status' ) && \property_exists( $object->Status, 'Code' ) ) {
521
			$status = Statuses::transform( (string) $object->Status->Code->Code );
522
523
			if ( PaymentStatus::FAILURE === $status ) {
524
				throw new \Exception(
525
					\sprintf(
526
						/* translators: 1: payment provider name, 2: status message, 3: status sub message*/
527
						__( 'Unable to create payment at gateway: %1$s%2$s', 'pronamic_ideal' ),
528
						$object->Status->Code->Description,
529
						\property_exists( $object->Status, 'SubCode' ) ? ' – ' . $object->Status->SubCode->Description : ''
530
					)
531
				);
532
			}
533
		}
534
	}
535
536
	/**
537
	 * JSON API Request.
538
	 *
539
	 * @param string      $method   HTTP request method.
540
	 * @param string      $endpoint JSON API endpoint.
541
	 * @param object|null $data     Data.
542
	 * @return object
543
	 */
544
	public function request( $method, $endpoint, $data = null ) {
545
		$host = 'checkout.buckaroo.nl';
546
547
		if ( self::MODE_TEST === $this->config->mode ) {
548
			$host = 'testcheckout.buckaroo.nl';
549
		}
550
551
		/**
552
		 * Authentication.
553
		 *
554
		 * 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.
555
		 *
556
		 * @link https://dev.buckaroo.nl/Apis/Description/json
557
		 * @link https://testcheckout.buckaroo.nl/json/Docs/Authentication
558
		 */
559
		$website_key         = $this->config->website_key;
560
		$request_http_method = $method;
561
		$request_uri         = $host . '/json/' . $endpoint;
562
		$request_timestamp   = \strval( \time() );
563
		$nonce               = \wp_generate_password( 32 );
564
		$request_content     = null === $data ? '' : \wp_json_encode( $data );
565
566
		$values = \implode(
567
			'',
568
			array(
569
				$website_key,
570
				$request_http_method,
571
				\strtolower( \rawurlencode( $request_uri ) ),
572
				$request_timestamp,
573
				$nonce,
574
				// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
575
				null === $data ? '' : \base64_encode( \md5( (string) $request_content, true ) ),
576
			)
577
		);
578
579
		$hash = \hash_hmac( 'sha256', $values, $this->config->secret_key, true );
580
581
		// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
582
		$hmac = \base64_encode( $hash );
583
584
		$authorization = \sprintf(
585
			'hmac %s:%s:%s:%s',
586
			$this->config->website_key,
587
			$hmac,
588
			$nonce,
589
			$request_timestamp
590
		);
591
592
		$response = \Pronamic\WordPress\Http\Facades\Http::request(
593
			'https://' . $request_uri,
594
			array(
595
				'method'  => $request_http_method,
596
				'headers' => array(
597
					'Authorization' => $authorization,
598
					'Content-Type'  => 'application/json',
599
				),
600
				'body'    => $request_content,
601
			)
602
		);
603
604
		try {
605
			$object = $response->json();
606
		} catch ( \Exception $e ) {
607
			// JSON error.
608
			$json_error = \json_last_error();
609
610
			// Check authorization error.
611
			if ( \JSON_ERROR_NONE !== $json_error && 400 === $response->status() ) {
612
				throw new \Exception( $response->body() );
613
			}
614
615
			// Re-throw original response exception.
616
			throw $e;
617
		}
618
619
		/**
620
		 * OK.
621
		 */
622
		return $object;
623
	}
624
625
	/**
626
	 * Update status of the specified payment
627
	 *
628
	 * @link https://testcheckout.buckaroo.nl/json/Docs/Api/GET-json-Transaction-Status-transactionKey
629
	 * @param Payment $payment Payment.
630
	 */
631
	public function update_status( Payment $payment ) {
632
		$transaction_key = $payment->get_transaction_id();
633
634
		if ( empty( $transaction_key ) ) {
635
			return;
636
		}
637
638
		$result = $this->request( 'GET', 'Transaction/Status/' . $transaction_key );
639
640
		$payment->set_status( Statuses::transform( \strval( $result->Status->Code->Code ) ) );
641
642
		/**
643
		 * Consumer bank details.
644
		 */
645
		$consumer_bank_details = $payment->get_consumer_bank_details();
646
647
		if ( null === $consumer_bank_details ) {
648
			$consumer_bank_details = new BankAccountDetails();
649
650
			$payment->set_consumer_bank_details( $consumer_bank_details );
651
		}
652
653
		/**
654
		 * Services.
655
		 */
656
		foreach ( $result->Services as $service ) {
657
			foreach ( $service->Parameters as $parameter ) {
658
				if ( 'consumerName' === $parameter->Name ) {
659
					$consumer_bank_details->set_name( $parameter->Value );
660
				}
661
662
				if ( \in_array(
663
					$parameter->Name,
664
					array(
665
						/**
666
						 * Payment method iDEAL.
667
						 * 
668
						 * @link https://dev.buckaroo.nl/PaymentMethods/Description/ideal
669
						 */
670
						'consumerIBAN',
671
						/**
672
						 * Payment method Sofort.
673
						 * 
674
						 * @link https://dev.buckaroo.nl/PaymentMethods/Description/sofort
675
						 */
676
						'CustomerIBAN',
677
					),
678
					true
679
				) ) {
680
					$consumer_bank_details->set_iban( $parameter->Value );
681
				}
682
683
				if ( \in_array(
684
					$parameter->Name,
685
					array(
686
						/**
687
						 * Payment method iDEAL.
688
						 * 
689
						 * @link https://dev.buckaroo.nl/PaymentMethods/Description/ideal
690
						 */
691
						'consumerName',
692
						/**
693
						 * Payment method Sofort.
694
						 * 
695
						 * @link https://dev.buckaroo.nl/PaymentMethods/Description/sofort
696
						 */
697
						'CustomerBIC',
698
					),
699
					true
700
				) ) {
701
					$consumer_bank_details->set_bic( $parameter->Value );
702
				}
703
			}
704
		}
705
706
		/**
707
		 * Refunds.
708
		 *
709
		 * @link https://testcheckout.buckaroo.nl/json/Docs/Api/GET-json-Transaction-RefundInfo-transactionKey
710
		 */
711
		$result = $this->request( 'GET', 'Transaction/RefundInfo/' . $transaction_key );
712
713
		if ( \property_exists( $result, 'RefundedAmount' ) && ! empty( $result->RefundedAmount ) ) {
714
			$refunded_amount = new Money( $result->RefundedAmount, $result->RefundCurrency );
715
716
			$payment->set_refunded_amount( $refunded_amount );
717
		}
718
	}
719
720
	/**
721
	 * Create refund.
722
	 *
723
	 * @param string $transaction_id Transaction ID.
724
	 * @param Money  $amount         Amount to refund.
725
	 * @param string $description    Refund reason.
726
	 * @return null|string
727
	 */
728
	public function create_refund( $transaction_id, Money $amount, $description = null ) {
729
		$original_transaction = $this->request( 'GET', 'Transaction/Status/' . $transaction_id );
730
731
		if ( ! \is_object( $original_transaction ) ) {
732
			throw new \Exception(
733
				sprintf(
734
					/* translators: %s: transaction key */
735
					__( 'Unable to create refund for transaction with transaction key: %s', 'pronamic_ideal' ),
736
					$transaction_id
737
				)
738
			);
739
		}
740
741
		$service_name = Util::get_transaction_service( $original_transaction );
742
743
		if ( null === $service_name ) {
744
			throw new \Exception(
745
				sprintf(
746
					/* translators: %s: transaction key */
747
					__( 'Unable to create refund for transaction without service name. Transaction key: %s', 'pronamic_ideal' ),
748
					$transaction_id
749
				)
750
			);
751
		}
752
753
		// Invoice.
754
		$payment = \get_pronamic_payment_by_transaction_id( $transaction_id );
755
756
		$invoice = null;
757
758
		if ( null !== $payment ) {
759
			$invoice = Util::get_invoice_number( (string) $this->config->get_invoice_number(), $payment );
760
		}
761
762
		// Refund request.
763
		$data = (object) array(
764
			'Channel'                => 'Web',
765
			'Currency'               => $amount->get_currency()->get_alphabetic_code(),
766
			/**
767
			 * The credit amount for the request. This is in decimal format,
768
			 * with a point as the decimal separator. For example, if the
769
			 * currency is specified as EUR, sending “1” will mean that 1 euro
770
			 * will be paid. “1.00” is also 1 euro. “0.01” means 1 cent.
771
			 * Please note, a transaction must have either a debit amount or a
772
			 * credit amount and it cannot have both.
773
			 *
774
			 * @link https://dev.buckaroo.nl/Apis
775
			 */
776
			'AmountCredit'           => $amount->format( null, '.', '' ),
777
			'Invoice'                => $invoice,
778
			'OriginalTransactionKey' => $transaction_id,
779
			'Services'               => array(
780
				'ServiceList' => array(
781
					array(
782
						'Name'   => $service_name,
783
						'Action' => 'Refund',
784
					),
785
				),
786
			),
787
		);
788
789
		$refund = $this->request( 'POST', 'Transaction', $data );
790
791
		// Check refund object.
792
		if ( ! \is_object( $refund ) ) {
793
			return null;
794
		}
795
796
		// Check refund status.
797
		if ( \property_exists( $refund, 'Status' ) && \property_exists( $refund->Status, 'Code' ) ) {
798
			$status = Statuses::transform( (string) $refund->Status->Code->Code );
799
800
			if ( PaymentStatus::SUCCESS !== $status ) {
801
				throw new \Exception(
802
					\sprintf(
803
						/* translators: 1: payment provider name, 2: status message, 3: status sub message*/
804
						__( 'Unable to create refund at %1$s gateway: %2$s%3$s', 'pronamic_ideal' ),
805
						__( 'Buckaroo', 'pronamic_ideal' ),
806
						$refund->Status->Code->Description,
807
						\property_exists( $refund->Status, 'SubCode' ) ? ' – ' . $refund->Status->SubCode->Description : ''
808
					)
809
				);
810
			}
811
		}
812
813
		// Update payment refunded amount.
814
		if ( null !== $payment ) {
815
			$result = $this->request( 'GET', 'Transaction/RefundInfo/' . $transaction_id );
816
817
			if ( \property_exists( $result, 'RefundedAmount' ) && ! empty( $result->RefundedAmount ) ) {
818
				$refunded_amount = new Money( $result->RefundedAmount, $result->RefundCurrency );
819
820
				$payment->set_refunded_amount( $refunded_amount );
821
			}
822
		}
823
824
		// Return.
825
		$refund_id = \property_exists( $refund, 'Key' ) ? $refund->Key : null;
826
827
		return $refund_id;
828
	}
829
}
830