PluginProbe
PayPlug for WooCommerce (Official) / 3.0.0
PayPlug for WooCommerce (Official) v3.0.0
3.0.0 2.18.0 1.0.17 1.0.18 1.0.19 1.0.20 1.0.21 1.0.22 1.0.3 1.0.4 1.0.5 1.0.6 1.0.7 1.0.8 1.1.0 1.10.0 1.10.1 1.2.1 1.2.10 1.2.11 1.2.2 1.2.3 1.2.4 1.2.5 1.2.6 All 101 releases
payplug / src / Gateway / PayplugResponse.php

PayplugResponse.php in PayPlug for WooCommerce (Official) 3.0.0, at src/Gateway/PayplugResponse.php

526 lines 18.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Payplug\PayplugWoocommerce\Gateway;
4
5 // Exit if accessed directly
6 if (!defined('ABSPATH')) {
7 exit;
8 }
9
10 use Automattic\WooCommerce\Utilities\OrderUtil;
11 use Payplug\PayplugWoocommerce\PayplugWoocommerceHelper;
12 use Payplug\Resource\IVerifiableAPIResource;
13 use Payplug\Resource\Payment as PaymentResource;
14 use Payplug\Resource\Refund as RefundResource;
15 use WC_Payment_Token_CC;
16 use WC_Payment_Tokens;
17
18 /**
19 * Process responses from PayPlug.
20 */
21 class PayplugResponse
22 {
23 private $gateway;
24
25 public function __construct(PayplugGateway $gateway)
26 {
27 $this->gateway = $gateway;
28 }
29
30 /**
31 * Process payment.
32 *
33 * @param $resource
34 * @param $is_payment_with_token
35 *
36 * @throws \WC_Data_Exception
37 *
38 * @return void
39 */
40 public function process_payment($resource, $is_payment_with_token = false, $source = ''): void
41 {
42 $order_id = wc_clean($resource->metadata['order_id']);
43
44 $order = wc_get_order($order_id);
45 $gateway_id = $order->get_payment_method();
46 $metadata = PayplugWoocommerceHelper::extract_transaction_metadata($resource);
47
48 // Ignore undefined orders
49 if (!$order) {
50 PayplugGateway::log(sprintf('Coudn\'t find order #%s (Transaction %s).', $order_id, wc_clean($resource->id)), 'error');
51
52 return;
53 }
54
55 /**
56 * Checking if it is coming from the order confirmation page or the IPN
57 */
58 if (($gateway_id == $this->gateway->id) || (!empty($source) && ($source === 'ipn'))) {
59 // Ignore cancelled orders
60 if ($order->has_status('refunded')) {
61 PayplugGateway::log(sprintf('Order #%s : ' . $this->gateway_name($gateway_id) . ' order has been refunded. Ignoring IPN', $order_id));
62
63 return;
64 }
65 $paid_date = $order->get_date_paid();
66 if (isset($paid_date) && !$order->is_paid()) {
67 $finished_status = wc_get_is_paid_statuses();
68 $order->set_status($finished_status[0]);
69 }
70
71 // Ignore paid orders
72 if ($order->is_paid()) {
73 PayplugGateway::log(sprintf('Order #%s : ' . $this->gateway_name($gateway_id) . ' order is already complete. Ignoring IPN. "' . !empty($source) ? '[IPN]' : '' . '"', $order_id));
74
75 return;
76 }
77
78 // Handle failed payments
79 if (!empty($resource->failure)) {
80 if ($order->get_status() != 'failed') {
81 $order->update_status('failed', sprintf(__('PayPlug %s OK | Transaction %s failed : %s', 'payplug'), strtoupper($source), $resource->id, wc_clean($resource->failure->message)));
82 }
83
84 /** This action is documented in src/Gateway/PayplugResponse */
85 \do_action('payplug_gateway_payment_response_processed', $order_id, $resource);
86 PayplugWoocommerceHelper::set_flag_ipn_order($order, $metadata, false);
87 PayplugGateway::log(sprintf('Order #%s : ' . $this->gateway_name($gateway_id) . ' payment IPN %s processing completed but failed.', $order_id, $resource->id));
88 wc_increase_stock_levels($order);
89
90 return;
91 } elseif (($resource->failure == null) && !empty($resource->payment_method['is_pending'])
92 && ($resource->payment_method['is_pending'] == true)) {
93 $this->handle_pending_oney($order, $resource, $source);
94 }
95
96 // Save Logs of the payment for the different payment gateways:
97 // For Oney 4 gateways & Bancontact gateway: "$resource->payment_method" exists and is an array
98 // but not for Payplug credit card gateway (in this case we check the payment_method from the order itself not the $resource)
99 if (isset($resource->payment_method) && is_array($resource->payment_method)) {
100 $gateway_id = $resource->payment_method['type'];
101
102 switch ($gateway_id) {
103 case 'oney_' === substr($gateway_id, 0, 5):
104 $this->oney_ipn($resource);
105 break;
106 case 'bancontact':
107 $this->bancontact_ipn($resource, $source);
108 break;
109 case 'apple_pay':
110 $this->apple_pay_ipn($resource, $source);
111 break;
112 case 'american_express':
113 $this->amex_ipn($resource, $source);
114 break;
115 }
116 } elseif ('payplug' == $gateway_id) {
117 if (!$is_payment_with_token) {
118 $this->payplug_ipn($resource, $source);
119 }
120 }
121
122 // Handle successful payments
123 if ($resource->is_paid) {
124 if (!$is_payment_with_token) {
125 $this->maybe_save_card($resource);
126 }
127 $this->maybe_save_address_hash($resource);
128 $order->add_order_note(sprintf(__('PayPlug %s OK | Transaction %s', 'payplug'), strtoupper($source), wc_clean($resource->id)));
129 $order->payment_complete(wc_clean($resource->id));
130 if (PayplugWoocommerceHelper::is_pre_30()) {
131 $order->reduce_order_stock();
132 }
133 /**
134 * Fires once a payment response has been processed.
135 *
136 * @param int $order_id Order ID
137 * @param PaymentResource $resource Payment resource
138 */
139 \do_action('payplug_gateway_payment_response_processed', $order_id, $resource);
140 PayplugWoocommerceHelper::set_flag_ipn_order($order, $metadata, false);
141 PayplugGateway::log(sprintf('Order #%s : ' . $this->gateway_name($gateway_id) . ' payment %s %s processing completed successfully.', strtoupper($source), $order_id, $resource->id));
142 }
143 }
144 }
145
146 /**
147 * Handle pending oney payments orders
148 *
149 * @param $order
150 * @param $resource
151 *
152 * @return void
153 */
154 public function handle_pending_oney($order, $resource, $source): void
155 {
156 $order->add_order_note(sprintf(__('PayPlug %s OK | Transaction %s | Payment PENDING to be checked by an Oney agent', 'payplug'), strtoupper($source), wc_clean($resource->id)));
157 wc_reduce_stock_levels($order);
158 }
159
160 /**
161 * Payplug IPN
162 *
163 * @param $resource
164 */
165 public function payplug_ipn($resource, $source): void
166 {
167 $order_id = wc_clean($resource->metadata['order_id']);
168 PayplugGateway::log(sprintf('Order #%s : Begin processing Payplug payment %s %s', $order_id, strtoupper($source), $resource->id));
169 }
170
171 /**
172 * Bancontact IPN
173 *
174 * @param $resource
175 */
176 public function bancontact_ipn($resource, $source): void
177 {
178 $order_id = wc_clean($resource->metadata['order_id']);
179 PayplugGateway::log(sprintf('Order #%s : Begin processing Bancontact payment %s %s', $order_id, strtoupper($source), $resource->id));
180 }
181
182 /**
183 * Apple Pay IPN
184 *
185 * @param $resource
186 */
187 public function apple_pay_ipn($resource, $source): void
188 {
189 $order_id = wc_clean($resource->metadata['order_id']);
190 PayplugGateway::log(sprintf('Order #%s : Begin processing Apple Pay payment %s %s', $order_id, strtoupper($source), $resource->id));
191 }
192
193 /**
194 * American Express IPN
195 *
196 * @param $resource
197 */
198 public function amex_ipn($resource, $source): void
199 {
200 $order_id = wc_clean($resource->metadata['order_id']);
201 PayplugGateway::log(sprintf('Order #%s : Begin processing Amex payment %s %s', $order_id, strtoupper($source), $resource->id));
202 }
203
204 /**
205 * Oney IPN
206 *
207 * @param $resource
208 */
209 public function oney_ipn($resource): void
210 {
211 $gateway_id = $resource->payment_method['type'];
212 $order_id = wc_clean($resource->metadata['order_id']);
213
214 if (($resource->is_paid == true) && ($resource->failure == null)) {
215 PayplugGateway::log(sprintf('Order #%s : ' . $this->gateway_name($gateway_id) . ' payment SUCCESS', $order_id));
216 }
217
218 if (($resource->is_paid == false) && ($resource->failure != null)) {
219 PayplugGateway::log(sprintf('Order #%s : ' . $this->gateway_name($gateway_id) . ' payment FAILED', $order_id));
220 }
221
222 if (($resource->is_paid == false) && ($resource->failure == null) && ($resource->payment_method['is_pending'] == true)) {
223 PayplugGateway::log(sprintf('Order #%s : ' . $this->gateway_name($gateway_id) . ' payment PENDING and checked by an Oney agent', $order_id));
224 }
225 }
226
227 /**
228 * Get the gateway name from the gateway_id
229 *
230 * @param $gateway_id
231 */
232 private function gateway_name($gateway_id)
233 {
234 return ucwords(str_replace('_', ' ', $gateway_id));
235 }
236
237 /**
238 * Process refund.
239 *
240 * @param $resource
241 * @param bool $ignore_woocommerce_refund Flag to determine if IPN notification for refunds created by WooCommerce
242 * should be ignored or not. Default to true.
243 *
244 * @throws \Exception
245 */
246 public function process_refund($resource, $ignore_woocommerce_refund = true): void
247 {
248 $refund_id = wc_clean($resource->id);
249 $transaction_id = wc_clean($resource->payment_id);
250 $order = $this->get_order_from_transaction_id($transaction_id);
251 if (!$order) {
252 PayplugGateway::log(sprintf('Coudn\'t find order for transaction %s (Refund %s).', wc_clean($resource->payment_id), wc_clean($resource->id)), 'error');
253
254 return;
255 }
256 $order_id = PayplugWoocommerceHelper::is_pre_30() ? $order->id : $order->get_id();
257
258 PayplugGateway::log(sprintf('Order #%s : Begin processing refund IPN %s', $order_id, $resource->id));
259
260 $refund_exist = $this->refund_exist_for_order($order_id, $refund_id);
261 if ($refund_exist) {
262 PayplugGateway::log(sprintf('Order #%s : Refund has already been processed. Ignoring IPN.', $order_id));
263
264 return;
265 }
266
267 // Since refund notification doesn't contain the full resource, we need to retrieve
268 // the refund resource to access its metadata.
269 try {
270 $refund = $this->gateway->payplug_api->refund_retrieve($transaction_id, $refund_id);
271 } catch (\Exception $e) {
272 PayplugGateway::log(sprintf('Order #%s : Fail to retrieve refund data with error %s', $order_id, $e->getMessage()));
273
274 return;
275 }
276
277 if (
278 $ignore_woocommerce_refund
279 && isset($refund->metadata['refund_from'])
280 && 'woocommerce' === $refund->metadata['refund_from']
281 ) {
282 PayplugGateway::log(sprintf('Order #%s : Refund created by WooCommerce. Ignoring IPN.', $order_id));
283
284 return;
285 }
286
287 $refund = wc_create_refund([
288 'amount' => ((int) $resource->amount) / 100,
289 'reason' => isset($resource->metadata['reason']) ? $resource->metadata['reason'] : null,
290 'order_id' => (int) $order_id,
291 'refund_id' => 0,
292 'refund_payment' => false,
293 ]);
294 if (is_wp_error($refund)) {
295 PayplugGateway::log($refund->get_error_message());
296 }
297
298 $refund_meta_key = sprintf('_pr_%s', wc_clean($resource->id));
299 if (PayplugWoocommerceHelper::is_pre_30()) {
300 update_post_meta($order_id, $refund_meta_key, $resource->id);
301 } else {
302 $order->add_meta_data($refund_meta_key, $resource->id, true);
303 $order->save();
304 }
305
306 $note = sprintf(__('Refund %s : Refunded %s', 'payplug'), wc_clean($resource->id), wc_price(((int) $resource->amount) / 100));
307 if (!empty($resource->metadata['reason'])) {
308 $note .= sprintf(' (%s)', esc_html($resource->metadata['reason']));
309 }
310 $order->add_order_note($note);
311
312 /**
313 * Fires once a refund response has been processed.
314 *
315 * @param int $order_id Order ID
316 * @param RefundResource $resource Refund resource
317 */
318 \do_action('payplug_gateway_refund_response_processed', $order_id, $resource);
319
320 try {
321 $payment = $this->gateway->payplug_api->payment_retrieve($transaction_id);
322 $metadata = PayplugWoocommerceHelper::extract_transaction_metadata($payment);
323 PayplugWoocommerceHelper::save_transaction_metadata($order, $metadata);
324 } catch (\Exception $e) {
325 }
326
327 PayplugGateway::log(sprintf('Order #%s : Refund IPN %s processing completed.', $order_id, $resource->id));
328 }
329
330 /**
331 * Get an order from its transaction id.
332 *
333 * @param int $transaction_id
334 *
335 * @return bool|\WC_Order
336 */
337 protected function get_order_from_transaction_id($transaction_id)
338 {
339 global $wpdb;
340
341 if (OrderUtil::custom_orders_table_usage_is_enabled()) {
342 $orders = wc_get_orders(
343 [
344 'field_query' => [
345 [
346 'field' => 'transaction_id',
347 'value' => $transaction_id,
348 ],
349 ],
350 ]
351 );
352 $order = $orders[0];
353 $order_id = $order->get_id();
354 //$sql = "SELECT o.id FROM $wpdb->wc_orders o WHERE o.transaction_id = %s";
355 } else {
356 $order_id = $wpdb->get_var(
357 $wpdb->prepare(
358 "SELECT post_id FROM $wpdb->postmeta WHERE meta_key = '_transaction_id' AND meta_value = %s",
359 $transaction_id
360 )
361 );
362 }
363
364 return !is_null($order_id) ? wc_get_order($order_id) : false;
365 }
366
367 /**
368 * Check if a refund id already exist for the order.
369 *
370 * @param string $refund_id
371 *
372 * @return bool
373 */
374 protected function refund_exist_for_order($order_id, $refund_id)
375 {
376 global $wpdb;
377
378 if (OrderUtil::custom_orders_table_usage_is_enabled()) {
379 $results = wc_get_orders(
380 [
381 'meta_query' => [
382 [
383 'field_query' => [
384 'relation' => 'AND',
385 [
386 'field' => 'meta_key',
387 'value' => '_pr_' . esc_sql($refund_id),
388 ],
389 [
390 'field' => 'meta_value',
391 'value' => $refund_id,
392 ],
393 [
394 'field' => 'order_id',
395 'value' => (int) $order_id,
396 ],
397 ],
398 ],
399 ],
400 ]
401 );
402 } else {
403 $sql = "
404 SELECT p.ID
405 FROM $wpdb->posts p
406 INNER JOIN $wpdb->postmeta pm
407 ON p.ID = pm.post_id
408 WHERE 1=1
409 AND p.post_type = %s
410 AND p.ID = %d
411 AND pm.meta_key LIKE '_pr_" . esc_sql($refund_id) . "'
412 AND pm.meta_value = %s
413 LIMIT 1
414 ";
415
416 $results = $wpdb->get_col(
417 $wpdb->prepare(
418 $sql,
419 'shop_order',
420 (int) $order_id,
421 $refund_id
422 )
423 );
424 }
425
426 return !empty($results) ? true : false;
427 }
428
429 /**
430 * Save card from the transaction.
431 *
432 * @param IVerifiableAPIResource $resource
433 *
434 * @return bool
435 */
436 protected function maybe_save_card($resource)
437 {
438 if (!isset($resource->card) || empty($resource->card->id)) {
439 return false;
440 }
441
442 if (!isset($resource->metadata['customer_id'])) {
443 return false;
444 }
445
446 $customer = get_user_by('id', $resource->metadata['customer_id']);
447 if (!$customer || 0 === (int) $customer->ID) {
448 return false;
449 }
450
451 $merchant_id = $this->gateway->get_merchant_id();
452 if (empty($merchant_id)) {
453 return false;
454 }
455
456 PayplugGateway::log(sprintf('Saving card from transaction %s for customer %s', wc_clean($resource->id), $customer->ID));
457
458 $token = new WC_Payment_Token_CC();
459 $existing_tokens = WC_Payment_Tokens::get_customer_tokens($customer->ID, $this->gateway->id);
460 $set_token = wc_clean($resource->card->id);
461 $set_last4 = wc_clean($resource->card->last4);
462 $set_expiry_year = wc_clean($resource->card->exp_year);
463 $set_expiry_month = zeroise((int) wc_clean($resource->card->exp_month), 2);
464 $set_card_type = \strtolower(wc_clean($resource->card->brand));
465 if (!empty($existing_tokens)) {
466 foreach ($existing_tokens as $token_id => $existing_token) {
467 $current_data = $existing_token->get_data();
468 if ($current_data['token'] === $set_token &&
469 $current_data['last4'] === $set_last4 &&
470 $current_data['expiry_year'] === $set_expiry_year &&
471 $current_data['expiry_month'] === $set_expiry_month &&
472 $current_data['card_type'] === $set_card_type) {
473 $token->set_id($current_data['id']);
474 }
475 }
476 }
477
478 $token->set_token($set_token);
479 $token->set_gateway_id('payplug');
480 $token->set_last4($set_last4);
481 $token->set_expiry_year($set_expiry_year);
482 $token->set_expiry_month($set_expiry_month);
483 $token->set_card_type($set_card_type);
484 $token->set_user_id($customer->ID);
485 $token->add_meta_data('mode', $resource->is_live ? 'live' : 'test', true);
486 $token->add_meta_data('payplug_account', \wc_clean($merchant_id), true);
487 $token->save();
488
489 PayplugGateway::log(sprintf('Payment card saved', wc_clean($resource->id), $customer->ID));
490
491 return true;
492 }
493
494 /**
495 * Save shipping address.
496 *
497 * @param IVerifiableAPIResource $resource
498 *
499 * @return bool
500 */
501 protected function maybe_save_address_hash($resource)
502 {
503 if (!isset($resource->metadata['customer_id'])) {
504 return false;
505 }
506
507 $customer = get_user_by('id', $resource->metadata['customer_id']);
508 if (!$customer || 0 === (int) $customer->ID) {
509 return false;
510 }
511
512 $shipping = [];
513 foreach (PayplugAddressData::$address_fields as $field) {
514 $shipping[$field] = $resource->shipping->{$field};
515 }
516
517 $shipping_hash = PayplugAddressData::hash_address($shipping);
518 $hash_list = PayplugAddressData::get_customer_addresses_hash($customer->ID);
519 $hash_list[] = $shipping_hash;
520 $hash_list = array_unique($hash_list);
521 PayplugAddressData::update_customer_addresses_hash($customer->ID, $hash_list);
522
523 return true;
524 }
525 }
526