@@ -200,81 +200,96 @@ private function verify_and_complete_order( $order_id, $payment ) {
200200 return ;
201201 }
202202
203- // Check if payment was already processed (prevent duplicate processing)
204- $ processed_payment_id = $ order ->get_meta ( '_monei_payment_id_processed ' , true );
205- if ( $ processed_payment_id === $ payment ->getId () ) {
206- WC_Monei_Logger::log ( sprintf ( '[MONEI] Payment already processed via IPN [payment_id=%s, order_id=%s] ' , $ payment ->getId (), $ order_id ), 'debug ' );
203+ // Acquire lock to prevent race condition with IPN webhook.
204+ // IMPORTANT: Must use same lock key pattern as IPN to prevent race conditions.
205+ $ lock_key = WC_Monei_Lock_Helper::get_payment_lock_key ( $ payment ->getId () );
206+ $ lock_value = wp_rand ();
207+
208+ if ( ! WC_Monei_Lock_Helper::acquire_lock ( $ lock_key , $ lock_value , 60 ) ) {
209+ WC_Monei_Logger::log ( sprintf ( '[MONEI] Order completion already in progress [order_id=%s, payment_id=%s] ' , $ order_id , $ payment ->getId () ), 'debug ' );
207210 return ;
208211 }
209212
210- /** @var string $payment_status */
211- $ payment_status = $ payment ->getStatus ();
212- $ order_status = $ order ->get_status ();
213+ try {
214+ // Check if payment was already processed (prevent duplicate processing)
215+ $ processed_payment_id = $ order ->get_meta ( '_monei_payment_id_processed ' , true );
216+ if ( $ processed_payment_id === $ payment ->getId () ) {
217+ WC_Monei_Logger::log ( sprintf ( '[MONEI] Payment already processed via IPN [payment_id=%s, order_id=%s] ' , $ payment ->getId (), $ order_id ), 'debug ' );
218+ return ;
219+ }
213220
214- WC_Monei_Logger::log ( sprintf ( '[MONEI] Redirect verification [payment_id=%s, order_id=%s, payment_status=%s, order_status=%s] ' , $ payment ->getId (), $ order_id , $ payment_status , $ order_status ), 'debug ' );
221+ /** @var string $payment_status */
222+ $ payment_status = $ payment ->getStatus ();
223+ $ order_status = $ order ->get_status ();
215224
216- // Only process if order is still pending/on-hold/failed and payment succeeded
217- if ( ! in_array ( $ order_status , array ( 'pending ' , 'on-hold ' , 'failed ' ), true ) ) {
218- WC_Monei_Logger::log ( sprintf ( '[MONEI] Order already processed, skipping [order_id=%s, status=%s] ' , $ order_id , $ order_status ), 'debug ' );
219- return ;
220- }
225+ WC_Monei_Logger::log ( sprintf ( '[MONEI] Redirect verification [payment_id=%s, order_id=%s, payment_status=%s, order_status=%s] ' , $ payment ->getId (), $ order_id , $ payment_status , $ order_status ), 'debug ' );
221226
222- // If payment is SUCCEEDED or AUTHORIZED, complete the order
223- if ( PaymentStatus::SUCCEEDED === $ payment_status || PaymentStatus::AUTHORIZED === $ payment_status ) {
224- $ amount = $ payment ->getAmount ();
225- $ order_total = $ order ->get_total ();
226-
227- // Verify amounts match (with 1 cent exception for subscriptions)
228- if ( ( (int ) $ amount !== monei_price_format ( $ order_total ) ) && ( 1 !== $ amount ) ) {
229- $ order ->update_status (
230- 'on-hold ' ,
231- sprintf (
232- /* translators: 1: Order amount, 2: Payment amount */
233- __ ( 'Validation error: Order vs. Payment amounts do not match (order: %1$s - received: %2$s). ' , 'monei ' ),
234- monei_price_format ( $ order_total ),
235- $ amount
236- )
237- );
238- WC_Monei_Logger::log ( sprintf ( '[MONEI] Amount mismatch [order_id=%s, order_amount=%s, payment_amount=%s] ' , $ order_id , monei_price_format ( $ order_total ), $ amount ), 'error ' );
227+ // Only process if order is still pending/on-hold/failed and payment succeeded
228+ if ( ! in_array ( $ order_status , array ( 'pending ' , 'on-hold ' , 'failed ' ), true ) ) {
229+ WC_Monei_Logger::log ( sprintf ( '[MONEI] Order already processed, skipping [order_id=%s, status=%s] ' , $ order_id , $ order_status ), 'debug ' );
239230 return ;
240231 }
241232
242- // Mark payment as processed to prevent duplicate processing by IPN
243- $ order ->update_meta_data ( '_monei_payment_id_processed ' , $ payment ->getId () );
244- $ order ->update_meta_data ( '_payment_order_number_monei ' , $ payment ->getId () );
245- $ order ->update_meta_data ( '_payment_order_status_monei ' , $ payment_status );
246- $ order ->update_meta_data ( '_payment_order_status_code_monei ' , $ payment ->getStatusCode () );
247- $ order ->update_meta_data ( '_payment_order_status_message_monei ' , $ payment ->getStatusMessage () );
248-
249- // Store formatted payment method display
250- $ payment_method_display = $ this ->paymentMethodFormatter ->get_payment_method_display_from_payment ( $ payment );
251- if ( $ payment_method_display ) {
252- $ order ->update_meta_data ( '_monei_payment_method_display ' , $ payment_method_display );
253- }
233+ // If payment is SUCCEEDED or AUTHORIZED, complete the order
234+ if ( PaymentStatus::SUCCEEDED === $ payment_status || PaymentStatus::AUTHORIZED === $ payment_status ) {
235+ $ amount = $ payment ->getAmount ();
236+ $ order_total = $ order ->get_total ();
237+
238+ // Verify amounts match (with 1 cent exception for subscriptions)
239+ if ( ( (int ) $ amount !== monei_price_format ( $ order_total ) ) && ( 1 !== $ amount ) ) {
240+ $ order ->update_status (
241+ 'on-hold ' ,
242+ sprintf (
243+ /* translators: 1: Order amount, 2: Payment amount */
244+ __ ( 'Validation error: Order vs. Payment amounts do not match (order: %1$s - received: %2$s). ' , 'monei ' ),
245+ monei_price_format ( $ order_total ),
246+ $ amount
247+ )
248+ );
249+ WC_Monei_Logger::log ( sprintf ( '[MONEI] Amount mismatch [order_id=%s, order_amount=%s, payment_amount=%s] ' , $ order_id , monei_price_format ( $ order_total ), $ amount ), 'error ' );
250+ return ;
251+ }
254252
255- if ( PaymentStatus::AUTHORIZED === $ payment_status ) {
256- $ order ->update_meta_data ( '_payment_not_captured_monei ' , 1 );
257- $ order_note = __ ( 'Payment verified via redirect - <strong>Payment Authorized</strong> ' , 'monei ' ) . '. <br><br> ' ;
258- $ order_note .= __ ( 'MONEI Transaction id: ' , 'monei ' ) . $ payment ->getId () . '. <br><br> ' ;
259- $ order_note .= __ ( 'MONEI Status Message: ' , 'monei ' ) . $ payment ->getStatusMessage ();
260- $ order ->add_order_note ( $ order_note );
261- $ order ->update_status ( 'on-hold ' , __ ( 'Order On-Hold by MONEI ' , 'monei ' ) );
262- } else {
263- // SUCCEEDED
264- $ order_note = __ ( 'Payment verified via redirect - <strong>Payment Completed</strong> ' , 'monei ' ) . '. <br><br> ' ;
265- $ order_note .= __ ( 'MONEI Transaction id: ' , 'monei ' ) . $ payment ->getId () . '. <br><br> ' ;
266- $ order_note .= __ ( 'MONEI Status Message: ' , 'monei ' ) . $ payment ->getStatusMessage ();
267- $ order ->add_order_note ( $ order_note );
268- $ order ->payment_complete ();
253+ // Mark payment as processed to prevent duplicate processing by IPN
254+ $ order ->update_meta_data ( '_monei_payment_id_processed ' , $ payment ->getId () );
255+ $ order ->update_meta_data ( '_payment_order_number_monei ' , $ payment ->getId () );
256+ $ order ->update_meta_data ( '_payment_order_status_monei ' , $ payment_status );
257+ $ order ->update_meta_data ( '_payment_order_status_code_monei ' , $ payment ->getStatusCode () );
258+ $ order ->update_meta_data ( '_payment_order_status_message_monei ' , $ payment ->getStatusMessage () );
259+
260+ // Store formatted payment method display
261+ $ payment_method_display = $ this ->paymentMethodFormatter ->get_payment_method_display_from_payment ( $ payment );
262+ if ( $ payment_method_display ) {
263+ $ order ->update_meta_data ( '_monei_payment_method_display ' , $ payment_method_display );
264+ }
269265
270- $ payment_method_woo_id = $ order ->get_payment_method ();
271- if ( 'completed ' === monei_get_settings ( 'orderdo ' , $ payment_method_woo_id ) ) {
272- $ order ->update_status ( 'completed ' , __ ( 'Order Completed by MONEI ' , 'monei ' ) );
266+ if ( PaymentStatus::AUTHORIZED === $ payment_status ) {
267+ $ order ->update_meta_data ( '_payment_not_captured_monei ' , 1 );
268+ $ order_note = __ ( 'Payment verified via redirect - <strong>Payment Authorized</strong> ' , 'monei ' ) . '. <br><br> ' ;
269+ $ order_note .= __ ( 'MONEI Transaction id: ' , 'monei ' ) . $ payment ->getId () . '. <br><br> ' ;
270+ $ order_note .= __ ( 'MONEI Status Message: ' , 'monei ' ) . $ payment ->getStatusMessage ();
271+ $ order ->add_order_note ( $ order_note );
272+ $ order ->update_status ( 'on-hold ' , __ ( 'Order On-Hold by MONEI ' , 'monei ' ) );
273+ } else {
274+ // SUCCEEDED
275+ $ order_note = __ ( 'Payment verified via redirect - <strong>Payment Completed</strong> ' , 'monei ' ) . '. <br><br> ' ;
276+ $ order_note .= __ ( 'MONEI Transaction id: ' , 'monei ' ) . $ payment ->getId () . '. <br><br> ' ;
277+ $ order_note .= __ ( 'MONEI Status Message: ' , 'monei ' ) . $ payment ->getStatusMessage ();
278+ $ order ->add_order_note ( $ order_note );
279+ $ order ->payment_complete ();
280+
281+ $ payment_method_woo_id = $ order ->get_payment_method ();
282+ if ( 'completed ' === monei_get_settings ( 'orderdo ' , $ payment_method_woo_id ) ) {
283+ $ order ->update_status ( 'completed ' , __ ( 'Order Completed by MONEI ' , 'monei ' ) );
284+ }
273285 }
274- }
275286
276- $ order ->save ();
277- WC_Monei_Logger::log ( sprintf ( '[MONEI] Order completed via redirect verification [order_id=%s, payment_status=%s] ' , $ order_id , $ payment_status ), 'debug ' );
287+ $ order ->save ();
288+ WC_Monei_Logger::log ( sprintf ( '[MONEI] Order completed via redirect verification [order_id=%s, payment_status=%s] ' , $ order_id , $ payment_status ), 'debug ' );
289+ }
290+ } finally {
291+ // Always release the lock.
292+ WC_Monei_Lock_Helper::release_lock ( $ lock_key , $ lock_value );
278293 }
279294 }
280295}
0 commit comments