feat: IEO后端 — 申购+配售码+开奖+后台
This commit is contained in:
@@ -0,0 +1,190 @@
|
||||
<?php
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\IeoProject;
|
||||
use App\IeoOrder;
|
||||
use App\IeoAllocationCode;
|
||||
use App\UsersWallet;
|
||||
use App\AccountLog;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class IeoController extends Controller
|
||||
{
|
||||
public function projectIndex() { return view('manages.ieo.projects'); }
|
||||
public function orderIndex() { return view('manages.ieo.orders'); }
|
||||
|
||||
public function projectList(Request $request)
|
||||
{
|
||||
$data = IeoProject::orderBy('id', 'desc')->paginate($request->input('limit', 20));
|
||||
return $this->layuiData($data);
|
||||
}
|
||||
|
||||
public function projectSave(Request $request)
|
||||
{
|
||||
$id = $request->input('id');
|
||||
$fields = $request->only([
|
||||
'name','symbol','description','whitepaper_url','thumb_image',
|
||||
'token_price','total_supply','min_buy','max_buy','start_time','end_time',
|
||||
'type','status','fake_progress',
|
||||
'subscription_limit','announce_time','listing_time','unlock_time'
|
||||
]);
|
||||
// Normalize empty datetime strings to null to avoid MySQL errors
|
||||
foreach (['announce_time','listing_time','unlock_time','start_time','end_time'] as $k) {
|
||||
if (isset($fields[$k]) && $fields[$k] === '') $fields[$k] = null;
|
||||
}
|
||||
if ($id) {
|
||||
IeoProject::where('id', $id)->update($fields);
|
||||
} else {
|
||||
IeoProject::create($fields);
|
||||
}
|
||||
return $this->success('OK');
|
||||
}
|
||||
|
||||
public function projectDelete(Request $request)
|
||||
{
|
||||
IeoProject::destroy($request->input('id'));
|
||||
return $this->success('Deleted');
|
||||
}
|
||||
|
||||
public function orderList(Request $request)
|
||||
{
|
||||
$query = IeoOrder::query()
|
||||
->leftJoin('users', 'ieo_order.user_id', '=', 'users.id')
|
||||
->leftJoin('ieo_project', 'ieo_order.project_id', '=', 'ieo_project.id')
|
||||
->select('ieo_order.*', 'users.phone as user_phone', 'users.email as user_email', 'ieo_project.name as project_name', 'ieo_project.symbol');
|
||||
|
||||
$project_id = $request->input('project_id');
|
||||
if ($project_id) $query->where('ieo_order.project_id', $project_id);
|
||||
|
||||
$data = $query->orderBy('ieo_order.id', 'desc')->paginate($request->input('limit', 20));
|
||||
$statusMap = ['0'=>'待开奖','1'=>'已中签','2'=>'未中签'];
|
||||
$forceMap = [null=>'-','1'=>'强制中签','2'=>'强制未中'];
|
||||
foreach ($data as $item) {
|
||||
$item->status_text = $statusMap[$item->status] ?? '';
|
||||
$item->force_text = $forceMap[$item->force_result] ?? '-';
|
||||
}
|
||||
return $this->layuiData($data);
|
||||
}
|
||||
|
||||
public function forceResult(Request $request)
|
||||
{
|
||||
$ids = $request->input('ids');
|
||||
$result = $request->input('result'); // 1=win, 2=lose
|
||||
if (!$ids) return $this->error('No orders selected');
|
||||
IeoOrder::whereIn('id', explode(',', $ids))->update(['force_result' => $result]);
|
||||
return $this->success('Set ' . ($result == 1 ? 'Force Win' : 'Force Lose'));
|
||||
}
|
||||
|
||||
public function lottery(Request $request)
|
||||
{
|
||||
$project_id = $request->input('project_id');
|
||||
$project = IeoProject::find($project_id);
|
||||
if (!$project) return $this->error('Project not found');
|
||||
|
||||
$orders = IeoOrder::where('project_id', $project_id)->where('status', 0)->get();
|
||||
if ($orders->isEmpty()) return $this->error('No pending orders');
|
||||
|
||||
$won = 0; $lost = 0;
|
||||
DB::beginTransaction();
|
||||
try {
|
||||
foreach ($orders as $order) {
|
||||
if ($order->force_result == 1) $result = 1;
|
||||
elseif ($order->force_result == 2) $result = 2;
|
||||
else $result = rand(1, 100) <= 50 ? 1 : 2;
|
||||
|
||||
if ($result == 1) {
|
||||
$order->update(['status' => 1, 'token_amount' => $project->token_price > 0 ? sprintf("%.6f", $order->amount / $project->token_price) : 0]);
|
||||
$won++;
|
||||
} else {
|
||||
$wallet = UsersWallet::where('user_id', $order->user_id)->where('currency', 3)->first();
|
||||
if ($wallet) {
|
||||
$before = $wallet->legal_balance;
|
||||
$wallet->legal_balance = bc_add($wallet->legal_balance, $order->amount, 6);
|
||||
$wallet->save();
|
||||
AccountLog::insertLog(
|
||||
['user_id'=>$order->user_id,'value'=>$order->amount,'info'=>'IEO Refund - '.$project->name,'type'=>AccountLog::IEO_OPERATION,'currency'=>3],
|
||||
['balance_type'=>1,'wallet_id'=>$wallet->id,'lock_type'=>0,'before'=>$before,'change'=>$order->amount,'after'=>$wallet->legal_balance]
|
||||
);
|
||||
}
|
||||
$order->update(['status' => 2]);
|
||||
$lost++;
|
||||
}
|
||||
}
|
||||
$creditOrders = \App\CreditIeoOrder::where('project_id', $project_id)->where('status', 0)->get();
|
||||
$creditWon = 0; $creditLost = 0;
|
||||
foreach ($creditOrders as $co) {
|
||||
$cResult = rand(1, 100) <= 50 ? 1 : 2;
|
||||
if ($cResult == 1) {
|
||||
$co->update([
|
||||
'status' => 4,
|
||||
'token_amount' => $project->token_price > 0 ? sprintf("%.6f", $co->amount / $project->token_price) : 0,
|
||||
]);
|
||||
$creditWon++;
|
||||
} else {
|
||||
$line = \App\CreditLine::find($co->credit_line_id);
|
||||
if ($line) {
|
||||
$line->used_amount = max(0, bc_sub($line->used_amount, $co->amount, 6));
|
||||
$line->available_amount = bc_add($line->available_amount, $co->amount, 6);
|
||||
if ($line->available_amount > $line->total_amount) $line->available_amount = $line->total_amount;
|
||||
$line->save();
|
||||
}
|
||||
$co->update(['status' => 2]);
|
||||
$creditLost++;
|
||||
}
|
||||
}
|
||||
|
||||
$project->update(['status' => 2]);
|
||||
DB::commit();
|
||||
return $this->success("Lottery: {$won} won/{$lost} lost, Credit: {$creditWon} won/{$creditLost} lost");
|
||||
} catch (\Exception $e) {
|
||||
DB::rollBack();
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function generateCodes(Request $request)
|
||||
{
|
||||
$project_id = intval($request->input('project_id'));
|
||||
$count = intval($request->input('count'));
|
||||
$amount = floatval($request->input('amount'));
|
||||
if (!$project_id || $count <= 0 || $amount <= 0) return $this->error('Invalid params');
|
||||
if ($count > 1000) return $this->error('Max 1000 per batch');
|
||||
|
||||
$project = IeoProject::find($project_id);
|
||||
if (!$project) return $this->error('Project not found');
|
||||
|
||||
$generated = 0; $attempts = 0;
|
||||
$maxAttempts = $count * 5;
|
||||
while ($generated < $count && $attempts < $maxAttempts) {
|
||||
$attempts++;
|
||||
$code = strtoupper(substr(str_shuffle('ABCDEFGHJKLMNPQRSTUVWXYZ23456789'), 0, 6));
|
||||
$exists = IeoAllocationCode::where('code', $code)->exists();
|
||||
if ($exists) continue;
|
||||
IeoAllocationCode::create([
|
||||
'project_id' => $project_id,
|
||||
'code' => $code,
|
||||
'amount' => $amount,
|
||||
'used' => 0,
|
||||
'user_id' => 0,
|
||||
]);
|
||||
$generated++;
|
||||
}
|
||||
return $this->success("Generated {$generated} codes");
|
||||
}
|
||||
|
||||
public function codeList(Request $request)
|
||||
{
|
||||
$project_id = $request->input('project_id');
|
||||
$query = IeoAllocationCode::query()
|
||||
->leftJoin('users', 'ieo_allocation_code.user_id', '=', 'users.id')
|
||||
->leftJoin('ieo_project', 'ieo_allocation_code.project_id', '=', 'ieo_project.id')
|
||||
->select('ieo_allocation_code.*', 'users.phone as user_phone', 'users.email as user_email', 'ieo_project.name as project_name');
|
||||
if ($project_id) $query->where('ieo_allocation_code.project_id', $project_id);
|
||||
$data = $query->orderBy('ieo_allocation_code.id', 'desc')->paginate($request->input('limit', 50));
|
||||
foreach ($data as $item) {
|
||||
$item->used_text = $item->used ? 'Used' : 'Available';
|
||||
}
|
||||
return $this->layuiData($data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
<?php
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Api\Controller;
|
||||
use App\IeoProject;
|
||||
use App\IeoOrder;
|
||||
use App\IeoAllocationCode;
|
||||
use App\UsersWallet;
|
||||
use App\AccountLog;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Token;
|
||||
use App\Users;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class IeoController extends Controller
|
||||
{
|
||||
public function list(Request $request)
|
||||
{
|
||||
$status = $request->input('status', 'all');
|
||||
$query = IeoProject::query();
|
||||
if ($status !== 'all') $query->where('status', $status);
|
||||
$list = $query->orderBy('id', 'desc')->get();
|
||||
foreach ($list as $item) {
|
||||
$real_pct = $item->total_supply > 0 ? round($item->real_sold / $item->total_supply * 100, 2) : 0;
|
||||
$item->progress = $item->type == 1 ? max($item->fake_progress, $real_pct) : min($item->fake_progress, 100);
|
||||
}
|
||||
return $this->success($list);
|
||||
}
|
||||
|
||||
public function detail(Request $request)
|
||||
{
|
||||
$id = $request->input('id');
|
||||
$project = IeoProject::find($id);
|
||||
if (!$project) return $this->error('Not found');
|
||||
$real_pct = $project->total_supply > 0 ? round($project->real_sold / $project->total_supply * 100, 2) : 0;
|
||||
$project->progress = $project->type == 1 ? max($project->fake_progress, $real_pct) : min($project->fake_progress, 100);
|
||||
|
||||
$user_id = Token::getUserIdByToken(Token::getToken());
|
||||
$user = \App\Users::find($user_id);
|
||||
$project->my_order = null;
|
||||
$project->my_order_count = 0;
|
||||
if ($user) {
|
||||
$order = IeoOrder::where('user_id', $user->id)->where('project_id', $id)->orderBy('id', 'desc')->first();
|
||||
if ($order) {
|
||||
$statusMap = ['0' => 'Pending', '1' => 'Won', '2' => 'Lost'];
|
||||
$order->status_text = $statusMap[$order->status] ?? '';
|
||||
$project->my_order = $order;
|
||||
}
|
||||
$project->my_order_count = IeoOrder::where('user_id', $user->id)->where('project_id', $id)->count();
|
||||
}
|
||||
return $this->success($project);
|
||||
}
|
||||
|
||||
public function subscribe(Request $request)
|
||||
{
|
||||
$user_id = Token::getUserIdByToken(Token::getToken());
|
||||
$user = \App\Users::find($user_id);
|
||||
if (!$user) return $this->error('Please login');
|
||||
$project_id = $request->input('project_id');
|
||||
$amount = floatval($request->input('amount'));
|
||||
$pay_pwd = $request->input('pay_pwd');
|
||||
|
||||
if ($amount <= 0) return $this->error('Invalid amount');
|
||||
$project = IeoProject::find($project_id);
|
||||
if (!$project || $project->status != 1) return $this->error('IEO not active');
|
||||
if ($amount < $project->min_buy) return $this->error('Min: ' . $project->min_buy . ' USDC');
|
||||
if ($project->max_buy > 0 && $amount > $project->max_buy) return $this->error('Max: ' . $project->max_buy . ' USDC');
|
||||
|
||||
$limit = intval($project->subscription_limit);
|
||||
if ($limit > 0) {
|
||||
$userOrderCount = IeoOrder::where('user_id', $user->id)->where('project_id', $project_id)->count();
|
||||
if ($userOrderCount >= $limit) return $this->error('Subscription limit reached: ' . $limit);
|
||||
}
|
||||
|
||||
$existingCredit = \App\CreditIeoOrder::where('user_id', $user->id)->where('project_id', $project_id)->whereIn('status', [0, 1])->exists();
|
||||
if ($existingCredit) return $this->error('Already subscribed via credit IEO');
|
||||
|
||||
$wallet = UsersWallet::where('user_id', $user->id)->where('currency', 3)->lockForUpdate()->first();
|
||||
if (!$wallet || $wallet->legal_balance < $amount) return $this->error('Insufficient balance');
|
||||
|
||||
DB::beginTransaction();
|
||||
try {
|
||||
$before = $wallet->legal_balance;
|
||||
$wallet->legal_balance = bc_sub($wallet->legal_balance, $amount, 6);
|
||||
$wallet->save();
|
||||
AccountLog::insertLog(
|
||||
['user_id' => $user->id, 'value' => -$amount, 'info' => 'IEO Subscribe - ' . $project->name, 'type' => AccountLog::IEO_OPERATION, 'currency' => 3],
|
||||
['balance_type' => 1, 'wallet_id' => $wallet->id, 'lock_type' => 0, 'before' => $before, 'change' => -$amount, 'after' => $wallet->legal_balance]
|
||||
);
|
||||
$token_amount = $project->token_price > 0 ? sprintf("%.6f", $amount / $project->token_price) : 0;
|
||||
IeoOrder::create([
|
||||
'user_id' => $user->id, 'project_id' => $project_id,
|
||||
'amount' => $amount, 'token_amount' => $token_amount, 'status' => 0,
|
||||
]);
|
||||
$project->increment('real_sold', $token_amount);
|
||||
DB::commit();
|
||||
return $this->success('Subscription successful');
|
||||
} catch (\Exception $e) {
|
||||
DB::rollBack();
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function myOrders(Request $request)
|
||||
{
|
||||
$user_id = Token::getUserIdByToken(Token::getToken());
|
||||
$user = \App\Users::find($user_id);
|
||||
if (!$user) return $this->error('Please login');
|
||||
$list = IeoOrder::where('user_id', $user->id)->with('project:id,name,symbol')->orderBy('id', 'desc')->get();
|
||||
$statusMap = ['0' => 'Pending', '1' => 'Won', '2' => 'Lost'];
|
||||
foreach ($list as $item) {
|
||||
$item->status_text = $statusMap[$item->status] ?? '';
|
||||
}
|
||||
return $this->success($list);
|
||||
}
|
||||
|
||||
public function redeemCode(Request $request)
|
||||
{
|
||||
$user_id = Token::getUserIdByToken(Token::getToken());
|
||||
$user = \App\Users::find($user_id);
|
||||
if (!$user) return $this->error('Please login');
|
||||
$code = trim((string)$request->input('code'));
|
||||
$project_id = intval($request->input('project_id'));
|
||||
if ($code === '' || !$project_id) return $this->error('Invalid params');
|
||||
|
||||
$project = IeoProject::find($project_id);
|
||||
if (!$project || $project->status != 1) return $this->error('IEO not active');
|
||||
|
||||
DB::beginTransaction();
|
||||
try {
|
||||
$allocCode = IeoAllocationCode::where('project_id', $project_id)
|
||||
->where('code', $code)
|
||||
->lockForUpdate()
|
||||
->first();
|
||||
if (!$allocCode) { DB::rollBack(); return $this->error('Invalid code'); }
|
||||
if ($allocCode->used) { DB::rollBack(); return $this->error('Code already used'); }
|
||||
|
||||
$amount = floatval($allocCode->amount);
|
||||
if ($amount <= 0) { DB::rollBack(); return $this->error('Invalid code amount'); }
|
||||
|
||||
$wallet = UsersWallet::where('user_id', $user->id)->where('currency', 3)->first();
|
||||
if (!$wallet || $wallet->legal_balance < $amount) { DB::rollBack(); return $this->error('Insufficient balance'); }
|
||||
|
||||
$before = $wallet->legal_balance;
|
||||
$wallet->legal_balance = bc_sub($wallet->legal_balance, $amount, 6);
|
||||
$wallet->save();
|
||||
AccountLog::insertLog(
|
||||
['user_id' => $user->id, 'value' => -$amount, 'info' => 'IEO Allocation - ' . $project->name, 'type' => AccountLog::IEO_OPERATION, 'currency' => 3],
|
||||
['balance_type' => 1, 'wallet_id' => $wallet->id, 'lock_type' => 0, 'before' => $before, 'change' => -$amount, 'after' => $wallet->legal_balance]
|
||||
);
|
||||
|
||||
$token_amount = $project->token_price > 0 ? sprintf("%.6f", $amount / $project->token_price) : 0;
|
||||
IeoOrder::create([
|
||||
'user_id' => $user->id, 'project_id' => $project_id,
|
||||
'amount' => $amount, 'token_amount' => $token_amount, 'status' => 1,
|
||||
]);
|
||||
$project->increment('real_sold', $token_amount);
|
||||
|
||||
$allocCode->used = 1;
|
||||
$allocCode->user_id = $user->id;
|
||||
$allocCode->save();
|
||||
|
||||
DB::commit();
|
||||
return $this->success('Redeemed successfully');
|
||||
} catch (\Exception $e) {
|
||||
DB::rollBack();
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function allocationOrders(Request $request)
|
||||
{
|
||||
$user_id = Token::getUserIdByToken(Token::getToken());
|
||||
$user = \App\Users::find($user_id);
|
||||
if (!$user) return $this->error('Please login');
|
||||
$list = IeoAllocationCode::where('user_id', $user->id)
|
||||
->where('used', 1)
|
||||
->with('project:id,name,symbol,token_price,listing_time,unlock_time')
|
||||
->orderBy('id', 'desc')
|
||||
->get();
|
||||
return $this->success($list);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
namespace App;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class IeoAllocationCode extends Model
|
||||
{
|
||||
protected $table = 'ieo_allocation_code';
|
||||
protected $fillable = ['project_id','code','amount','used','user_id'];
|
||||
|
||||
public function project() { return $this->belongsTo(IeoProject::class, 'project_id'); }
|
||||
public function user() { return $this->belongsTo(Users::class, 'user_id'); }
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
namespace App;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class IeoOrder extends Model
|
||||
{
|
||||
protected $table = 'ieo_order';
|
||||
protected $fillable = ['user_id','project_id','amount','token_amount','status','force_result'];
|
||||
|
||||
public function project() { return $this->belongsTo(IeoProject::class, 'project_id'); }
|
||||
public function user() { return $this->belongsTo(Users::class, 'user_id'); }
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
namespace App;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class IeoProject extends Model
|
||||
{
|
||||
protected $table = 'ieo_project';
|
||||
protected $fillable = ['name','symbol','description','whitepaper_url','thumb_image','token_price','total_supply','min_buy','max_buy','start_time','end_time','type','status','fake_progress','real_sold','subscription_limit','announce_time','listing_time','unlock_time'];
|
||||
}
|
||||
Reference in New Issue
Block a user