@@ -91,6 +91,9 @@ public function add_notice_monei_order_cancelled( $order_id ) {
9191 * We trigger this same behaviour in order received page. After a successful payment in MONEI we are redirected
9292 * to order_received_page. If there is a token available, we need to save it.
9393 * We don't do this at IPN level, since right now, token doesn't come thru.
94+ *
95+ * Also, we verify the payment status from the API to complete the order if the IPN hasn't processed yet (race condition).
96+ *
9497 * todo: refactor and split code for is_add_payment_method_page and is_order_received_page to make it more readable.
9598 */
9699 public function save_payment_token () {
@@ -125,9 +128,15 @@ public function save_payment_token() {
125128 $ payment = $ this ->moneiPaymentServices ->get_payment ( $ payment_id );
126129 $ payment_token = $ payment ->getPaymentToken ();
127130
131+ // Verify payment status and complete order if needed (race condition fix)
132+ // If user arrives before IPN webhook processes, we complete the order here
133+ if ( $ order_id && is_order_received_page () ) {
134+ $ this ->verify_and_complete_order ( $ order_id , $ payment );
135+ }
136+
128137 // A payment can come without token, user didn't check on save payment method.
129138 // We just ignore it then and do nothing.
130- if ( ! $ payment_token || empty ( $ payment_token ) ) {
139+ if ( ! $ payment_token ) {
131140 return ;
132141 }
133142
@@ -168,6 +177,91 @@ public function save_payment_token() {
168177 WC_Monei_Logger::log ( $ e ->getMessage (), 'error ' );
169178 }
170179 }
180+
181+ /**
182+ * Verify payment status and complete order if needed (race condition fix).
183+ * When user returns from MONEI before IPN webhook processes, we need to complete the order here.
184+ *
185+ * @param int $order_id Order ID.
186+ * @param object $payment MONEI payment object.
187+ * @return void
188+ */
189+ private function verify_and_complete_order ( $ order_id , $ payment ) {
190+ $ order = wc_get_order ( $ order_id );
191+ if ( ! $ order ) {
192+ return ;
193+ }
194+
195+ // Check if payment was already processed (prevent duplicate processing)
196+ $ processed_payment_id = $ order ->get_meta ( '_monei_payment_id_processed ' , true );
197+ if ( $ processed_payment_id === $ payment ->getId () ) {
198+ WC_Monei_Logger::log ( sprintf ( '[MONEI] Payment already processed via IPN [payment_id=%s, order_id=%s] ' , $ payment ->getId (), $ order_id ), 'debug ' );
199+ return ;
200+ }
201+
202+ $ payment_status = $ payment ->getStatus ();
203+ $ order_status = $ order ->get_status ();
204+
205+ 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 ' );
206+
207+ // Only process if order is still pending/on-hold and payment succeeded
208+ if ( ! in_array ( $ order_status , array ( 'pending ' , 'on-hold ' ), true ) ) {
209+ WC_Monei_Logger::log ( sprintf ( '[MONEI] Order already processed, skipping [order_id=%s, status=%s] ' , $ order_id , $ order_status ), 'debug ' );
210+ return ;
211+ }
212+
213+ // If payment is SUCCEEDED or AUTHORIZED, complete the order
214+ if ( 'SUCCEEDED ' === $ payment_status || 'AUTHORIZED ' === $ payment_status ) {
215+ $ amount = $ payment ->getAmount ();
216+ $ order_total = $ order ->get_total ();
217+
218+ // Verify amounts match (with 1 cent exception for subscriptions)
219+ if ( ( (int ) $ amount !== monei_price_format ( $ order_total ) ) && ( 1 !== $ amount ) ) {
220+ $ order ->update_status (
221+ 'on-hold ' ,
222+ sprintf (
223+ /* translators: 1: Order amount, 2: Payment amount */
224+ __ ( 'Validation error: Order vs. Payment amounts do not match (order: %1$s - received: %2$s). ' , 'monei ' ),
225+ monei_price_format ( $ order_total ),
226+ $ amount
227+ )
228+ );
229+ 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 ' );
230+ return ;
231+ }
232+
233+ // Mark payment as processed to prevent duplicate processing by IPN
234+ $ order ->update_meta_data ( '_monei_payment_id_processed ' , $ payment ->getId () );
235+ $ order ->update_meta_data ( '_payment_order_number_monei ' , $ payment ->getId () );
236+ $ order ->update_meta_data ( '_payment_order_status_monei ' , $ payment_status );
237+ $ order ->update_meta_data ( '_payment_order_status_code_monei ' , $ payment ->getStatusCode () );
238+ $ order ->update_meta_data ( '_payment_order_status_message_monei ' , $ payment ->getStatusMessage () );
239+
240+ if ( 'AUTHORIZED ' === $ payment_status ) {
241+ $ order ->update_meta_data ( '_payment_not_captured_monei ' , 1 );
242+ $ order_note = __ ( 'Payment verified via redirect - <strong>Payment Authorized</strong> ' , 'monei ' ) . '. <br><br> ' ;
243+ $ order_note .= __ ( 'MONEI Transaction id: ' , 'monei ' ) . $ payment ->getId () . '. <br><br> ' ;
244+ $ order_note .= __ ( 'MONEI Status Message: ' , 'monei ' ) . $ payment ->getStatusMessage ();
245+ $ order ->add_order_note ( $ order_note );
246+ $ order ->update_status ( 'on-hold ' , __ ( 'Order On-Hold by MONEI ' , 'monei ' ) );
247+ } else {
248+ // SUCCEEDED
249+ $ order_note = __ ( 'Payment verified via redirect - <strong>Payment Completed</strong> ' , 'monei ' ) . '. <br><br> ' ;
250+ $ order_note .= __ ( 'MONEI Transaction id: ' , 'monei ' ) . $ payment ->getId () . '. <br><br> ' ;
251+ $ order_note .= __ ( 'MONEI Status Message: ' , 'monei ' ) . $ payment ->getStatusMessage ();
252+ $ order ->add_order_note ( $ order_note );
253+ $ order ->payment_complete ();
254+
255+ $ payment_method_woo_id = $ order ->get_payment_method ();
256+ if ( 'completed ' === monei_get_settings ( 'orderdo ' , $ payment_method_woo_id ) ) {
257+ $ order ->update_status ( 'completed ' , __ ( 'Order Completed by MONEI ' , 'monei ' ) );
258+ }
259+ }
260+
261+ $ order ->save ();
262+ WC_Monei_Logger::log ( sprintf ( '[MONEI] Order completed via redirect verification [order_id=%s, payment_status=%s] ' , $ order_id , $ payment_status ), 'debug ' );
263+ }
264+ }
171265}
172266
173267new WC_Monei_Redirect_Hooks ();
0 commit comments