From d12b38a1e8dff72d1eb2b7b365f876049b88a37c Mon Sep 17 00:00:00 2001 From: adonis Date: Mon, 31 Aug 2026 22:14:36 +0330 Subject: [PATCH] fix(montzar): persist local order before provider cover and record success or failure Create the Elif order first (pending colleague), then submit to Liman so failed quote or wallet attempts stay visible instead of leaving orphan provider trades. Co-authored-by: Cursor --- .../Single/SingleOrderController.php | 240 ++++++++++++------ app/Models/Order.php | 6 +- 2 files changed, 164 insertions(+), 82 deletions(-) diff --git a/app/Http/Controllers/Single/SingleOrderController.php b/app/Http/Controllers/Single/SingleOrderController.php index 9ec2bea..905b861 100644 --- a/app/Http/Controllers/Single/SingleOrderController.php +++ b/app/Http/Controllers/Single/SingleOrderController.php @@ -357,52 +357,50 @@ class SingleOrderController extends Controller throw new UnprocessableEntityHttpException('موجودی شما کمتر از میزان سفارش است'); } - $this->assertProviderOrderAccepted($data); - - $this->syncOrderProfitFields($data); - $data['order'] = $order = Order::create($data); + $order = $this->openOrderThenCoverWithProvider($data); $abshode = $this->resolveAbshodeProductContext($data); $productId = (int) ($data['product_id'] ?? $abshode['id'] ?? 0); $usesProductWallet = $productId > 0 && !$config->isMainWalletProduct($productId); - if ($usesProductWallet) { - new TransactionInterface($wallet_interface, [ - "cash" => $data['payment_amount'], - "product_id" => $productId, - 'expire_at' => now(), - 'active' => true, - 'order_id' => $order->id, - 'start_at' => now(), - 'title' => 'برداشت وجه نقد', - ], 12, false, false); - $wallet_interface_product = $this->productWalletInterface($productId); - $this->normalizeProductWalletGold($wallet_interface_product); - new TransactionInterface($wallet_interface_product, [ - "gold" => $data['amount'], - "product_id" => $productId, - 'expire_at' => now(), - 'active' => true, - 'order_id' => $order->id, - 'start_at' => now(), - 'title' => $this->meltedOrProductBuyTitle($data), - ], 1, true, false); - } else { - $buyTransaction = [ - "cash" => $data['payment_amount'], - "gold" => $data['amount'], - 'expire_at' => now(), - 'active' => true, - 'order_id' => $order->id, - 'start_at' => now(), - 'title' => $this->meltedOrProductBuyTitle($data), - ]; - if ($productId > 0) { - $buyTransaction['product_id'] = $productId; - $buyTransaction['note'] = 'product_id:' . $productId; + $this->runAfterProviderCover($order, $data, function () use (&$data, $wallet_interface, $productId, $usesProductWallet, $order) { + if ($usesProductWallet) { + new TransactionInterface($wallet_interface, [ + "cash" => $data['payment_amount'], + "product_id" => $productId, + 'expire_at' => now(), + 'active' => true, + 'order_id' => $order->id, + 'start_at' => now(), + 'title' => 'برداشت وجه نقد', + ], 12, false, false); + $wallet_interface_product = $this->productWalletInterface($productId); + $this->normalizeProductWalletGold($wallet_interface_product); + new TransactionInterface($wallet_interface_product, [ + "gold" => $data['amount'], + "product_id" => $productId, + 'expire_at' => now(), + 'active' => true, + 'order_id' => $order->id, + 'start_at' => now(), + 'title' => $this->meltedOrProductBuyTitle($data), + ], 1, true, false); + } else { + $buyTransaction = [ + "cash" => $data['payment_amount'], + "gold" => $data['amount'], + 'expire_at' => now(), + 'active' => true, + 'order_id' => $order->id, + 'start_at' => now(), + 'title' => $this->meltedOrProductBuyTitle($data), + ]; + if ($productId > 0) { + $buyTransaction['product_id'] = $productId; + $buyTransaction['note'] = 'product_id:' . $productId; + } + new TransactionInterface($wallet_interface, $buyTransaction, 1, true, false); } - new TransactionInterface($wallet_interface, $buyTransaction, 1, true, false); - } - $this->finalizeProviderOrder($data); + }); $response['1'] = 2; return $response; @@ -642,47 +640,45 @@ class SingleOrderController extends Controller throw new UnprocessableEntityHttpException($this->insufficientGoldBalanceMessage($data)); } - $this->assertProviderOrderAccepted($data); + $order = $this->openOrderThenCoverWithProvider($data); - $this->syncOrderProfitFields($data); - $data['order'] = $order = Order::create($data); - - if ($usesProductWallet) { - $this->normalizeProductWalletGold($wallet_interface_product); - new TransactionInterface($wallet_interface_product, [ - "gold" => $goldDebit, - "product_id" => $productId, - 'expire_at' => now(), - 'active' => true, - 'order_id' => $order->id, - 'start_at' => now(), - 'title' => $this->meltedOrProductSellTitle($data), - ], 1, false, false); - new TransactionInterface($wallet_interface, [ - "cash" => $data['total_price'], - "product_id" => $productId, - 'expire_at' => now(), - 'active' => true, - 'order_id' => $order->id, - 'start_at' => now(), - 'title' => 'واریز وجه نقد', - ], 12, false, true); - } else { - $sellTransaction = [ - "gold" => $goldDebit, - "cash" => $data['total_price'], - 'expire_at' => now(), - 'active' => true, - 'order_id' => $order->id, - 'title' => $this->meltedOrProductSellTitle($data), - ]; - if ($productId > 0) { - $sellTransaction['product_id'] = $productId; - $sellTransaction['note'] = 'product_id:' . $productId; + $this->runAfterProviderCover($order, $data, function () use (&$data, $wallet_interface, $wallet_interface_product, $productId, $usesProductWallet, $goldDebit, $order) { + if ($usesProductWallet) { + $this->normalizeProductWalletGold($wallet_interface_product); + new TransactionInterface($wallet_interface_product, [ + "gold" => $goldDebit, + "product_id" => $productId, + 'expire_at' => now(), + 'active' => true, + 'order_id' => $order->id, + 'start_at' => now(), + 'title' => $this->meltedOrProductSellTitle($data), + ], 1, false, false); + new TransactionInterface($wallet_interface, [ + "cash" => $data['total_price'], + "product_id" => $productId, + 'expire_at' => now(), + 'active' => true, + 'order_id' => $order->id, + 'start_at' => now(), + 'title' => 'واریز وجه نقد', + ], 12, false, true); + } else { + $sellTransaction = [ + "gold" => $goldDebit, + "cash" => $data['total_price'], + 'expire_at' => now(), + 'active' => true, + 'order_id' => $order->id, + 'title' => $this->meltedOrProductSellTitle($data), + ]; + if ($productId > 0) { + $sellTransaction['product_id'] = $productId; + $sellTransaction['note'] = 'product_id:' . $productId; + } + new TransactionInterface($wallet_interface, $sellTransaction, 2, false, true); } - new TransactionInterface($wallet_interface, $sellTransaction, 2, false, true); - } - $this->finalizeProviderOrder($data); + }); } private function processSellTransactionProduct(&$data, $wallet, $wallet_interface) @@ -1034,11 +1030,92 @@ class SingleOrderController extends Controller return; } - $this->assertNoRecentDuplicateLocalOrder($data); + if (!isset($data['order'])) { + $this->assertNoRecentDuplicateLocalOrder($data); + } $data['_montzar_parsed'] = app(MontzarService::class)->submitOrder($data); } + /** + * اول رکورد محلی (در انتظار همکار)، بعد ارسال به لیمان، بعد کیف. + * اگر همکار رد کند یا کیف بترکد، همان رکورد با وضعیت ناموفق و شرح خطا می‌ماند. + */ + private function openOrderThenCoverWithProvider(array &$data): Order + { + $this->syncOrderProfitFields($data); + $montzar = app(ConfigService::class)->usesMontzarPricing(); + if ($montzar) { + $this->assertNoRecentDuplicateLocalOrder($data); + $data['sts'] = 4; + $data['payment_sts'] = 1; + $this->stampColleagueQuoteOnOrderData($data); + } + + $order = Order::create($data); + $data['order'] = $order; + + if ($montzar) { + try { + $this->assertProviderOrderAccepted($data); + $this->finalizeProviderOrder($data); + $order->refresh(); + } catch (\Throwable $e) { + $this->markOrderFailed($order, $e->getMessage()); + throw $e instanceof UnprocessableEntityHttpException + ? $e + : new UnprocessableEntityHttpException( + $e->getMessage() !== '' ? $e->getMessage() : 'ارسال سفارش به همکار ناموفق بود' + ); + } + } + + return $order; + } + + private function runAfterProviderCover(Order $order, array &$data, callable $walletFn): void + { + try { + $walletFn(); + $this->markOrderSucceeded($order); + } catch (\Throwable $e) { + $this->markOrderFailed($order, $e->getMessage()); + throw $e instanceof UnprocessableEntityHttpException + ? $e + : new UnprocessableEntityHttpException( + $e->getMessage() !== '' ? $e->getMessage() : 'ثبت کیف پس از ارسال به همکار ناموفق بود' + ); + } + } + + private function stampColleagueQuoteOnOrderData(array &$data): void + { + $config = app(ConfigService::class); + $live = (float) $config->get('montzar_live_price', 0); + $diff = (float) $config->get('montzar_diff', 0); + $unit = ((int) ($data['direction'] ?? 0) === 1) + ? ($live + $diff) + : ($live - $diff); + $data['total_price_montzar'] = $unit > 0 ? $unit : 0; + } + + private function markOrderSucceeded(Order $order): void + { + $order->sts = 2; + $order->payment_sts = 4; + $order->description = null; + $order->save(); + } + + private function markOrderFailed(Order $order, string $message): void + { + $order->sts = 3; + $order->payment_sts = 7; + $trimmed = trim($message); + $order->description = $trimmed !== '' ? mb_substr($trimmed, 0, 1000) : 'ناموفق'; + $order->save(); + } + private function finalizeProviderOrder(array &$data): void { if (!app(ConfigService::class)->usesMontzarPricing()) { @@ -1067,6 +1144,7 @@ class SingleOrderController extends Controller $duplicate = Order::where('customer_id', $customerId) ->where('direction', $direction) + ->whereIn('sts', [2, 4]) ->where('created_at', '>=', now()->subSeconds($windowSeconds)) ->whereRaw('ROUND(amount, 6) = ?', [$amount]) ->exists(); diff --git a/app/Models/Order.php b/app/Models/Order.php index 4c019e8..5a39676 100644 --- a/app/Models/Order.php +++ b/app/Models/Order.php @@ -37,6 +37,9 @@ class Order extends Model 'type',//نوع مشتری 'pay_at',//تاریخ انجام معامله در پنل ادمین 'price_id',// + 'provider_id', + 'total_price_montzar', + 'description', ]; protected $casts = [ @@ -46,7 +49,8 @@ class Order extends Model static $STS = [ 1 => 'باز', 2 => 'موفق', - 3 => 'ناموفق' + 3 => 'ناموفق', + 4 => 'در انتظار همکار', ]; static $DIRECTION = [ 1 => 'خرید',