feat: 贷款模块后端 — 申请+审批+放款
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Loan;
|
||||
use App\UsersWallet;
|
||||
use App\AccountLog;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class LoanController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
return view('manages.loan.index');
|
||||
}
|
||||
|
||||
public function list(Request $request)
|
||||
{
|
||||
$limit = $request->input('limit', 20);
|
||||
$status = $request->input('status', '');
|
||||
|
||||
$query = Loan::query()
|
||||
->leftJoin('users', 'loan.user_id', '=', 'users.id')
|
||||
->select('loan.*', 'users.phone as user_phone', 'users.email as user_email', 'users.account_number');
|
||||
|
||||
if ($status !== '') {
|
||||
$query->where('loan.status', $status);
|
||||
}
|
||||
|
||||
$keyword = $request->input('keyword');
|
||||
if ($keyword) {
|
||||
$query->where(function ($q) use ($keyword) {
|
||||
$q->where('loan.user_id', $keyword)
|
||||
->orWhere('users.phone', 'like', "%{$keyword}%")
|
||||
->orWhere('users.email', 'like', "%{$keyword}%");
|
||||
});
|
||||
}
|
||||
|
||||
$data = $query->orderBy('loan.id', 'desc')->paginate($limit);
|
||||
|
||||
return $this->layuiData($data);
|
||||
}
|
||||
|
||||
public function approve(Request $request)
|
||||
{
|
||||
$id = $request->input('id');
|
||||
$loan = Loan::find($id);
|
||||
if (!$loan) return $this->error('Loan not found');
|
||||
if ($loan->status != 0) return $this->error('Loan already processed');
|
||||
|
||||
DB::beginTransaction();
|
||||
try {
|
||||
$wallet = UsersWallet::where('user_id', $loan->user_id)
|
||||
->where('currency', 3)->lockForUpdate()->first();
|
||||
if (!$wallet) {
|
||||
DB::rollBack();
|
||||
return $this->error('User wallet not found');
|
||||
}
|
||||
|
||||
$before = $wallet->legal_balance;
|
||||
$wallet->legal_balance = bc_add($wallet->legal_balance, $loan->amount, 6);
|
||||
$wallet->save();
|
||||
|
||||
AccountLog::insertLog(
|
||||
[
|
||||
'user_id' => $loan->user_id,
|
||||
'value' => $loan->amount,
|
||||
'created_time' => time(),
|
||||
'info' => 'Loan Approved - ' . $loan->institution,
|
||||
'type' => AccountLog::USER_LOAN_ORDER_BUY,
|
||||
'currency' => 3,
|
||||
],
|
||||
[
|
||||
'balance_type' => 1,
|
||||
'wallet_id' => $wallet->id,
|
||||
'lock_type' => 0,
|
||||
'before' => $before,
|
||||
'change' => $loan->amount,
|
||||
'after' => $wallet->legal_balance,
|
||||
'create_time' => time(),
|
||||
]
|
||||
);
|
||||
|
||||
$loan->status = 1;
|
||||
$loan->update_time = now();
|
||||
$loan->save();
|
||||
|
||||
DB::commit();
|
||||
return $this->success('Loan approved');
|
||||
} catch (\Exception $e) {
|
||||
DB::rollBack();
|
||||
return $this->error('Error: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function reject(Request $request)
|
||||
{
|
||||
$id = $request->input('id');
|
||||
$remark = $request->input('remark', '');
|
||||
$loan = Loan::find($id);
|
||||
if (!$loan) return $this->error('Loan not found');
|
||||
if ($loan->status != 0) return $this->error('Loan already processed');
|
||||
|
||||
$loan->status = 2;
|
||||
$loan->remark = $remark;
|
||||
$loan->update_time = now();
|
||||
$loan->save();
|
||||
|
||||
return $this->success('Loan rejected');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Api\Controller;
|
||||
use App\Loan;
|
||||
use App\UsersWallet;
|
||||
use App\AccountLog;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Token;
|
||||
use App\Users;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class LoanController extends Controller
|
||||
{
|
||||
public function institution()
|
||||
{
|
||||
$name = DB::table('settings')->where('key', 'loan_institution')->value('value') ?? 'Global Finance';
|
||||
return $this->success(['institution' => $name]);
|
||||
}
|
||||
|
||||
public function submit(Request $request)
|
||||
{
|
||||
$user_id = Token::getUserIdByToken(Token::getToken());
|
||||
$user = \App\Users::find($user_id);
|
||||
if (!$user) return $this->error('Please login first');
|
||||
|
||||
$amount = $request->input('amount');
|
||||
$card_image = $request->input('card_image', '');
|
||||
$cardbm_image = $request->input('cardbm_image', '');
|
||||
$hand_image = $request->input('hand_image', '');
|
||||
|
||||
if (!$amount || !is_numeric($amount) || $amount <= 0) {
|
||||
return $this->error('Please enter a valid amount');
|
||||
}
|
||||
if (!$card_image || !$cardbm_image || !$hand_image) {
|
||||
return $this->error('Please upload all required documents');
|
||||
}
|
||||
|
||||
$pending = Loan::where('user_id', $user->id)->where('status', 0)->first();
|
||||
if ($pending) {
|
||||
return $this->error('You already have a pending loan application');
|
||||
}
|
||||
|
||||
$institution = DB::table('settings')->where('key', 'loan_institution')->value('value') ?? 'Global Finance';
|
||||
|
||||
Loan::create([
|
||||
'user_id' => $user->id,
|
||||
'amount' => $amount,
|
||||
'card_image' => $card_image,
|
||||
'cardbm_image' => $cardbm_image,
|
||||
'hand_image' => $hand_image,
|
||||
'institution' => $institution,
|
||||
'status' => 0,
|
||||
'create_time' => now(),
|
||||
]);
|
||||
|
||||
return $this->success('Loan application submitted successfully');
|
||||
}
|
||||
|
||||
public function myLoans(Request $request)
|
||||
{
|
||||
$user_id = Token::getUserIdByToken(Token::getToken());
|
||||
$user = \App\Users::find($user_id);
|
||||
if (!$user) return $this->error('Please login first');
|
||||
|
||||
$list = Loan::where('user_id', $user->id)->orderBy('id', 'desc')->limit(20)->get();
|
||||
$statusMap = ['0' => 'Pending', '1' => 'Approved', '2' => 'Rejected'];
|
||||
foreach ($list as $item) {
|
||||
$item->status_text = $statusMap[$item->status] ?? '';
|
||||
}
|
||||
return $this->success($list);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace App;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Loan extends Model
|
||||
{
|
||||
protected $table = 'loan';
|
||||
public $timestamps = false;
|
||||
protected $fillable = ['user_id', 'amount', 'card_image', 'cardbm_image', 'hand_image', 'institution', 'status', 'remark', 'create_time', 'update_time'];
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(Users::class, 'user_id', 'id');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
@extends('admin._layoutNew')
|
||||
@section('title', '贷款管理')
|
||||
@section('page-content')
|
||||
<div class="layui-card">
|
||||
<div class="layui-card-header">贷款审核管理</div>
|
||||
<div class="layui-card-body">
|
||||
<form class="layui-form" style="margin-bottom:10px;">
|
||||
<div class="layui-inline">
|
||||
<input id="f_keyword" class="layui-input" placeholder="用户ID/手机号/邮箱">
|
||||
</div>
|
||||
<div class="layui-inline">
|
||||
<button type="button" class="layui-btn layui-btn-sm" onclick="searchList()">搜索</button>
|
||||
</div>
|
||||
</form>
|
||||
<div class="layui-btn-group" style="margin-bottom:15px;">
|
||||
<button class="layui-btn layui-btn-sm" onclick="loadData()"><i class="layui-icon"></i> 刷新</button>
|
||||
<button class="layui-btn layui-btn-sm layui-btn-primary" onclick="filterStatus('')">全部</button>
|
||||
<button class="layui-btn layui-btn-sm layui-btn-warm" onclick="filterStatus(0)">待审核</button>
|
||||
<button class="layui-btn layui-btn-sm layui-btn-normal" onclick="filterStatus(1)">已通过</button>
|
||||
<button class="layui-btn layui-btn-sm layui-btn-danger" onclick="filterStatus(2)">已驳回</button>
|
||||
</div>
|
||||
<table id="loanTable" lay-filter="loanTable"></table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="text/html" id="imageTpl">
|
||||
@{{# if(d.card_image){ }}
|
||||
<img src="@{{ d.card_image }}" style="height:40px;cursor:pointer;" onclick="layer.photos({photos:{data:[{src:this.src}]}})">
|
||||
@{{# } }}
|
||||
</script>
|
||||
|
||||
<script type="text/html" id="statusTpl">
|
||||
@{{# if(d.status == 0){ }}
|
||||
<span class="layui-badge layui-bg-orange">待审核</span>
|
||||
@{{# } else if(d.status == 1){ }}
|
||||
<span class="layui-badge layui-bg-green">已通过</span>
|
||||
@{{# } else { }}
|
||||
<span class="layui-badge">已驳回</span>
|
||||
@{{# } }}
|
||||
</script>
|
||||
|
||||
<script type="text/html" id="actionTpl">
|
||||
@{{# if(d.status == 0){ }}
|
||||
<button class="layui-btn layui-btn-xs layui-btn-normal" lay-event="approve">通过</button>
|
||||
<button class="layui-btn layui-btn-xs layui-btn-danger" lay-event="reject">驳回</button>
|
||||
@{{# } }}
|
||||
</script>
|
||||
|
||||
@endsection
|
||||
@section('scripts')
|
||||
<script>
|
||||
var currentStatus = '';
|
||||
layui.use(['table', 'layer'], function(){
|
||||
var table = layui.table;
|
||||
|
||||
window.loadData = function(){
|
||||
table.reload('loanTable', {where: {status: currentStatus, keyword: $('#f_keyword').val()}});
|
||||
};
|
||||
window.searchList = loadData;
|
||||
window.filterStatus = function(s){
|
||||
currentStatus = s;
|
||||
loadData();
|
||||
};
|
||||
|
||||
table.render({
|
||||
elem: '#loanTable',
|
||||
url: '/admin/loan/list',
|
||||
page: true,
|
||||
cols: [[
|
||||
{field:'id', title:'ID', width:80, sort:true},
|
||||
{field:'user_id', title:'用户ID', width:80},
|
||||
{field:'user_email', title:'邮箱', width:180},
|
||||
{field:'account_number', title:'账号', width:120},
|
||||
{field:'amount', title:'贷款金额(USDT)', width:130},
|
||||
{field:'institution', title:'机构', width:120},
|
||||
{field:'card_image', title:'证件正面', width:80, templet:'#imageTpl'},
|
||||
{field:'cardbm_image', title:'证件背面', width:80, templet:function(d){
|
||||
return d.cardbm_image ? '<img src="'+d.cardbm_image+'" style="height:40px;cursor:pointer;" onclick="layer.photos({photos:{data:[{src:this.src}]}})">':'';
|
||||
}},
|
||||
{field:'hand_image', title:'手持照片', width:80, templet:function(d){
|
||||
return d.hand_image ? '<img src="'+d.hand_image+'" style="height:40px;cursor:pointer;" onclick="layer.photos({photos:{data:[{src:this.src}]}})">':'';
|
||||
}},
|
||||
{field:'status', title:'状态', width:90, templet:'#statusTpl'},
|
||||
{field:'remark', title:'备注', width:150},
|
||||
{field:'create_time', title:'申请时间', width:170},
|
||||
{fixed:'right', title:'操作', width:150, toolbar:'#actionTpl'}
|
||||
]]
|
||||
});
|
||||
|
||||
table.on('tool(loanTable)', function(obj){
|
||||
var data = obj.data;
|
||||
if(obj.event === 'approve'){
|
||||
layer.confirm('确定通过该笔贷款 '+data.amount+' USDT?', function(index){
|
||||
$.post('/admin/loan/approve', {id: data.id, _token: '{{csrf_token()}}'}, function(res){
|
||||
layer.close(index);
|
||||
if(res.type=='ok') { layer.msg('审核通过'); loadData(); }
|
||||
else layer.msg(res.message, {icon:2});
|
||||
});
|
||||
});
|
||||
}
|
||||
if(obj.event === 'reject'){
|
||||
layer.prompt({title:'驳回原因(可选)', formType:2}, function(remark, index){
|
||||
$.post('/admin/loan/reject', {id: data.id, remark: remark, _token: '{{csrf_token()}}'}, function(res){
|
||||
layer.close(index);
|
||||
if(res.type=='ok') { layer.msg('已驳回'); loadData(); }
|
||||
else layer.msg(res.message, {icon:2});
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@endsection
|
||||
Reference in New Issue
Block a user