feat: 期权交易后端 — 下单+自动结算+后台管理
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\OptionPair;
|
||||
use App\OptionOrder;
|
||||
use App\UsersWallet;
|
||||
use App\AccountLog;
|
||||
use App\CurrencyMatch;
|
||||
use App\Currency;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class OptionController extends Controller
|
||||
{
|
||||
public function orderIndex() { return view('manages.option.orders'); }
|
||||
|
||||
public function orderList(Request $request)
|
||||
{
|
||||
$query = OptionOrder::query()
|
||||
->leftJoin('users', 'option_order.user_id', '=', 'users.id')
|
||||
->leftJoin('option_pair', 'option_order.pair_id', '=', 'option_pair.id')
|
||||
->select('option_order.*', 'users.phone as user_phone', 'users.email as user_email', 'option_pair.coinname');
|
||||
|
||||
$keyword = $request->input('keyword');
|
||||
if ($keyword) {
|
||||
$query->where(function ($q) use ($keyword) {
|
||||
$q->where('option_order.user_id', $keyword)
|
||||
->orWhere('users.email', 'like', "%{$keyword}%")
|
||||
->orWhere('users.phone', 'like', "%{$keyword}%");
|
||||
});
|
||||
}
|
||||
$status = $request->input('status');
|
||||
if ($status !== null && $status !== '') {
|
||||
$query->where('option_order.status', intval($status));
|
||||
}
|
||||
|
||||
$data = $query->orderBy('option_order.id', 'desc')->paginate($request->input('limit', 20));
|
||||
$statusMap = ['0'=>'待交割','1'=>'盈利','2'=>'亏损','3'=>'已取消'];
|
||||
foreach ($data as $item) {
|
||||
$item->status_text = $statusMap[$item->status] ?? '';
|
||||
$item->direction_text = $item->direction == 'up' ? '买涨' : '买跌';
|
||||
}
|
||||
return $this->layuiData($data);
|
||||
}
|
||||
|
||||
public function settle(Request $request)
|
||||
{
|
||||
$ids = $request->input('ids');
|
||||
$force = $request->input('force', '');
|
||||
if (!$ids) return $this->error('请先选择订单');
|
||||
$orders = OptionOrder::whereIn('id', explode(',', $ids))->where('status', 0)->get();
|
||||
if ($orders->isEmpty()) return $this->error('没有待结算订单');
|
||||
|
||||
$settled = 0;
|
||||
DB::beginTransaction();
|
||||
try {
|
||||
foreach ($orders as $order) {
|
||||
$pair = OptionPair::find($order->pair_id);
|
||||
$match = $pair ? CurrencyMatch::find($pair->currency_match_id) : null;
|
||||
$currency = $match ? Currency::find($match->currency_id) : null;
|
||||
$close_price = $currency ? floatval($currency->price) : 0;
|
||||
|
||||
if ($force === 'win') $win = true;
|
||||
elseif ($force === 'lose') $win = false;
|
||||
else $win = $order->direction == 'up' ? $close_price > $order->open_price : $close_price < $order->open_price;
|
||||
|
||||
$income = $win ? sprintf("%.6f", $order->amount * $order->profit_rate / 100) : -$order->amount;
|
||||
$order->update(['close_price' => $close_price, 'income' => $income, 'status' => $win ? 1 : 2]);
|
||||
|
||||
$wallet = UsersWallet::where('user_id', $order->user_id)->where('currency', 3)->first();
|
||||
if ($wallet) {
|
||||
$return_amount = $win ? $order->amount + abs($income) : 0;
|
||||
if ($return_amount > 0) {
|
||||
$before = $wallet->legal_balance;
|
||||
$wallet->legal_balance = bc_add($wallet->legal_balance, $return_amount, 6);
|
||||
$wallet->save();
|
||||
AccountLog::insertLog(
|
||||
['user_id'=>$order->user_id,'value'=>$return_amount,'info'=>'Option '.($win?'Win':'Refund').' - '.$order->symbol,'type'=>AccountLog::MICRO_TRADE_CLOSE_SETTLE,'currency'=>3],
|
||||
['balance_type'=>1,'wallet_id'=>$wallet->id,'lock_type'=>0,'before'=>$before,'change'=>$return_amount,'after'=>$wallet->legal_balance]
|
||||
);
|
||||
}
|
||||
}
|
||||
$settled++;
|
||||
}
|
||||
DB::commit();
|
||||
return $this->success("已结算 {$settled} 个订单");
|
||||
} catch (\Exception $e) {
|
||||
DB::rollBack();
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
<?php
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Api\Controller;
|
||||
use App\OptionPair;
|
||||
use App\OptionOrder;
|
||||
use App\UsersWallet;
|
||||
use App\AccountLog;
|
||||
use App\CurrencyMatch;
|
||||
use App\Currency;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Token;
|
||||
use App\Users;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class OptionController extends Controller
|
||||
{
|
||||
public function pairs()
|
||||
{
|
||||
$pairs = OptionPair::where('status', 1)->orderBy('weigh', 'desc')->get();
|
||||
foreach ($pairs as $p) {
|
||||
$match = CurrencyMatch::find($p->currency_match_id);
|
||||
if ($match) {
|
||||
$currency = Currency::find($match->currency_id);
|
||||
$p->close = $currency ? $currency->price : 0;
|
||||
$p->price_change = $currency ? ($currency->change ?? 0) : 0;
|
||||
$p->high = $currency ? ($currency->high ?? $p->close) : 0;
|
||||
$p->low = $currency ? ($currency->low ?? $p->close) : 0;
|
||||
$p->match_legal_id = $match->legal_id;
|
||||
$p->match_currency_id = $match->currency_id;
|
||||
$p->volume_24h = $currency ? ($currency->vol ?? 0) : 0;
|
||||
}
|
||||
}
|
||||
return $this->success($pairs);
|
||||
}
|
||||
|
||||
public function placeOrder(Request $request)
|
||||
{
|
||||
$user_id = Token::getUserIdByToken(Token::getToken());
|
||||
$user = \App\Users::find($user_id);
|
||||
if (!$user) return $this->error('Please login');
|
||||
|
||||
$pair_id = $request->input('pair_id');
|
||||
$direction = $request->input('direction');
|
||||
$amount = floatval($request->input('amount'));
|
||||
$delivery_time = $request->input('delivery_time');
|
||||
|
||||
if (!in_array($direction, ['up', 'down'])) return $this->error('Invalid direction');
|
||||
if ($amount <= 0) return $this->error('Invalid amount');
|
||||
if (!$delivery_time) return $this->error('Select delivery time');
|
||||
|
||||
$pair = OptionPair::where('id', $pair_id)->where('status', 1)->first();
|
||||
if (!$pair) return $this->error('Pair not available');
|
||||
if ($amount < $pair->min_amount) return $this->error('Min: ' . $pair->min_amount . ' USDC');
|
||||
if ($amount > $pair->max_amount) return $this->error('Max: ' . $pair->max_amount . ' USDC');
|
||||
|
||||
$delivery_ts = is_numeric($delivery_time) ? date('Y-m-d H:i:s', $delivery_time) : $delivery_time;
|
||||
if (strtotime($delivery_ts) <= time() + 10) return $this->error('Delivery time must be in the future');
|
||||
|
||||
$match = CurrencyMatch::find($pair->currency_match_id);
|
||||
$currency = $match ? Currency::find($match->currency_id) : null;
|
||||
$open_price = $currency ? floatval($currency->price) : 0;
|
||||
$fee = sprintf("%.6f", $amount * $pair->fee_rate / 100);
|
||||
|
||||
DB::beginTransaction();
|
||||
try {
|
||||
$wallet = UsersWallet::where('user_id', $user->id)->where('currency', 3)->lockForUpdate()->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' => 'Option ' . strtoupper($direction) . ' - ' . $pair->coinname, 'type' => AccountLog::MICRO_TRADE_SUBMIT, 'currency' => 3],
|
||||
['balance_type' => 1, 'wallet_id' => $wallet->id, 'lock_type' => 0, 'before' => $before, 'change' => -$amount, 'after' => $wallet->legal_balance]
|
||||
);
|
||||
OptionOrder::create([
|
||||
'user_id' => $user->id, 'pair_id' => $pair_id,
|
||||
'symbol' => $pair->symbol, 'direction' => $direction,
|
||||
'amount' => $amount, 'fee' => $fee,
|
||||
'open_price' => $open_price, 'profit_rate' => $pair->profit_rate,
|
||||
'delivery_time' => $delivery_ts, 'status' => 0,
|
||||
]);
|
||||
DB::commit();
|
||||
return $this->success('Order placed');
|
||||
} 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');
|
||||
$status = $request->input('status', 'all');
|
||||
$query = OptionOrder::where('user_id', $user->id)->with('pair:id,coinname');
|
||||
if ($status !== 'all') $query->where('status', $status);
|
||||
$list = $query->orderBy('id', 'desc')->paginate(20);
|
||||
$statusMap = ['0'=>'Pending','1'=>'Win','2'=>'Lose','3'=>'Canceled'];
|
||||
foreach ($list as $item) {
|
||||
$item->status_text = $statusMap[$item->status] ?? '';
|
||||
$item->direction_text = $item->direction == 'up' ? 'Buy Up' : 'Buy Down';
|
||||
$item->coinname = $item->pair ? $item->pair->coinname : $item->symbol;
|
||||
}
|
||||
return $this->success($list);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user