PluginProbe
MultiSafepay plugin for WooCommerce / trunk
MultiSafepay plugin for WooCommerce vtrunk
6.11.1 6.12.0 6.13.0 6.2.0 6.2.1 6.3.0 6.3.1 6.4.0 6.4.1 6.4.2 6.4.3 6.5.0 6.5.1 6.6.0 6.6.1 6.6.2 6.7.0 6.7.1 6.7.2 6.7.3 6.8.0 6.8.1 6.8.2 6.8.3 6.9.0 All 84 releases
multisafepay / src / PaymentMethods / PaymentMethodCallback.php

PaymentMethodCallback.php in MultiSafepay plugin for WooCommerce trunk, at src/PaymentMethods/PaymentMethodCallback.php

338 lines 17.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php declare(strict_types=1);
2
3 namespace MultiSafepay\WooCommerce\PaymentMethods;
4
5 use MultiSafepay\Api\Transactions\Transaction;
6 use MultiSafepay\Api\Transactions\TransactionResponse;
7 use MultiSafepay\Exception\ApiException;
8 use MultiSafepay\WooCommerce\Services\PaymentMethodService;
9 use MultiSafepay\WooCommerce\Services\SdkService;
10 use MultiSafepay\WooCommerce\Settings\SettingsFields;
11 use MultiSafepay\WooCommerce\Utils\Logger;
12 use MultiSafepay\WooCommerce\Utils\Order as OrderUtil;
13 use MultiSafepay\WooCommerce\Utils\PaymentMethodTitleBuilder;
14 use Psr\Http\Client\ClientExceptionInterface;
15 use WC_Data_Exception;
16 use WC_Order;
17
18 /**
19 * The payment method callback handle the notification process.
20 */
21 class PaymentMethodCallback {
22
23 public const CREDIT_CARD_GATEWAYS = array( 'VISA', 'MASTERCARD', 'AMEX', 'MAESTRO' );
24
25 /**
26 * The WooCommerce Order Id.
27 *
28 * @var int The WooCommerce Order Id.
29 */
30 private $woocommerce_order_id;
31
32 /**
33 * The MultiSafepay Order Id.
34 *
35 * @var string The MultiSafepay Order Id.
36 */
37 private $multisafepay_order_id;
38
39 /**
40 * The time stamp of the callback
41 *
42 * @var string The timestamp
43 */
44 private $time_stamp;
45
46 /**
47 * The WooCommerce order object
48 *
49 * @var WC_Order The WooCommerce order object
50 */
51 private $order;
52
53 /**
54 * The MultiSafepay transaction
55 *
56 * @var TransactionResponse The MultiSafepay transaction
57 */
58 private $multisafepay_transaction;
59
60 /**
61 * @var Logger
62 */
63 private $logger;
64
65 /**
66 * @param string $multisafepay_order_id
67 * @param TransactionResponse|null $multisafepay_transaction
68 * @param Logger|null $logger
69 */
70 public function __construct( string $multisafepay_order_id, ?TransactionResponse $multisafepay_transaction = null, ?Logger $logger = null ) {
71 $this->logger = $logger ?? new Logger();
72 $this->multisafepay_order_id = $multisafepay_order_id;
73 $this->multisafepay_transaction = $multisafepay_transaction ?? $this->get_transaction();
74 $this->woocommerce_order_id = $this->get_woocommerce_order_id();
75 $this->time_stamp = date( 'd/m/Y H:i:s' );
76 $this->order = wc_get_order( $this->woocommerce_order_id );
77 }
78
79 /**
80 * Return the MultiSafepay Transaction
81 *
82 * @return TransactionResponse
83 */
84 private function get_transaction(): TransactionResponse {
85 $transaction_manager = ( new SdkService() )->get_transaction_manager();
86 try {
87 $transaction = $transaction_manager->get( $this->multisafepay_order_id );
88 } catch ( ClientExceptionInterface $client_exception ) {
89 $this->logger->log_error( $client_exception->getMessage() );
90 wp_die( esc_html__( 'Invalid request', 'multisafepay' ), esc_html__( 'Invalid request', 'multisafepay' ), 400 );
91 } catch ( ApiException $api_exception ) {
92 $this->logger->log_error( $api_exception->getMessage() );
93 wp_die( esc_html__( 'Invalid request', 'multisafepay' ), esc_html__( 'Invalid request', 'multisafepay' ), 400 );
94 }
95 return $transaction;
96 }
97
98 /**
99 * Return the WooCommerce order id
100 *
101 * @return int
102 */
103 private function get_woocommerce_order_id(): int {
104 // For most transactions, var2 contains the order id since the order request is being registered using order number
105 if ( ! empty( $this->multisafepay_transaction->getVar2() ) ) {
106 return (int) $this->multisafepay_transaction->getVar2();
107 }
108 return apply_filters( 'multisafepay_transaction_order_id', $this->multisafepay_order_id );
109 }
110
111 /**
112 * Return the WooCommerce order status
113 *
114 * @return string
115 */
116 private function get_woocommerce_order_status(): string {
117 return $this->order->get_status();
118 }
119
120 /**
121 * Return the gateway code registered in MultiSafepay for this transaction
122 *
123 * @param bool $apply_group_credit_cards
124 * @return string
125 */
126 private function get_multisafepay_transaction_gateway_code( bool $apply_group_credit_cards = true ): string {
127 $code = $this->multisafepay_transaction->getPaymentDetails()->getType();
128 if (
129 $apply_group_credit_cards &&
130 in_array( $code, self::CREDIT_CARD_GATEWAYS, true ) &&
131 get_option( 'multisafepay_group_credit_cards', false )
132 ) {
133 $code = 'CREDITCARD';
134 }
135 if ( 0 === stripos( $code, 'coupon::' ) ) {
136 $data = $this->multisafepay_transaction->getPaymentDetails()->getData();
137 return $data['coupon_brand'];
138 }
139 return $code;
140 }
141
142 /**
143 * Return the wallet code registered in MultiSafepay for this transaction
144 *
145 * @return string
146 */
147 private function get_multisafepay_transaction_wallet_code(): string {
148 return (string) $this->multisafepay_transaction->getPaymentDetails()->get( 'wallet' );
149 }
150
151 /**
152 * Return the status of the transaction in MultiSafepay
153 *
154 * @return string
155 */
156 private function get_multisafepay_transaction_status(): string {
157 return $this->multisafepay_transaction->getStatus();
158 }
159
160 /**
161 * Return the PSP ID
162 *
163 * @return string
164 */
165 private function get_multisafepay_transaction_id(): string {
166 return $this->multisafepay_transaction->getTransactionId();
167 }
168
169 /**
170 * Return if the order status is the same as the final
171 * order status configured in the plugin settings
172 *
173 * @param string $order_status
174 * @return bool
175 */
176 private function is_completed_the_final_status( string $order_status ): bool {
177 $final_order_status = get_option( 'multisafepay_final_order_status', false );
178 return $final_order_status && ( 'completed' === $order_status );
179 }
180
181 /**
182 * Return the initial order status configured in the payment method settings
183 *
184 * @param PaymentMethodService $payment_method_service
185 * @return string|null
186 */
187 private function get_initial_order_status( PaymentMethodService $payment_method_service ): ?string {
188 $registered_payment_method = $payment_method_service->get_woocommerce_payment_gateway_by_id( $this->order->get_payment_method() );
189 return $registered_payment_method ? $registered_payment_method->initial_order_status : null;
190 }
191
192 /**
193 * Check if the order status should be updated or not
194 *
195 * @return bool
196 */
197 private function should_status_be_updated(): bool {
198 // Check if the WooCommerce completed order status is considered as the final one
199 if ( $this->is_completed_the_final_status( $this->get_woocommerce_order_status() ) ) {
200 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
201 $message = 'It seems a notification is trying to process an order which already has defined completed as the final order status. For this reason notification is being ignored. Transaction ID received is ' . sanitize_text_field( (string) wp_unslash( $this->get_multisafepay_transaction_id() ) ) . ' with status ' . $this->get_multisafepay_transaction_status();
202 $this->logger->log_warning( $message );
203 $this->order->add_order_note( $message );
204 return false;
205 }
206
207 // The order status can be updated
208 return true;
209 }
210
211 /**
212 * Process the callback.
213 *
214 * @return void
215 * @throws WC_Data_Exception
216 */
217 public function process_callback(): void {
218 // On pre-transactions notification, and using sequential order numbers plugins, var 2 is not received in the notification, then order doesn't exist
219 if ( ! $this->order ) {
220 $this->logger->log_info( 'Notification has been received for the transaction ID ' . $this->multisafepay_order_id . ' but WooCommerce order object has not been found' );
221 header( 'Content-type: text/plain' );
222 die( 'OK' );
223 }
224
225 // If payment method of the order does not belong to MultiSafepay
226 if ( ! OrderUtil::is_multisafepay_order( $this->order ) ) {
227 $message = 'It seems a notification is trying to process an order processed by another payment method. Transaction ID received is ' . $this->order->get_id();
228 $this->logger->log_info( $message );
229 header( 'Content-type: text/plain' );
230 die( 'OK' );
231 }
232
233 if ( $this->get_woocommerce_order_status() === 'trash' ) {
234 $message = 'It seems a notification is trying to change the order status, but the order has been moved to the trash. Transaction ID received is ' . $this->order->get_id() . ' and transaction status is ' . $this->get_multisafepay_transaction_status();
235 $this->logger->log_info( $message );
236 OrderUtil::add_order_note( $this->order, $message, true );
237 header( 'Content-type: text/plain' );
238 die( 'OK' );
239 }
240
241 // If the transaction status is partial_refunded, we just register a new order note.
242 if ( $this->get_multisafepay_transaction_status() === Transaction::PARTIAL_REFUNDED ) {
243 $message = 'A partial refund has been registered within MultiSafepay Control for Order ID: ' . $this->woocommerce_order_id . ' and Order Number: ' . $this->multisafepay_order_id;
244 OrderUtil::add_order_note( $this->order, $message );
245 header( 'Content-type: text/plain' );
246 die( 'OK' );
247 }
248
249 $payment_method_service = new PaymentMethodService();
250 $payment_method_title_builder = new PaymentMethodTitleBuilder();
251 $registered_by_multisafepay_payment_method_object = $payment_method_service->get_woocommerce_payment_gateway_by_multisafepay_gateway_code( $this->get_multisafepay_transaction_gateway_code() );
252 $payment_method_title_registered_by_multisafepay = $registered_by_multisafepay_payment_method_object ? $registered_by_multisafepay_payment_method_object->get_payment_method_title() : '';
253 $wallet_code = $this->get_multisafepay_transaction_wallet_code();
254 $wallet_payment_method_object = $wallet_code ? $payment_method_service->get_woocommerce_payment_gateway_by_multisafepay_gateway_code( $wallet_code ) : false;
255 $wallet_payment_method_title = $wallet_payment_method_object ? $wallet_payment_method_object->get_payment_method_title() : '';
256 $payment_method_object_to_persist = $registered_by_multisafepay_payment_method_object;
257 if ( $wallet_payment_method_object ) {
258 $payment_method_object_to_persist = $wallet_payment_method_object;
259 }
260 $payment_method_id_to_persist = $payment_method_object_to_persist ? $payment_method_object_to_persist->get_payment_method_id() : false;
261 // Keep the gateway title as fallback and only enrich it when wallet context is available.
262 $payment_method_title_to_display = $payment_method_title_registered_by_multisafepay;
263 if ( ! empty( $wallet_payment_method_title ) ) {
264 $payment_method_title_to_display = $wallet_payment_method_title;
265 // Use the raw transaction gateway code (without CREDITCARD grouping) to decide wallet-title eligibility.
266 $underlying_payment_method_gateway_code = $this->get_multisafepay_transaction_gateway_code( false );
267 if ( $payment_method_title_builder->can_build_wallet_combined_payment_method_title( $underlying_payment_method_gateway_code ) ) {
268 $underlying_payment_method_title = $payment_method_title_builder->get_underlying_payment_method_title( $underlying_payment_method_gateway_code );
269 $payment_method_title_to_display = $payment_method_title_builder->build_combined_payment_method_title( $wallet_payment_method_title, $underlying_payment_method_title );
270 }
271 }
272 $payment_method_id_registered_by_wc = $this->order->get_payment_method();
273 $payment_method_title_registered_by_wc = $this->order->get_payment_method_title();
274 $initial_order_status = $this->get_initial_order_status( $payment_method_service );
275 $default_order_status = SettingsFields::get_multisafepay_order_statuses();
276
277 // Check if the WooCommerce Order status do not match with the order status received in notification, to avoid to process repeated of notification.
278 // Or if the custom initial order status of the gateway is different than the general one, and the MultiSafepay transaction status is initialized, and custom initial order status is different than the current WooCommerce order status
279 if (
280 $this->get_woocommerce_order_status() !== str_replace( 'wc-', '', get_option( 'multisafepay_' . $this->get_multisafepay_transaction_status() . '_status', $default_order_status[ $this->get_multisafepay_transaction_status() . '_status' ]['default'] ) ) ||
281 get_option( 'multisafepay_' . $this->get_multisafepay_transaction_status() . '_status', $default_order_status[ $this->get_multisafepay_transaction_status() . '_status' ]['default'] ) !== $initial_order_status && $this->get_multisafepay_transaction_status() === Transaction::INITIALIZED && $this->get_woocommerce_order_status() !== $initial_order_status
282 ) {
283 // If MultiSafepay transaction status is initialized, check if there is a custom initial order status for this payment method.
284 if ( $this->get_multisafepay_transaction_status() === Transaction::INITIALIZED ) {
285 if ( $initial_order_status && 'wc-default' !== $initial_order_status && $this->get_woocommerce_order_status() !== $initial_order_status ) {
286 $this->order->update_status( str_replace( 'wc-', '', $initial_order_status ), __( 'Transaction has been initialized.', 'multisafepay' ) );
287 }
288 if ( ! $initial_order_status || 'wc-default' === $initial_order_status ) {
289 $this->order->update_status( str_replace( 'wc-', '', get_option( 'multisafepay_' . $this->get_multisafepay_transaction_status() . '_status', $default_order_status[ $this->get_multisafepay_transaction_status() . '_status' ]['default'] ) ) );
290 }
291 }
292
293 // If MultiSafepay transaction status is completed, payment_complete function will handle the order status change
294 if ( $this->get_multisafepay_transaction_status() === Transaction::COMPLETED ) {
295 $payment_complete = $this->order->payment_complete( $this->get_multisafepay_transaction_id() );
296 if ( $payment_complete ) {
297 $this->order->update_meta_data( '_multisafepay_order_environment', get_option( 'multisafepay_testmode', false ) ? 'test' : 'live' );
298 $this->order->save();
299 }
300 }
301
302 // If MultiSafepay transaction status is not completed and not initialized, process the notification according order status settings
303 if (
304 $this->get_multisafepay_transaction_status() !== 'completed' &&
305 $this->get_multisafepay_transaction_status() !== 'initialized' &&
306 $this->should_status_be_updated()
307 ) {
308 $this->order->update_status( str_replace( 'wc-', '', get_option( 'multisafepay_' . $this->get_multisafepay_transaction_status() . '_status', $default_order_status[ $this->get_multisafepay_transaction_status() . '_status' ]['default'] ) ) );
309 }
310
311 $message = 'Callback received for Order ID: ' . $this->woocommerce_order_id . ' and Order Number: ' . $this->multisafepay_order_id . ' on ' . $this->time_stamp . ' with status: ' . $this->get_multisafepay_transaction_status() . ' and PSP ID: ' . $this->get_multisafepay_transaction_id() . '.';
312 $this->logger->log_info( $message );
313 OrderUtil::add_order_note( $this->order, $message, true );
314 }
315
316 // If the payment method changed in MultiSafepay payment page, after leave WooCommerce checkout page
317 if ( $payment_method_id_to_persist && $payment_method_id_registered_by_wc !== $payment_method_id_to_persist ) {
318 $message = 'Callback received with a different payment method for Order ID: ' . $this->woocommerce_order_id . ' and Order Number: ' . $this->multisafepay_order_id . ' on ' . $this->time_stamp . '. Payment method changed from ' . $payment_method_title_registered_by_wc . ' to ' . $payment_method_title_to_display . '.';
319 $this->logger->log_info( $message );
320 OrderUtil::add_order_note( $this->order, $message, true );
321 $this->order = wc_get_order( $this->woocommerce_order_id );
322 $this->order->set_payment_method( $payment_method_object_to_persist );
323 if ( ! empty( $payment_method_title_to_display ) ) {
324 $this->order->set_payment_method_title( $payment_method_title_to_display );
325 }
326 $this->order->save();
327 }
328
329 if ( ! empty( $wallet_payment_method_title ) && ! empty( $payment_method_title_to_display ) && $this->order->get_payment_method_title() !== $payment_method_title_to_display ) {
330 $this->order->set_payment_method_title( $payment_method_title_to_display );
331 $this->order->save();
332 }
333
334 header( 'Content-type: text/plain' );
335 die( 'OK' );
336 }
337 }
338