'698fb2555f8b2efc74f60b2121421f45', 'txaction' => 'paid', 'clearingtype' => 'wlt', 'userid' => '158723953', 'txid' => '321623031', 'price' => '89.00', 'param' => '1', //$this->shopping_order->id, 'reference' => '15c83aee2766c3', ]; */ if (! isset($data['key']) || ! isset($data['param']) || ! isset($data['userid']) || ! isset($data['txid']) || ! isset($data['reference']) || ! isset($data['price'])) { MyLog::writeLog( 'payone', 'error', 'Error:2001 App\Http\Controllers\Api\PayoneController::paymentStatus parameter incomplete', $data ); echo 'TSOK'; exit; } if ($data['key'] != config('payone.defaults.key')) { MyLog::writeLog( 'payone', 'error', 'Error:2002 App\Http\Controllers\Api\PayoneController::paymentStatus Key error', $data ); echo 'TSOK'; exit; } $shopping_order = ShoppingOrder::find($data['param']); if (! $shopping_order) { MyLog::writeLog( 'payone', 'error', 'Error:2003 App\Http\Controllers\Api\PayoneController::paymentStatus ShoppingOrder not found:', $data ); echo 'TSOK'; exit; } $shopping_payment = ShoppingPayment::where('reference', $data['reference'])->first(); if (! $shopping_payment) { MyLog::writeLog( 'payone', 'error', 'Error:2004 App\Http\Controllers\Api\PayoneController::paymentStatus ShoppingPayment not found', $data ); echo 'TSOK'; exit; } if ($shopping_payment->shopping_order_id != $shopping_order->id) { MyLog::writeLog( 'payone', 'error', 'Error:2005 App\Http\Controllers\Api\PayoneController::paymentStatus ShoppingPayment no realation ShoppingOrder', $data ); echo 'TSOK'; exit; } $price = number_format((round($data['price'], 2) * 100), 0, '.', ''); $price_amount = number_format($shopping_payment->amount, 0, '.', ''); if ($price_amount != $price) { $data['shopping_payment-amount'] = $price_amount; $data['price-amount'] = $price; MyLog::writeLog( 'payone', 'error', 'Error:2006 App\Http\Controllers\Api\PayoneController::paymentStatus Price error', $data ); echo 'TSOK'; exit; } /* * Payone sendet dieselbe txaction oft mehrfach (v. a. "appointed"). War der Status * bereits auf ShoppingPayment gespeichert, ist das ein Duplikat: TSOK, keine Doppel-Verarbeitung. * Ausnahme: erneutes "paid", obwohl die Bestellung noch nicht als bezahlt gefuehrt wird (Recovery). */ if ($shopping_payment->txaction == $data['txaction']) { if ($data['txaction'] === 'paid' && $shopping_order->txaction === 'paid') { MyLog::writeLog( 'payone', 'notice', 'App\Http\Controllers\Api\PayoneController::paymentStatus duplicate callback ignored (already paid)', $data, false ); echo 'TSOK'; exit; } if (in_array($data['txaction'], ['appointed', 'failed', 'pending'], true)) { MyLog::writeLog( 'payone', 'info', 'App\Http\Controllers\Api\PayoneController::paymentStatus duplicate callback ignored (same txaction)', [ 'reference' => $data['reference'] ?? null, 'param' => $data['param'] ?? null, 'txaction' => $data['txaction'], 'txid' => $data['txid'] ?? null, ], false ); echo 'TSOK'; exit; } } // create transaction PaymentTransaction::create([ 'shopping_payment_id' => $shopping_payment->id, 'request' => 'transaction', 'txid' => $data['txid'], 'userid' => $data['userid'], 'status' => 'PAYONE', 'key' => $data['key'], 'txaction' => $data['txaction'], 'transmitted_data' => Util::utf8ize($data), 'mode' => $data['mode'], ]); // Define txaction priority (higher number = higher priority) $txaction_priority = [ 'appointed' => 1, 'pending' => 2, 'failed' => 3, 'paid' => 10, // highest priority - final state ]; $current_priority = isset($txaction_priority[$shopping_order->txaction]) ? $txaction_priority[$shopping_order->txaction] : 0; $new_priority = isset($txaction_priority[$data['txaction']]) ? $txaction_priority[$data['txaction']] : 0; // Only update txaction if new priority is higher than current if ($new_priority > $current_priority) { $shopping_order->txaction = $data['txaction']; $shopping_order->save(); $shopping_payment->txaction = $data['txaction']; $shopping_payment->save(); } else { MyLog::writeLog( 'payone', 'info', 'App\Http\Controllers\Api\PayoneController::paymentStatus - txaction not updated (current: '.$shopping_order->txaction.' has higher/equal priority than new: '.$data['txaction'].')', $data, false ); } $send_link = false; $send_mail = true; if ($data['txaction'] === 'failed') { $shopping_order->setUserHistoryValue(['status' => 6]); Util::setInstanceStatusByPayment($shopping_payment, 5); } if ($data['txaction'] === 'appointed') { $shopping_order->setUserHistoryValue(['status' => 7]); ShoppingUserService::snycOrdersByShoppingOrder($shopping_order); Util::setInstanceStatusByPayment($shopping_payment, 4); } if ($data['txaction'] === 'paid') { // Use DB transaction and row locking to prevent race conditions \DB::beginTransaction(); try { // Lock the shopping order row to prevent concurrent processing $locked_order = ShoppingOrder::where('id', $shopping_order->id) ->lockForUpdate() ->first(); // Double-check if payment was already processed if (! $locked_order->paid) { $send_link = Payment::paymentStatusPaidAction($locked_order, true, $shopping_payment); \DB::commit(); } else { $send_mail = false; \DB::commit(); } } catch (\Exception $e) { \DB::rollBack(); MyLog::writeLog( 'payone', 'error', 'Error:2008 App\Http\Controllers\Api\PayoneController::paymentStatus Transaction failed', ['error' => $e->getMessage(), 'data' => $data] ); throw $e; } } $data['send_link'] = $send_link; if ($send_mail) { Payment::paymentStatusSendMail($shopping_order, $shopping_payment, $data); } echo 'TSOK'; exit; } }