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('موجودی شما کمتر از میزان سفارش است');
|
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);
|
||||||
|
|
||||||
if ($usesProductWallet) {
|
$this->runAfterProviderCover($order, $data, function () use (&$data, $wallet_interface, $productId, $usesProductWallet, $order) {
|
||||||
new TransactionInterface($wallet_interface, [
|
if ($usesProductWallet) {
|
||||||
"cash" => $data['payment_amount'],
|
new TransactionInterface($wallet_interface, [
|
||||||
"product_id" => $productId,
|
"cash" => $data['payment_amount'],
|
||||||
'expire_at' => now(),
|
"product_id" => $productId,
|
||||||
'active' => true,
|
'expire_at' => now(),
|
||||||
'order_id' => $order->id,
|
'active' => true,
|
||||||
'start_at' => now(),
|
'order_id' => $order->id,
|
||||||
'title' => 'برداشت وجه نقد',
|
'start_at' => now(),
|
||||||
], 12, false, false);
|
'title' => 'برداشت وجه نقد',
|
||||||
$wallet_interface_product = $this->productWalletInterface($productId);
|
], 12, false, false);
|
||||||
$this->normalizeProductWalletGold($wallet_interface_product);
|
$wallet_interface_product = $this->productWalletInterface($productId);
|
||||||
new TransactionInterface($wallet_interface_product, [
|
$this->normalizeProductWalletGold($wallet_interface_product);
|
||||||
"gold" => $data['amount'],
|
new TransactionInterface($wallet_interface_product, [
|
||||||
"product_id" => $productId,
|
"gold" => $data['amount'],
|
||||||
'expire_at' => now(),
|
"product_id" => $productId,
|
||||||
'active' => true,
|
'expire_at' => now(),
|
||||||
'order_id' => $order->id,
|
'active' => true,
|
||||||
'start_at' => now(),
|
'order_id' => $order->id,
|
||||||
'title' => $this->meltedOrProductBuyTitle($data),
|
'start_at' => now(),
|
||||||
], 1, true, false);
|
'title' => $this->meltedOrProductBuyTitle($data),
|
||||||
} else {
|
], 1, true, false);
|
||||||
$buyTransaction = [
|
} else {
|
||||||
"cash" => $data['payment_amount'],
|
$buyTransaction = [
|
||||||
"gold" => $data['amount'],
|
"cash" => $data['payment_amount'],
|
||||||
'expire_at' => now(),
|
"gold" => $data['amount'],
|
||||||
'active' => true,
|
'expire_at' => now(),
|
||||||
'order_id' => $order->id,
|
'active' => true,
|
||||||
'start_at' => now(),
|
'order_id' => $order->id,
|
||||||
'title' => $this->meltedOrProductBuyTitle($data),
|
'start_at' => now(),
|
||||||
];
|
'title' => $this->meltedOrProductBuyTitle($data),
|
||||||
if ($productId > 0) {
|
];
|
||||||
$buyTransaction['product_id'] = $productId;
|
if ($productId > 0) {
|
||||||
$buyTransaction['note'] = 'product_id:' . $productId;
|
$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;
|
$response['1'] = 2;
|
||||||
|
|
||||||
return $response;
|
return $response;
|
||||||
@@ -642,47 +640,45 @@ 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);
|
$this->runAfterProviderCover($order, $data, function () use (&$data, $wallet_interface, $wallet_interface_product, $productId, $usesProductWallet, $goldDebit, $order) {
|
||||||
$data['order'] = $order = Order::create($data);
|
if ($usesProductWallet) {
|
||||||
|
$this->normalizeProductWalletGold($wallet_interface_product);
|
||||||
if ($usesProductWallet) {
|
new TransactionInterface($wallet_interface_product, [
|
||||||
$this->normalizeProductWalletGold($wallet_interface_product);
|
"gold" => $goldDebit,
|
||||||
new TransactionInterface($wallet_interface_product, [
|
"product_id" => $productId,
|
||||||
"gold" => $goldDebit,
|
'expire_at' => now(),
|
||||||
"product_id" => $productId,
|
'active' => true,
|
||||||
'expire_at' => now(),
|
'order_id' => $order->id,
|
||||||
'active' => true,
|
'start_at' => now(),
|
||||||
'order_id' => $order->id,
|
'title' => $this->meltedOrProductSellTitle($data),
|
||||||
'start_at' => now(),
|
], 1, false, false);
|
||||||
'title' => $this->meltedOrProductSellTitle($data),
|
new TransactionInterface($wallet_interface, [
|
||||||
], 1, false, false);
|
"cash" => $data['total_price'],
|
||||||
new TransactionInterface($wallet_interface, [
|
"product_id" => $productId,
|
||||||
"cash" => $data['total_price'],
|
'expire_at' => now(),
|
||||||
"product_id" => $productId,
|
'active' => true,
|
||||||
'expire_at' => now(),
|
'order_id' => $order->id,
|
||||||
'active' => true,
|
'start_at' => now(),
|
||||||
'order_id' => $order->id,
|
'title' => 'واریز وجه نقد',
|
||||||
'start_at' => now(),
|
], 12, false, true);
|
||||||
'title' => 'واریز وجه نقد',
|
} else {
|
||||||
], 12, false, true);
|
$sellTransaction = [
|
||||||
} else {
|
"gold" => $goldDebit,
|
||||||
$sellTransaction = [
|
"cash" => $data['total_price'],
|
||||||
"gold" => $goldDebit,
|
'expire_at' => now(),
|
||||||
"cash" => $data['total_price'],
|
'active' => true,
|
||||||
'expire_at' => now(),
|
'order_id' => $order->id,
|
||||||
'active' => true,
|
'title' => $this->meltedOrProductSellTitle($data),
|
||||||
'order_id' => $order->id,
|
];
|
||||||
'title' => $this->meltedOrProductSellTitle($data),
|
if ($productId > 0) {
|
||||||
];
|
$sellTransaction['product_id'] = $productId;
|
||||||
if ($productId > 0) {
|
$sellTransaction['note'] = 'product_id:' . $productId;
|
||||||
$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)
|
private function processSellTransactionProduct(&$data, $wallet, $wallet_interface)
|
||||||
@@ -1034,11 +1030,92 @@ class SingleOrderController extends Controller
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->assertNoRecentDuplicateLocalOrder($data);
|
if (!isset($data['order'])) {
|
||||||
|
$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();
|
||||||
|
|||||||
@@ -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 => 'خرید',
|
||||||
|
|||||||
Reference in New Issue
Block a user