Failed Conditions
Push — develop ( e6f9d0...decc4a )
by Remco
05:55
created

Gateway::handle_merchant_order_status_changed()   B

Complexity

Conditions 6
Paths 5

Size

Total Lines 42
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 42

Importance

Changes 6
Bugs 0 Features 0
Metric Value
cc 6
eloc 20
c 6
b 0
f 0
nc 5
nop 1
dl 0
loc 42
ccs 0
cts 27
cp 0
crap 42
rs 8.9777
1
<?php
2
/**
3
 * Gateway
4
 *
5
 * @author    Pronamic <[email protected]>
6
 * @copyright 2005-2021 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 Pronamic\WordPress\Money\TaxedMoney;
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
18
/**
19
 * Gateway
20
 *
21
 * @author  Remco Tolsma
22
 * @version 2.2.4
23
 * @since   1.0.0
24
 */
25
class Gateway extends Core_Gateway {
26
	/**
27
	 * Client.
28
	 *
29
	 * @var Client
30
	 */
31
	private $client;
32
33
	/**
34
	 * Constructs and initializes an OmniKassa 2.0 gateway.
35
	 *
36
	 * @param Config $config Config.
37
	 */
38
	public function __construct( Config $config ) {
39
		parent::__construct( $config );
40
41
		$this->set_method( self::METHOD_HTTP_REDIRECT );
42
43
		// Supported features.
44
		$this->supports = array(
45
			'webhook_log',
46
		);
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 payment methods.
64
	 *
65
	 * @see \Pronamic_WP_Pay_Gateway::get_supported_payment_methods()
66
	 * @return array<string>
67
	 */
68
	public function get_supported_payment_methods() {
69
		return array(
70
			PaymentMethods::AFTERPAY,
71
			PaymentMethods::BANCONTACT,
72
			PaymentMethods::CREDIT_CARD,
73
			PaymentMethods::IDEAL,
74
			PaymentMethods::MAESTRO,
75
			PaymentMethods::PAYPAL,
76
		);
77
	}
78
79
	/**
80
	 * Start.
81
	 *
82
	 * @param Payment $payment Payment.
83
	 * @return void
84
	 * @throws \Exception Throws exception when payment could not start at Rabobank OmniKassa 2.0.
85
	 * @see Core_Gateway::start()
86
	 */
87
	public function start( Payment $payment ) {
88
		// Merchant order ID.
89
		$merchant_order_id = $payment->format_string( $this->config->order_id );
90
91
		$payment->set_meta( 'omnikassa_2_merchant_order_id', $merchant_order_id );
92
93
		// New order.
94
		$merchant_return_url = $payment->get_return_url();
95
96
		/**
97
		 * Filters the OmniKassa 2.0 merchant return URL.
98
		 *
99
		 * @param string $merchant_return_url Merchant return URL.
100
		 */
101
		$merchant_return_url = \apply_filters( 'pronamic_pay_omnikassa_2_merchant_return_url', $merchant_return_url );
102
103
		$order = new Order(
104
			$merchant_order_id,
105
			MoneyTransformer::transform( $payment->get_total_amount() ),
106
			$merchant_return_url
107
		);
108
109
		// Shipping address.
110
		$order->set_shipping_detail( AddressTransformer::transform( $payment->get_shipping_address() ) );
111
112
		// Billing address.
113
		$order->set_billing_detail( AddressTransformer::transform( $payment->get_billing_address() ) );
114
115
		// Customer information.
116
		$customer = $payment->get_customer();
117
118
		if ( null !== $customer ) {
119
			// Language.
120
			$language = $customer->get_language();
121
122
			if ( null !== $language ) {
123
				$order->set_language( \strtoupper( $language ) );
124
			}
125
126
			// Customer information.
127
			$customer_information = new CustomerInformation();
128
129
			$customer_information->set_email_address( $customer->get_email() );
130
			$customer_information->set_date_of_birth( $customer->get_birth_date() );
131
			$customer_information->set_gender( Gender::transform( $customer->get_gender() ) );
132
			$customer_information->set_telephone_number( $customer->get_phone() );
133
134
			$name = $customer->get_name();
135
136
			if ( null !== $name ) {
137
				$customer_information->set_initials( $name->get_initials() );
138
			}
139
140
			$order->set_customer_information( $customer_information );
141
		}
142
143
		// Payment brand.
144
		$payment_brand = PaymentBrands::transform( $payment->get_method() );
145
146
		$order->set_payment_brand( $payment_brand );
147
148
		if ( null !== $payment_brand ) {
149
			// Payment brand force should only be set if payment brand is not empty.
150
			$order->set_payment_brand_force( PaymentBrandForce::FORCE_ONCE );
151
		}
152
153
		// Description.
154
		$description = $payment->get_description();
155
156
		if ( null !== $description ) {
157
			$order->set_description( DataHelper::sanitize_an( $description, 35 ) );
158
		}
159
160
		// Lines.
161
		$lines = $payment->get_lines();
162
163
		if ( null !== $lines ) {
164
			$order_items = $order->new_items();
165
166
			$i = 1;
167
168
			foreach ( $lines as $line ) {
169
				$name = \sprintf(
170
					/* translators: %s: item index */
171
					\__( 'Item %s', 'pronamic_ideal' ),
172
					$i++
173
				);
174
175
				$line_name = $line->get_name();
176
177
				if ( null !== $line_name && '' !== $line_name ) {
178
					$name = $line_name;
179
				}
180
181
				$unit_price = $line->get_unit_price();
182
183
				if ( null === $unit_price ) {
184
					$unit_price = new TaxedMoney();
185
				}
186
187
				$item = $order_items->new_item(
188
					DataHelper::sanitize_an( $name, 50 ),
189
					(int) $line->get_quantity(),
190
					// The amount in cents, including VAT, of the item each, see below for more details.
191
					MoneyTransformer::transform( $unit_price ),
192
					ProductCategories::transform( $line->get_type() )
193
				);
194
195
				$item->set_id( $line->get_id() );
196
197
				// Description.
198
				$description = $line->get_description();
199
200
				if ( empty( $description ) && PaymentBrands::AFTERPAY === $payment_brand ) {
201
					/*
202
					 * The `OrderItem.description` field is documented as `0..1` (optional),
203
					 * but for AfterPay payments it is required.
204
					 *
205
					 * @link https://github.com/wp-pay-gateways/omnikassa-2/tree/feature/post-pay/documentation#error-5024
206
					 */
207
					$description = $name;
208
				}
209
210
				if ( null !== $description ) {
211
					$description = DataHelper::sanitize_an( $description, 100 );
212
				}
213
214
				$item->set_description( $description );
215
216
				$tax_amount = $unit_price->get_tax_amount();
217
218
				if ( null !== $tax_amount ) {
219
					// The VAT of the item each, see below for more details.
220
					$item->set_tax( MoneyTransformer::transform( $tax_amount ) );
221
				}
222
			}
223
		}
224
225
		// Maybe update access token.
226
		$this->maybe_update_access_token();
227
228
		// Announce order.
229
		$response = $this->client->order_announce( $this->config, $order );
230
231
		$payment->set_transaction_id( $response->get_omnikassa_order_id() );
232
		$payment->set_action_url( $response->get_redirect_url() );
233
	}
234
235
	/**
236
	 * Update status of the specified payment.
237
	 *
238
	 * @param Payment $payment Payment.
239
	 * @return void
240
	 */
241
	public function update_status( Payment $payment ) {
242
		// phpcs:ignore WordPress.Security.NonceVerification.Recommended, SlevomatCodingStandard.Variables.DisallowSuperGlobalVariable.DisallowedSuperGlobalVariable
243
		if ( ! ReturnParameters::contains( $_GET ) ) {
244
			return;
245
		}
246
247
		// phpcs:ignore WordPress.Security.NonceVerification.Recommended, SlevomatCodingStandard.Variables.DisallowSuperGlobalVariable.DisallowedSuperGlobalVariable
248
		$parameters = ReturnParameters::from_array( $_GET );
249
250
		// Note.
251
		$note_values = array(
252
			'order_id'  => $parameters->get_order_id(),
253
			'status'    => $parameters->get_status(),
254
			'signature' => (string) $parameters->get_signature(),
255
			'valid'     => $parameters->is_valid( $this->config->signing_key ) ? 'true' : 'false',
256
		);
257
258
		$note = '';
259
260
		$note .= '<p>';
261
		$note .= \__( 'OmniKassa 2.0 return URL requested:', 'pronamic_ideal' );
262
		$note .= '</p>';
263
264
		$note .= '<dl>';
265
266
		foreach ( $note_values as $key => $value ) {
267
			$note .= \sprintf( '<dt>%s</dt>', \esc_html( $key ) );
268
			$note .= \sprintf( '<dd>%s</dd>', \esc_html( $value ) );
269
		}
270
271
		$note .= '</dl>';
272
273
		$payment->add_note( $note );
274
275
		// Validate.
276
		if ( ! $parameters->is_valid( $this->config->signing_key ) ) {
277
			return;
278
		}
279
280
		// Status.
281
		$pronamic_status = Statuses::transform( $parameters->get_status() );
282
283
		if ( null !== $pronamic_status ) {
284
			$payment->set_status( $pronamic_status );
285
		}
286
	}
287
288
	/**
289
	 * Handle notification.
290
	 *
291
	 * @param Notification $notification Notification.
292
	 * @return void
293
	 */
294
	public function handle_notification( Notification $notification ) {
295
		if ( ! $notification->is_valid( $this->config->signing_key ) ) {
296
			return;
297
		}
298
299
		switch ( $notification->get_event_name() ) {
300
			case 'merchant.order.status.changed':
301
				$this->handle_merchant_order_status_changed( $notification );
302
		}
303
	}
304
305
	/**
306
	 * Handle `merchant.order.status.changed` event.
307
	 *
308
	 * @param Notification $notification Notification.
309
	 * @return void
310
	 */
311
	private function handle_merchant_order_status_changed( Notification $notification ) {
312
		do {
313
			$order_results = $this->client->get_order_results( $notification->get_authentication() );
314
315
			if ( ! $order_results->is_valid( $this->config->signing_key ) ) {
316
				throw new InvalidSignatureException(
317
					'Signature on order results message does not match gateway configuration signature.'
318
				);				
319
			}
320
321
			foreach ( $order_results as $order_result ) {
322
				$pronamic_status = Statuses::transform( $order_result->get_order_status() );
323
324
				$payment = \get_pronamic_payment_by_transaction_id( $order_result->get_omnikassa_order_id() );
325
326
				/**
327
				 * Webhook log payment.
328
				 *
329
				 * @param Payment $payment Payment to log.
330
				 */
331
				\do_action( 'pronamic_pay_webhook_log_payment', $payment );
332
333
				if ( empty( $payment ) ) {
334
					continue;
335
				}
336
337
				if ( null !== $pronamic_status ) {
338
					$payment->set_status( $pronamic_status );
339
				}
340
341
				// Note.
342
				$note = \sprintf(
343
					'<p>%s</p><pre>%s</pre>',
344
					\__( 'OmniKassa 2.0 webhook URL requested:', 'pronamic_ideal' ),
345
					(string) \wp_json_encode( $order_result, \JSON_PRETTY_PRINT )
346
				);
347
348
				$payment->add_note( $note );
349
350
				$payment->save();
351
			}
352
		} while ( $order_results->more_available() );
353
	}
354
355
	/**
356
	 * Maybe update access token.
357
	 *
358
	 * @return void
359
	 */
360
	private function maybe_update_access_token() {
361
		if ( $this->config->is_access_token_valid() ) {
362
			return;
363
		}
364
365
		$data = $this->client->get_access_token_data();
366
367
		if ( isset( $data->token ) ) {
368
			$this->config->access_token = $data->token;
369
370
			\update_post_meta( $this->config->post_id, '_pronamic_gateway_omnikassa_2_access_token', $data->token );
371
		}
372
373
		/*
374
		 * @codingStandardsIgnoreStart
375
		 *
376
		 * Ignore coding standards because of sniff WordPress.NamingConventions.ValidVariableName.NotSnakeCaseMemberVar
377
		 */
378
		if ( isset( $data->validUntil ) ) {
379
			$this->config->access_token_valid_until = $data->validUntil;
380
381
			\update_post_meta(
382
				$this->config->post_id,
383
				'_pronamic_gateway_omnikassa_2_access_token_valid_until',
384
				$data->validUntil
385
			);
386
		}
387
		// @codingStandardsIgnoreEnd
388
	}
389
}
390