Files
boursecu_server/app/Http/Controllers/Customer/WalletController.php
T
2026-06-09 16:44:02 +03:30

692 lines
26 KiB
PHP

<?php
namespace App\Http\Controllers\Customer;
use App\DB\Te\TableEngine;
use App\Http\Controllers\Controller;
use App\Models\Address;
use App\Models\Customer;
use App\Models\Order;
use App\Models\Product;
use App\Models\User;
use App\Models\Withdraw;
use App\Models\BankAccount;
use App\Models\Price;
use App\Models\Setting;
use App\Http\Controllers\Admin\TahesabController;
use App\Services\AssetSummaryService;
use App\Services\ZaarboxPriceService;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Http;
use Rooyesh\Wallet\interfaces\TransactionInterface;
use Rooyesh\Wallet\interfaces\WalletInterface;
use Rooyesh\Wallet\models\Transaction;
use Rooyesh\Wallet\models\Wallet;
use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException;
class WalletController extends Controller
{
public function withdrawTable(Request $request)
{
$records = Withdraw::with(['transaction'])->where('customer_id', auth('customer')->id())->whereIn('sts', [2,1])->orderBy('id','desc');
TableEngine::create($records, $request->all())
->initSearch(['customer.name'])
->select([
'*',Withdraw::$SELECT_STS_STR,Withdraw::$SELECT_DAJ
])->export($records);
return response()->json($records);
}
public function walletMe()
{
$wallet = Wallet::select(['owner', 'id', 'gold', 'cash'])->where('owner', auth('customer')->id())
->first();
// $wallet['gold_sum'] = Wallet::where('owner', auth('customer')->id())->get();
$wallet['gold_sum'] = round(Wallet::where('owner', auth('customer')->id())->where('product_id', null)->sum('gold'), 2);
return response()->json($wallet);
}
public function assetsSummary(AssetSummaryService $assetSummaryService)
{
$user = auth('customer')->user();
return response()->json(
$assetSummaryService->buildForCustomer((int) $user->id, (int) $user->type)
);
}
public function priceData(Request $request, ZaarboxPriceService $zaarboxPriceService)
{
return response()->json([
'code' => 0,
'message' => 'عملیات با موفقیت انجام شد.',
'data' => [
'prices' => $zaarboxPriceService->getAllPrices(),
],
]);
}
public function transactionTable(Request $request)
{
if (isset($request->wallet_id)){
$wallet = Wallet::with('product')->where('id', $request->wallet_id)->where('owner', auth('customer')->id())->get();
}else{
$wallet = Wallet::with('product')->where('owner', auth('customer')->id())->get();
}
if ($wallet->isEmpty()) {
$records = ['query' => ['total' => 0], 'rows' => []];
} else {
$records = Transaction::with([
'order' => function ($q) {
$q->select(['*', Order::$SELECT_STS_STR]);
},
'withdraw' => function ($q) {
$q->select(['*', Withdraw::$SELECT_STS_STR]);
},
'wallet.product',
'product',
])->whereIn('wallet', $wallet->pluck('id'))->whereNull('fund_id');
TableEngine::create($records, $request->all())
->initSearch(['customer.name'])
->select([
'*', Transaction::$SELECT_CAJ, Transaction::$SELECT_TYPE_STR, Transaction::$SELECT_SAJ, Transaction::$SELECT_EAJ
])->export($records);
}
foreach ($records['rows'] as &$record) {
if ($this->isShopGoldTransaction($record)) {
$this->applyShopTransactionPresentation($record);
$this->applyAbshodeProductTransactionTitle($record);
$this->applyMetalTypeTransactionTitle($record);
$this->applyProductTransactionTitle($record);
$this->applyProductOrderDisplayFields($record);
$this->applyProductTransactionUnit($record);
continue;
}
// بقیه رکوردها فقط اگر title ندارند یا تعریف نشده هستند اصلاح شوند
if (empty($record->title) || $record->title == "تعریف نشده") {
$record->title = $record->type_str;
$record->unit = "گرم";
if (isset($record->wallet)) {
$wallet = Wallet::with('product')->where('id', $record->wallet)->first();
if (isset($wallet->product)) {
$direction = $record->order->direction ?? null;
if ((int) $direction === 1) {
$record->title = self::productBuyTitle($wallet->product->title);
} elseif ((int) $direction === 2) {
$record->title = self::productSellTitle($wallet->product->title);
} else {
$record->title = ($record->gold > 0 ? self::productBuyTitle($wallet->product->title) : self::productSellTitle($wallet->product->title));
}
$record->unit = self::productQuantityUnit([
'unit' => $wallet->product->unit ?? null,
'direct_price' => $wallet->product->direct_price ?? null,
'group_type' => $wallet->product->group_type ?? null,
]);
} else {
$record->title = $this->resolveMainWalletTransactionTitle($record);
}
} else {
$record->title = $record->type_str;
}
$record->save();
}
$this->applyAbshodeProductTransactionTitle($record);
$this->applyMetalTypeTransactionTitle($record);
$this->applyProductTransactionTitle($record);
$this->applyProductOrderDisplayFields($record);
$this->applyProductTransactionUnit($record);
}
$records['rows'] = $this->serializeTransactionRowsForApi($records['rows']);
return response()->json($records);
}
public function transactionTableb(Request $request)
{
$wallet = Wallet::with('product')->where('owner', auth('customer')->id())->whereNull('fund_id')->get();
if (is_null($wallet)) {
$records = ['query' => ['total' => 0], 'rows' => []];
} else {
$records = Transaction::with(['order' => function ($q) {
return $q->select(['*', Order::$SELECT_STS_STR]);
}, 'withdraw' => function ($q) {
return $q->select(['*', Order::$SELECT_STS_STR]);
}, 'wallet.product'])->whereIn('wallet', $wallet->pluck('id'))->whereNull('fund_id');
TableEngine::create($records, $request->all())
->initSearch(['customer.name'])
->select([
'*', Transaction::$SELECT_CAJ, Transaction::$SELECT_TYPE_STR, Transaction::$SELECT_SAJ, Transaction::$SELECT_EAJ
])->export($records);
}
foreach ($records['rows'] as &$record) {
return $record['wallet'];
if (isset($record->wallet->product->title)) {
$record->title = $record->wallet->product->title."22";
$record->save();
} else {
$record->title = $record->type_str."22";
$record->save();
}
}
return response()->json($records);
}
public function withdraw(Request $request)
{
$customer = auth('customer')->user();
$data = $request->all();
if (!isset($data['card_id'])) {
return response()->json(['message' => 'لطفا کارت بانکی را وارد کنید'], 422);
}
$bCart = BankAccount::where('id', $data['card_id'])->first();
if ($bCart->sts != 2) {
return response()->json(['message' => 'کارت انتخاب شده نا معتبر است'], 422);
}
$wallet_interface_fund = new WalletInterface(auth('customer')->id(), auth('customer')->user()->type);
if ($data['value'] < 0) {
throw new UnprocessableEntityHttpException('مقدار برداشت باید مثبت باشد');
}
$this->validateWithdrawAmount((int) $data['value']);
$Withdraw = Withdraw::create([
'customer_id' => auth('customer')->id(),
"cash" => $data['value'],
"note" => "برداشت ریالی",
"bank_account_id" => $data['card_id'],
'sts' => 1,
'type' => 2
]);
$transaction = new TransactionInterface($wallet_interface_fund, $_inp = [
"cash" => $data['value'],
'expire_at' => now(),
'withdraw_id' => $Withdraw->id,
'active' => true
], 4, false, false);
$userMobile = $customer->mobile; // User's mobile number
$userName = $customer->name; // User's name
$withdrawAmount = $data['value']; // amount
if($this->Config('send_to_tahesab') == "true") {
$tahesab = new TahesabController();
$result = $tahesab->wallet([
"customer_id" => auth('customer')->id(),
"value" => $data['value'],
"direction" => 1,
]);
\Log::info('Tahesab withdraw result', [
'customer_id' => auth('customer')->id(),
'value' => $data['value'],
'result' => $result,
]);
}
$companyName = $this->Config('name_fa'); // Company name from configuration
$message = "کاربر $userName با شماره موبایل $userMobile درخواست برداشت مبلغ " . number_format($withdrawAmount) . " ریال را دارد. با تشکر، $companyName";
if (isset($request->otp)) {
if (Customer::where("id", $customer->id)->first()->token_sms != $request->otp) {
return response()->json(['message' => 'رمز دوم اشتباه میباشد'], 422);
}
}
User::where('send_sms', 1)->get()->each(function ($admin) use ($message) {
$this->Send($admin->mobile, $message . ' ' . now()->format('H:i:s'));
});
return response()->json($Withdraw);
}
public function withdraw_physical(Request $request)
{
$data = $request->all();
$data['customer'] = Customer::with([/*"wallet"*/])->where('id', auth('customer')->id())->first();
$data['customer']['wallet_interface'] = new WalletInterface(auth('customer')->id(), auth('customer')->user()->type);
if (!isset($data['address_id'])) {
return response()->json(['message' => 'آدرس انتخاب نشده است'], 422);
}
$data['adress'] = Address::find($data['address_id']);
if (!$data['adress'] || $data['adress']->customer_id != $data['customer']->id) {
return response()->json(['message' => 'آدرس معتبر نیست یا متعلق به شما نمی‌باشد'], 403);
}
foreach ($data['wallets'] as $key => $wallet) {
$data['wallets'][$key]['wallet'] = Wallet::with(["product"])->find($wallet['wallet_id']);
if (!$data['wallets'][$key]['wallet']) {
return response()->json(['message' => 'ولت مورد نظر یافت نشد.'], 422);
}
if ($data['wallets'][$key]['wallet']->owner != $data['customer']['id']) {
return response()->json(['message' => 'ولت انتخاب شده متعلق به شما نمی‌باشد.'], 422);
}
// بررسی وجود محصول در ولت
if (!$data['wallets'][$key]['wallet']['product']) {
return response()->json(['message' => 'محصولی در ولت مورد نظر یافت نشد.'], 422);
}
if ($data['wallets'][$key]['value'] > $data['wallets'][$key]['wallet']['gold']){
return response()->json([
'message' => "شما به اندازه کافی از {$data['wallets'][$key]['wallet']['product']['title']} ندارید"
], 422);
}
if ($data['wallets'][$key]['value'] == 0) {
return response()->json([
'message' => "مقدار انتخابی نمی‌تواند صفر باشد. لطفاً مقدار صحیحی را برای {$data['wallets'][$key]['wallet']['product']['title']} وارد کنید."
], 422);
}
$data['wallets'][$key]['wallet_interface'] = new WalletInterface(auth('customer')->id(), auth('customer')->user()->type, null, $data['wallets'][$key]['wallet']["product_id"]);
}
$data['withdraw'] = new Withdraw(); // Create a new instance of the Withdraw model
$data['withdraw']->note = "برداشت فیزیکی"; // 50 for phisicals
$data['withdraw']->address_id = $data['address_id'];
$data['withdraw']->customer_id = $data['customer']['id'];
$data['withdraw']->sts = 50;
$data['withdraw']->save();
foreach ($data['wallets'] as $key => $wallet) {
$data['wallets'][$key]['transaction'][] = new TransactionInterface($data['wallets'][$key]['wallet_interface'], $_inp = [
"gold" => $data['wallets'][$key]['value'],
"number" => $data['wallets'][$key]['wallet']['product']['amount']*$data['wallets'][$key]['value'],
"product_id" => $data['wallets'][$key]['wallet']['product']['id'],
'expire_at' => now(),
'active' => true,
'title' => " برداشت {$data['wallets'][$key]['wallet']['product']['title']}",
'note' => " برداشت محصول {$data['wallets'][$key]['wallet']['product']['title']} شماره سند {$data['withdraw']->id}",
'withdraw_id' => $data['withdraw']->id,
], 6, false);
}
return response()->json($data['withdraw']['id']);
}
public function indexWithdraw(Request $request){
$records = Withdraw::with(['customer','bank_account','Address',"Transaction"])->where("customer_id",auth('customer')->id());
TableEngine::create($records, $request->all())
->initSearch(['customer.name'])
->select([
'*',Withdraw::$SELECT_STS_STR,Withdraw::$SELECT_DAJ
])->export($records);
return response()->json($records);
}
public function index_withdraw_physical(Request $request){
$records = Withdraw::with(['customer','bank_account','Address',"Transaction.product"])->where("customer_id",auth('customer')->id())->whereBetween('sts', [49, 70]);
TableEngine::create($records, $request->all())
->initSearch(['customer.name'])
->select([
'*',Withdraw::$SELECT_STS_STR,Withdraw::$SELECT_DAJ
])->export($records);
return response()->json($records);
}
public function withdrawOtp(Request $request)
{
$data = $request->all();
if (!isset($data['value'])) {
return response()->json(['message' => 'مبلغ برداشت مشخص نشده است'], 422);
}
if ($data['value'] < 0) {
throw new UnprocessableEntityHttpException('مقدار برداشت باید مثبت باشد');
}
$this->validateWithdrawAmount((int) $data['value']);
$customer = auth('customer')->user();
$otp = rand(1000, 9999);
$customer->token_sms = $otp;
$customer->save();
$this->Send(
$customer->mobile,
'رمز عبور یکبار مصرف شما جهت برداشت پول از اپلیکیشن ' . $this->Config('name_fa') . ' می باشد: ' . $otp
);
return response()->json(true);
}
private function validateWithdrawAmount(int $value): void
{
$wallet_interface_fund = new WalletInterface(auth('customer')->id(), auth('customer')->user()->type);
$wallet = $wallet_interface_fund->wallet;
if ($value > $wallet->cash) {
throw new UnprocessableEntityHttpException('مقدار برداشت بیشتر از موجودی است');
}
$min_withdraw = Setting::where('is_active', 1)
->where('key', 'min_withdraw')
->first();
if ($min_withdraw && (int) $min_withdraw->value > $value) {
throw new UnprocessableEntityHttpException('حداقل مقدار برداشت ' . number_format((int) $min_withdraw->value) . ' ریال است');
}
}
private function applyAbshodeProductTransactionTitle($record): void
{
if (self::isCashTransactionTitle($record->title ?? null)) {
return;
}
$productId = $this->resolveAbshodeProductId($record);
if (!$productId) {
return;
}
$productTitle = $record->product->title
?? Product::query()->whereKey($productId)->value('title');
if (!$productTitle) {
return;
}
$direction = (int) ($record->order->direction ?? 0);
if ($direction === 1 || ($direction === 0 && (float) ($record->gold ?? 0) > 0)) {
$record->title = self::productBuyTitle($productTitle);
return;
}
if ($direction === 2 || ($direction === 0 && (float) ($record->gold ?? 0) < 0)) {
$record->title = self::productSellTitle($productTitle);
}
}
private function resolveAbshodeProductId($record): ?int
{
if (!empty($record->product_id)) {
return (int) $record->product_id;
}
$note = (string) ($record->note ?? '');
if ($note !== '' && preg_match('/(?:^|;)product_id:(\d+)/', $note, $matches)) {
return (int) $matches[1];
}
if (empty($record->order_id)) {
return null;
}
$siblingProductId = Transaction::query()
->where('order_id', $record->order_id)
->whereNotNull('product_id')
->where('product_id', '>', 0)
->value('product_id');
return $siblingProductId ? (int) $siblingProductId : null;
}
private function applyMetalTypeTransactionTitle($record): void
{
if ($this->resolveAbshodeProductId($record)) {
return;
}
if (!empty($record->product_id)) {
return;
}
$wallet = $record->wallet ?? null;
if ($wallet && !empty($wallet->product)) {
return;
}
if (self::isCashTransactionTitle($record->title)) {
return;
}
if (!self::isGenericMetalTransactionTitle($record->title)) {
return;
}
if (preg_match('/^(موجودی|واریز)/u', (string) $record->title)) {
$record->title = self::meltedGoldDepositTitle();
} elseif (preg_match('/^برداشت /u', (string) $record->title)) {
$record->title = self::meltedGoldWithdrawTitle();
} elseif (preg_match('/^خرید /u', (string) $record->title)) {
$record->title = self::meltedGoldBuyTitle();
} elseif (preg_match('/^فروش /u', (string) $record->title)) {
$record->title = self::meltedGoldSellTitle();
}
}
private function applyProductTransactionTitle($record): void
{
if (self::isCashTransactionTitle($record->title)) {
return;
}
$productId = $this->resolveAbshodeProductId($record);
if (!$productId) {
return;
}
$productTitle = $record->product->title
?? $record->wallet->product->title
?? Product::query()->whereKey($productId)->value('title');
if (!$productTitle) {
return;
}
$expectedBuy = self::productBuyTitle($productTitle);
$expectedSell = self::productSellTitle($productTitle);
if (in_array($record->title, [$expectedBuy, $expectedSell], true)) {
return;
}
$direction = (int) ($record->order->direction ?? 0);
if ($direction === 1 || ($direction === 0 && (float) ($record->gold ?? 0) > 0)) {
$record->title = $expectedBuy;
return;
}
if ($direction === 2 || ($direction === 0 && (float) ($record->gold ?? 0) < 0)) {
$record->title = $expectedSell;
}
}
private function resolveMainWalletTransactionTitle($record): string
{
if ((int) $record->type === 1) {
return self::meltedGoldBuyTitle();
}
if ((int) $record->type === 2) {
return self::meltedGoldSellTitle();
}
return $record->type_str;
}
private function applyProductOrderDisplayFields($record): void
{
if (empty($record->order_id) || empty($record->order) || empty($record->product_id)) {
return;
}
$order = $record->order;
$totalPrice = (int) ($order->total_price ?? $order->payment_amount ?? 0);
if ($totalPrice <= 0) {
return;
}
if ((int) ($record->cash ?? 0) === 0) {
$direction = (int) ($order->direction ?? 0);
$record->cash = $direction === 1 ? -$totalPrice : $totalPrice;
}
if ($this->isProductWalletTransaction($record)) {
$mainBalanceCash = $this->resolveMainWalletBalanceCashForOrder($record);
if ($mainBalanceCash !== null) {
$record->balance_cash = $mainBalanceCash;
}
}
if (!empty($record->wallet?->product)) {
$record->unit = self::productQuantityUnit([
'unit' => $record->wallet->product->unit ?? null,
'direct_price' => $record->wallet->product->direct_price ?? null,
'group_type' => $record->wallet->product->group_type ?? null,
]);
}
}
private function applyProductTransactionUnit($record): void
{
$product = $record->wallet->product ?? $record->product ?? null;
if (!$product) {
if (!empty($record->gold) && empty($record->wallet?->product)) {
$record->unit = self::tradeWeightUnit();
}
return;
}
$record->unit = self::productQuantityUnit([
'unit' => $product->unit ?? null,
'direct_price' => $product->direct_price ?? null,
'group_type' => $product->group_type ?? null,
]);
}
private function isProductWalletTransaction($record): bool
{
return !empty($record->wallet?->product);
}
/**
* عنوان/وضعیت تراکنش‌های API فروشگاه — فقط نمایش؛ واریز/برداشت عادی دست نخورده.
*/
private function applyShopTransactionPresentation($record): void
{
if ($this->isShopGoldTransaction($record)) {
if (empty($record->title) || $record->title === 'تعریف نشده') {
$record->title = 'برداشت طلایی';
}
}
}
/**
* خروجی JSON — withdraw/order را صریحاً می‌گذارد (setRelation گاهی در JSON نمی‌آید).
*/
private function serializeTransactionRowsForApi($rows): array
{
$out = [];
foreach ($rows as $record) {
$row = $record instanceof Transaction ? $record->toArray() : (array) $record;
if ($this->isShopRialTransaction($record)) {
$row['withdraw'] = ['sts_str' => 'برداشت ریالی توسط فروشگاه'];
}
if ($this->isShopGoldTransaction($record)) {
$order = $row['order'] ?? [];
if (!is_array($order)) {
$order = $order instanceof Order ? $order->toArray() : [];
}
$order['sts_str'] = 'برداشت طلایی توسط فروشگاه';
if (empty($order['id']) && !empty($record->order_id)) {
$order['id'] = $record->order_id;
}
$row['order'] = $order;
$row['title'] = 'برداشت طلایی';
$row['type_str'] = 'برداشت طلایی';
}
$out[] = $row;
}
return $out;
}
/** برداشت ریال API فروشگاه — type=4، بدون withdraw_id، مبلغ منفی */
private function isShopRialTransaction($record): bool
{
if ((int) $record->type !== 4 || !empty($record->withdraw_id)) {
return false;
}
return (int) ($record->cash ?? 0) < 0;
}
/** برداشت طلا API فروشگاه — type=10 + order_id، بدون withdraw */
private function isShopGoldTransaction($record): bool
{
if ((int) $record->type !== 10 || empty($record->order_id) || !empty($record->withdraw_id)) {
return false;
}
$order = $this->resolveOrderForTransaction($record);
if (!$order) {
return true;
}
if ((int) ($order->direction ?? 0) !== 2) {
return false;
}
$montify = trim((string) $this->Config('montify_name'));
if ($montify !== '' && str_contains((string) ($order->description ?? ''), $montify)) {
return true;
}
return (int) ($order->sts ?? 0) === 2 && (int) ($order->payment_sts ?? 0) === 4;
}
private function resolveOrderForTransaction($record): ?Order
{
if ($record->order instanceof Order) {
return $record->order;
}
if (empty($record->order_id)) {
return null;
}
return Order::query()
->select(['*', Order::$SELECT_STS_STR])
->find($record->order_id);
}
private function resolveMainWalletBalanceCashForOrder($record): ?int
{
$customerId = $record->customer_id ?? auth('customer')->id();
$mainWalletIds = Wallet::where('owner', $customerId)
->whereNull('product_id')
->whereNull('fund_id')
->pluck('id');
if ($mainWalletIds->isEmpty()) {
return null;
}
$mainTx = Transaction::where('order_id', $record->order_id)
->whereIn('wallet', $mainWalletIds)
->whereNotNull('balance_cash')
->orderBy('id')
->first();
if ($mainTx) {
return (int) $mainTx->balance_cash;
}
$previousTx = Transaction::whereIn('wallet', $mainWalletIds)
->where('id', '<', $record->id)
->whereNotNull('balance_cash')
->orderByDesc('id')
->first();
return $previousTx ? (int) $previousTx->balance_cash : null;
}
}