Files

482 lines
19 KiB
PHP
Raw Permalink Normal View History

2025-11-07 09:56:20 +08:00
<?php
namespace app\api\controller;
use support\Request;
use support\think\Db;
use app\model\Recharge as RechargeModel;
use app\model\User as UserModel;
use hg\apidoc\annotation as Apidoc;
/**
* 充值模块
2026-02-15 19:41:56 +08:00
* @Apidoc\NotParse()
* @Apidoc\NotDebug()
2025-11-07 09:56:20 +08:00
*/
class RechargeController extends BaseController{
/**
* 不需要鉴权的方法
* @var array
*/
public $noNeedAuth = ['*'];
/**
* 无需登录及鉴权的方法
* @var array
*/
public $noNeedLogin = ['notify','watch_list','updateUserBalance'];
/**
* 列表
* @Apidoc\Method("POST")
* @Apidoc\Query("page", type="int", require=true, desc="页码",default=1)
* @Apidoc\Query("limit", type="int", require=true, desc="分页大小",default=10)
*/
public function list()
{
$limit = (int)input('limit',10);
$status = Input('status',null);
if(!is_null($status)){
$list = RechargeModel::where('user_id',\support\Jwt\JwtToken::getCurrentId())->where('status',$status)->order('id desc')->paginate($limit);
}else{
$list = RechargeModel::where('user_id',\support\Jwt\JwtToken::getCurrentId())->order('id desc')->paginate($limit);
}
return $this->success(__('successful'),$list->toArray());
}
/**
* 创建
* @Apidoc\Method("POST")
* @Apidoc\Param("amount", type="string", require=true, desc="金额")
* @Apidoc\Param("network", type="string", require=true, desc="网络")
*/
function create(){
return $this->create1();
}
public function create2()
{
$user = \support\Jwt::getUser();
$data = [
'user_id' => $user->id,
'amount' => input('amount',0),
'network' => input('network',''),
'status' => \app\enum\WithdrawlStatus::CREATED->value
];
//验证最小金额
if($data['amount'] < Config('site.recharge_minimum')){
return $this->error(__('Minimum recharge of %num%',[
'%num%' => Config('site.recharge_minimum')
]));
}
if (!$data['network'] || !in_array($data['network'],['BEP-20','TRC-20','wxpay','alipay','qqpay'])) {
return $this->error(__('Network is incorrect'));
}
if(in_array($data['network'],['BEP-20','TRC-20'])){
$ing_count = RechargeModel::where('user_id',$data['user_id'])->where('status',\app\enum\RechargeStatus::CREATED->value)->count('id');
if($ing_count >= 2){
return $this->error(__('You have reached the limit of uncompleted orders ( %max% ). Please complete the existing orders before trying to create another one.',[
'%max%' => 2
]));
}
Db::startTrans();
try {
/** @var RechargeModel $order */
$order = RechargeModel::create($data);
//$order->notify_url = Request()->domain().'/api/recharge/notify';
$order->notify_url = config('pay.notify_server').'/api/recharge/notify';
$order->out_trade_no = $order->id;
$order->env = 'product';
$order->appid = Config('pay.appid');
$order->created_at = time();
$postdata = $order->toArray();
unset($postdata['id']);
//转换成USDT
$postdata['amount'] = bcdiv($order->amount,Config('site.money_to_usdt_rate'),4);
//折扣
$postdata['amount'] = bcdiv($postdata['amount'],Config('site.usdt_recharge_discount'),4);
$res = post(Config('pay.server').'/recharge/create',$postdata);
\support\Log::alert("create res:".$res);
//cp($res);
$res = json_decode($res,true);
if($res['code'] === 0){
$order->address = $res['data']['address'];
}else{
Db::rollback();
return $this->error(__('Failed to create recharge order, please try again later'));
}
$order->allowField(['address'])->save();
Db::commit();
return $this->success(__('successful'),[
'order' => $order
]);
} catch (\Exception $e) {
Db::rollback();
\support\Log::alert("create error".$e->getMessage());
return $this->error(__('Failed to create recharge order, please try again later'));
}
}else{
2026-03-01 21:05:19 +08:00
$sign = \support\Encrypt::aesencode(json_encode($data));
2025-11-07 09:56:20 +08:00
return $this->success(__('successful'),[
'order' => [
'id' => 0,
'network' => $data['network'],
'amount' => $data['amount'],
"url" => 'http://43.240.74.89:9595/pay/index?sign='.$sign
]
]);
}
}
public function create1()
{
$user = \support\Jwt::getUser();
$data = [
'user_id' => $user->id,
'amount' => input('amount',0),
'network' => input('network',''),
'status' => \app\enum\WithdrawlStatus::CREATED->value
];
//验证最小金额
if($data['amount'] < Config('site.recharge_minimum')){
return $this->error(__('Minimum recharge of %num%',[
'%num%' => Config('site.recharge_minimum')
]));
}
if (!$data['network'] || !in_array($data['network'],['BEP-20','TRC-20','wxpay','alipay','qqpay'])) {
return $this->error(__('Network is incorrect'));
}
if(in_array($data['network'],['BEP-20','TRC-20'])){
$ing_count = RechargeModel::where('user_id',$data['user_id'])->where('status',\app\enum\RechargeStatus::CREATED->value)->count('id');
if($ing_count >= 2){
return $this->error(__('You have reached the limit of uncompleted orders ( %max% ). Please complete the existing orders before trying to create another one.',[
'%max%' => 2
]));
}
}
Db::startTrans();
try {
/** @var RechargeModel $order */
$order = RechargeModel::create($data);
if(in_array($data['network'],['BEP-20','TRC-20'])){
//$order->notify_url = Request()->domain().'/api/recharge/notify';
$order->notify_url = config('pay.notify_server').'/api/recharge/notify';
$order->out_trade_no = $order->id;
$order->env = 'product';
$order->appid = Config('pay.appid');
$order->created_at = time();
$postdata = $order->toArray();
unset($postdata['id']);
//转换成USDT
$postdata['amount'] = bcdiv($order->amount,Config('site.money_to_usdt_rate'),4);
//折扣
$postdata['amount'] = bcmul($postdata['amount'],Config('site.usdt_recharge_discount'),4);
$res = post(Config('pay.server').'/recharge/create',$postdata);
\support\Log::alert("create res:".$res);
//cp($res);
$res = json_decode($res,true);
if($res['code'] === 0){
$order->address = $res['data']['address'];
}else{
Db::rollback();
return $this->error(__('Failed to create recharge order, please try again later'));
}
$order->allowField(['address'])->save();
}else{
$postdata = [
"pid" => '144604',
"type" => $order->network,
"out_trade_no" => $order->id,
"notify_url" => Config('pay.notify_server').'/api/recharge/notify_ok',
"return_url" => Config('pay.notify_server').'/api/recharge/notify_ok',
"name" => 'VIP会员',
"money" => $order->amount,
"device" => "mobile",
"clientip" => request()->getRealIp()
];
$postdata['money'] = bcdiv($postdata['money'] ,Config('site.rmb_recharge_discount'),2);
//\support\Log::alert("create postdata:".json_encode($postdata));
$this->getsign($postdata);
$res = post('https://pay.yf2.cn/mapi.php',$postdata);
\support\Log::alert("create res:".$res);
$res = json_decode($res,true);
if($res['code'] === 1){
if(isset($res['payurl'])){
$order->address = $res['qrcode'];
}elseif(isset($res['qrcode'])){
$order->address = $res['qrcode'];
}elseif(isset($res['urlscheme'])){
$order->address = $res['urlscheme'];
}
}else{
Db::rollback();
\support\Log::error(json_encode($res));
throw new \Exception($res['msg']);
}
$order->allowField(['address'])->save();
}
Db::commit();
return $this->success(__('successful'),[
'order' => $order
]);
} catch (\Exception $e) {
Db::rollback();
\support\Log::alert("create error".$e->getMessage());
return $this->error(__('Failed to create recharge order, please try again later'));
}
}
/**
* 更新
* @Apidoc\Method("POST")
* @Apidoc\Param("id", type="int", require=true, desc="ID")
* @Apidoc\Param("amount", type="string", require=true, desc="金额")
* @Apidoc\Param("network", type="string", require=true, desc="网络")
*/
function update(){
$id = input('id');
$amount = input('amount');
$network = input('network');
$data = [];
if(!$id){
return $this->error(__('Invalid parameters'));
}
/** @var RechargeModel $order */
$order = RechargeModel::where('id',$id)->find();
if(!$order){
return $this->error(__('Order not exsit'));
}
if($amount && $amount != $order->amount){
$data['amount'] = $amount;
//验证最小金额
if($data['amount'] < Config('site.recharge_minimum')){
return $this->error(__('Minimum recharge of %num%',[
'%num%' => Config('site.recharge_minimum')
]));
}
$data['amount'] = bcdiv($data['amount'],Config('site.money_to_usdt_rate'),4);
}
if($network && $network != $order->network){
$data['network'] = $network;
if (!$data['network'] || !in_array($data['network'],['BEP-20','TRC-20'])) {
return $this->error(__('Network is incorrect'));
}
}
if(empty($data)){
return $this->error(__('Invalid parameters'));
}
foreach($data as $field=>$value){
$order->$field = $value;
}
$data['out_trade_no'] = $order->id;
$data['action'] = 'update';
$data['appid'] = Config('pay.appid');
$res = post(Config('pay.server').'/recharge/create',$data);
//\support\Log::alert("update res:".$res);
$res = json_decode($res,true);
//cp($res);
if($res['code'] === 0){
if($order->address != $res['data']['address']){
$order->address = $res['data']['address'];
}
}else{
return $this->error(__('Failed to create recharge order, please try again later'));
}
$order->save();
return $this->success(__('successful'),[
'order' => $order
]);
}
/**
* 详情
* @Apidoc\Query("id", type="string", require=true, desc="ID")
*/
public function detail(){
$appid = input('id');
$vo = RechargeModel::where('id',$appid)->find();
if($vo) {
return $this->success(__('successful'),[
'order' => $vo
]);
}
return $this->error(__("Record does not exist"));
}
/**
* 转账成功异步通知
* @Apidoc\NotParse()
* @Apidoc\NotDebug()
*/
public function notify(){
2026-03-01 21:05:19 +08:00
$data = \support\Encrypt::aesdecode(input('data'));
2025-11-07 09:56:20 +08:00
$data = json_decode($data,true);
/** @var RechargeModel $vo */
$vo = RechargeModel::where('id',$data['out_trade_no'])->find();
if($vo){
if($data['result'] == 'SUCCESS'){
if($vo->status != \app\enum\RechargeStatus::COMPLETE->value){
try{
$vo->status = \app\enum\RechargeStatus::COMPLETE->value;
$vo->txid = $data['txid'];
$vo->real_amount = isset($data['real_amount']) ? $data['real_amount'] : 0;
$vo->transfer_at = $data['transfer_at'] ?: time();
$vo->save();
//消除USDT和人民币的误差,因为USDT支付可能不是一模一样的金额
$money = bcmul($vo->amount ,Config('site.rmb_to_money_rate'));
UserModel::money($vo->user_id,$money,\app\enum\BalanceType::RECHARGE,$vo->id);
Hook('recharge.success',$vo);
}catch(\Exception $e){
2026-02-21 08:21:05 +08:00
log_alert('充值回调失败:'.$e->getMessage());
log_alert($data);
2025-11-07 09:56:20 +08:00
}
}
}else{
$vo->status = \app\enum\RechargeStatus::FAIL->value;
$vo->txid = $data['txid'];
$vo->reason = $data['reason'];
$vo->save();
}
}
return response("SUCCESS");
}
/**
* 人民币转账成功异步通知
* @Apidoc\NotParse()
* @Apidoc\NotDebug()
*/
public function notify_ok(){
$data = Input('post.');
$sign = get_pay_sign($data);
if($sign != $data['sign']){
return response("FAIL");
}
/** @var RechargeModel $vo */
$vo = RechargeModel::where('id',$data['out_trade_no'])->find();
if($vo && isset($data['trade_status'])){
if($data['trade_status'] === 'TRADE_SUCCESS'){
if($vo->status != \app\enum\RechargeStatus::COMPLETE->value){
try{
$vo->status = \app\enum\RechargeStatus::COMPLETE->value;
$vo->txid = isset($data['trade_no']) ? $data['trade_no'] : '';
$vo->from='pay.yf2.cn';
$vo->real_amount = isset($data['money']) ? $data['money'] : 0;
$vo->save();
$money = bcmul($vo->real_amount ,Config('site.rmb_to_money_rate'));
UserModel::money($vo->user_id,$money,\app\enum\BalanceType::RECHARGE,$vo->id);
Hook('recharge.success',$vo);
}catch(\Exception $e){
2026-02-21 08:21:05 +08:00
log_alert('充值回调失败:'.$e->getMessage());
log_alert($data);
2025-11-07 09:56:20 +08:00
return response("FAIL");
}
}
}
}
return response("SUCCESS");
}
/**
* 根据TXID更新用户余额,补单的功能
* @Apidoc\NotParse()
* @Apidoc\NotDebug()
* @return \support\Response
*/
function update_balance_by_txid(){
$user_id = \support\Jwt\JwtToken::getCurrentId();
$data = [
'txid' => input('txid'),
'id' => input('id'),
'status' => \app\enum\WithdrawlStatus::CREATED->value
];
/** @var RechargeModel $vo */
$vo = RechargeModel::where('id',$data['id'])->find();
if($vo->user_id !=$user_id){
return $this->error(__('permission denied'));
}
//验证最小金额
if(!$data['txid']){
return $this->error(__('Invalid parameters'));
}
$res = get(Config('pay.server').'/Util/txid?txid='.$data['txid']);
$res = json_decode($res,true);
if($res['code'] !== 0){
return $this->error($res['msg']);
}
$vo->txid = $data['txid'];
$vo->result = $res['result'];
if($res['result'] == 'SUCCESS'){
$vo->from = $res['from'];
$vo->real_amount = $res['real_amount'];
$vo->pay_time = $res['timestamp'];
$vo->status = \app\enum\RechargeStatus::COMPLETE->value;
}else{
$vo->result = $res['from'];
$vo->reason = $res['real_amount'];
$vo->status = \app\enum\RechargeStatus::FAIL->value;
}
$vo->save();
return $this->success(__('successful'));
}
/**
* 根据监视上报数据更新用户余额
* @Apidoc\NotParse()
* @Apidoc\NotDebug()
*/
function updateUserBalance(Request $request){
$decimal_part = $request->post('decimal_part');
$data = [
'user_id' => 0 ,
'amount' => $request->post('amount'),
'network' => $request->post('chain'),
'address' => $request->post('to'),
'from' => $request->post('from'),
'txid' => $request->post('txid'),
'pay_time' => $request->post('pay_time'),
'created_at' => $request->post('pay_time'),
];
if($data['network'] == 'TRC-20'){
$data['user_id'] = UserModel::where('trc_recharge_address',$data['address'])->where('decimal_part',$decimal_part)->value('id');
}else{
$data['user_id'] = UserModel::where('bep_recharge_address',$data['address'])->where('decimal_part',$decimal_part)->value('id');
}
if(!$data['user_id']){
return $this->success(__('user not exist'));
}
$data['real_amount'] = $data['amount'];
//$data['pay_time'] = time();
$data['status'] = 2;
$vo = RechargeModel::where('txid',$data['txid'])->find();
if($vo){
return $this->success(__('exist'));
}
Db::startTrans();
try{
$idata = RechargeModel::create($data);
UserModel::score($data['user_id'],$data['amount'],\app\enum\BalanceType::RECHARGE,$idata['id'].'');
Db::commit();
Hook('recharge.success',$idata);
return $this->success(__('successful'));
}catch(\Exception $e){
Db::rollback();
return $this->error($e->getMessage());
}
}
/**
* 监视列表
* @Apidoc\NotParse()
* @Apidoc\NotDebug()
*/
function watch_list(){
//$bep = UserModel::distinct(true)->column('LOWER(bep_recharge_address)');
//$trc = UserModel::distinct(true)->column('LOWER(trc_recharge_address)');
$bep = UserModel::distinct(true)->column('bep_recharge_address');
$trc = UserModel::distinct(true)->column('trc_recharge_address');
return $this->success(__('successful'),[
'BEP-20'=>$bep,
'TRC-20'=>$trc,
]);
}
protected function getsign(&$data, $key = "3E7551E3707DFB6B6E8A6E83B01FF437") {
return get_pay_sign($data,$key);
}
}