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,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();