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 <cursoragent@cursor.com>
This commit is contained in:
2026-08-31 22:14:36 +03:30
co-authored by Cursor
parent f6118689e1
commit d12b38a1e8
2 changed files with 164 additions and 82 deletions
@@ -357,14 +357,12 @@ class SingleOrderController extends Controller
throw new UnprocessableEntityHttpException('موجودی شما کمتر از میزان سفارش است'); throw new UnprocessableEntityHttpException('موجودی شما کمتر از میزان سفارش است');
} }
$this->assertProviderOrderAccepted($data); $order = $this->openOrderThenCoverWithProvider($data);
$this->syncOrderProfitFields($data);
$data['order'] = $order = Order::create($data);
$abshode = $this->resolveAbshodeProductContext($data); $abshode = $this->resolveAbshodeProductContext($data);
$productId = (int) ($data['product_id'] ?? $abshode['id'] ?? 0); $productId = (int) ($data['product_id'] ?? $abshode['id'] ?? 0);
$usesProductWallet = $productId > 0 && !$config->isMainWalletProduct($productId); $usesProductWallet = $productId > 0 && !$config->isMainWalletProduct($productId);
$this->runAfterProviderCover($order, $data, function () use (&$data, $wallet_interface, $productId, $usesProductWallet, $order) {
if ($usesProductWallet) { if ($usesProductWallet) {
new TransactionInterface($wallet_interface, [ new TransactionInterface($wallet_interface, [
"cash" => $data['payment_amount'], "cash" => $data['payment_amount'],
@@ -402,7 +400,7 @@ class SingleOrderController extends Controller
} }
new TransactionInterface($wallet_interface, $buyTransaction, 1, true, false); new TransactionInterface($wallet_interface, $buyTransaction, 1, true, false);
} }
$this->finalizeProviderOrder($data); });
$response['1'] = 2; $response['1'] = 2;
return $response; return $response;
@@ -642,11 +640,9 @@ class SingleOrderController extends Controller
throw new UnprocessableEntityHttpException($this->insufficientGoldBalanceMessage($data)); throw new UnprocessableEntityHttpException($this->insufficientGoldBalanceMessage($data));
} }
$this->assertProviderOrderAccepted($data); $order = $this->openOrderThenCoverWithProvider($data);
$this->syncOrderProfitFields($data);
$data['order'] = $order = Order::create($data);
$this->runAfterProviderCover($order, $data, function () use (&$data, $wallet_interface, $wallet_interface_product, $productId, $usesProductWallet, $goldDebit, $order) {
if ($usesProductWallet) { if ($usesProductWallet) {
$this->normalizeProductWalletGold($wallet_interface_product); $this->normalizeProductWalletGold($wallet_interface_product);
new TransactionInterface($wallet_interface_product, [ new TransactionInterface($wallet_interface_product, [
@@ -682,7 +678,7 @@ class SingleOrderController extends Controller
} }
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) private function processSellTransactionProduct(&$data, $wallet, $wallet_interface)
@@ -1034,11 +1030,92 @@ class SingleOrderController extends Controller
return; return;
} }
if (!isset($data['order'])) {
$this->assertNoRecentDuplicateLocalOrder($data); $this->assertNoRecentDuplicateLocalOrder($data);
}
$data['_montzar_parsed'] = app(MontzarService::class)->submitOrder($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 private function finalizeProviderOrder(array &$data): void
{ {
if (!app(ConfigService::class)->usesMontzarPricing()) { if (!app(ConfigService::class)->usesMontzarPricing()) {
@@ -1067,6 +1144,7 @@ class SingleOrderController extends Controller
$duplicate = Order::where('customer_id', $customerId) $duplicate = Order::where('customer_id', $customerId)
->where('direction', $direction) ->where('direction', $direction)
->whereIn('sts', [2, 4])
->where('created_at', '>=', now()->subSeconds($windowSeconds)) ->where('created_at', '>=', now()->subSeconds($windowSeconds))
->whereRaw('ROUND(amount, 6) = ?', [$amount]) ->whereRaw('ROUND(amount, 6) = ?', [$amount])
->exists(); ->exists();
+5 -1
View File
@@ -37,6 +37,9 @@ class Order extends Model
'type',//نوع مشتری 'type',//نوع مشتری
'pay_at',//تاریخ انجام معامله در پنل ادمین 'pay_at',//تاریخ انجام معامله در پنل ادمین
'price_id',// 'price_id',//
'provider_id',
'total_price_montzar',
'description',
]; ];
protected $casts = [ protected $casts = [
@@ -46,7 +49,8 @@ class Order extends Model
static $STS = [ static $STS = [
1 => 'باز', 1 => 'باز',
2 => 'موفق', 2 => 'موفق',
3 => 'ناموفق' 3 => 'ناموفق',
4 => 'در انتظار همکار',
]; ];
static $DIRECTION = [ static $DIRECTION = [
1 => 'خرید', 1 => 'خرید',