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:
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user