Files
boursecu_server/app/Http/Controllers/Single/SingleOrderController.php
T
2026-07-10 22:52:28 +03:30

1298 lines
56 KiB
PHP

<?php
namespace App\Http\Controllers\Single;
use App\DB\Te\TableEngine;
use App\Http\Controllers\Controller;
use App\Http\Controllers\Admin\TahesabController;
use App\Models\Customer;
use App\Models\Order;
use App\Models\Price;
use App\Models\Product;
use App\Models\User;
use App\Mail\YourMailableClass;
use Illuminate\Support\Facades\Mail;
use App\Models\Withdraw;
use Illuminate\Http\Request;
use Rooyesh\Wallet\interfaces\TransactionInterface;
use Rooyesh\Wallet\interfaces\WalletInterface;
use Rooyesh\Wallet\models\Transaction;
use Rooyesh\Wallet\models\Wallet;
use App\Models\TransactionsAdmin;
use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException;
use Illuminate\Support\Facades\Http;
use App\Models\Setting;
use App\Models\Cnt;
use App\Services\MontzarService;
use App\Services\ConfigService;
use App\Services\ProductPriceService;
use App\Services\ProductDisplayPriceService;
use App\Services\ReferralGiftService;
use App\Services\WalletBroadcastService;
class SingleOrderController extends Controller
{
public function store(Request $request)
{
$data = $request->all();
$data['payment_product_id'] = $this->resolveGoldPaymentProductId($data);
if (preg_match('/^gold:\d+$/', (string) ($data['method'] ?? ''))) {
$data['method'] = 'gold';
}
$data['is_katod'] = ($data['method'] ?? null) === 'katod';
if ($data['is_katod']) {
$data['method'] = 'gold';
}
$config = app(ConfigService::class);
// فیلتر ساعت معامله per-product (سراسری یا override محصول) پایین‌تر در assertProductTradingOpen اعمال می‌شود.
// throw new UnprocessableEntityHttpException('به دلیل نوسانات سفارشات بسته میباشد.');
$transactionType = $data['direction'] == 1 ? "خرید" : "فروش";
$customer = Customer::find(auth('customer')->id() ?? ($data['customer_id'] ?? null));
$data['first_buy'] = auth('customer')->id() && $customer && $customer->first_buy_order !== 1;
if (auth('customer')->id()) {
$data['customer_id'] = auth('customer')->id();
$orderProductId = (int) ($data['abshode_id'] ?? $data['product_id'] ?? $config->mainProductId() ?? 0);
$orderProduct = $orderProductId > 0 ? Product::find($orderProductId) : null;
$basePrice = app(ProductDisplayPriceService::class)->resolveOrderBasePrice($orderProduct);
if ($basePrice <= 0) {
if ($config->isOutsideTradingHours()) {
throw new UnprocessableEntityHttpException($config->tradingClosedMessage());
}
throw new UnprocessableEntityHttpException('عدم ارائه سرویس.');
}
$latestPrice = Price::latest()->first();
$data['all_price'] = $latestPrice;
$data['price'] = $basePrice;
$data['price_id'] = $latestPrice?->id;
$data['difference'] = $config->orderPriceDiffRial();
$data['service_charge_percentage'] = $config->orderServiceChargePercentage();
$fee_free = Setting::where('key', 'fee_free')->where('is_active', true)->value('value');
if ($fee_free && $this->feeFree($data['customer_id'], $fee_free)) {
$data['service_charge_percentage'] = 0;
// $data['difference'] = 0;
$data['fee_free'] = true;
}
$data['pay_at'] = (new \DateTime())->format('Y-m-d H:i:s');
} else if (auth('user')->id()) {//admin
$data['service_charge_percentage'] = 0;
}
if($data['price'] === 0){
throw new UnprocessableEntityHttpException(' سفارشات بسته میباشد.');
}
$data['sts'] = 2;
$data['type'] = 2;
$data['payment_sts'] = 4;
$data['price_original'] = $data['price'];
$data['payment_remind'] = 0;
$data['fee'] = $data['difference'];
$ownerType = (int) ($customer?->type ?? 2);
$wallet_interface = new WalletInterface($data['customer_id'], $ownerType);
$wallet = $wallet_interface->wallet;
if (!$wallet) {
throw new UnprocessableEntityHttpException('کیف پول یافت نشد.');
}
$response = [];
// Check if the user wants to transact with a product or non-product (gold)
if (isset($data['product_id']) and $data['product'] = Product::find($data['product_id'])) {
$config->assertProductTradingOpen($data['product']);
// Check if the user wants to buy
if ($data['direction'] == 1) {
$data['service_charge_percentage'] = isset($data['fee_free']) && $data['fee_free'] === true ? 0 : ($data['product']['percent_fee_buy']) * $data['number'];
$data['difference'] = $data['fee'] = $this->resolveCashPriceDiff($config, $data['product'], 1);
$data['total_fee'] = $data['number'] * $data['fee'];
$data['price_original'] = $data['product']['price'] + $data['product']['price_profit'];
$data['total_price_original'] = $data['product']['amount'] * ($data['price_original'] ?? 0);
// Check if the method is cash
if ($data['method'] == "cash") {
$response['buy'] = $this->processBuyTransactionProduct($data, $wallet, $wallet_interface);
} // Check if the method is gold
else if ($data['method'] == "gold") {
$response['buy'] = $this->processBuyTransactionProductByGold($data, $wallet, $wallet_interface);
} else {
throw new UnprocessableEntityHttpException('روش پرداخت نامعتبر است.');
}
} // Check if the user wants to sell
else if ($data['direction'] == 2) {
$data['service_charge_percentage'] = isset($data['fee_free']) && $data['fee_free'] === true ? 0 : ($data['product']['percent_fee_sell']) * $data['number'];
$data['difference'] = $data['fee'] = $this->resolveCashPriceDiff($config, $data['product'], 2);
$data['total_fee'] = $data['number'] * $data['fee'];
$data['price_original'] = $data['product']['price'] + $data['product']['price_profit'];
$data['total_price_original'] = $data['product']['amount'] * ($data['price_original'] ?? 0);
$this->assertMinimumSellCash($data);
// Check if the method is cash
if ($data['method'] == "cash") {
$response['sell'] = $this->processSellTransactionProduct($data, $wallet, $wallet_interface);
} // Check if the method is gold
else if ($data['method'] == "gold") {
$response['sell'] = $this->processSellTransactionProductByGold($data, $wallet, $wallet_interface);
} else {
throw new UnprocessableEntityHttpException('روش پرداخت نامعتبر است.');
}
}
} else {
// Calculate total price and fee for non-product (gold) transaction
$data['total_price_original'] = $data['amount'] * ($data['price_original'] ?? 0);
$mainProductId = app(\App\Services\ConfigService::class)->mainProductId();
$productId = $data['abshode_id'] ?? $data['product_id'] ?? $mainProductId;
$data['product'] = $productId ? Product::find($productId) : null;
if (!$data['product']) {
throw new UnprocessableEntityHttpException('محصول آبشده یافت نشد.');
}
$config->assertProductTradingOpen($data['product']);
if ($data['direction'] == 1) {
$data['difference'] = $this->resolveCashPriceDiff($config, $data['product'], 1);
$data['service_charge_percentage'] = $data['product']['percent_fee_buy'];
} elseif ($data['direction'] == 2) {
$data['difference'] = $this->resolveCashPriceDiff($config, $data['product'], 2);
$data['service_charge_percentage'] = $data['product']['percent_fee_sell'];
}
$data['total_fee'] = $data['amount'] * $data['difference'];
$data['product_id'] = $data['product']->id;
// Check if the user wants to buy
if ($data['direction'] == 1) {
if (!$data['product']['open_buy']) {
throw new UnprocessableEntityHttpException('خرید این محصول در این لحظه مقدور نمیباشد');
}
$response['buy'] = $this->processBuyTransaction($data, $wallet, $wallet_interface);
} // Check if the user wants to sell
else if ($data['direction'] == 2) {
if (!$data['product']['open_sell']) {
throw new UnprocessableEntityHttpException('خرید این محصول در این لحظه مقدور نمیباشد');
}
$this->assertMinimumSellCash($data);
$response['sell'] = $this->processSellTransaction($data, $wallet, $wallet_interface);
}
}
// دفترکل داخلی همیشه ثبت شود — مستقل از اتصال به حسابداری بیرونی
if (isset($data['order'])) {
try {
app(\App\Services\AccountingLedgerService::class)->recordTrade($data['order']);
} catch (\Throwable $e) {
report($e);
}
}
if ($config->isTahesabAccounting()) {
try {
$data['tahesab'] = app(TahesabController::class)->storeTrade($data);
} catch (\Exception $e) {
report($e);
}
}
if ($config->isPargarAccounting()) {
try {
app(\App\Services\PargarSyncService::class)->recordTrade($data['order'] ?? null);
} catch (\Throwable $e) {
report($e);
}
}
// $response['reward'] = $this->give_order_reward($data, $wallet, $wallet_interface);
if ((int) $data['direction'] === 1 && $customer) {
$referrer = Customer::where('referral_id', $customer->referral_to_id)->first();
if (!empty($referrer) && $referrer->first_buy_order == 1 && $customer->first_buy_order !== 1) {
$customer->first_buy_order = true;
$customer->save();
$response['bonus_gift'] = $this->bonusGift($customer, $referrer);
}
}
if ($customer && isset($data['order'])) {
try {
app(ReferralGiftService::class)->tryGiveOnFirstOrder($customer);
} catch (\Throwable $e) {
report($e);
}
}
if (isset($data['order'])) {
try {
$response['Notifications'] = $this->sendTransactionNotifications($data, $transactionType);
} catch (\Throwable $e) {
report($e);
}
}
if ($customer) {
app(WalletBroadcastService::class)->broadcastForCustomer(
(int) $customer->id,
$ownerType
);
}
return response()->json([$response, $data]);
}
private function processBuyTransaction(&$data, $wallet, $wallet_interface)
{
$response = [];
$config = app(ConfigService::class);
$targetCash = $this->resolveTargetCash($data);
// منطق واحد و مستقل از fee_deduct_from_gold:
// • کاربر مبلغ ریالی زده (rialLocked) → دقیقاً همان مبلغ پرداخت می‌شود و کارمزد از طلا کم می‌شود.
// • کاربر وزن زده (gramLocked) → دقیقاً همان وزن دریافت می‌شود و کارمزد به ریال قابل پرداخت اضافه می‌شود.
$rialLocked = $targetCash !== null;
$feeRate = isset($data['fee_free']) && $data['fee_free'] === true
? 0.0
: (float) ($data['service_charge_percentage'] ?? 0);
$data['price'] += $data['difference'];
if ($rialLocked) {
// مبلغ ریالی دقیق — کارمزد از طلای دریافتی کسر می‌شود
$unitPrice = (float) $data['price'];
$grossGold = $unitPrice > 0 ? $targetCash / $unitPrice : 0.0;
$data['service_charge'] = $this->normalizeGoldAmount($feeRate * $grossGold);
$data['amount'] = $this->normalizeGoldAmount($grossGold - $data['service_charge']);
$data['service_charge_price'] = $data['service_charge'] * $data['price_original'];
$data['total_price'] = $data['amount'] * $data['price'];
$data['payment_amount'] = $targetCash;
} else {
// وزن دقیق — کارمزد به مبلغ ریالی پرداختی اضافه می‌شود
$data['amount'] = $this->normalizeGoldAmount((float) ($data['amount'] ?? 0));
$data['service_charge'] = $this->normalizeGoldAmount($feeRate * $data['amount']);
$data['service_charge_price'] = $data['service_charge'] * $data['price_original'];
$data['total_price'] = $data['amount'] * $data['price'];
$data['payment_amount'] = $data['total_price'] + $data['service_charge_price'];
}
$this->enforceProductOrderLimits($data);
if (!empty($data['first_buy']) && ($minFirstBuy = Setting::where('is_active', 1)->where('key', 'min_first_buy')->value('value'))) {
if ($data['payment_amount'] < (int) $minFirstBuy) {
throw new UnprocessableEntityHttpException('حداقل سفارش اول ' . number_format((int) $minFirstBuy) . ' ریال میباشد.');
}
}
if ($wallet->cash < $data['payment_amount']) {
throw new UnprocessableEntityHttpException('موجودی شما کمتر از میزان سفارش است');
}
$this->assertProviderOrderAccepted($data);
$data['order'] = $order = Order::create($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;
}
new TransactionInterface($wallet_interface, $buyTransaction, 1, true, false);
}
$this->finalizeProviderOrder($data);
$response['1'] = 2;
return $response;
}
private function processBuyTransactionProduct(&$data, $wallet, $wallet_interface)
{
$data['price'] = $data['product']['price'] + $data['product']['price_profit'];
$data['price'] += $data['difference'];
$data['amount'] = $data['number'] * $data['product']['amount'];
$data['total_amount'] = $data['amount'] + ($data['product']['percent_fee_buy'] * $data['amount']);
$data['total_price'] = $data['total_amount'] * $data['price'];
$data['payment_amount'] = $data['total_price'];
$this->enforceProductOrderLimits($data, (float) $data['total_price']);
if (!empty($data['first_buy']) && ($minFirstBuy = Setting::where('is_active', 1)->where('key', 'min_first_buy')->value('value'))) {
if ($data['total_price'] < (int) $minFirstBuy) {
throw new UnprocessableEntityHttpException('حداقل سفارش اول ' . number_format((int) $minFirstBuy) . ' ریال میباشد.');
}
}
$data['service_charge'] = isset($data['fee_free']) && $data['fee_free'] === true ? 0 : ($data['product']['percent_fee_buy'] * $data['amount']);
$data['service_charge_price'] = $data['service_charge'] * $data['price_original'];
$data['wallet'] = $wallet;
if ($wallet->cash < $data['payment_amount']) {
throw new UnprocessableEntityHttpException('موجودی شما کمتر از میزان سفارش است');
}
if (!$data['product']['open_buy']) {
throw new UnprocessableEntityHttpException('خرید این محصول در این لحظه مقدور نمیباشد');
}
$data['order'] = $order = Order::create($data);
$data['transaction'][] = new TransactionInterface($wallet_interface, $_inp = [
"number" => $data['number'],
"cash" => $data['total_price'],
"product_id" => $data["product_id"],
'expire_at' => now(),
'active' => true,
'order_id' => $order->id,
'title' => "برداشت وجه نقد"
], 12, false, false);
$wallet_interface_product = $this->productWalletInterface((int) $data["product_id"]);
$this->normalizeProductWalletGold($wallet_interface_product);
new TransactionInterface($wallet_interface_product, [
"gold" => $data['number'],
"product_id" => $data["product_id"],
'expire_at' => now(),
'active' => true,
'order_id' => $order->id,
'start_at' => now(),
'title' => $this->meltedOrProductBuyTitle($data),
], 1, true, false);
}
private function processBuyTransactionProductByGold(&$data, $wallet, $wallet_interface)
{
$data['price'] += $data['difference'];
$data['amount'] = $data['number'] * $data['product']['amount'];
$data['total_amount'] = $data['amount'] + ($data['product']['percent_fee_buy'] * $data['amount']);
$data['total_price'] = $data['total_amount'] * $data['price'];
$data['payment_amount'] = $data['total_price'];
$this->enforceProductOrderLimits($data, (float) $data['total_price']);
$data['service_charge'] = isset($data['fee_free']) && $data['fee_free'] === true ? 0 : ($data['product']['percent_fee_buy'] * $data['amount']);
$data['total_fee'] = 0;
$data['service_charge_price'] = $data['service_charge'] * $data['price_original'];
$data['wallet'] = $wallet;
$paymentProductId = (int) ($data['payment_product_id'] ?? 0);
$paymentWalletInterface = $wallet_interface;
$paymentProduct = null;
$paymentWallet = $wallet;
if ($paymentProductId > 0) {
$paymentProduct = Product::find($paymentProductId);
if (
!$paymentProduct
|| (int) ($paymentProduct->metal_id ?? 0) !== (int) ($data['product']['metal_id'] ?? 0)
) {
throw new UnprocessableEntityHttpException('روش پرداخت نامعتبر است.');
}
$paymentWalletInterface = $this->productWalletInterface($paymentProductId);
$paymentWallet = $paymentWalletInterface->wallet;
if (!$paymentWallet) {
throw new UnprocessableEntityHttpException('کیف پول یافت نشد.');
}
}
if ((float) $paymentWallet->gold < $data['total_amount']) {
throw new UnprocessableEntityHttpException($this->insufficientGoldBalanceMessage($data));
}
if (!$data['product']['open_buy']) {
throw new UnprocessableEntityHttpException('خرید این محصول در این لحظه مقدور نمیباشد');
}
$data['order'] = $order = Order::create($data);
$paymentSellTitle = $paymentProduct
? self::productSellTitle($paymentProduct->title ?? '')
: self::mainMetalSellTitle();
$paymentTransaction = [
"number" => $data['number'],
"gold" => $data['total_amount'],
'expire_at' => now(),
'active' => true,
'order_id' => $order->id,
'title' => $paymentSellTitle,
];
if ($paymentProductId > 0) {
$paymentTransaction['product_id'] = $paymentProductId;
$this->normalizeProductWalletGold($paymentWalletInterface);
$data['transaction'][] = new TransactionInterface(
$paymentWalletInterface,
$paymentTransaction,
1,
false,
false
);
} else {
$data['transaction'][] = new TransactionInterface(
$paymentWalletInterface,
$paymentTransaction,
12,
false,
true
);
}
$wallet_interface_product = $this->productWalletInterface((int) $data["product_id"]);
$this->normalizeProductWalletGold($wallet_interface_product);
new TransactionInterface($wallet_interface_product, [
"gold" => $data['number'],
"product_id" => $data["product_id"],
'expire_at' => now(),
'active' => true,
'order_id' => $order->id,
'start_at' => now(),
'title' => $this->meltedOrProductBuyTitle($data),
], 1, true, false);
}
private function processSellTransaction(&$data, $wallet, $wallet_interface)
{
$response = [];
$config = app(ConfigService::class);
$targetCash = $this->resolveTargetCash($data);
// منطق واحد و مستقل از fee_deduct_from_gold:
// • کاربر مبلغ ریالی زده (rialLocked) → دقیقاً همان مبلغ واریز می‌شود و کارمزد از طلای کیف کسر می‌شود.
// • کاربر وزن زده (gramLocked) → دقیقاً همان وزن فروخته می‌شود و کارمزد از مبلغ ریالی دریافتی کم می‌شود.
$rialLocked = $targetCash !== null;
$feeRate = isset($data['fee_free']) && $data['fee_free'] === true
? 0.0
: (float) ($data['service_charge_percentage'] ?? 0);
$data['price'] -= $data['difference'];
if ($rialLocked) {
// مبلغ ریالی دقیق دریافتی — کارمزد از طلای کیف (کسر اضافه‌تر) تأمین می‌شود
$unitPrice = (float) $data['price'];
$data['amount'] = $unitPrice > 0
? $this->normalizeGoldAmount($targetCash / $unitPrice)
: 0.0;
$data['service_charge'] = $this->normalizeGoldAmount($feeRate * $data['amount']);
$data['service_charge_price'] = $data['service_charge'] * $data['price_original'];
$data['total_price'] = $targetCash;
$data['payment_amount'] = $targetCash;
$goldDebit = $this->normalizeGoldAmount($data['amount'] + $data['service_charge']);
} else {
// وزن دقیق فروش — کارمزد از مبلغ ریالی دریافتی کم می‌شود
$data['amount'] = $this->normalizeGoldAmount((float) ($data['amount'] ?? 0));
$data['service_charge'] = $this->normalizeGoldAmount($feeRate * $data['amount']);
$data['service_charge_price'] = $data['service_charge'] * $data['price_original'];
$data['total_price'] = $data['amount'] * $data['price'] - $data['service_charge_price'];
$data['payment_amount'] = $data['total_price'];
$goldDebit = $data['amount'];
}
$grossTotal = $data['amount'] * $data['price'];
$this->enforceProductOrderLimits($data, (float) $grossTotal);
$abshode = $this->resolveAbshodeProductContext($data);
$productId = (int) ($data['product_id'] ?? $abshode['id'] ?? 0);
$usesProductWallet = $productId > 0 && !$config->isMainWalletProduct($productId);
$goldWallet = $wallet;
$wallet_interface_product = null;
if ($usesProductWallet) {
$wallet_interface_product = $this->productWalletInterface($productId);
$goldWallet = $wallet_interface_product->wallet;
if (!$goldWallet) {
throw new UnprocessableEntityHttpException('کیف پول محصول یافت نشد.');
}
}
if ($goldWallet->gold < $goldDebit) {
throw new UnprocessableEntityHttpException($this->insufficientGoldBalanceMessage($data));
}
$this->assertProviderOrderAccepted($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;
}
new TransactionInterface($wallet_interface, $sellTransaction, 2, false, true);
}
$this->finalizeProviderOrder($data);
}
private function processSellTransactionProduct(&$data, $wallet, $wallet_interface)
{
$data['price'] = $data['product']['price'] + $data['product']['price_profit'];
$data['price'] -= $data['difference'];
$data['amount'] = $data['number'] * $data['product']['amount'];
$data['total_amount'] = $data['amount'] - ($data['product']['percent_fee_sell'] * $data['amount']);
$data['total_price'] = $data['total_amount'] * $data['price'];
$data['payment_amount'] = $data['total_price'];
$grossTotal = (float) ($data['amount'] * $data['price']);
$this->enforceProductOrderLimits($data, $grossTotal);
$data['service_charge'] = isset($data['fee_free']) && $data['fee_free'] === true ? 0 : ($data['product']['percent_fee_sell'] * $data['amount']);
$data['service_charge_price'] = $data['service_charge'] * $data['price_original'];
$wallet_interface_product = $this->productWalletInterface((int) $data["product_id"]);
if ($wallet_interface_product->wallet->gold < $data['number']) {
throw new UnprocessableEntityHttpException('موجودی شما کمتر از میزان سفارش است');
}
if (!$data['product']['open_sell']) {
throw new UnprocessableEntityHttpException('فروش این محصول در این لحظه مقدور نمیباشد');
}
$data['order'] = $order = Order::create($data);
$this->normalizeProductWalletGold($wallet_interface_product);
new TransactionInterface($wallet_interface_product, [
"gold" => $data['number'],
"product_id" => $data["product_id"],
'expire_at' => now(),
'active' => true,
'order_id' => $order->id,
'start_at' => now(),
'title' => $this->meltedOrProductSellTitle($data),
], 1, false, false);
$data['transaction'][] = new TransactionInterface($wallet_interface, $_inp = [
"number" => $data['number'],
"cash" => $data['total_price'],
"product_id" => $data["product_id"],
'expire_at' => now(),
'active' => true,
'order_id' => $order->id,
'title' => "واریز وجه نقد"
], 12, false, true);
}
private function processSellTransactionProductByGold(&$data, $wallet, $wallet_interface)
{
$data['price'] -= $data['difference'];
$data['amount'] = $data['number'] * $data['product']['amount'];
$data['total_amount'] = $data['amount'] - ($data['product']['percent_fee_sell'] * $data['amount']);
$data['total_price'] = $data['total_amount'] * $data['price'];
$data['payment_amount'] = $data['total_price'];
$grossTotal = (float) ($data['amount'] * $data['price']);
$this->enforceProductOrderLimits($data, $grossTotal);
$data['service_charge'] = isset($data['fee_free']) && $data['fee_free'] === true ? 0 : ($data['product']['percent_fee_sell'] * $data['amount']);
$data['total_fee'] = 0;
$data['service_charge_price'] = $data['service_charge'] * $data['price_original'];
$wallet_interface_product = $this->productWalletInterface((int) $data["product_id"]);
if ($wallet_interface_product->wallet->gold < $data['number']) {
throw new UnprocessableEntityHttpException('موجودی شما کمتر از میزان سفارش است');
}
if (!$data['product']['open_sell']) {
throw new UnprocessableEntityHttpException('فروش این محصول در این لحظه مقدور نمیباشد');
}
$data['order'] = $order = Order::create($data);
$data['transaction'][] = new TransactionInterface($wallet_interface, $_inp = [
"number" => $data['number'],
"gold" => $data['total_amount'],
"product_id" => $data["product_id"],
'expire_at' => now(),
'active' => true,
'order_id' => $order->id,
'title' => $this->meltedOrProductSellTitle($data),
], 12, true, false);
$this->normalizeProductWalletGold($wallet_interface_product);
new TransactionInterface($wallet_interface_product, [
"gold" => $data['number'],
"product_id" => $data["product_id"],
'expire_at' => now(),
'active' => true,
'order_id' => $order->id,
'start_at' => now(),
'title' => $this->meltedOrProductSellTitle($data),
], 1, false, false);
}
/**
* کیف مؤثر محصول برای تراکنش سفارش.
* محصولات متصل به کیف اصلی (main_wallet_products) مستقیماً روی کیف اصلی می‌نشینند.
*/
private function productWalletInterface(int $productId): WalletInterface
{
return new WalletInterface(
auth('customer')->id(),
auth('customer')->user()->type,
null,
self::effectiveProductWalletId($productId)
);
}
/** رفتار قدیمیِ رُند کردن موجودی فقط برای کیف خود محصول؛ کیف اصلی هرگز رُند نمی‌شود */
private function normalizeProductWalletGold(WalletInterface $walletInterface): void
{
if ($walletInterface->wallet->product_id === null) {
return;
}
$walletInterface->wallet->gold = intval($walletInterface->wallet->gold);
$walletInterface->wallet->save();
}
private function give_order_reward(&$data, $wallet, $wallet_interface)
{
$data['aCustomers'] = [];
$i = 0;
$data['aCustomers'][$i] = Customer::findOrFail($data['customer_id']);
while ($data['aCustomers'][$i + 1] = Customer::where('referral_id', $data['aCustomers'][$i]->referral_to_id)->first()) {
if ($data['aCustomers'][$i + 1]->profit_amount_by_lvl) {
$levels = json_decode(str_replace(['{', '}'], ['[', ']'], $data['aCustomers'][$i + 1]->profit_amount_by_lvl), true);
foreach ($levels as $level) {
if ($level[0] == $i + 1) {
// [$level[1],$data['aCustomers'][$i+1]];
if ($this->Config('has_page_earn_money') == true) {
$gift_for_invite = ($data['order']['service_charge'] * $level[1]) / 100;
$profit_receiver = $data['aCustomers'][$i + 1]->id;
$profit_sender = $data['aCustomers'][0]->id;
$Withdraw = Withdraw::create([
'user_id' => $profit_sender,
'customer_id' => $profit_receiver,
"cash" => 0,
"gold" => $gift_for_invite,
'sts' => 9,
]);
$wallet_interface = new WalletInterface($profit_receiver, 2);
new TransactionInterface($wallet_interface, $_inp = [
"cash" => 0,
"gold" => $gift_for_invite,
'expire_at' => now(),
'active' => true,
'withdraw_id' => $Withdraw->id,
'start_at' => now(),
], 9, true, false);
$transaction = Transaction::where('customer_id', $profit_receiver)->latest()->first();
TransactionsAdmin::create([
'customer_id' => $profit_sender,
'customer_to_id' => $profit_receiver,
'user_id' => $profit_sender,
'order_id' => $data['order']['id'],
'wallet' => null, // یا مقدار مناسب دیگری
'transaction_id' => $transaction->id,
'cash' => 0,
'note' => ' سود بابت سفارش',
'type' => 1, // یا مقدار مناسب دیگری
'gold' => $gift_for_invite,
'balance_cash' => 0, // یا مقدار مناسب دیگری
'balance_gold' => $gift_for_invite, // مقدار صحیح برای `balance_gold`
'withdraw_id' => $Withdraw->id,
]);
}
}
}
}
if ($i == 100) break;
$i++; // برای جلوگیری از حلقه بی‌نهایت
}
return $data;
}
private function sendTransactionNotifications(array $data, string $transactionType)
{
$customer = Customer::findOrFail($data['customer_id']);
$companyName = $this->Config('name_fa');
$amount = $data['amount'] ?? (
isset($data['number'], $data['product']['amount'])
? $data['number'] * $data['product']['amount']
: 0
);
$totalPrice = $data['total_price'] ?? ($data['payment_amount'] ?? 0);
$subjectTitle = $this->transactionSubjectTitle($data);
$message = "کاربر {$customer->name} با شماره موبایل {$customer->mobile} اقدام به $transactionType {$subjectTitle} به مقدار " .
"{$amount} گرم به مبلغ " . number_format($totalPrice) .
" ریال کرد. با تشکر، $companyName";
User::where('send_sms', 1)->get()->each(function ($admin) use ($message, $data, $transactionType, $companyName) {
$this->send($admin->mobile, $message);
});
$wallet = Wallet::where('owner', $data['customer_id'])
->whereNull('fund_id')
->whereNull('product_id')
->first();
$balanceLabel = self::metalTypeLabel();
$goldBalance = $wallet ? ($wallet->gold ?? 0) : 0;
$cashBalance = $wallet ? ($wallet->cash ?? 0) : 0;
$customerMessage = "$companyName\n$transactionType {$subjectTitle}\nگرم: {$amount}\n" .
"مبلغ کل: " . number_format($totalPrice) . "\n\n" .
"موجودی {$balanceLabel}: {$goldBalance}\n" .
"مانده ریالی: " . number_format($cashBalance) . "\n" .
$this->Config('app_url');
// ارسال SMS به مشتری
$this->send($customer->mobile, $customerMessage);
}
private function assertProviderOrderAccepted(array &$data): void
{
if (!app(ConfigService::class)->usesMontzarPricing()) {
return;
}
$this->assertNoRecentDuplicateLocalOrder($data);
$data['_montzar_parsed'] = app(MontzarService::class)->submitOrder($data);
}
private function finalizeProviderOrder(array &$data): void
{
if (!app(ConfigService::class)->usesMontzarPricing()) {
return;
}
$parsed = $data['_montzar_parsed'] ?? null;
if (!is_array($parsed)) {
return;
}
app(MontzarService::class)->applyParsedOrder($data, $parsed);
unset($data['_montzar_parsed']);
}
private function assertNoRecentDuplicateLocalOrder(array $data): void
{
$customerId = (int) ($data['customer_id'] ?? 0);
if ($customerId <= 0) {
return;
}
$direction = (int) ($data['direction'] ?? 0);
$amount = round((float) ($data['amount'] ?? 0), 6);
$windowSeconds = 20;
$duplicate = Order::where('customer_id', $customerId)
->where('direction', $direction)
->where('created_at', '>=', now()->subSeconds($windowSeconds))
->whereRaw('ROUND(amount, 6) = ?', [$amount])
->exists();
if ($duplicate) {
throw new UnprocessableEntityHttpException(
'در حال حاضر سفارش مشابه ثبت شده است. لطفاً چند ثانیه دیگر تلاش کنید.'
);
}
}
public function storeBackUp(Request $request)
{
$data = $request->all();
if (auth('customer')->id()) {// متد در پنل کاربر
$data['customer_id'] = auth('customer')->id();
$data['price'] = Price::orderBy('id', 'desc')->first()->price;
} elseif (auth('user')->user()) {// متد در پنل ادمین
$data['customer_id'] = $request->customer_id;
$data['price'] = $request->price;
}
$data['sts'] = 2;
$data['type'] = 2;
$data['payment_sts'] = 4;
$data['price_original'] = $data['price'];
$add_sell_price = config('add_sell_price.single');
$customer = Customer::findOrFail($data['customer_id']);
$wallet_interface = new WalletInterface($data['customer_id'], (int) $customer->type);
$wallet = $wallet_interface->wallet;
$data['total_price_original'] = $data['amount'] * $data['price_original'];
$data['payment_remind'] = 0;
$data['fee'] = $add_sell_price;
$data['total_fee'] = $data['amount'] * $data['fee'];
if ($data['direction'] == 1) {
$data['price'] += $add_sell_price;
$data['total_price'] = $data['amount'] * $data['price'];
$data['payment_amount'] = $data['total_price'];
if ($wallet->cash < $data['total_price']) {
throw new UnprocessableEntityHttpException('موجودی شما کمتر از میزان سفارش است');
}
$order = Order::create($data);//buy
new TransactionInterface($wallet_interface, $_inp = [
"cash" => $data['total_price'],
"gold" => $data['amount'],
'expire_at' => now(),
'active' => true,
'order_id' => $order->id,
'start_at' => now(),
], 1, true, false);
} else {//sell
$data['price'] -= $add_sell_price;
$data['total_price'] = $data['amount'] * $data['price'];
$data['payment_amount'] = $data['total_price'];
if ($wallet->gold < $data['amount']) {
throw new UnprocessableEntityHttpException('موجودی شما کمتر از میزان سفارش است');
}
$order = Order::create($data);
new TransactionInterface($wallet_interface, $_inp = [
"gold" => $data['amount'],
"cash" => $data['total_price'],
'expire_at' => now(),
'active' => true,
'order_id' => $order->id
], 2, false, true);
}
//----------------------------------------------------------------------------sms
// Determine transaction type
$transactionType = ($data['direction'] == 1) ? "خرید" : "فروش";
// User information
$customer = Customer::where("id", $data['customer_id'])->firstOrFail();
$userMobile = $customer->mobile; // User's mobile number
$userName = $customer->name; // User's name
$chargedAmount = $data['total_price']; // Total price in Rial
$chargedAmountGold = $data['amount']; // Amount of gold
// Company name
$companyName = $this->Config('name_fa'); // Company name from configuration
// Constructing the SMS message
$message = "کاربر $userName با شماره موبایل $userMobile اقدام به $transactionType طلا به مقدار " . $chargedAmountGold . " گرم به مبلغ " . number_format($chargedAmount) . " ریال کرد. با تشکر، $companyName";
// Getting admin mobile numbers and sending the SMS
User::where('active', 1)->get()->each(function ($admin) use ($message) {
$this->Send($admin->mobile, $message);
});
$mainWallet = Wallet::where('owner', $data['customer_id'])
->where('owner_type', $customer->type)
->main()
->first();
$companyurl = $this->Config('app_url');
$weight = $data['amount'];
$totalAmount = $data['total_price'];
$pricePerGram = $weight > 0 ? $totalAmount / $weight : 0;
$goldBalance = $mainWallet->gold ?? 0;
$remainingBalance = $mainWallet->cash ?? 0;
$message = "$companyName\n";
$message .= "خرید آبشده\n";
$message .= "گرم: $weight\n";
$message .= "قیمت گرم: " . number_format($pricePerGram) . "\n";
$message .= "مبلغ کل: " . number_format($totalAmount) . "\n\n";
$message .= "موجودی طلایی: $goldBalance\n";
$message .= "مانده ریالی: " . number_format($remainingBalance) . "\n";
$message .= "$companyurl" . "\n";
$this->Send($customer->mobile, $message);
return response()->json($order);
}
public function table(Request $request)
{
$records = Order::where('customer_id', auth('customer')->id())
->where('type', 2);
TableEngine::create($records, $request->all())
->initSearch([])
->select([
'*', Order::$SELECT_STS_STR
])->export($records);
return response()->json($records);
}
private function feeFree($customer_id, $count)
{
return Order::where('customer_id', $customer_id)
->where('type', 2)
->where('sts', 2)
->where('payment_sts', 4)
->count() < (int)$count;
}
private function resolveOrderAmount(array $data): ?float
{
if (isset($data['amount'])) {
return (float) $data['amount'];
}
if (!empty($data['product_id']) && isset($data['number'])) {
$product = $data['product'] ?? Product::find($data['product_id']);
if ($product) {
return (float) $data['number'] * (float) $product->amount;
}
}
return null;
}
private function assertMinimumSellCash(array $data): void
{
$minSell = Setting::where('key', 'minimum_sell')->where('is_active', 1)->first();
if (!$minSell) {
return;
}
$minRial = (float) $minSell->value;
if ($minRial <= 0) {
return;
}
$sellCash = $this->resolveSellOrderCashRial($data);
if ($sellCash === null || $sellCash >= $minRial) {
return;
}
throw new UnprocessableEntityHttpException(
'حداقل مبلغ فروش ' . number_format((int) round($minRial)) . ' ریال می‌باشد'
);
}
private function resolveSellOrderCashRial(array $data): ?float
{
$targetCash = $this->resolveTargetCash($data);
if ($targetCash !== null) {
return (float) $targetCash;
}
if (!empty($data['product_id']) && isset($data['number'])) {
$product = $data['product'] ?? Product::find($data['product_id']);
if ($product) {
$unitPrice = (float) ($product->price + $product->price_profit);
$difference = (float) ($data['difference'] ?? 0);
$amount = (float) $data['number'] * (float) $product->amount;
$sellUnitPrice = max(0.0, $unitPrice - $difference);
return $amount * $sellUnitPrice;
}
}
if (isset($data['amount'])) {
$amount = (float) $data['amount'];
if ($amount > 0) {
$price = (float) ($data['price'] ?? 0);
$difference = (float) ($data['difference'] ?? 0);
$sellUnitPrice = max(0.0, $price - $difference);
return $amount * $sellUnitPrice;
}
}
return null;
}
private function resolveTargetCash(array $data): ?int
{
if (!isset($data['target_cash'])) {
return null;
}
$target = (int) round((float) $data['target_cash']);
return $target > 0 ? $target : null;
}
private function normalizeGoldAmount(float $amount): float
{
return round($amount, 10);
}
private function resolveGoldPaymentProductId(array $data): ?int
{
if (!empty($data['payment_product_id'])) {
return (int) $data['payment_product_id'];
}
$method = (string) ($data['method'] ?? '');
if (preg_match('/^gold:(\d+)$/', $method, $matches)) {
return (int) $matches[1];
}
return null;
}
private function resolveAbshodeProductContext(array $data): array
{
$productId = (int) ($data['product_id'] ?? $data['abshode_id'] ?? 0);
$product = null;
if (!empty($data['product'])) {
$candidate = $data['product'];
if ($candidate instanceof Product) {
$product = $candidate;
} elseif (is_array($candidate) && !empty($candidate['id'])) {
$product = Product::find($candidate['id']);
}
}
if (!$product && $productId > 0) {
$product = Product::find($productId);
}
if (!$product) {
return ['id' => 0, 'title' => ''];
}
return [
'id' => (int) $product->id,
'title' => trim((string) $product->title),
];
}
private function productTitleFromData(array $data): ?string
{
$product = $data['product'] ?? null;
$title = null;
if ($product) {
if (is_array($product)) {
$title = $product['title'] ?? null;
} else {
$title = $product->title ?? null;
}
}
$title = trim((string) $title);
if ($title === '' && !empty($data['product_id'])) {
$title = trim((string) Product::query()->whereKey((int) $data['product_id'])->value('title'));
}
return $title !== '' ? $title : null;
}
private function isProductOrder(array $data): bool
{
return !empty($data['product_id']) && $this->productTitleFromData($data) !== null;
}
private function transactionSubjectTitle(array $data): string
{
return $this->productTitleFromData($data) ?? self::meltedGoldLabel();
}
private function meltedOrProductBuyTitle(array $data): string
{
$productTitle = $this->productTitleFromData($data);
return $productTitle !== null
? self::productBuyTitle($productTitle)
: self::meltedGoldBuyTitle();
}
private function meltedOrProductSellTitle(array $data): string
{
$productTitle = $this->productTitleFromData($data);
return $productTitle !== null
? self::productSellTitle($productTitle)
: self::meltedGoldSellTitle();
}
private function insufficientGoldBalanceMessage(array $data): string
{
return 'موجودی ' . self::metalTypeLabel() . ' شما کمتر از میزان سفارش است';
}
/**
* گلد علی / zaarbox / main_product: اختلاف سفارش = buy_fee/sell_fee نمایش (مثل /api/price/last).
* montzar و بقیه: همان cash_fee_buy/sell محصول (رفتار قبلی ابشده‌خانگی).
*/
private function resolveCashPriceDiff(ConfigService $config, Product $product, int $direction): float
{
$provider = $config->priceProvider();
$alignWithDisplay = in_array($provider, ['zaarbox', 'main_product'], true);
if ($alignWithDisplay) {
$display = $direction === 1
? $config->displayBuyPriceDiffRial()
: $config->displaySellPriceDiffRial();
if ($display > 0) {
return $display;
}
}
return ProductPriceService::resolveCashFeeRial(
$product,
$direction,
ProductPriceService::productBasePriceRial($product)
);
}
private function bonusGift($customer, $referrer)
{
$bonus = Setting::where('key', 'referral_bonus_amount')->where('is_active', 1)->first();
if ($bonus === null) {
return false;
}
$bonus = json_decode($bonus->value, true);
$customer_bonus = $bonus[0]['customer'] ?? 0;
$referrer_bonus = $bonus[0]['referrer'] ?? 0;
if ($customer_bonus > 0) {
$wallet_interface = new WalletInterface($customer->id, $customer->type);
$Withdraw = Withdraw::create([
'customer_id' => $customer->id,
'cash' => $customer_bonus,
'note' => 'پاداش اولین سفارش',
'sts' => 4,
]);
$transaction = new TransactionInterface($wallet_interface, $_inp = [
'gold' => 0,
'cash' => $customer_bonus,
'expire_at' => now(),
'note' => 'پاداش اولین سفارش',
'withdraw_id' => $Withdraw->id,
'active' => true,
], 3, true, true, 0);
$message = 'کیف پول شما به اندازه ' . number_format($customer_bonus) . ' ریال در تاریخ ' . verta()->format('Y/n/j H:i:s') . " شارژ شد بابت\n"
. 'پاداش اولین سفارش'
. ' '
. $this->Config('name_fa');
$this->Send($customer->mobile, $message);
TransactionsAdmin::create([
'customer_id' => $customer->id,
'customer_to_id' => $customer->referral->id ?? null,
'user_id' => $customer->id,
'wallet' => null,
'transaction_id' => $transaction->id ?? null,
'cash' => $customer_bonus,
'note' => 'پاداش خرید اول',
'type' => 1,
'gold' => 0,
'balance_cash' => 0,
'withdraw_id' => $Withdraw->id,
]);
}
if ($referrer_bonus > 0) {
$wallet_interface = new WalletInterface($referrer->id, $referrer->type);
$Withdraw = Withdraw::create([
'customer_id' => $referrer->id,
'cash' => $referrer_bonus,
'note' => 'پاداش اولین سفارش زیرمجموعه',
'sts' => 4,
]);
$referrerTransaction = new TransactionInterface($wallet_interface, $_inp = [
'gold' => 0,
'cash' => $referrer_bonus,
'expire_at' => now(),
'note' => 'پاداش اولین سفارش زیرمجموعه',
'withdraw_id' => $Withdraw->id,
'active' => true,
], 3, true, true, 0);
$message = 'کیف پول شما به اندازه ' . number_format($referrer_bonus) . ' ریال در تاریخ ' . verta()->format('Y/n/j H:i:s') . " شارژ شد بابت\n"
. 'بابت پاداش اولین سفارش زیرمجموعه'
. ' '
. $this->Config('name_fa');
$this->Send($referrer->mobile, $message);
TransactionsAdmin::create([
'customer_id' => $customer->id,
'customer_to_id' => $referrer->id,
'user_id' => $customer->id,
'wallet' => null,
'transaction_id' => $referrerTransaction->id ?? null,
'cash' => $referrer_bonus,
'note' => 'پاداش خرید اول زیرمجموعه',
'type' => 1,
'gold' => 0,
'balance_cash' => 0,
'withdraw_id' => $Withdraw->id,
]);
}
return false;
}
private function enforceProductOrderLimits(array &$data, ?float $totalPriceRial = null): void
{
$product = $data['product'] ?? null;
if (!$product instanceof Product) {
$productId = (int) ($data['abshode_id'] ?? $data['product_id'] ?? 0);
if ($productId) {
$product = Product::find($productId);
if ($product) {
$data['product'] = $product;
}
}
} elseif (is_array($product)) {
$product = Product::find($product['id'] ?? $data['product_id'] ?? 0);
if ($product) {
$data['product'] = $product;
}
}
if ($product instanceof Product) {
$product->validateOrderCashLimit($this->resolveOrderLimitCashRial($data, $totalPriceRial));
}
}
/**
* مبلغ مؤثر برای min/max محصول — با target_cash همان مبلغی که کاربر وارد کرده
* (نه total_price موقت قبل از رُند گرم که ممکن است چند ریال کمتر شود).
*/
private function resolveOrderLimitCashRial(array $data, ?float $fallbackTotal = null): float
{
$targetCash = $this->resolveTargetCash($data);
if ($targetCash !== null) {
return (float) $targetCash;
}
if ($fallbackTotal !== null) {
return $fallbackTotal;
}
return (float) ($data['payment_amount'] ?? $data['total_price'] ?? 0);
}
}