Failed Conditions
Push — feature/webhook-status ( 9b31b2 )
by Reüel
07:56
created

Gateway::get_supported_features()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 0
dl 0
loc 3
ccs 0
cts 3
cp 0
crap 2
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * Gateway
4
 *
5
 * @author    Pronamic <[email protected]>
6
 * @copyright 2005-2019 Pronamic
7
 * @license   GPL-3.0-or-later
8
 * @package   Pronamic\WordPress\Pay\Gateways\OmniKassa2
9
 */
10
11
namespace Pronamic\WordPress\Pay\Gateways\OmniKassa2;
12
13
use Exception;
14
use Pronamic\WordPress\Pay\Core\Gateway as Core_Gateway;
15
use Pronamic\WordPress\Pay\Core\PaymentMethods;
16
use Pronamic\WordPress\Pay\Payments\Payment;
17
use Pronamic\WordPress\Pay\WebhookManager;
0 ignored issues
show
Bug introduced by
The type Pronamic\WordPress\Pay\WebhookManager 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...
18
use WP_Error;
19
20
/**
21
 * Gateway
22
 *
23
 * @author  Remco Tolsma
24
 * @version 2.1.1
25
 * @since   1.0.0
26
 */
27
class Gateway extends Core_Gateway {
28
	/**
29
	 * Client.
30
	 *
31
	 * @var Client
32
	 */
33
	private $client;
34
35
	/**
36
	 * Constructs and initializes an OmniKassa 2.0 gateway.
37
	 *
38
	 * @param Config $config Config.
39
	 */
40
	public function __construct( Config $config ) {
41
		parent::__construct( $config );
42
43
		$this->set_method( self::METHOD_HTTP_REDIRECT );
44
45
		// Supported features.
46
		$this->supports = self::get_supported_features();
47
48
		// Client.
49
		$this->client = new Client();
50
51
		$url = Client::URL_PRODUCTION;
52
53
		if ( self::MODE_TEST === $config->mode ) {
54
			$url = Client::URL_SANDBOX;
55
		}
56
57
		$this->client->set_url( $url );
58
		$this->client->set_refresh_token( $config->refresh_token );
59
		$this->client->set_signing_key( $config->signing_key );
60
	}
61
62
	/**
63
	 * Get supported features.
64
	 *
65
	 * @return array
66
	 */
67
	public static function get_supported_features() {
68
		return array(
69
			'webhook_manual_config',
70
		);
71
	}
72
73
	/**
74
	 * Get supported payment methods.
75
	 *
76
	 * @see \Pronamic_WP_Pay_Gateway::get_supported_payment_methods()
77
	 * @return array
78
	 */
79
	public function get_supported_payment_methods() {
80
		return array(
81
			PaymentMethods::AFTERPAY,
82
			PaymentMethods::BANCONTACT,
83
			PaymentMethods::CREDIT_CARD,
84
			PaymentMethods::IDEAL,
85
			PaymentMethods::MAESTRO,
86
			PaymentMethods::PAYPAL,
87
		);
88
	}
89
90
	/**
91
	 * Start.
92
	 *
93
	 * @see Core_Gateway::start()
94
	 *
95
	 * @param Payment $payment Payment.
96
	 */
97
	public function start( Payment $payment ) {
98
		// Merchant order ID.
99
		$merchant_order_id = $payment->format_string( $this->config->order_id );
100
101
		$payment->set_meta( 'omnikassa_2_merchant_order_id', $merchant_order_id );
102
103
		// New order.
104
		$merchant_return_url = $payment->get_return_url();
105
		$merchant_return_url = apply_filters( 'pronamic_pay_omnikassa_2_merchant_return_url', $merchant_return_url );
106
107
		try {
108
			$order = new Order(
109
				$merchant_order_id,
110
				MoneyTransformer::transform( $payment->get_total_amount() ),
111
				$merchant_return_url
112
			);
113
114
			// Shipping address.
115
			$order->set_shipping_detail( AddressTransformer::transform( $payment->get_shipping_address() ) );
116
117
			// Billing address.
118
			$order->set_billing_detail( AddressTransformer::transform( $payment->get_billing_address() ) );
119
120
			// Customer information.
121
			$customer = $payment->get_customer();
122
123
			if ( null !== $customer ) {
124
				// Language.
125
				$language = $customer->get_language();
126
127
				if ( null !== $language ) {
128
					$order->set_language( strtoupper( $language ) );
129
				}
130
131
				// Customer information.
132
				$customer_information = new CustomerInformation();
133
134
				$customer_information->set_email_address( $customer->get_email() );
135
				$customer_information->set_date_of_birth( $customer->get_birth_date() );
136
				$customer_information->set_gender( Gender::transform( $customer->get_gender() ) );
137
				$customer_information->set_telephone_number( $customer->get_phone() );
138
139
				$name = $customer->get_name();
140
141
				if ( null !== $name ) {
142
					$customer_information->set_initials( $name->get_initials() );
143
				}
144
145
				$order->set_customer_information( $customer_information );
146
			}
147
148
			// Payment brand.
149
			$payment_brand = PaymentBrands::transform( $payment->get_method() );
150
151
			$order->set_payment_brand( $payment_brand );
152
153
			if ( null !== $payment_brand ) {
154
				// Payment brand force should only be set if payment brand is not empty.
155
				$order->set_payment_brand_force( PaymentBrandForce::FORCE_ONCE );
156
			}
157
158
			// Description.
159
			$order->set_description( DataHelper::shorten( $payment->get_description(), 35 ) );
160
161
			// Lines.
162
			$lines = $payment->get_lines();
163
164
			if ( null !== $lines ) {
165
				$order_items = $order->new_items();
166
167
				$i = 1;
168
169
				foreach ( $lines as $line ) {
170
					/* translators: %s: item index */
171
					$name = sprintf( __( 'Item %s', 'pronamic_ideal' ), $i ++ );
172
173
					if ( null !== $line->get_name() && '' !== $line->get_name() ) {
174
						$name = $line->get_name();
175
					}
176
177
					// Don't add free line items as PayPal payments will fail.
178
					$price_value = $line->get_unit_price()->get_value();
179
180
					if ( PaymentBrands::PAYPAL === $payment_brand && empty( $price_value ) ) {
181
						continue;
182
					}
183
184
					$item = $order_items->new_item(
185
						DataHelper::shorten( $name, 50 ),
186
						$line->get_quantity(),
187
						// The amount in cents, including VAT, of the item each, see below for more details.
188
						MoneyTransformer::transform( $line->get_unit_price() ),
189
						ProductCategories::transform( $line->get_type() )
190
					);
191
192
					$item->set_id( $line->get_id() );
193
194
					// Description.
195
					$description = $line->get_description();
196
197
					if ( empty( $description ) && PaymentBrands::AFTERPAY === $payment_brand ) {
198
						/*
199
						 * The `OrderItem.description` field is documentated as `0..1` (optional),
200
						 * but for AfterPay payments it is required.
201
						 *
202
						 * @link https://github.com/wp-pay-gateways/omnikassa-2/tree/feature/post-pay/documentation#error-5024
203
						 */
204
						$description = $name;
205
					}
206
207
					if ( null !== $description ) {
208
						$description = DataHelper::shorten( $description, 100 );
209
					}
210
211
					$item->set_description( $description );
212
213
					$tax_amount = $line->get_unit_price()->get_tax_amount();
214
215
					if ( null !== $tax_amount ) {
216
						// The VAT of the item each, see below for more details.
217
						$item->set_tax( MoneyTransformer::transform( $tax_amount ) );
218
					}
219
				}
220
			}
221
		} catch ( Exception $e ) {
222
			$this->error = new WP_Error( 'omnikassa_2_error', $e->getMessage() );
223
224
			return;
225
		}
226
227
		// Maybe update access token.
228
		$this->maybe_update_access_token();
229
230
		// Handle errors.
231
		if ( $this->get_client_error() ) {
232
			return;
233
		}
234
235
		// Announce order.
236
		$response = $this->client->order_announce( $this->config, $order );
237
238
		// Handle errors.
239
		if ( $this->get_client_error() ) {
240
			return;
241
		}
242
243
		if ( false === $response ) {
244
			return;
245
		}
246
247
		if ( ! $response->is_valid( $this->config->signing_key ) ) {
248
			return;
249
		}
250
251
		$payment->set_action_url( $response->get_redirect_url() );
252
	}
253
254
	/**
255
	 * Update status of the specified payment.
256
	 *
257
	 * @param Payment $payment Payment.
258
	 */
259
	public function update_status( Payment $payment ) {
260
		if ( ! ReturnParameters::contains( $_GET ) ) { // WPCS: CSRF ok.
261
			return;
262
		}
263
264
		$parameters = ReturnParameters::from_array( $_GET ); // WPCS: CSRF ok.
265
266
		// Note.
267
		$note_values = array(
268
			'order_id'  => $parameters->get_order_id(),
269
			'status'    => $parameters->get_status(),
270
			'signature' => $parameters->get_signature(),
271
			'valid'     => $parameters->is_valid( $this->config->signing_key ) ? 'true' : 'false',
272
		);
273
274
		$note = '';
275
276
		$note .= '<p>';
277
		$note .= __( 'OmniKassa 2.0 return URL requested:', 'pronamic_ideal' );
278
		$note .= '</p>';
279
280
		$note .= '<dl>';
281
282
		foreach ( $note_values as $key => $value ) {
283
			$note .= sprintf( '<dt>%s</dt>', esc_html( $key ) );
284
			$note .= sprintf( '<dd>%s</dd>', esc_html( $value ) );
285
		}
286
287
		$note .= '</dl>';
288
289
		$payment->add_note( $note );
290
291
		// Validate.
292
		if ( ! $parameters->is_valid( $this->config->signing_key ) ) {
293
			return;
294
		}
295
296
		// Status.
297
		$pronamic_status = Statuses::transform( $parameters->get_status() );
298
299
		if ( null !== $pronamic_status ) {
300
			$payment->set_status( $pronamic_status );
301
		}
302
	}
303
304
	/**
305
	 * Handle notification.
306
	 *
307
	 * @param Notification $notification Notification.
308
	 *
309
	 * @return void
310
	 */
311
	public function handle_notification( Notification $notification ) {
312
		if ( ! $notification->is_valid( $this->config->signing_key ) ) {
313
			return;
314
		}
315
316
		switch ( $notification->get_event_name() ) {
317
			case 'merchant.order.status.changed':
318
				$this->handle_merchant_order_status_changed( $notification );
319
		}
320
	}
321
322
	/**
323
	 * Handle `merchant.order.status.changed` event.
324
	 *
325
	 * @param Notification $notification Notification.
326
	 *
327
	 * @return void
328
	 */
329
	private function handle_merchant_order_status_changed( Notification $notification ) {
330
		do {
331
			$order_results = $this->client->get_order_results( $notification->get_authentication() );
332
333
			if ( false === $order_results ) {
334
				return;
335
			}
336
337
			if ( ! $order_results->is_valid( $this->config->signing_key ) ) {
338
				return;
339
			}
340
341
			foreach ( $order_results as $order_result ) {
342
				$payment = get_pronamic_payment_by_meta( '_pronamic_payment_omnikassa_2_merchant_order_id', $order_result->get_merchant_order_id() );
343
344
				// Log webhook request.
345
				WebhookManager::log_payment( $payment );
346
347
				if ( empty( $payment ) ) {
348
					continue;
349
				}
350
351
				$payment->set_transaction_id( $order_result->get_omnikassa_order_id() );
352
353
				$pronamic_status = Statuses::transform( $order_result->get_order_status() );
354
355
				if ( null !== $pronamic_status ) {
356
					$payment->set_status( $pronamic_status );
357
				}
358
359
				// Note.
360
				$note = '';
361
362
				$note .= '<p>';
363
				$note .= __( 'OmniKassa 2.0 webhook URL requested:', 'pronamic_ideal' );
364
				$note .= '</p>';
365
				$note .= '<pre>';
366
				$note .= wp_json_encode( $order_result->get_json(), JSON_PRETTY_PRINT );
367
				$note .= '</pre>';
368
369
				$payment->add_note( $note );
370
371
				$payment->save();
372
			}
373
		} while ( $order_results->more_available() );
374
	}
375
376
	/**
377
	 * Maybe update access token.
378
	 *
379
	 * @return void
380
	 */
381
	private function maybe_update_access_token() {
382
		if ( $this->config->is_access_token_valid() ) {
383
			return;
384
		}
385
386
		$data = $this->client->get_access_token_data();
387
388
		if ( ! is_object( $data ) ) {
389
			return;
390
		}
391
392
		if ( isset( $data->token ) ) {
393
			$this->config->access_token = $data->token;
394
395
			update_post_meta( $this->config->post_id, '_pronamic_gateway_omnikassa_2_access_token', $data->token );
396
		}
397
398
		/*
399
		 * @codingStandardsIgnoreStart
400
		 *
401
		 * Ignore coding standards because of sniff WordPress.NamingConventions.ValidVariableName.NotSnakeCaseMemberVar
402
		 */
403
		if ( isset( $data->validUntil ) ) {
404
			$this->config->access_token_valid_until = $data->validUntil;
405
406
			update_post_meta(
407
				$this->config->post_id,
408
				'_pronamic_gateway_omnikassa_2_access_token_valid_until',
409
				$data->validUntil
410
			);
411
		}
412
		// @codingStandardsIgnoreEnd
413
	}
414
415
	/**
416
	 * Get client error.
417
	 *
418
	 * @return \WP_Error|bool
419
	 */
420
	private function get_client_error() {
421
		$error = $this->client->get_error();
422
423
		if ( is_wp_error( $error ) ) {
424
			$this->error = $error;
425
426
			return $error;
427
		}
428
429
		return false;
430
	}
431
}
432