更精确的计费 采用 bc ath
This commit is contained in:
parent
5214362404
commit
e0d8fe9cdd
10
app/Exceptions/Transaction/TransactionFailedException.php
Normal file
10
app/Exceptions/Transaction/TransactionFailedException.php
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Exceptions\Transaction;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
|
||||||
|
class TransactionFailedException extends Exception
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
@ -5,7 +5,6 @@
|
|||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Models\Balance;
|
use App\Models\Balance;
|
||||||
use App\Models\Host;
|
use App\Models\Host;
|
||||||
use App\Models\Transaction;
|
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use App\Models\UserGroup;
|
use App\Models\UserGroup;
|
||||||
use App\Models\WorkOrder\WorkOrder;
|
use App\Models\WorkOrder\WorkOrder;
|
||||||
@ -101,8 +100,6 @@ public function update(Request $request, User $user): RedirectResponse
|
|||||||
'id_card' => 'nullable|string|size:18',
|
'id_card' => 'nullable|string|size:18',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$transaction = new Transaction();
|
|
||||||
|
|
||||||
if ($request->input('is_banned')) {
|
if ($request->input('is_banned')) {
|
||||||
$user->banned_at = Carbon::now();
|
$user->banned_at = Carbon::now();
|
||||||
|
|
||||||
@ -125,9 +122,13 @@ public function update(Request $request, User $user): RedirectResponse
|
|||||||
} else if ($one_time_action == 'stop_all_hosts') {
|
} else if ($one_time_action == 'stop_all_hosts') {
|
||||||
$user->hosts()->update(['status' => 'stopped', 'suspended_at' => null]);
|
$user->hosts()->update(['status' => 'stopped', 'suspended_at' => null]);
|
||||||
} else if ($one_time_action == 'add_balance') {
|
} else if ($one_time_action == 'add_balance') {
|
||||||
$transaction->addAmount($user->id, 'console', $request->balance ?? 0, '管理员添加。', true);
|
$description = '管理员 ' . $request->user('admin')->name . " 增加。";
|
||||||
|
|
||||||
|
$user->charge($request->input('balance'), 'console', $description);
|
||||||
} else if ($one_time_action == 'reduce_balance') {
|
} else if ($one_time_action == 'reduce_balance') {
|
||||||
$transaction->reduceAmount($user->id, $request->balance ?? 0, '管理员扣除。');
|
$description = '管理员 ' . $request->user('admin')->name . " 扣除。";
|
||||||
|
|
||||||
|
$user->reduce($request->input('balance'), $description);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -6,12 +6,14 @@
|
|||||||
use App\Models\Transaction;
|
use App\Models\Transaction;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use App\Support\RealNameSupport;
|
use App\Support\RealNameSupport;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Illuminate\View\View;
|
||||||
|
|
||||||
class RealNameController extends Controller
|
class RealNameController extends Controller
|
||||||
{
|
{
|
||||||
public function verify(Request $request)
|
public function verify(Request $request): JsonResponse
|
||||||
{
|
{
|
||||||
$result = (new RealNameSupport())->verify($request->all());
|
$result = (new RealNameSupport())->verify($request->all());
|
||||||
|
|
||||||
@ -20,7 +22,7 @@ public function verify(Request $request)
|
|||||||
return $this->error('实名认证失败。');
|
return $this->error('实名认证失败。');
|
||||||
}
|
}
|
||||||
|
|
||||||
$user = User::find($result['user_id']);
|
$user = (new User)->find($result['user_id']);
|
||||||
$user->real_name = $result['name'];
|
$user->real_name = $result['name'];
|
||||||
$user->id_card = $result['id_card'];
|
$user->id_card = $result['id_card'];
|
||||||
$user->save();
|
$user->save();
|
||||||
@ -31,7 +33,7 @@ public function verify(Request $request)
|
|||||||
return $this->success('实名认证成功。');
|
return $this->success('实名认证成功。');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function process()
|
public function process(): View
|
||||||
{
|
{
|
||||||
return view('real_name.process');
|
return view('real_name.process');
|
||||||
}
|
}
|
||||||
|
@ -48,7 +48,6 @@ public function store(Request $request): RedirectResponse
|
|||||||
*/
|
*/
|
||||||
public function show(Request $request, Balance $balance): RedirectResponse|JsonResponse|View
|
public function show(Request $request, Balance $balance): RedirectResponse|JsonResponse|View
|
||||||
{
|
{
|
||||||
|
|
||||||
if ($balance->isPaid()) {
|
if ($balance->isPaid()) {
|
||||||
if ($request->ajax()) {
|
if ($request->ajax()) {
|
||||||
return $this->success($balance);
|
return $this->success($balance);
|
||||||
@ -228,8 +227,7 @@ function notify(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($is_paid) {
|
if ($is_paid) {
|
||||||
(new Transaction)->addAmount($balance->user_id, $balance->payment, $balance->amount);
|
// $balance->user->charge($balance->amount, $balance->payment, $balance->order_id);
|
||||||
|
|
||||||
$balance->update([
|
$balance->update([
|
||||||
'paid_at' => now()
|
'paid_at' => now()
|
||||||
]);
|
]);
|
||||||
|
@ -4,13 +4,15 @@
|
|||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Support\RealNameSupport;
|
use App\Support\RealNameSupport;
|
||||||
|
use Illuminate\Http\RedirectResponse;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Carbon;
|
use Illuminate\Support\Carbon;
|
||||||
use Illuminate\Support\Facades\Cache;
|
use Illuminate\Support\Facades\Cache;
|
||||||
|
use Illuminate\View\View;
|
||||||
|
|
||||||
class RealNameController extends Controller
|
class RealNameController extends Controller
|
||||||
{
|
{
|
||||||
public function store(Request $request)
|
public function store(Request $request): RedirectResponse
|
||||||
{
|
{
|
||||||
$request->validate([
|
$request->validate([
|
||||||
'real_name' => 'required|string',
|
'real_name' => 'required|string',
|
||||||
@ -54,7 +56,7 @@ public function store(Request $request)
|
|||||||
return redirect($output);
|
return redirect($output);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function create()
|
public function create(): View
|
||||||
{
|
{
|
||||||
return view('real_name.create');
|
return view('real_name.create');
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
namespace App\Http\Controllers\Web;
|
namespace App\Http\Controllers\Web;
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Models\Transaction;
|
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use Illuminate\Http\RedirectResponse;
|
use Illuminate\Http\RedirectResponse;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
@ -11,8 +10,6 @@
|
|||||||
|
|
||||||
class TransferController extends Controller
|
class TransferController extends Controller
|
||||||
{
|
{
|
||||||
//
|
|
||||||
|
|
||||||
public function index(Request $request): View
|
public function index(Request $request): View
|
||||||
{
|
{
|
||||||
$user = $request->user();
|
$user = $request->user();
|
||||||
@ -24,7 +21,7 @@ public function index(Request $request): View
|
|||||||
public function transfer(Request $request): RedirectResponse
|
public function transfer(Request $request): RedirectResponse
|
||||||
{
|
{
|
||||||
$request->validate([
|
$request->validate([
|
||||||
'amount' => 'numeric|min:1|max:100',
|
'amount' => 'string|min:1|max:100',
|
||||||
'description' => 'nullable|string|max:100',
|
'description' => 'nullable|string|max:100',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@ -38,14 +35,15 @@ public function transfer(Request $request): RedirectResponse
|
|||||||
return back()->withErrors(['to' => '不能转给自己。']);
|
return back()->withErrors(['to' => '不能转给自己。']);
|
||||||
}
|
}
|
||||||
|
|
||||||
$transaction = new Transaction();
|
$amount = $request->input('amount');
|
||||||
|
|
||||||
if ($user->balance < $request->input('amount')) {
|
// 使用 bc 判断金额是否足够
|
||||||
return back()->withErrors(['amount' => '您的余额不足。']);
|
if (bccomp($amount, $user->balance, 2) > 0) {
|
||||||
} else {
|
return back()->withErrors(['amount' => '余额不足。']);
|
||||||
$transaction->transfer($user, $to, $request->input('amount'), $request->input('description'));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$user->startTransfer($to, $amount, $request->input('description'));
|
||||||
|
|
||||||
return back()->with('success', '转账成功,已达对方账户。');
|
return back()->with('success', '转账成功,已达对方账户。');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
|
|
||||||
use App\Jobs\Job;
|
use App\Jobs\Job;
|
||||||
use App\Models\Balance;
|
use App\Models\Balance;
|
||||||
use App\Models\Transaction;
|
|
||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
use Yansongda\LaravelPay\Facades\Pay;
|
use Yansongda\LaravelPay\Facades\Pay;
|
||||||
use Yansongda\Pay\Exception\ContainerException;
|
use Yansongda\Pay\Exception\ContainerException;
|
||||||
@ -62,8 +61,6 @@ public function checkAndCharge(Balance $balance, $check = false): bool
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
(new Transaction)->addAmount($balance->user_id, 'alipay', $balance->amount);
|
|
||||||
|
|
||||||
$balance->update([
|
$balance->update([
|
||||||
'paid_at' => now()
|
'paid_at' => now()
|
||||||
]);
|
]);
|
||||||
|
@ -48,6 +48,8 @@ protected static function boot()
|
|||||||
if ($balance->paid_at) {
|
if ($balance->paid_at) {
|
||||||
$balance->notify(new UserCharged());
|
$balance->notify(new UserCharged());
|
||||||
broadcast(new Users($balance->user, 'balance.updated', $balance));
|
broadcast(new Users($balance->user, 'balance.updated', $balance));
|
||||||
|
|
||||||
|
$balance->user->charge($balance->amount, $balance->payment, $balance->order_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -45,11 +45,11 @@ protected static function boot()
|
|||||||
$model->minute_at = now()->minute;
|
$model->minute_at = now()->minute;
|
||||||
|
|
||||||
if ($model->price !== null) {
|
if ($model->price !== null) {
|
||||||
$model->price = round($model->price, 2);
|
$model->price = bcdiv($model->price, 1, 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($model->managed_price !== null) {
|
if ($model->managed_price !== null) {
|
||||||
$model->managed_price = round($model->managed_price, 2);
|
$model->managed_price = bcdiv($model->managed_price, 1, 2);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -153,7 +153,7 @@ public function safeDelete(): bool
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function cost($amount = null, $auto = true): bool
|
public function cost(string $amount = null, $auto = true): bool
|
||||||
{
|
{
|
||||||
$this->load('user');
|
$this->load('user');
|
||||||
$user = $this->user;
|
$user = $this->user;
|
||||||
@ -177,20 +177,23 @@ public function cost($amount = null, $auto = true): bool
|
|||||||
$append_description = '';
|
$append_description = '';
|
||||||
if ($user_group) {
|
if ($user_group) {
|
||||||
if ($user_group->discount !== 100 && $user_group->discount !== null) {
|
if ($user_group->discount !== 100 && $user_group->discount !== null) {
|
||||||
$real_price = $real_price * ($user_group->discount / 100);
|
$real_price = bcmul($real_price, bcdiv($user_group->discount, "100", 2), 2);
|
||||||
|
|
||||||
$append_description = ' (折扣 ' . $user_group->discount . '%)';
|
$append_description = ' (折扣 ' . $user_group->discount . '%)';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if ($auto) {
|
if ($auto) {
|
||||||
// 获取本月天数
|
// 获取本月天数
|
||||||
$days = now()->daysInMonth;
|
$days = now()->daysInMonth;
|
||||||
// 本月每天的每小时的价格
|
// 本月每天的每小时的价格
|
||||||
$real_price = $real_price / $days / 24;
|
// 使用 bcmath 函数,解决浮点数计算精度问题
|
||||||
|
$real_price = bcdiv($real_price, $days, 4);
|
||||||
|
$real_price = bcdiv($real_price, 24, 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($real_price == 0) {
|
if ($real_price == 0) {
|
||||||
|
echo '价格为 0,不扣费';
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -199,9 +202,7 @@ public function cost($amount = null, $auto = true): bool
|
|||||||
$real_price = 0.0001;
|
$real_price = 0.0001;
|
||||||
}
|
}
|
||||||
|
|
||||||
$real_price = round($real_price ?? 0, 4);
|
$real_price = bcdiv($real_price, 1, 4);
|
||||||
|
|
||||||
$transaction = new Transaction();
|
|
||||||
|
|
||||||
$month = now()->month;
|
$month = now()->month;
|
||||||
|
|
||||||
@ -215,7 +216,7 @@ public function cost($amount = null, $auto = true): bool
|
|||||||
$hosts_balances[$this->id] = $real_price;
|
$hosts_balances[$this->id] = $real_price;
|
||||||
}
|
}
|
||||||
|
|
||||||
$hosts_balances[$this->id] = round($hosts_balances[$this->id], 4);
|
$hosts_balances[$this->id] = bcdiv($hosts_balances[$this->id], 1, 4);
|
||||||
|
|
||||||
Cache::put($month_cache_key, $hosts_balances, 604800);
|
Cache::put($month_cache_key, $hosts_balances, 604800);
|
||||||
|
|
||||||
@ -229,7 +230,12 @@ public function cost($amount = null, $auto = true): bool
|
|||||||
$description .= $append_description;
|
$description .= $append_description;
|
||||||
}
|
}
|
||||||
|
|
||||||
$left = $transaction->reduceHostAmount($this->user_id, $this->id, $this->module_id, $real_price, $description);
|
$data = [
|
||||||
|
'host_id' => $this->id,
|
||||||
|
'module_id' => $this->module_id,
|
||||||
|
];
|
||||||
|
|
||||||
|
$left = $user->reduce($real_price, $description, false, $data);
|
||||||
|
|
||||||
$this->addLog($real_price);
|
$this->addLog($real_price);
|
||||||
|
|
||||||
@ -244,9 +250,9 @@ public function cost($amount = null, $auto = true): bool
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function addLog(float|null $amount = 0): bool
|
public function addLog(string $amount = "0"): bool
|
||||||
{
|
{
|
||||||
if ($amount === 0 || $amount === null) {
|
if ($amount === "0") {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -256,12 +262,12 @@ public function addLog(float|null $amount = 0): bool
|
|||||||
|
|
||||||
$cache_key = 'module_earning_' . $this->module_id;
|
$cache_key = 'module_earning_' . $this->module_id;
|
||||||
|
|
||||||
$commission = (float)config('billing.commission');
|
$commission = config('billing.commission');
|
||||||
|
|
||||||
$should_amount = round($amount * $commission, 2);
|
$should_amount = bcmul($amount, $commission, 2);
|
||||||
|
|
||||||
// 应得的余额
|
// 应得的余额
|
||||||
$should_balance = $amount - $should_amount;
|
$should_balance = bcsub($amount, $should_amount, 2);
|
||||||
|
|
||||||
$earnings = Cache::get($cache_key, []);
|
$earnings = Cache::get($cache_key, []);
|
||||||
|
|
||||||
@ -270,8 +276,10 @@ public function addLog(float|null $amount = 0): bool
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isset($earnings[$current_year][$current_month])) {
|
if (isset($earnings[$current_year][$current_month])) {
|
||||||
$earnings[$current_year][$current_month]['balance'] += $amount;
|
$earnings[$current_year][$current_month]['balance'] = bcadd($earnings[$current_year][$current_month]['balance'], $amount, 2);
|
||||||
$earnings[$current_year][$current_month]['should_balance'] += $should_balance;
|
$earnings[$current_year][$current_month]['should_balance'] = bcadd($earnings[$current_year][$current_month]['should_balance'], $should_balance, 2);
|
||||||
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
$earnings[$current_year][$current_month] = [
|
$earnings[$current_year][$current_month] = [
|
||||||
'balance' => $amount,
|
'balance' => $amount,
|
||||||
|
@ -2,11 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
use App\Exceptions\User\BalanceNotEnoughException;
|
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
use Illuminate\Database\Eloquent\HigherOrderBuilderProxy;
|
|
||||||
use Illuminate\Support\Facades\Cache;
|
|
||||||
use Illuminate\Support\HigherOrderCollectionProxy;
|
|
||||||
use Jenssegers\Mongodb\Eloquent\Model;
|
use Jenssegers\Mongodb\Eloquent\Model;
|
||||||
|
|
||||||
class Transaction extends Model
|
class Transaction extends Model
|
||||||
@ -27,7 +23,7 @@ class Transaction extends Model
|
|||||||
];
|
];
|
||||||
|
|
||||||
protected $fillable = [
|
protected $fillable = [
|
||||||
// 交易类型
|
// 类型
|
||||||
'type',
|
'type',
|
||||||
|
|
||||||
// 交易渠道
|
// 交易渠道
|
||||||
@ -36,15 +32,12 @@ class Transaction extends Model
|
|||||||
// 描述
|
// 描述
|
||||||
'description',
|
'description',
|
||||||
|
|
||||||
// 入账
|
// 交易金额,负数则是扣除
|
||||||
'income',
|
'amount',
|
||||||
|
|
||||||
// 出账
|
// 剩余余额
|
||||||
'outcome',
|
'user_remain',
|
||||||
|
'module_remain',
|
||||||
// 可用余额
|
|
||||||
'balances',
|
|
||||||
'balance',
|
|
||||||
|
|
||||||
// 赠送金额
|
// 赠送金额
|
||||||
'gift',
|
'gift',
|
||||||
@ -59,219 +52,32 @@ public function scopeThisUser($query)
|
|||||||
return $query->where('user_id', auth()->id());
|
return $query->where('user_id', auth()->id());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function reduceAmount($user_id, $amount = 0, $description = '扣除费用请求。')
|
// on create
|
||||||
|
protected static function boot()
|
||||||
{
|
{
|
||||||
|
parent::boot();
|
||||||
|
|
||||||
$lock = Cache::lock("user_balance_lock_" . $user_id, 10);
|
static::creating(function (self $transaction) {
|
||||||
try {
|
$user = null;
|
||||||
|
$module = null;
|
||||||
|
|
||||||
$lock->block(5);
|
if ($transaction->user_id) {
|
||||||
|
$user = (new User)->find($transaction->user_id);
|
||||||
$user = (new User)->findOrFail($user_id);
|
|
||||||
|
|
||||||
$user->balance -= $amount;
|
|
||||||
$user->save();
|
|
||||||
|
|
||||||
$this->addPayoutBalance($user_id, $amount, $description);
|
|
||||||
} finally {
|
|
||||||
optional($lock)->release();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $user->balance;
|
if ($transaction->module_id) {
|
||||||
|
$module = (new Module)->find($transaction->module_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function addPayoutBalance($user_id, $amount, $description, $module_id = null)
|
if ($user) {
|
||||||
{
|
$transaction->user_remain = $user->balance;
|
||||||
$data = [
|
|
||||||
'type' => 'payout',
|
|
||||||
'payment' => 'balance',
|
|
||||||
'description' => $description,
|
|
||||||
'income' => 0,
|
|
||||||
'outcome' => (float)$amount,
|
|
||||||
];
|
|
||||||
|
|
||||||
if ($module_id) {
|
|
||||||
$data['module_id'] = $module_id;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->addLog($user_id, $data);
|
if ($module) {
|
||||||
|
$transaction->module_remain = $module->balance;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function addLog($user_id, $data)
|
$transaction->expired_at = Carbon::now()->addSeconds(7)->toString();
|
||||||
{
|
});
|
||||||
$user = (new User)->find($user_id);
|
|
||||||
|
|
||||||
$current = [
|
|
||||||
'balance' => (float)$user->balance,
|
|
||||||
'user_id' => intval($user_id),
|
|
||||||
];
|
|
||||||
|
|
||||||
// merge
|
|
||||||
$data = array_merge($data, $current);
|
|
||||||
|
|
||||||
// add expired at
|
|
||||||
$data['expired_at'] = now()->addSeconds(7);
|
|
||||||
|
|
||||||
/** @noinspection PhpUndefinedMethodInspection */
|
|
||||||
return $this->create($data);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @throws BalanceNotEnoughException
|
|
||||||
*/
|
|
||||||
public function reduceAmountModuleFail($user_id, $module_id, $amount = 0, $description = '扣除费用请求。')
|
|
||||||
{
|
|
||||||
|
|
||||||
$lock = Cache::lock("user_balance_lock_" . $user_id, 10);
|
|
||||||
try {
|
|
||||||
|
|
||||||
$lock->block(5);
|
|
||||||
|
|
||||||
$user = (new User)->findOrFail($user_id);
|
|
||||||
|
|
||||||
$user->balance -= $amount;
|
|
||||||
|
|
||||||
// if balance < 0
|
|
||||||
if ($user->balance < 0) {
|
|
||||||
throw new BalanceNotEnoughException('余额不足。');
|
|
||||||
}
|
|
||||||
|
|
||||||
$user->save();
|
|
||||||
|
|
||||||
$this->addPayoutBalance($user_id, $amount, $description, $module_id);
|
|
||||||
} finally {
|
|
||||||
optional($lock)->release();
|
|
||||||
}
|
|
||||||
|
|
||||||
return $user->balance;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function reduceHostAmount($user_id, $host_id, $module_id, $amount = 0, $description = '扣除费用请求。')
|
|
||||||
{
|
|
||||||
|
|
||||||
$lock = Cache::lock("user_balance_lock_" . $user_id, 10);
|
|
||||||
try {
|
|
||||||
|
|
||||||
$lock->block(5);
|
|
||||||
|
|
||||||
$user = (new User)->findOrFail($user_id);
|
|
||||||
|
|
||||||
$user->balance -= $amount;
|
|
||||||
$user->save();
|
|
||||||
|
|
||||||
$this->addHostPayoutBalance($user_id, $host_id, $module_id, $amount, $description);
|
|
||||||
} finally {
|
|
||||||
optional($lock)->release();
|
|
||||||
}
|
|
||||||
|
|
||||||
return $user->balance;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function addHostPayoutBalance($user_id, $host_id, $module_id, $amount, $description)
|
|
||||||
{
|
|
||||||
$data = [
|
|
||||||
'type' => 'payout',
|
|
||||||
'payment' => 'balance',
|
|
||||||
'description' => $description,
|
|
||||||
'income' => 0,
|
|
||||||
'outcome' => (float)$amount,
|
|
||||||
'host_id' => $host_id,
|
|
||||||
'module_id' => $module_id,
|
|
||||||
];
|
|
||||||
|
|
||||||
return $this->addLog($user_id, $data);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param $user_id
|
|
||||||
* @param string $payment
|
|
||||||
* @param int $amount
|
|
||||||
* @param null $description
|
|
||||||
* @param bool $add_charge_log
|
|
||||||
*
|
|
||||||
* @return float|HigherOrderBuilderProxy|HigherOrderCollectionProxy|int|mixed|string
|
|
||||||
*/
|
|
||||||
public function addAmount($user_id, string $payment = 'console', int $amount = 0, $description = null, bool $add_charge_log = false): mixed
|
|
||||||
{
|
|
||||||
$lock = Cache::lock("user_balance_lock_" . $user_id, 10);
|
|
||||||
try {
|
|
||||||
|
|
||||||
$lock->block(5);
|
|
||||||
|
|
||||||
$user = (new User)->findOrFail($user_id);
|
|
||||||
|
|
||||||
$left_balance = $user->balance + $amount;
|
|
||||||
|
|
||||||
$user->increment('balance', $amount);
|
|
||||||
|
|
||||||
if (!$description) {
|
|
||||||
$description = '充值 ' . $amount . ' 元';
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($add_charge_log) {
|
|
||||||
$data = [
|
|
||||||
'user_id' => $user_id,
|
|
||||||
'amount' => $amount,
|
|
||||||
'payment' => $payment,
|
|
||||||
'paid_at' => Carbon::now(),
|
|
||||||
];
|
|
||||||
|
|
||||||
(new Balance)->create($data);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->addIncomeBalance($user_id, $payment, $amount, $description);
|
|
||||||
} finally {
|
|
||||||
optional($lock)->release();
|
|
||||||
}
|
|
||||||
|
|
||||||
return $left_balance;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function addIncomeBalance($user_id, $payment, $amount, $description)
|
|
||||||
{
|
|
||||||
$data = [
|
|
||||||
'type' => 'income',
|
|
||||||
'payment' => $payment,
|
|
||||||
'description' => $description,
|
|
||||||
'income' => (float)$amount,
|
|
||||||
'outcome' => 0,
|
|
||||||
];
|
|
||||||
|
|
||||||
return $this->addLog($user_id, $data);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function transfer(User $user, User $to, float $amount, string|null $description): float
|
|
||||||
{
|
|
||||||
$lock = Cache::lock("user_balance_lock_" . $user->id, 10);
|
|
||||||
$lock_to = Cache::lock("user_balance_lock_" . $to->id, 10);
|
|
||||||
try {
|
|
||||||
|
|
||||||
$lock->block(5);
|
|
||||||
$lock_to->block(5);
|
|
||||||
|
|
||||||
$user->balance -= $amount;
|
|
||||||
$user->save();
|
|
||||||
|
|
||||||
$to->balance += $amount;
|
|
||||||
$to->save();
|
|
||||||
|
|
||||||
if (!$description) {
|
|
||||||
$description = '完成。';
|
|
||||||
}
|
|
||||||
|
|
||||||
$description_new = "转账给 $to->name($to->email) $amount 元,$description";
|
|
||||||
|
|
||||||
$this->addPayoutBalance($user->id, $amount, $description_new);
|
|
||||||
|
|
||||||
$description_new = "收到来自 $user->name($user->email) 转来的 $amount 元, $description";
|
|
||||||
|
|
||||||
$this->addIncomeBalance($to->id, 'transfer', $amount, $description_new);
|
|
||||||
} finally {
|
|
||||||
optional($lock)->release();
|
|
||||||
optional($lock_to)->release();
|
|
||||||
}
|
|
||||||
|
|
||||||
return $user->balance;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
// use Illuminate\Contracts\Auth\MustVerifyEmail;
|
// use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||||
|
use App\Exceptions\User\BalanceNotEnoughException;
|
||||||
use Carbon\Exceptions\InvalidFormatException;
|
use Carbon\Exceptions\InvalidFormatException;
|
||||||
use GeneaLabs\LaravelModelCaching\Traits\Cachable;
|
use GeneaLabs\LaravelModelCaching\Traits\Cachable;
|
||||||
use Illuminate\Contracts\Encryption\DecryptException;
|
use Illuminate\Contracts\Encryption\DecryptException;
|
||||||
@ -12,6 +13,7 @@
|
|||||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||||
use Illuminate\Notifications\Notifiable;
|
use Illuminate\Notifications\Notifiable;
|
||||||
|
use Illuminate\Support\Facades\Cache;
|
||||||
use Illuminate\Support\Facades\Crypt;
|
use Illuminate\Support\Facades\Crypt;
|
||||||
use Laravel\Sanctum\HasApiTokens;
|
use Laravel\Sanctum\HasApiTokens;
|
||||||
|
|
||||||
@ -164,4 +166,107 @@ public function selectPublic(): User
|
|||||||
// 过滤掉私有字段
|
// 过滤掉私有字段
|
||||||
return $this->select(['id', 'name', 'email_md5', 'created_at']);
|
return $this->select(['id', 'name', 'email_md5', 'created_at']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 扣除费用
|
||||||
|
*
|
||||||
|
* @param string $amount
|
||||||
|
* @param string $description
|
||||||
|
* @param bool $fail
|
||||||
|
* @param array $options
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function reduce(string $amount = "0", string $description = "消费", bool $fail = false, array $options = []): string
|
||||||
|
{
|
||||||
|
Cache::lock('user_balance_' . $this->id, 10)->block(10, function () use ($amount, $fail, $description, $options) {
|
||||||
|
$this->refresh();
|
||||||
|
|
||||||
|
if ($this->balance < $amount) {
|
||||||
|
if ($fail) {
|
||||||
|
throw new BalanceNotEnoughException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->balance = bcsub($this->balance, $amount, 2);
|
||||||
|
$this->save();
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'user_id' => $this->id,
|
||||||
|
'amount' => $amount,
|
||||||
|
'description' => $description,
|
||||||
|
'payment' => 'balance',
|
||||||
|
'type' => 'payout',
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($options) {
|
||||||
|
$data = array_merge($data, $options);
|
||||||
|
}
|
||||||
|
|
||||||
|
(new Transaction)->create($data);
|
||||||
|
});
|
||||||
|
|
||||||
|
return $this->balance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 增加余额
|
||||||
|
*
|
||||||
|
* @param string $amount
|
||||||
|
* @param string $payment
|
||||||
|
* @param string $description
|
||||||
|
* @param array $options
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function charge(string $amount = "0", string $payment = 'console', string $description = '充值', array $options = []): string
|
||||||
|
{
|
||||||
|
Cache::lock('user_balance_' . $this->id, 10)->block(10, function () use ($amount, $description, $payment, $options) {
|
||||||
|
$this->refresh();
|
||||||
|
$this->balance = bcadd($this->balance, $amount, 2);
|
||||||
|
$this->save();
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'user_id' => $this->id,
|
||||||
|
'amount' => $amount,
|
||||||
|
'payment' => $payment,
|
||||||
|
'description' => $description,
|
||||||
|
'type' => 'income',
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($options) {
|
||||||
|
$data = array_merge($data, $options);
|
||||||
|
}
|
||||||
|
|
||||||
|
(new Transaction)->create($data);
|
||||||
|
|
||||||
|
(new Balance)->create([
|
||||||
|
'user_id' => $this->id,
|
||||||
|
'amount' => $amount,
|
||||||
|
'payment' => $payment,
|
||||||
|
'description' => $description,
|
||||||
|
'paid_at' => now(),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
return $this->balance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function startTransfer(User $to, string $amount, string|null $description)
|
||||||
|
{
|
||||||
|
$description_from = "转账给 $to->name($to->email)";
|
||||||
|
$description_to = "收到 $this->name($this->email) 的转账";
|
||||||
|
|
||||||
|
if ($description) {
|
||||||
|
$description_from .= ",备注:$description";
|
||||||
|
$description_to .= ",备注:$description";
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->reduce($amount, $description_from, true);
|
||||||
|
|
||||||
|
$to->charge($amount, 'transfer', $description_to);
|
||||||
|
|
||||||
|
return $this->balance;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,14 +7,14 @@
|
|||||||
|
|
||||||
class Payment extends Component
|
class Payment extends Component
|
||||||
{
|
{
|
||||||
public string $payment = '';
|
public string|null $payment = '';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new component instance.
|
* Create a new component instance.
|
||||||
*
|
*
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function __construct(string $payment)
|
public function __construct(string|null $payment)
|
||||||
{
|
{
|
||||||
//
|
//
|
||||||
$this->payment = $payment;
|
$this->payment = $payment;
|
||||||
|
@ -30,7 +30,8 @@
|
|||||||
"spatie/laravel-tags": "^4.3",
|
"spatie/laravel-tags": "^4.3",
|
||||||
"spiral/roadrunner": "^2.8.2",
|
"spiral/roadrunner": "^2.8.2",
|
||||||
"symfony/psr-http-message-bridge": "^2.1",
|
"symfony/psr-http-message-bridge": "^2.1",
|
||||||
"yansongda/laravel-pay": "~3.2.0"
|
"yansongda/laravel-pay": "~3.2.0",
|
||||||
|
"ext-bcmath": "*"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"beyondcode/laravel-query-detector": "^1.6",
|
"beyondcode/laravel-query-detector": "^1.6",
|
||||||
|
@ -13,13 +13,12 @@
|
|||||||
<table class="table table-hover">
|
<table class="table table-hover">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="col">类型与模块</th>
|
<th scope="col">模块</th>
|
||||||
<th scope="col">支付方式</th>
|
<th scope="col">支付方式</th>
|
||||||
<th scope="col">说明</th>
|
<th scope="col">说明</th>
|
||||||
<th scope="col">用户 ID</th>
|
<th scope="col">用户 ID</th>
|
||||||
<th scope="col">主机 ID</th>
|
<th scope="col">主机 ID</th>
|
||||||
<th scope="col">入账</th>
|
<th scope="col">金额</th>
|
||||||
<th scope="col">支出</th>
|
|
||||||
<th scope="col">余额</th>
|
<th scope="col">余额</th>
|
||||||
<th scope="col">交易时间</th>
|
<th scope="col">交易时间</th>
|
||||||
</tr>
|
</tr>
|
||||||
@ -27,19 +26,8 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
@foreach ($transactions as $t)
|
@foreach ($transactions as $t)
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
@if ($t->type === 'payout')
|
|
||||||
<span class="text-danger">
|
|
||||||
支出
|
|
||||||
</span>
|
|
||||||
@elseif($t->type === 'income')
|
|
||||||
<span class="text-success">
|
|
||||||
收入
|
|
||||||
</span>
|
|
||||||
@endif
|
|
||||||
|
|
||||||
<span class="module_name" module="{{ $t->module_id }}">{{ $t->module_id }}</span>
|
<span class="module_name" module="{{ $t->module_id }}">{{ $t->module_id }}</span>
|
||||||
|
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<x-payment :payment="$t->payment"></x-payment>
|
<x-payment :payment="$t->payment"></x-payment>
|
||||||
@ -61,16 +49,21 @@
|
|||||||
<a href="?host_id={{ $t->host_id }}">筛选</a>
|
<a href="?host_id={{ $t->host_id }}">筛选</a>
|
||||||
@endif
|
@endif
|
||||||
</td>
|
</td>
|
||||||
<td class="text-success">
|
|
||||||
{{ $t->income }} 元
|
|
||||||
</td>
|
|
||||||
|
|
||||||
<td class="text-danger">
|
<td>
|
||||||
{{ $t->outcome }} 元
|
@if ($t->type === 'payout')
|
||||||
|
<span class="text-danger">
|
||||||
|
支出 {{ $t->amount }} 元
|
||||||
|
</span>
|
||||||
|
@elseif($t->type === 'income')
|
||||||
|
<span class="text-success">
|
||||||
|
收入 {{ $t->amount }} 元
|
||||||
|
</span>
|
||||||
|
@endif
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td>
|
<td>
|
||||||
{{ $t->balance ?? $t->balances }} 元
|
{{ $t->user_remain ?? $t->balance }} 元
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{{ $t->created_at }}
|
{{ $t->created_at }}
|
||||||
|
@ -13,11 +13,10 @@
|
|||||||
<table class="table table-hover">
|
<table class="table table-hover">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="col">类型与模块</th>
|
<th scope="col">模块</th>
|
||||||
<th scope="col">支付方式</th>
|
<th scope="col">支付方式</th>
|
||||||
<th scope="col">说明</th>
|
<th scope="col">说明</th>
|
||||||
<th scope="col">入账</th>
|
<th scope="col">金额</th>
|
||||||
<th scope="col">支出</th>
|
|
||||||
<th scope="col">余额</th>
|
<th scope="col">余额</th>
|
||||||
<th scope="col">交易时间</th>
|
<th scope="col">交易时间</th>
|
||||||
</tr>
|
</tr>
|
||||||
@ -26,17 +25,7 @@
|
|||||||
@foreach ($transactions as $t)
|
@foreach ($transactions as $t)
|
||||||
<tr>
|
<tr>
|
||||||
|
|
||||||
<td>
|
<td>
|
||||||
@if ($t->type === 'payout')
|
|
||||||
<span class="text-danger">
|
|
||||||
支出
|
|
||||||
</span>
|
|
||||||
@elseif($t->type === 'income')
|
|
||||||
<span class="text-success">
|
|
||||||
收入
|
|
||||||
</span>
|
|
||||||
@endif
|
|
||||||
|
|
||||||
<span class="module_name" module="{{ $t->module_id }}">{{ $t->module_id }}</span>
|
<span class="module_name" module="{{ $t->module_id }}">{{ $t->module_id }}</span>
|
||||||
|
|
||||||
</td>
|
</td>
|
||||||
@ -46,16 +35,21 @@
|
|||||||
<td>
|
<td>
|
||||||
{{ $t->description }}
|
{{ $t->description }}
|
||||||
</td>
|
</td>
|
||||||
<td class="text-success">
|
|
||||||
{{ $t->income }} 元
|
|
||||||
</td>
|
|
||||||
|
|
||||||
<td class="text-danger">
|
<td>
|
||||||
{{ $t->outcome }} 元
|
@if ($t->type === 'payout')
|
||||||
|
<span class="text-danger">
|
||||||
|
支出 {{ $t->amount }} 元
|
||||||
|
</span>
|
||||||
|
@elseif($t->type === 'income')
|
||||||
|
<span class="text-success">
|
||||||
|
收入 {{ $t->amount }} 元
|
||||||
|
</span>
|
||||||
|
@endif
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td>
|
<td>
|
||||||
{{ $t->balance ?? $t->balances }} 元
|
{{ $t->amount ?? $t->balance }} 元
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{{ $t->created_at }}
|
{{ $t->created_at }}
|
||||||
|
Loading…
Reference in New Issue
Block a user