2026-01-24 14:03:11 +00:00
<? 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 ;
2026-06-09 13:34:43 +03:30
use App\Models\Product ;
2026-01-24 14:03:11 +00:00
use App\Models\User ;
use App\Models\Withdraw ;
use App\Models\BankAccount ;
use App\Models\Price ;
2026-06-05 16:24:15 +03:30
use App\Models\Setting ;
2026-01-24 14:03:11 +00:00
use App\Http\Controllers\Admin\TahesabController ;
2026-07-05 16:43:34 +03:30
use App\Services\ConfigService ;
2026-06-09 16:44:02 +03:30
use App\Services\AssetSummaryService ;
2026-07-05 13:04:00 +03:30
use App\Services\WalletBalanceService ;
2026-09-05 17:01:39 +03:30
use App\Services\WalletBroadcastService ;
2026-06-02 12:24:54 +03:30
use App\Services\ZaarboxPriceService ;
2026-01-24 14:03:11 +00:00
use Illuminate\Http\Request ;
2026-09-05 17:01:39 +03:30
use Illuminate\Support\Facades\Cache ;
use Illuminate\Support\Facades\DB ;
2026-01-24 14:03:11 +00:00
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 )
{
2026-07-06 22:10:40 +03:30
$customerId = ( int ) auth ( 'customer' ) -> id ();
$records = Withdraw :: query ()
-> with ([ 'transaction' ])
-> where ( 'customer_id' , $customerId );
Withdraw :: scopeDeposits ( $records );
$records -> where ( function ( $q ) {
$q -> where ( 'cash' , '>' , 0 )
-> orWhere ( 'gold' , '>' , 0 );
}) -> orderByDesc ( 'id' );
2026-01-24 14:03:11 +00:00
TableEngine :: create ( $records , $request -> all ())
2026-07-06 22:10:40 +03:30
-> initSearch ([ 'customer.name' , 'note' ])
2026-01-24 14:03:11 +00:00
-> select ([
2026-07-06 22:10:40 +03:30
'*' , Withdraw :: $SELECT_STS_STR , Withdraw :: $SELECT_DAJ ,
2026-01-24 14:03:11 +00:00
]) -> export ( $records );
2026-07-06 22:10:40 +03:30
foreach ( $records [ 'rows' ] as & $record ) {
$this -> applyWithdrawDepositRowPresentation ( $record );
}
unset ( $record );
2026-01-24 14:03:11 +00:00
return response () -> json ( $records );
}
2026-07-06 22:10:40 +03:30
private function applyWithdrawDepositRowPresentation ( $record ) : void
{
$record -> title = $this -> resolveWithdrawDepositListTitle ( $record );
$record -> expire_at = $record -> date_at ?? $record -> created_at ;
$record -> type = 3 ;
$record -> unit = 'ریال' ;
$sts = ( int ) ( $record -> sts ?? 0 );
$record -> sts_str = Withdraw :: GetSts ( $sts ) ?? ( string ) $sts ;
}
private function resolveWithdrawDepositListTitle ( $record ) : string
{
$note = trim (( string ) ( $record -> note ?? '' ));
if ( $note !== '' ) {
return $note ;
}
return match (( int ) ( $record -> sts ?? 0 )) {
4 => self :: rialDepositTitle (),
5 => 'پاداش معرفی' ,
9 => 'سود زیر مجموعه' ,
default => self :: rialDepositTitle (),
};
}
2026-07-05 13:04:00 +03:30
public function walletMe ( WalletBalanceService $balanceService )
2026-01-24 14:03:11 +00:00
{
2026-07-05 13:04:00 +03:30
$customer = auth ( 'customer' ) -> user ();
$customerId = ( int ) $customer -> id ;
$ownerType = ( int ) $customer -> type ;
$wallets = Wallet :: query ()
2026-06-11 21:17:40 +03:30
-> where ( 'owner' , $customerId )
2026-07-05 13:04:00 +03:30
-> where ( 'owner_type' , $ownerType )
-> get ();
2026-01-24 14:03:11 +00:00
2026-07-05 13:04:00 +03:30
$mainWallet = $wallets -> first (
fn ( Wallet $row ) => $row -> fund_id === null && $row -> product_id === null
);
2026-06-11 21:17:40 +03:30
2026-07-05 13:04:00 +03:30
return response () -> json ([
'owner' => $customerId ,
'id' => $mainWallet ?-> id ,
'gold' => $balanceService -> mainWalletGold ( $wallets ),
'cash' => $balanceService -> mainWalletCash ( $wallets ),
'gold_sum' => round ( $balanceService -> totalGoldWeight ( $wallets ), 2 ),
]);
2026-01-24 14:03:11 +00:00
}
2026-06-09 16:44:02 +03:30
public function assetsSummary ( AssetSummaryService $assetSummaryService )
{
$user = auth ( 'customer' ) -> user ();
return response () -> json (
$assetSummaryService -> buildForCustomer (( int ) $user -> id , ( int ) $user -> type )
);
}
2026-06-02 12:24:54 +03:30
public function priceData ( Request $request , ZaarboxPriceService $zaarboxPriceService )
2026-01-24 14:03:11 +00:00
{
2026-06-02 12:24:54 +03:30
return response () -> json ([
'code' => 0 ,
'message' => 'عملیات با موفقیت انجام شد.' ,
'data' => [
'prices' => $zaarboxPriceService -> getAllPrices (),
],
]);
2026-01-24 14:03:11 +00:00
}
public function transactionTable ( Request $request )
{
2026-06-30 10:24:42 +03:30
$customerId = ( int ) auth ( 'customer' ) -> id ();
$this -> repairMisclassifiedRialTransactions ( $customerId );
2026-07-12 18:31:17 +03:30
$mainWalletProductIds = app ( ConfigService :: class ) -> mainWalletProductIds ();
$walletIds = $this -> resolveTransactionTableWalletIds ( $customerId , $request , $mainWalletProductIds );
2026-01-24 14:03:11 +00:00
2026-07-12 18:31:17 +03:30
if ( $walletIds -> isEmpty ()) {
2026-01-24 14:03:11 +00:00
$records = [ 'query' => [ 'total' => 0 ], 'rows' => []];
} else {
$records = Transaction :: with ([
'order' => function ( $q ) {
$q -> select ([ '*' , Order :: $SELECT_STS_STR ]);
},
'withdraw' => function ( $q ) {
2026-05-23 13:30:05 +03:30
$q -> select ([ '*' , Withdraw :: $SELECT_STS_STR ]);
2026-01-24 14:03:11 +00:00
},
2026-06-09 13:34:43 +03:30
'wallet.product' ,
'product' ,
2026-08-01 17:30:14 +03:30
]) -> whereIn ( 'wallet' , $walletIds );
// جدول عمومی تراکنشها: جابهجایی قلک را قاطی نکند؛ برای کیف قلک همهٔ ردیفها بیاید
$includesFundWallet = Wallet :: query ()
-> whereIn ( 'id' , $walletIds )
-> whereNotNull ( 'fund_id' )
-> exists ();
if ( ! $includesFundWallet ) {
$records -> whereNull ( 'fund_id' );
}
2026-01-24 14:03:11 +00:00
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 ) {
2026-06-04 19:22:01 +03:30
if ( $this -> isShopGoldTransaction ( $record )) {
$this -> applyShopTransactionPresentation ( $record );
2026-06-09 13:34:43 +03:30
$this -> applyAbshodeProductTransactionTitle ( $record );
2026-06-04 19:22:01 +03:30
$this -> applyMetalTypeTransactionTitle ( $record );
$this -> applyProductTransactionTitle ( $record );
$this -> applyProductOrderDisplayFields ( $record );
$this -> applyProductTransactionUnit ( $record );
2026-06-30 10:24:42 +03:30
self :: normalizeRialTransactionPresentation ( $record );
2026-06-04 19:22:01 +03:30
continue ;
}
2026-05-24 12:30:39 +03:30
// بقیه رکوردها فقط اگر title ندارند یا تعریف نشده هستند اصلاح شوند
if ( empty ( $record -> title ) || $record -> title == "تعریف نشده" ) {
$record -> title = $record -> type_str ;
$record -> unit = "گرم" ;
2026-07-18 15:23:16 +03:30
if (( int ) ( $record -> type ?? 0 ) === 22 ) {
2026-07-19 15:53:05 +03:30
$record -> title = 'تعدیل حسابداری پرگار' ;
2026-07-18 15:23:16 +03:30
$record -> unit = 'گرم' ;
2026-08-30 14:16:36 +03:30
} elseif (( int ) ( $record -> type ?? 0 ) === 23 ) {
$record -> title = 'تعدیل حسابداری تهحساب' ;
$record -> unit = 'گرم' ;
2026-07-18 15:23:16 +03:30
} elseif (( int ) ( $record -> type ?? 0 ) === 12 ) {
$record -> title = 'برداشت وجه نقد' ;
$record -> unit = 'ریال' ;
} elseif ( isset ( $record -> wallet )) {
2026-05-24 12:30:39 +03:30
$wallet = Wallet :: with ( 'product' ) -> where ( 'id' , $record -> wallet ) -> first ();
if ( isset ( $wallet -> product )) {
2026-06-03 21:37:05 +03:30
$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 );
2026-05-24 12:30:39 +03:30
} else {
2026-06-03 21:37:05 +03:30
$record -> title = ( $record -> gold > 0 ? self :: productBuyTitle ( $wallet -> product -> title ) : self :: productSellTitle ( $wallet -> product -> title ));
2026-05-24 12:30:39 +03:30
}
2026-06-22 21:29:25 +03:30
$record -> unit = self :: productQuantityUnit ( self :: productUnitPayload ( $wallet -> product ));
2026-06-03 21:37:05 +03:30
} else {
$record -> title = $this -> resolveMainWalletTransactionTitle ( $record );
2026-05-24 12:30:39 +03:30
}
2026-06-03 21:37:05 +03:30
} else {
$record -> title = $record -> type_str ;
2026-05-24 12:30:39 +03:30
}
2026-07-18 15:23:16 +03:30
if ( ! empty ( $record -> title ) && $record -> title !== 'تعریف نشده' ) {
$record -> save ();
}
2026-05-24 12:30:39 +03:30
}
2026-06-03 20:24:07 +03:30
2026-06-09 13:34:43 +03:30
$this -> applyAbshodeProductTransactionTitle ( $record );
2026-06-03 20:24:07 +03:30
$this -> applyMetalTypeTransactionTitle ( $record );
2026-06-03 21:37:05 +03:30
$this -> applyProductTransactionTitle ( $record );
$this -> applyProductOrderDisplayFields ( $record );
2026-06-03 21:52:37 +03:30
$this -> applyProductTransactionUnit ( $record );
2026-06-30 10:24:42 +03:30
self :: normalizeRialTransactionPresentation ( $record );
2026-01-24 14:03:11 +00:00
}
2026-06-04 19:22:01 +03:30
$records [ 'rows' ] = $this -> serializeTransactionRowsForApi ( $records [ 'rows' ]);
2026-01-24 14:03:11 +00:00
return response () -> json ( $records );
}
2026-07-12 18:31:17 +03:30
/**
* کیف(های) مؤثر برای جدول تراکنش.
* محصولات main_wallet_products: تراکنشها روی کیف اصلی (+ کیف legacy محصول) ثبت شدهاند.
*
* @return \Illuminate\Support\Collection<int, int>
*/
private function resolveTransactionTableWalletIds (
int $customerId ,
Request $request ,
array $mainWalletProductIds
) {
$requestedWalletId = $request -> input ( 'wallet_id' );
$requestedProductId = ( int ) $request -> input ( 'product_id' );
if ( $requestedWalletId ) {
$requestedWallet = Wallet :: query ()
-> where ( 'id' , $requestedWalletId )
-> where ( 'owner' , $customerId )
-> first ();
if ( ! $requestedWallet ) {
return collect ();
}
if (
$mainWalletProductIds !== []
&& (
( $requestedWallet -> product_id && self :: isMainWalletProduct (( int ) $requestedWallet -> product_id ))
|| (
$requestedWallet -> product_id === null
&& $requestedWallet -> fund_id === null
&& $requestedProductId > 0
&& self :: isMainWalletProduct ( $requestedProductId )
)
)
) {
return $this -> mainWalletProductTransactionWalletIds ( $customerId , $mainWalletProductIds );
}
return collect ([( int ) $requestedWallet -> id ]);
}
if (
$mainWalletProductIds !== []
&& $requestedProductId > 0
&& self :: isMainWalletProduct ( $requestedProductId )
) {
return $this -> mainWalletProductTransactionWalletIds ( $customerId , $mainWalletProductIds );
}
return Wallet :: query ()
-> where ( 'owner' , $customerId )
-> pluck ( 'id' );
}
/**
* کیف اصلی + کیفهای legacy محصولات متصل به کیف اصلی.
*
* @return \Illuminate\Support\Collection<int, int>
*/
private function mainWalletProductTransactionWalletIds ( int $customerId , array $mainWalletProductIds )
{
return Wallet :: query ()
-> where ( 'owner' , $customerId )
-> whereNull ( 'fund_id' )
-> where ( function ( $query ) use ( $mainWalletProductIds ) {
$query -> whereNull ( 'product_id' )
-> orWhereIn ( 'product_id' , $mainWalletProductIds );
})
-> pluck ( 'id' )
-> map ( fn ( $id ) => ( int ) $id )
-> unique ()
-> values ();
}
2026-06-30 10:24:42 +03:30
/**
* دادهٔ قدیمی: type=4 با cash مثبت (واریز ادمین) — قبل از فیلتر جدول اصلاح میشود.
*/
private function repairMisclassifiedRialTransactions ( int $customerId ) : void
{
if ( $customerId <= 0 ) {
return ;
}
Transaction :: query ()
-> where ( 'customer_id' , $customerId )
-> whereNull ( 'fund_id' )
-> where ( 'type' , 4 )
-> where ( 'cash' , '>' , 0 )
-> update ([
'type' => 3 ,
'title' => self :: rialDepositTitle (),
]);
Transaction :: query ()
-> where ( 'customer_id' , $customerId )
-> whereNull ( 'fund_id' )
-> where ( 'type' , 3 )
-> where ( 'cash' , '<' , 0 )
-> update ([
'type' => 4 ,
'title' => self :: rialWithdrawTitle (),
]);
}
2026-01-24 14:03:11 +00:00
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 ();
2026-10-20 09:10:01 +03:30
self :: assertCustomerIdentityVerified ( $customer instanceof Customer ? $customer : null );
2026-01-24 14:03:11 +00:00
$data = $request -> all ();
if ( ! isset ( $data [ 'card_id' ])) {
2026-05-23 13:30:05 +03:30
return response () -> json ([ 'message' => 'لطفا کارت بانکی را وارد کنید' ], 422 );
2026-01-24 14:03:11 +00:00
}
$bCart = BankAccount :: where ( 'id' , $data [ 'card_id' ]) -> first ();
2026-09-05 17:01:39 +03:30
if ( ! $bCart || $bCart -> sts != 2 ) {
2026-05-23 13:30:05 +03:30
return response () -> json ([ 'message' => 'کارت انتخاب شده نا معتبر است' ], 422 );
2026-01-24 14:03:11 +00:00
}
2026-09-05 17:01:39 +03:30
$amount = ( int ) ( $data [ 'value' ] ?? 0 );
if ( $amount <= 0 ) {
2026-01-24 14:03:11 +00:00
throw new UnprocessableEntityHttpException ( 'مقدار برداشت باید مثبت باشد' );
}
2026-06-05 16:24:15 +03:30
2026-06-18 19:32:28 +03:30
$extraNote = trim (( string ) ( $data [ 'extra_note' ] ?? '' ));
if ( strlen ( $extraNote ) > 500 ) {
return response () -> json ([ 'message' => 'توضیحات اضافی حداکثر ۵۰۰ کاراکتر است' ], 422 );
}
2026-09-05 17:01:39 +03:30
$otp = ( string ) ( $request -> otp ?? '' );
if ( $otp === '' || ( string ) ( $customer -> token_sms ?? '' ) !== $otp ) {
return response () -> json ([ 'message' => 'رمز دوم اشتباه میباشد' ], 422 );
}
$lock = Cache :: lock ( 'customer-rial-withdraw:' . $customer -> id , 20 );
if ( ! $lock -> get ()) {
return response () -> json ([ 'message' => 'درخواست برداشت در حال انجام است' ], 429 );
}
try {
$customer -> refresh ();
if (( string ) ( $customer -> token_sms ?? '' ) !== $otp ) {
return response () -> json ([ 'message' => 'رمز دوم اشتباه میباشد' ], 422 );
}
$this -> validateWithdrawAmount ( $amount );
$Withdraw = DB :: transaction ( function () use ( $customer , $data , $amount , $extraNote ) {
$withdraw = Withdraw :: create ([
'customer_id' => $customer -> id ,
'cash' => $amount ,
'note' => 'برداشت ریالی' ,
'extra_note' => $extraNote !== '' ? $extraNote : null ,
'bank_account_id' => $data [ 'card_id' ],
'sts' => 1 ,
'type' => 2 ,
]);
$this -> debitCustomerRialWithdrawOnce ( $withdraw , $amount , $customer );
$customer -> token_sms = null ;
$customer -> save ();
return $withdraw ;
});
} finally {
$lock -> release ();
}
try {
app ( WalletBroadcastService :: class ) -> broadcastForCustomer (
( int ) $customer -> id ,
( int ) $customer -> type
);
} catch ( \Throwable $e ) {
report ( $e );
}
2026-07-26 14:15:26 +03:30
try {
app ( \App\Services\PargarSyncService :: class ) -> queueWalletDocument (
$customer ,
'cash' ,
2026-09-05 17:01:39 +03:30
- 1 * ( float ) $amount ,
2026-07-26 14:15:26 +03:30
[
'withdraw_id' => $Withdraw -> id ,
'note' => 'برداشت ریالی' ,
'source' => 'customer_withdraw' ,
]
);
} catch ( \Throwable $e ) {
report ( $e );
}
2026-01-24 14:03:11 +00:00
$userMobile = $customer -> mobile ; // User's mobile number
$userName = $customer -> name ; // User's name
2026-09-05 17:01:39 +03:30
$withdrawAmount = $amount ; // amount
2026-07-05 16:43:34 +03:30
if ( app ( ConfigService :: class ) -> isTahesabAccounting ()) {
2026-06-25 22:20:40 +03:30
try {
$result = app ( TahesabController :: class ) -> wallet ([
"customer_id" => auth ( 'customer' ) -> id (),
2026-09-05 17:01:39 +03:30
"value" => $amount ,
2026-06-25 22:20:40 +03:30
"direction" => 1 ,
]);
2026-05-29 21:28:50 +03:30
2026-06-25 22:20:40 +03:30
\Log :: info ( 'Tahesab withdraw result' , [
'customer_id' => auth ( 'customer' ) -> id (),
2026-09-05 17:01:39 +03:30
'value' => $amount ,
2026-06-25 22:20:40 +03:30
'result' => $result ,
]);
} catch ( \Exception $e ) {
report ( $e );
}
2026-04-08 06:24:46 +00:00
}
2026-07-23 20:24:58 +03:30
if ( app ( ConfigService :: class ) -> isZarinAccounting ()) {
try {
app ( \App\Http\Controllers\Admin\ZarinController :: class ) -> wallet ([
"customer_id" => auth ( 'customer' ) -> id (),
2026-09-05 17:01:39 +03:30
"value" => $amount ,
2026-07-23 20:24:58 +03:30
"direction" => 1 ,
2026-09-05 17:01:39 +03:30
"withdraw_id" => $Withdraw -> id ,
"note" => 'برداشت ریالی' ,
2026-07-23 20:24:58 +03:30
]);
} catch ( \Exception $e ) {
report ( $e );
}
}
2026-10-05 17:12:43 +03:30
if ( app ( ConfigService :: class ) -> isShayeganAccounting ()) {
try {
app ( \App\Http\Controllers\Admin\ShayeganController :: class ) -> wallet ([
'customer_id' => auth ( 'customer' ) -> id (),
'value' => $amount ,
'direction' => 1 ,
'withdraw_id' => $Withdraw -> id ,
'note' => 'برداشت ریالی' ,
]);
} catch ( \Exception $e ) {
report ( $e );
}
}
2026-01-24 14:03:11 +00:00
$companyName = $this -> Config ( 'name_fa' ); // Company name from configuration
$message = "کاربر $userName با شماره موبایل $userMobile درخواست برداشت مبلغ " . number_format ( $withdrawAmount ) . " ریال را دارد. با تشکر، $companyName " ;
User :: where ( 'send_sms' , 1 ) -> get () -> each ( function ( $admin ) use ( $message ) {
2026-07-18 22:22:01 +03:30
if ( self :: isSmsNotifEnabled ( 'sms_notif_withdraw_admin' )) {
$this -> Send ( $admin -> mobile , $message . ' ' . now () -> format ( 'H:i:s' ));
}
2026-01-24 14:03:11 +00:00
});
2026-07-27 22:09:08 +03:30
try {
app ( \App\Services\CustomerAppNotifyService :: class ) -> notifyCustomer (
( int ) $customer -> id ,
'درخواست برداشت' ,
'درخواست برداشت مبلغ ' . number_format (( float ) $withdrawAmount ) . ' ریال ثبت شد.' ,
'info'
);
} catch ( \Throwable $e ) {
report ( $e );
}
2026-08-23 14:16:00 +03:30
\App\Services\Automation\AutomationEvents :: fire ( 'withdraw.request' , ( int ) $customer -> id );
2026-07-27 22:09:08 +03:30
2026-01-24 14:03:11 +00:00
return response () -> json ( $Withdraw );
}
2026-11-08 13:26:28 +03:30
public function depositRequest ( Request $request )
{
$customer = auth ( 'customer' ) -> user ();
if ( ! $customer ) {
throw new UnprocessableEntityHttpException ( 'کاربر یافت نشد.' );
}
$request -> validate ([
'cash' => 'required|numeric|min:1000' ,
'receipt' => 'required|file|mimes:jpeg,jpg,png,webp,pdf|max:10240' ,
'tracked_number' => 'nullable|string|max:100' ,
'extra_note' => 'nullable|string|max:500' ,
], [
'cash.required' => 'مبلغ واریزی الزامی است.' ,
'cash.numeric' => 'مبلغ واریزی باید به صورت عددی باشد.' ,
'cash.min' => 'حداقل مبلغ واریزی معتبر نیست.' ,
'receipt.required' => 'بارگذاری تصویر فیش واریزی الزامی است.' ,
'receipt.file' => 'فایل فیش ارسالی نامعتبر است.' ,
'receipt.mimes' => 'فرمت فیش واریزی باید یکی از فرمتهای jpg, png, webp یا pdf باشد.' ,
'receipt.max' => 'حجم فایل فیش واریزی نباید بیش از ۱۰ مگابایت باشد.' ,
]);
$uploaded = $request -> file ( 'receipt' );
$now = verta ();
$dirPath = 'receipts/' . $now -> year . '/' . sprintf ( '%02d' , $now -> month );
$disk = \Illuminate\Support\Facades\Storage :: disk ( 'public' );
if ( ! $disk -> exists ( $dirPath )) {
$disk -> makeDirectory ( $dirPath );
}
$ext = $uploaded -> getClientOriginalExtension () ?: 'jpg' ;
$fileName = md5 ( time () . '_' . $uploaded -> getClientOriginalName () . '_' . $customer -> id ) . '.' . $ext ;
$stored = $disk -> putFileAs ( $dirPath , $uploaded , $fileName );
if ( ! $stored ) {
throw new UnprocessableEntityHttpException ( 'خطا در ذخیره تصویر فیش واریزی.' );
}
$relativePath = 'storage/' . $stored ;
try {
\App\Models\File :: create ([
'original_name' => $uploaded -> getClientOriginalName (),
'ext' => $ext ,
'title' => 'فیش واریز ' . ( $customer -> name ?? '' ),
'has_thumb' => false ,
'size' => $uploaded -> getSize (),
'src' => $relativePath ,
'user_id' => $customer -> id ,
]);
} catch ( \Throwable $e ) {
report ( $e );
}
$nationalCode = trim (( string ) ( $customer -> n_code ?? '' ));
$withdraw = Withdraw :: create ([
'customer_id' => $customer -> id ,
'cash' => ( float ) $request -> cash ,
'gold' => 0 ,
'type' => 1 ,
'sts' => 1 ,
'tracked_number' => $request -> tracked_number ? trim (( string ) $request -> tracked_number ) : null ,
'deposit_identifier' => $nationalCode ?: null ,
'receipt' => $relativePath ,
'extra_note' => $request -> extra_note ? trim (( string ) $request -> extra_note ) : null ,
'note' => 'درخواست واریز نقدی (فیش بانکی)' ,
'date_at' => now (),
]);
try {
$companyName = $this -> Config ( 'name_fa' );
$amountFormatted = number_format (( int ) $request -> cash );
$userName = $customer -> name ?? 'مشتری' ;
$userMobile = $customer -> mobile ?? '' ;
$message = "کاربر $userName با شماره موبایل $userMobile درخواست واریز مبلغ { $amountFormatted } ریال با شناسه واریز { $nationalCode } ثبت نمود. با تشکر، $companyName " ;
User :: where ( 'send_sms' , 1 ) -> get () -> each ( function ( $admin ) use ( $message ) {
if ( self :: isSmsNotifEnabled ( 'sms_notif_withdraw_admin' )) {
$this -> Send ( $admin -> mobile , $message . ' ' . now () -> format ( 'H:i:s' ));
}
});
} catch ( \Throwable $e ) {
report ( $e );
}
return response () -> json ([
'message' => 'درخواست واریز شما با موفقیت ثبت شد و پس از بررسی تایید خواهد شد.' ,
'withdraw' => $withdraw -> loadMissing ( 'customer' ),
]);
}
2026-07-13 19:03:30 +03:30
private function formatDeliveryQuantity ( float $value ) : string
2026-07-13 13:31:44 +03:30
{
$rounded = round ( $value , 3 );
if ( fmod ( $rounded , 1.0 ) === 0.0 ) {
return number_format ( $rounded , 0 );
}
return rtrim ( rtrim ( number_format ( $rounded , 3 , '.' , '' ), '0' ), '.' );
}
2026-01-24 14:03:11 +00:00
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 );
2026-07-27 16:05:54 +03:30
if ( ! isset ( $data [ 'address_id' ]) || ( int ) $data [ 'address_id' ] <= 0 ) {
2026-01-24 14:03:11 +00:00
return response () -> json ([ 'message' => 'آدرس انتخاب نشده است' ], 422 );
}
2026-07-27 16:05:54 +03:30
$addressId = ( int ) $data [ 'address_id' ];
$data [ 'adress' ] = Address :: query ()
-> where ( 'id' , $addressId )
-> where ( 'customer_id' , $data [ 'customer' ] -> id )
-> first ();
if ( ! $data [ 'adress' ]) {
2026-01-24 14:03:11 +00:00
return response () -> json ([ 'message' => 'آدرس معتبر نیست یا متعلق به شما نمیباشد' ], 403 );
}
2026-07-27 16:05:54 +03:30
$data [ 'address_id' ] = $addressId ;
2026-07-06 17:06:03 +03:30
$assetSummaryService = app ( AssetSummaryService :: class );
2026-01-24 14:03:11 +00:00
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 );
}
2026-06-10 17:31:38 +03:30
2026-07-06 17:06:03 +03:30
$productId = ( int ) $data [ 'wallets' ][ $key ][ 'wallet' ][ 'product_id' ];
$customerId = ( int ) $data [ 'customer' ][ 'id' ];
$ownerType = ( int ) auth ( 'customer' ) -> user () -> type ;
// موجودی و کیف مؤثر — همسان با assets-summary و صفحهٔ اصلی
$effectiveProductId = $assetSummaryService
-> effectiveProductWalletIdForCustomer ( $customerId , $ownerType , $productId );
$availableGold = $assetSummaryService
-> productDisplayQuantity ( $customerId , $ownerType , $productId );
2026-06-10 17:31:38 +03:30
2026-07-13 19:03:30 +03:30
// گیت تحویل فیزیکی per-product — سوییچ محصول و حداقل مقدار (به واحد خود محصول)
2026-07-13 13:31:44 +03:30
$walletProduct = $data [ 'wallets' ][ $key ][ 'wallet' ][ 'product' ];
if ( Product :: hasPhysicalDeliveryColumns () && ! $walletProduct -> physicalDeliveryEnabled ()) {
return response () -> json ([
'message' => "امکان تحویل فیزیکی برای { $walletProduct [ 'title' ] } وجود ندارد"
], 422 );
}
2026-07-13 19:03:30 +03:30
$minDeliveryQuantity = Product :: hasPhysicalDeliveryColumns ()
? $walletProduct -> minPhysicalDeliveryQuantity ()
2026-07-13 13:31:44 +03:30
: 0.0 ;
2026-07-13 19:03:30 +03:30
if ( $minDeliveryQuantity > 0 ) {
// موجودی و حداقل هر دو با واحد خود محصول سنجیده میشوند:
// تعدادی → عدد، وزنی → گرم/کیلوگرم (همسان با inventory_cards)
2026-07-13 13:31:44 +03:30
$card = $assetSummaryService -> productInventoryCard ( $customerId , $ownerType , $productId );
2026-07-13 19:03:30 +03:30
$unitLabel = $card [ 'unit' ] ?? Controller :: productQuantityUnit (
Controller :: productUnitPayload ( $walletProduct )
);
2026-07-13 13:31:44 +03:30
2026-07-13 19:03:30 +03:30
if ( $availableGold + 0.0001 < $minDeliveryQuantity ) {
$remaining = max ( 0 , $minDeliveryQuantity - $availableGold );
2026-07-13 13:31:44 +03:30
return response () -> json ([
2026-07-13 19:03:30 +03:30
'message' => $this -> formatDeliveryQuantity ( $remaining )
. " { $unitLabel } مانده تا رسیدن به حداقل تحویل فیزیکی "
2026-07-13 13:31:44 +03:30
. " { $walletProduct [ 'title' ] } "
], 422 );
}
}
2026-06-10 17:31:38 +03:30
if ( $data [ 'wallets' ][ $key ][ 'value' ] > $availableGold ){
2026-01-24 14:03:11 +00:00
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 );
}
2026-07-16 17:29:18 +03:30
$requestValue = ( float ) $data [ 'wallets' ][ $key ][ 'value' ];
if ( Controller :: isCountBasedProduct ( Controller :: productUnitPayload ( $walletProduct ))) {
if ( abs ( $requestValue - round ( $requestValue )) > 0.000001 ) {
return response () -> json ([
'message' => "برای { $walletProduct [ 'title' ] } فقط تعداد صحیح مجاز است"
], 422 );
}
$data [ 'wallets' ][ $key ][ 'value' ] = ( int ) round ( $requestValue );
}
2026-01-24 14:03:11 +00:00
2026-06-10 17:31:38 +03:30
$data [ 'wallets' ][ $key ][ 'wallet_interface' ] = new WalletInterface ( auth ( 'customer' ) -> id (), auth ( 'customer' ) -> user () -> type , null , $effectiveProductId );
2026-01-24 14:03:11 +00:00
}
$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 );
}
2026-07-28 17:44:24 +03:30
foreach ( $data [ 'wallets' ] as $walletRow ) {
$product = $walletRow [ 'wallet' ][ 'product' ] ?? null ;
if ( ! $product ) {
continue ;
}
$productModel = $product instanceof Product ? $product : Product :: find ( $product [ 'id' ] ?? 0 );
if ( ! $productModel ) {
continue ;
}
try {
app ( \App\Services\AccountingWalletPush :: class ) -> pushProductWalletGold (
$data [ 'customer' ],
$productModel ,
( float ) $walletRow [ 'value' ],
false ,
( int ) $data [ 'withdraw' ] -> id ,
'برداشت فیزیکی ' . $productModel -> title
);
} catch ( \Throwable $e ) {
report ( $e );
}
}
2026-01-24 14:03:11 +00:00
return response () -> json ( $data [ 'withdraw' ][ 'id' ]);
}
public function indexWithdraw ( Request $request ){
2026-07-27 16:05:54 +03:30
$records = Withdraw :: with ([ 'customer' , 'bank_account' , 'address' , "Transaction" ]) -> where ( "customer_id" , auth ( 'customer' ) -> id ());
2026-01-24 14:03:11 +00:00
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 ){
2026-07-27 16:05:54 +03:30
$records = Withdraw :: with ([ 'customer' , 'bank_account' , 'address' , "Transaction.product.file" ]) -> where ( "customer_id" , auth ( 'customer' ) -> id ()) -> whereBetween ( 'sts' , [ 49 , 70 ]);
2026-01-24 14:03:11 +00:00
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 )
{
2026-06-05 16:45:40 +03:30
$data = $request -> all ();
if ( ! isset ( $data [ 'value' ])) {
return response () -> json ([ 'message' => 'مبلغ برداشت مشخص نشده است' ], 422 );
}
if ( $data [ 'value' ] < 0 ) {
throw new UnprocessableEntityHttpException ( 'مقدار برداشت باید مثبت باشد' );
}
$this -> validateWithdrawAmount (( int ) $data [ 'value' ]);
2026-01-24 14:03:11 +00:00
$customer = auth ( 'customer' ) -> user ();
2026-10-20 09:10:01 +03:30
self :: assertCustomerIdentityVerified ( $customer instanceof Customer ? $customer : null );
2026-01-24 14:03:11 +00:00
$otp = rand ( 1000 , 9999 );
$customer -> token_sms = $otp ;
$customer -> save ();
2026-07-18 22:22:01 +03:30
if ( self :: isSmsNotifEnabled ( 'sms_notif_withdraw_otp' )) {
$this -> Send (
$customer -> mobile ,
'رمز عبور یکبار مصرف شما جهت برداشت پول از اپلیکیشن ' . $this -> Config ( 'name_fa' ) . ' می باشد: ' . $otp
);
}
2026-01-24 14:03:11 +00:00
return response () -> json ( true );
}
2026-09-05 17:01:39 +03:30
private function debitCustomerRialWithdrawOnce ( Withdraw $withdraw , int $amount , Customer $customer ) : void
{
$alreadyDebited = Transaction :: query ()
-> where ( 'withdraw_id' , $withdraw -> id )
-> where ( 'cash' , '<' , 0 )
-> exists ();
if ( $alreadyDebited ) {
return ;
}
$wallet = new WalletInterface (( int ) $customer -> id , ( int ) $customer -> type );
new TransactionInterface ( $wallet , [
'cash' => $amount ,
'expire_at' => now (),
'withdraw_id' => $withdraw -> id ,
'active' => true ,
], 4 , false , false );
}
2026-06-05 16:45:40 +03:30
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 ) . ' ریال است' );
}
}
2026-06-09 13:34:43 +03:30
private function applyAbshodeProductTransactionTitle ( $record ) : void
{
if ( self :: isCashTransactionTitle ( $record -> title ?? null )) {
return ;
}
2026-06-29 22:15:48 +03:30
$product = $this -> resolveTransactionProductForLedgerTitle ( $record );
2026-06-22 21:42:48 +03:30
if ( ! $product || trim (( string ) $product -> title ) === '' ) {
2026-06-09 13:34:43 +03:30
return ;
}
2026-06-22 21:42:48 +03:30
$productTitle = trim (( string ) $product -> title );
2026-06-09 13:34:43 +03:30
$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 ;
}
2026-06-22 21:42:48 +03:30
$noteProductId = $this -> productIdFromTransactionNote ( $record -> note ?? null );
if ( $noteProductId ) {
return $noteProductId ;
}
$wallet = $this -> resolveTransactionWallet ( $record );
if ( $wallet && ! empty ( $wallet -> product_id )) {
return ( int ) $wallet -> product_id ;
2026-06-09 13:34:43 +03:30
}
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' );
2026-06-22 21:42:48 +03:30
if ( $siblingProductId ) {
return ( int ) $siblingProductId ;
}
$siblingNotes = Transaction :: query ()
-> where ( 'order_id' , $record -> order_id )
-> whereNotNull ( 'note' )
-> pluck ( 'note' );
foreach ( $siblingNotes as $note ) {
$productId = $this -> productIdFromTransactionNote ( $note );
if ( $productId ) {
return $productId ;
}
}
return null ;
}
private function productIdFromTransactionNote ( ? string $note ) : ? int
{
$note = ( string ) ( $note ?? '' );
if ( $note === '' || ! preg_match ( '/(?:^|;)product_id:(\d+)/' , $note , $matches )) {
return null ;
}
return ( int ) $matches [ 1 ];
2026-06-09 13:34:43 +03:30
}
2026-06-03 20:24:07 +03:30
private function applyMetalTypeTransactionTitle ( $record ) : void
{
2026-06-09 13:34:43 +03:30
if ( $this -> resolveAbshodeProductId ( $record )) {
return ;
}
2026-06-03 21:37:05 +03:30
if ( ! empty ( $record -> product_id )) {
return ;
}
2026-06-03 20:24:07 +03:30
2026-06-22 21:42:48 +03:30
$wallet = $this -> resolveTransactionWallet ( $record );
2026-06-03 21:37:05 +03:30
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 )) {
2026-06-24 01:17:34 +03:30
if (( int ) ( $record -> type ?? 0 ) === 7 ) {
$record -> title = self :: mainMetalDepositTitle ();
} else {
$record -> title = self :: meltedGoldDepositTitle ();
}
2026-06-03 21:37:05 +03:30
} elseif ( preg_match ( '/^برداشت /u' , ( string ) $record -> title )) {
2026-06-24 01:17:34 +03:30
if (( int ) ( $record -> type ?? 0 ) === 10 ) {
$record -> title = self :: mainMetalWithdrawTitle ();
} else {
$record -> title = self :: meltedGoldWithdrawTitle ();
}
2026-06-03 21:37:05 +03:30
} elseif ( preg_match ( '/^خرید /u' , ( string ) $record -> title )) {
$record -> title = self :: meltedGoldBuyTitle ();
} elseif ( preg_match ( '/^فروش /u' , ( string ) $record -> title )) {
$record -> title = self :: meltedGoldSellTitle ();
2026-06-03 20:24:07 +03:30
}
}
2026-06-03 21:37:05 +03:30
private function applyProductTransactionTitle ( $record ) : void
{
if ( self :: isCashTransactionTitle ( $record -> title )) {
return ;
}
2026-06-29 22:15:48 +03:30
$product = $this -> resolveTransactionProductForLedgerTitle ( $record );
2026-06-22 21:42:48 +03:30
if ( ! $product || trim (( string ) $product -> title ) === '' ) {
2026-06-03 21:37:05 +03:30
return ;
}
2026-06-22 21:42:48 +03:30
$productTitle = trim (( string ) $product -> title );
2026-06-03 21:37:05 +03:30
$expectedBuy = self :: productBuyTitle ( $productTitle );
$expectedSell = self :: productSellTitle ( $productTitle );
2026-06-09 13:34:43 +03:30
if ( in_array ( $record -> title , [ $expectedBuy , $expectedSell ], true )) {
2026-06-03 21:37:05 +03:30
return ;
}
2026-06-09 13:34:43 +03:30
$direction = ( int ) ( $record -> order -> direction ?? 0 );
if ( $direction === 1 || ( $direction === 0 && ( float ) ( $record -> gold ?? 0 ) > 0 )) {
2026-06-03 21:37:05 +03:30
$record -> title = $expectedBuy ;
return ;
}
2026-06-09 13:34:43 +03:30
if ( $direction === 2 || ( $direction === 0 && ( float ) ( $record -> gold ?? 0 ) < 0 )) {
2026-06-03 21:37:05 +03:30
$record -> title = $expectedSell ;
2026-06-22 21:42:48 +03:30
return ;
}
if ( self :: isGenericMetalTransactionTitle ( $record -> title )) {
$record -> title = $expectedBuy ;
2026-06-03 21:37:05 +03:30
}
}
private function resolveMainWalletTransactionTitle ( $record ) : string
{
2026-06-22 21:42:48 +03:30
$product = $this -> resolveTransactionProduct ( $record );
if ( $product && trim (( string ) $product -> title ) !== '' ) {
$productTitle = trim (( string ) $product -> title );
if (( int ) $record -> type === 1 ) {
return self :: productBuyTitle ( $productTitle );
}
if (( int ) $record -> type === 2 ) {
return self :: productSellTitle ( $productTitle );
}
}
2026-06-03 21:37:05 +03:30
if (( int ) $record -> type === 1 ) {
2026-06-24 01:17:34 +03:30
return self :: mainMetalBuyTitle ();
2026-06-03 21:37:05 +03:30
}
if (( int ) $record -> type === 2 ) {
2026-06-24 01:17:34 +03:30
return self :: mainMetalSellTitle ();
}
if (( int ) $record -> type === 7 ) {
return self :: mainMetalDepositTitle ();
}
if (( int ) $record -> type === 10 ) {
return self :: mainMetalWithdrawTitle ();
2026-06-03 21:37:05 +03:30
}
2026-07-18 15:23:16 +03:30
if (( int ) $record -> type === 12 ) {
return 'برداشت وجه نقد' ;
}
if (( int ) $record -> type === 22 ) {
2026-07-19 15:53:05 +03:30
return 'تعدیل حسابداری پرگار' ;
2026-07-18 15:23:16 +03:30
}
2026-08-30 14:16:36 +03:30
if (( int ) $record -> type === 23 ) {
return 'تعدیل حسابداری تهحساب' ;
}
2026-06-03 21:37:05 +03:30
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 )) {
2026-06-22 21:29:25 +03:30
$this -> applyProductTransactionUnit ( $record );
2026-06-03 21:37:05 +03:30
}
}
2026-06-22 21:42:48 +03:30
private function resolveTransactionWallet ( $record ) : ? Wallet
{
if ( $record instanceof Transaction ) {
$relation = $record -> getRelationValue ( 'wallet' );
if ( $relation instanceof Wallet ) {
return $relation ;
}
}
$walletId = ( int ) ( $record -> getAttributes ()[ 'wallet' ] ?? 0 );
if ( $walletId <= 0 ) {
return null ;
}
return Wallet :: with ( 'product' ) -> find ( $walletId );
}
2026-06-29 22:15:48 +03:30
/**
* عنوان هر سطر دفترکل — محصول همان کیف پول (مثلاً کاتد در سطر برداشت کاتد برای خرید مفتول).
*/
private function resolveTransactionProductForLedgerTitle ( $record ) : ? Product
{
$wallet = $this -> resolveTransactionWallet ( $record );
if ( $wallet ?-> product instanceof Product && trim (( string ) $wallet -> product -> title ) !== '' ) {
return $wallet -> product ;
}
return $this -> resolveTransactionProduct ( $record );
}
2026-06-22 21:29:25 +03:30
private function resolveTransactionProduct ( $record ) : ? Product
{
2026-06-22 21:42:48 +03:30
$productId = $this -> resolveAbshodeProductId ( $record ) ?? 0 ;
$wallet = $this -> resolveTransactionWallet ( $record );
2026-06-22 21:29:25 +03:30
if ( $productId <= 0 ) {
2026-06-22 21:42:48 +03:30
$partial = $wallet ?-> product ?? $record -> product ?? null ;
2026-06-22 21:29:25 +03:30
return $partial instanceof Product ? $partial : null ;
}
2026-06-22 21:42:48 +03:30
$partial = $record -> product ?? $wallet ?-> product ?? null ;
if ( $partial instanceof Product && ( int ) $partial -> id === $productId && trim (( string ) $partial -> title ) !== '' ) {
return $partial ;
}
2026-06-22 21:29:25 +03:30
return Product :: query ()
-> select ([
'id' ,
'title' ,
'group_type' ,
'unit' ,
'direct_price' ,
'amount' ,
'trade_by_quantity' ,
'metal_id' ,
])
-> find ( $productId ) ?? ( $partial instanceof Product ? $partial : null );
}
2026-06-03 21:52:37 +03:30
private function applyProductTransactionUnit ( $record ) : void
{
2026-06-22 21:29:25 +03:30
$product = $this -> resolveTransactionProduct ( $record );
2026-06-03 21:52:37 +03:30
if ( ! $product ) {
2026-06-22 21:29:25 +03:30
if ( ! empty ( $record -> gold )) {
2026-06-09 11:17:07 +03:30
$record -> unit = self :: tradeWeightUnit ();
}
2026-06-03 21:52:37 +03:30
return ;
}
2026-06-22 21:29:25 +03:30
$payload = self :: productUnitPayload ( $product );
$onMainWallet = empty ( $record -> wallet ?-> product_id );
[ $displayGold , $unit ] = self :: resolveTransactionDisplayGoldAndUnit (
$payload ,
( float ) ( $record -> gold ?? 0 ),
$onMainWallet
);
$record -> gold = $displayGold ;
$record -> unit = $unit ;
2026-06-03 21:52:37 +03:30
}
2026-06-03 21:37:05 +03:30
private function isProductWalletTransaction ( $record ) : bool
{
2026-06-22 21:42:48 +03:30
$wallet = $this -> resolveTransactionWallet ( $record );
return ! empty ( $wallet ?-> product );
2026-06-03 21:37:05 +03:30
}
2026-06-04 18:53:06 +03:30
/**
2026-06-04 19:22:01 +03:30
* عنوان/وضعیت تراکنشهای API فروشگاه — فقط نمایش؛ واریز/برداشت عادی دست نخورده.
2026-06-04 18:53:06 +03:30
*/
2026-06-04 19:22:01 +03:30
private function applyShopTransactionPresentation ( $record ) : void
2026-06-04 18:53:06 +03:30
{
2026-06-04 19:09:58 +03:30
if ( $this -> isShopGoldTransaction ( $record )) {
2026-06-04 19:22:01 +03:30
if ( empty ( $record -> title ) || $record -> title === 'تعریف نشده' ) {
$record -> title = 'برداشت طلایی' ;
}
2026-06-04 19:09:58 +03:30
}
}
2026-06-04 19:22:01 +03:30
/**
* خروجی JSON — withdraw/order را صریحاً میگذارد (setRelation گاهی در JSON نمیآید).
*/
private function serializeTransactionRowsForApi ( $rows ) : array
{
$out = [];
foreach ( $rows as $record ) {
$row = $record instanceof Transaction ? $record -> toArray () : ( array ) $record ;
2026-07-02 21:11:37 +03:30
$this -> attachMissingOrderForApi ( $row );
2026-06-04 19:22:01 +03:30
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' ] = 'برداشت طلایی' ;
}
2026-07-02 20:59:26 +03:30
$this -> normalizeApiRelationStsStr ( $row , 'order' , Order :: $STS );
$this -> normalizeApiRelationStsStr ( $row , 'withdraw' , Withdraw :: $STS );
2026-06-04 19:22:01 +03:30
$out [] = $row ;
}
return $out ;
}
/** برداشت ریال API فروشگاه — type=4، بدون withdraw_id، مبلغ منفی */
2026-06-04 19:16:37 +03:30
private function isShopRialTransaction ( $record ) : bool
{
2026-06-04 19:22:01 +03:30
if (( int ) $record -> type !== 4 || ! empty ( $record -> withdraw_id )) {
2026-06-04 19:16:37 +03:30
return false ;
}
2026-06-04 19:22:01 +03:30
return ( int ) ( $record -> cash ?? 0 ) < 0 ;
2026-06-04 19:16:37 +03:30
}
2026-06-04 19:22:01 +03:30
/** برداشت طلا API فروشگاه — type=10 + order_id، بدون withdraw */
2026-06-04 19:09:58 +03:30
private function isShopGoldTransaction ( $record ) : bool
{
2026-06-04 19:22:01 +03:30
if (( int ) $record -> type !== 10 || empty ( $record -> order_id ) || ! empty ( $record -> withdraw_id )) {
2026-06-04 19:09:58 +03:30
return false ;
}
2026-06-04 19:22:01 +03:30
$order = $this -> resolveOrderForTransaction ( $record );
if ( ! $order ) {
return true ;
}
2026-06-04 19:09:58 +03:30
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 ;
}
2026-06-04 19:22:01 +03:30
return ( int ) ( $order -> sts ?? 0 ) === 2 && ( int ) ( $order -> payment_sts ?? 0 ) === 4 ;
2026-06-04 19:09:58 +03:30
}
2026-06-04 19:22:01 +03:30
private function resolveOrderForTransaction ( $record ) : ? Order
2026-06-04 19:09:58 +03:30
{
2026-06-04 19:22:01 +03:30
if ( $record -> order instanceof Order ) {
return $record -> order ;
2026-06-04 18:53:06 +03:30
}
2026-06-04 19:16:37 +03:30
2026-06-04 19:22:01 +03:30
if ( empty ( $record -> order_id )) {
return null ;
}
return Order :: query ()
-> select ([ '*' , Order :: $SELECT_STS_STR ])
-> find ( $record -> order_id );
2026-06-04 18:53:06 +03:30
}
2026-06-03 21:37:05 +03:30
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 ;
}
2026-07-02 20:59:26 +03:30
private function normalizeApiRelationStsStr ( array & $row , string $key , array $stsMap ) : void
{
if ( empty ( $row [ $key ]) || ! is_array ( $row [ $key ])) {
return ;
}
$relation = & $row [ $key ];
$sts = $relation [ 'sts' ] ?? null ;
if ( $sts === null && isset ( $relation [ 'sts_str' ]) && is_numeric ( $relation [ 'sts_str' ])) {
$sts = ( int ) $relation [ 'sts_str' ];
}
if ( $sts !== null && isset ( $stsMap [ $sts ])) {
$relation [ 'sts_str' ] = $stsMap [ $sts ];
return ;
}
$current = trim (( string ) ( $relation [ 'sts_str' ] ?? '' ));
if ( $current !== '' && $current !== 'بدون مقدار' ) {
return ;
}
if ( $key === 'order' && ( int ) ( $relation [ 'payment_sts' ] ?? 0 ) === 4 ) {
$relation [ 'sts_str' ] = 'موفق' ;
}
}
2026-07-02 21:11:37 +03:30
/**
* بعضی تراکنشهای قدیمی order_id دارند ولی ردیف orders حذف شده — برای UI وضعیت/قیمت را بازسازی میکنیم.
*/
private function attachMissingOrderForApi ( array & $row ) : void
{
$orderId = ( int ) ( $row [ 'order_id' ] ?? 0 );
if ( $orderId <= 0 ) {
return ;
}
$order = $row [ 'order' ] ?? null ;
if ( is_array ( $order ) && ! empty ( $order [ 'id' ])) {
return ;
}
$loaded = Order :: query () -> find ( $orderId );
if ( $loaded ) {
$row [ 'order' ] = $loaded -> toArray ();
return ;
}
$type = ( int ) ( $row [ 'type' ] ?? 0 );
if ( ! in_array ( $type , [ 1 , 2 , 10 ], true )) {
return ;
}
$row [ 'order' ] = [
'id' => $orderId ,
'sts' => 2 ,
'payment_sts' => 4 ,
'sts_str' => 'موفق' ,
'price' => $this -> inferTradePriceFromTransactionRow ( $row ),
'amount' => abs (( float ) ( $row [ 'gold' ] ?? 0 )),
];
}
private function inferTradePriceFromTransactionRow ( array $row ) : int
{
$cash = abs (( int ) ( $row [ 'cash' ] ?? 0 ));
$gold = abs (( float ) ( $row [ 'gold' ] ?? 0 ));
if ( $gold > 0 && $cash > 0 ) {
return ( int ) round ( $cash / $gold );
}
return 0 ;
}
2026-01-24 14:03:11 +00:00
}