Ken 3 dagar sedan
förälder
incheckning
d9074998ec

+ 11 - 5
app/Http/Controllers/admin/Wallet.php

@@ -19,6 +19,7 @@ use App\Services\IssueService;
 use App\Services\GameplayRuleService;
 use App\Models\Config;
 use App\Services\ConfigService;
+use App\Services\Payment\QianBaoService;
 use App\Services\Payment\SanJinService;
 use App\Services\PaymentOrderService;
 
@@ -282,13 +283,18 @@ class Wallet extends Controller
         // $awards = IssueService::award([7,7,7]);
         // $result = BetService::betSettled2('3356003',$awards);
         // $result = IssueService::sendLotteryImage($memberId, 3356000);
-        // $order_no = SanJinService::createOrderNo();
+        // $order_no = QianBaoService::createOrderNo();
         // echo $order_no;
-        // $result = SanJinService::pay(100,$order_no);
-        // $result = SanJinService::payout(100,$order_no,'厦门银行','张三',1245679451259741541,SanJinService::ALIPAY_TO_ALIPAY);
-        // $result = PaymentOrderService::createPayout($memberId,100,SanJinService::ALIPAY_TO_ALIPAY,'支付宝','张三','17605957777');
+        // $result = QianBaoService::pay(100,$order_no);
+        // $result = QianBaoService::payout(100,$order_no,'厦门银行','张三',1245679451259741541,QianBaoService::ALIPAY_TO_ALIPAY);
+        // $result = PaymentOrderService::createPayout($memberId,100,QianBaoService::ALIPAY_TO_ALIPAY,'支付宝','张三','17605957777');
         // PaymentOrderService::sendMessage($result['chat_id'],$result['text']);
-        $result = PaymentOrderService::createPay($memberId,100,SanJinService::PAY_ZFB1,'张三');
+        // $result = PaymentOrderService::createPay($memberId,100,QianBaoService::PAY_ZFB1,'张三');
+        // $result = mb_strlen('哈哈哈123ABC');
+        // $order_no = SanJinService::createOrderNo();
+        // $result = SanJinService::pay(10000,$order_no);
+        $result = PaymentOrderService::createPay($memberId,200,'test');
+        PaymentOrderService::sendMessage($result['chat_id'],$result['text'],[],$result['image']??'');
         echo "<pre>";
         var_dump($result);
     }

+ 12 - 0
app/Http/Controllers/api/Pay.php

@@ -30,4 +30,16 @@ class Pay extends Controller
         echo 'success';
     }
 
+    // 用户支付异步通知
+    public function harvestHandle(Request $request)
+    {
+        $data = $request->all();
+        // 记录到专用支付日志
+        Log::channel('payment')->info('三斤支付回调', $data);
+
+        $res = PaymentOrderService::receivePay($data);
+        var_dump($res);
+        echo 'success';
+    }
+
 }

+ 1 - 1
app/Models/PaymentOrder.php

@@ -17,7 +17,7 @@ class PaymentOrder extends Authenticatable
     use HasApiTokens, Notifiable;
     protected $table = 'payment_orders';
     // protected $hidden = ['created_at', 'updated_at'];
-    protected $fillable = ['type', 'order_no', 'member_id', 'amount' ,'channel' ,'bank_name' ,'account' ,'card_no' ,'status' ,'callback_url' ,'callback_data' ,'remark'];
+    protected $fillable = ['type', 'order_no', 'member_id', 'amount' ,'channel' ,'bank_name' ,'account' ,'card_no' ,'status' ,'callback_url' ,'callback_data' ,'remark' ,'pay_no' ,'pay_url' ,'pay_data' ,'fee'];
 
     protected function getCreatedAtAttribute($value)
     {

+ 67 - 0
app/Services/BaseService.php

@@ -424,4 +424,71 @@ class BaseService
         return $prefix . $timePart . $randomPart . $memberSuffix;
     }
 
+
+    /**
+     * @description: 生成支付二维码
+     * @param {*} $address 支付地址
+     * @return {*}
+     */
+    public static function createPaymentQrCode($address = '')
+    {
+        // $content = $address;
+        $content = '';
+        $qrSize = 300;
+        $font = 4;
+        $textHeight = 20;
+        $padding = 10;
+
+        // 生成二维码图像对象
+        $result = Builder::create()
+            ->writer(new PngWriter())
+            ->data($address)
+            ->size($qrSize)
+            ->margin(0)
+            ->build();
+
+        $qrImage = imagecreatefromstring($result->getString());
+
+        // 创建画布(加上下方文字区和边距)
+        $canvasWidth = $qrSize + $padding * 2;
+        $canvasHeight = $qrSize + $textHeight + $padding * 2;
+        $image = imagecreatetruecolor($canvasWidth, $canvasHeight);
+
+        // 背景白色
+        $white = imagecolorallocate($image, 255, 255, 255);
+        imagefill($image, 0, 0, $white);
+
+        // 黑色字体
+        $black = imagecolorallocate($image, 0, 0, 0);
+
+        // 合并二维码图像
+        imagecopy($image, $qrImage, $padding, $padding, 0, 0, $qrSize, $qrSize);
+
+        // 写文字
+        $textWidth = imagefontwidth($font) * strlen($content);
+        $x = ($canvasWidth - $textWidth) / 2;
+        $y = $qrSize + $padding + 5;
+        imagestring($image, $font, $x, $y, $content, $black);
+
+        $address_name = self::generateRandomString(20).time();
+
+        // 生成文件名
+        $filename = $address_name. '.png';
+        $relativePath = 'payment/' . $filename;
+        $storagePath = storage_path('app/public/' . $relativePath);
+
+        // 确保目录存在
+        @mkdir(dirname($storagePath), 0777, true);
+
+        // 保存图片到文件
+        imagepng($image, $storagePath);
+
+        // 清理
+        imagedestroy($qrImage);
+        imagedestroy($image);
+
+        // 返回 public 存储路径(可用于 URL)
+        return 'storage/'.$relativePath; // 或返回 Storage::url($relativePath);
+    }
+
 }

+ 1 - 1
app/Services/IssueService.php

@@ -289,7 +289,7 @@ class IssueService extends BaseService
                 SendTelegramGroupMessageJob::dispatch($text, $buttons, $image, true);
             }
             //暂时注释
-           $recordImage = self::lotteryImage($info->issue_no);
+        //    $recordImage = self::lotteryImage($info->issue_no);
             if ($recordImage) {
                 // self::bettingGroupNotice('', [], url($recordImage));
                 SendTelegramGroupMessageJob::dispatch('', [], url($recordImage), false);

+ 3 - 2
app/Services/Payment/QianBaoService.php

@@ -29,7 +29,8 @@ class QianBaoService extends BaseService
     // 获取异步的通知地址
     public static function getNotifyUrl()
     {
-        return 'https://botpc28.testx2.cc/api/pay/notify';
+        $host = config('app.url');
+        return $host.'/api/pay/notify';
     }
 
     // 获取商户ID
@@ -147,7 +148,7 @@ class QianBaoService extends BaseService
         $signStr = $merchant_id .  $order_no . $amount . $secret;
         $sign = md5($signStr);
         $data['sign'] = $sign;
-        var_dump($data);
+        
         $client = self::getClient();
         $response = $client->post('api/pay', [
                 'form_params' => $data,

+ 141 - 0
app/Services/Payment/SanJinService.php

@@ -0,0 +1,141 @@
+<?php 
+
+namespace App\Services\Payment;
+
+use GuzzleHttp\Client;
+use GuzzleHttp\Exception\RequestException;
+use GuzzleHttp\Psr7\Response;
+use App\Services\BaseService;
+
+class SanJinService extends BaseService
+{
+    const REQUEST_URL = 'https://jkapi-sanjin.jkcbb.com/';
+
+    const PRODUCT_TEST = 'T888'; // 测试支付通道
+
+
+    public static $PRODUCT = [
+        'T888' => [
+            'type' => 'test',
+            'rate' => 0.02,
+            'max' => 5000,
+            'min' => 10
+        ],
+        'ZPB001' => [
+            'type' => 'zfb',
+            'rate' => 0.085,
+            'max' => 200,
+            'min' => 100
+        ],
+        'ZPB002' => [
+            'type' => 'zfb',
+            'rate' => 0.057,
+            'max' => 200,
+            'min' => 1000
+        ],
+        'ZPB003' => [
+            'type' => 'zfb',
+            'rate' => 0.052,
+            'max' => 1000,
+            'min' => 3000
+        ],
+        'ZPB004' => [
+            'type' => 'zfb',
+            'rate' => 0.042,
+            'max' => 3000,
+            'min' => 20000
+        ],
+    ];
+
+    // 获取商户ID
+    public static function getMerchantId()
+    {
+        return config('app.tree_pay_mch_id');
+    }
+    // 获取商户秘钥
+    public static function getSecret()
+    {
+        return config('app.tree_pay_key');
+    }
+
+    // 获取异步的通知地址
+    public static function getNotifyUrl()
+    {
+        $host = config('app.url');
+        return $host.'/api/pay/harvest';
+    }
+
+
+    /**
+     * @description: 获取请求客户端
+     * @return {*}
+     */
+    public static function getClient(): Client
+    {
+        return new Client([
+            'base_uri' => self::REQUEST_URL,
+            'timeout' => 5.0,
+        ]);
+    }
+
+    // 签名
+    public static function signature($params = [],$must = [])
+    {
+        if($must){
+            foreach($params as $k => $v){
+                if(!in_array($k,$must)){
+                    unset($params[$k]);
+                }
+            }
+        }
+        ksort($params, SORT_STRING);
+     
+
+        $parts = [];
+        foreach($params as $k => $v){
+            array_push($parts,$k.'='.$v);
+        }
+        $mch_key = self::getSecret();
+        $parts[] = "key=".$mch_key;
+
+        $sign = md5(implode('&',$parts));
+        return $sign;
+    }
+
+    /**
+     * @description: 发起支付订单
+     * @param {*} $amount 金额单位分
+     * @param {*} $orderNo  订单号
+     * @param {*} $type 支付通道
+     * @return {*}
+     */    
+    public static function pay($amount,$orderNo,$type = self::PRODUCT_TEST)
+    {
+
+        $must = ['mchId','productId','outTradeNo','amount','reqTime','notifyUrl'];
+        $mch_id = self::getMerchantId();
+
+        
+        
+        $data = [];
+        $data['mchId'] = $mch_id;
+        $data['amount'] = $amount;
+        $data['outTradeNo'] = $orderNo;
+        $data['notifyUrl'] = self::getNotifyUrl();
+        $data['reqTime'] = time() * 1000;
+        $data['productId'] = $type;
+
+        
+        $data['sign'] = self::signature($data,$must);
+        $client = self::getClient();
+        $response = $client->post('api/v1/pay/unifiedOrder', [
+            'json' => $data,
+            'headers' => [
+                'Content-Type' => 'application/json',
+            ]
+        ]);
+
+        $body = $response->getBody();
+        return json_decode($body->getContents(), true);
+    }
+}

+ 148 - 7
app/Services/PaymentOrderService.php

@@ -12,6 +12,7 @@ use Illuminate\Support\Facades\Cache;
 use Illuminate\Support\Facades\Log;
 
 use App\Services\Payment\QianBaoService;
+use App\Services\Payment\SanJinService;
 
 use App\Services\WalletService; 
 use App\Services\BalanceLogService; 
@@ -151,17 +152,157 @@ class PaymentOrderService extends BaseService
      * @description: 创建代收订单
      * @param {*} $memberId
      * @param {*} $amount
-     * @param {*} $channel
-     * @param {*} $account
+     * @param {*} $paymentType 支付类型:支付宝、数字人民币
      * @return {*}
      */    
-    public static function createPay($memberId,$amount,$channel,$account)
+    public static function createPay($memberId,$amount,$paymentType)
     {
+        $result = [];
+        $result['chat_id'] = $memberId;
+        $channel = ''; // 支付的通道
+        $product = SanJinService::$PRODUCT;
+        $max = 0;
+        $min = 0;
+        $rate = 0;
+        foreach($product as $k => $v){
+            if($v['type'] == $paymentType){
+                if($amount >= $v['min'] && $amount <= $v['max']){
+                    $channel = $k;
+                    $rate = $v['rate'];
+                    
+                }
+                if($min == 0){
+                    $min = $v['min'];
+                }
+                if($max == 0){
+                    $max = $v['max'];
+                }
+                if($min > $v['min']){
+                    $min = $v['min'];
+                }
+                if($max < $v['max']){
+                    $max = $v['max'];
+                }
+            }
+        }
+
+        // 没有找到支付通道
+        if(empty($channel)){
+            $text = "发起充值失败 \n";
+            $text .= "最低充值:".$min." \n";
+            $text .= "最高充值:".$max." \n";
+            $text .= "请重新填写充值的金额!";
+            $result['text'] = $text;
+            return $result;
+        }
+
         $data = [];
         $data['type'] = self::TYPE_PAY;
+        $data['member_id'] = $memberId;
+        $data['amount'] = $amount;
+        $data['channel'] = $channel;
+        $data['fee'] = $amount * $rate;
         $order_no = self::createOrderNo('sj'.$data['type'].'_', $memberId);
-        $ret = QianBaoService::pay($amount, $order_no, $channel);
-        return $ret;
+        $data['order_no'] = $order_no;
+        $data['callback_url'] = SanJinService::getNotifyUrl();
+        $data['remark'] = '充值费率:'.$rate;
+        $data['status'] = self::STATUS_STAY;
+        $ret = SanJinService::pay(($amount*100), $order_no, $channel);
+        if($ret['code'] == 0){
+
+            $qrCode = asset(self::createPaymentQrCode($ret['data']['payUrl']));
+            $result['image'] = $qrCode;
+            $item = $ret['data'];
+            $data['status'] = self::STATUS_PROCESS;
+            $data['pay_no'] = $item['tradeNo'];
+            $data['pay_url'] = $item['payUrl'];
+            $data['pay_data'] = json_encode($ret,JSON_UNESCAPED_UNICODE);
+            $info = self::model()::create($data);
+            $text = "✅ 支付提示 \n";
+            $text .= "请扫码支付 \n";
+            $text .= "支付金额:".$amount." RMB \n";
+            $text .= "请按实际支付金额进行付款,否则影响到账 \n";
+            $text .= "支付完成后请耐心等待,支付到账会第一时间通知您! \n";
+            $result['text'] = $text;
+        }else{
+            $result['text'] = $ret['message'];
+        }
+        return $result;
+    }
+
+    /**
+     * @description: 接收支付的通知
+     * @param {*} $params
+     * @return {*}
+     */    
+    public static function receivePay($params)
+    {
+        // 判断商户号
+        if($params['mchId'] == SanJinService::getMerchantId()){
+            $must = ['mchId','productId','tradeNo','outTradeNo','amount','payAmount','state','createTime','payTime'];
+            
+
+            $info = self::findOne(['order_no' => $params['outTradeNo']]);
+            if($info){
+                // 平台以分计算转成元
+                $payAmount = $params['payAmount'] / 100;
+                // 判断金额是不是正确认
+                if($info->amount != $payAmount){
+                    $text = '❌ 支付失败提醒 \n';
+                    $text .= "订单金额:{$info->amount} \n";
+                    $text .= "实际支付:{$payAmount} \n";
+                    $text .= "订单号:{$params['outTradeNo']} \n";
+                    $text .= "失败原因:支付金额与订单金额不一致 \n";
+                    $text .= "请联系客服处理!";
+                    self::sendMessage($info->member_id,$text);
+                    return false;
+                }
+                
+                if($params['sign'] != SanJinService::signature($params,$must)){
+                    return false;
+                }
+
+                if($info->status != self::STATUS_PROCESS){
+                    return false;
+                }
+          
+                // 付款
+                if($info->type == self::TYPE_PAY){
+                    if($params['state'] == 1){
+                        $info->status = self::STATUS_SUCCESS;
+                        $wallet = WalletService::findOne(['member_id' => $info->member_id]);
+                        $balance = $wallet->available_balance;
+                        $available_balance = bcadd($balance,$payAmount,10);
+                        $wallet->available_balance = $available_balance;
+                        $wallet->save();
+
+                        // 记录余额变动日志
+                        BalanceLogService::addLog(
+                            $info->member_id,
+                            $payAmount,
+                            $balance,
+                            $available_balance,
+                            '三方充值',
+                            $info->id,
+                            ''
+                        );
+
+                        $text = "✅ 支付成功 \n";
+                        $text .= "充值金额:{$payAmount} RMB \n";
+                        $text .= "订单号:{$params['outTradeNo']} \n";
+                        $text .= "您充值的金额已到账,请注意查收!";
+                        self::sendMessage($info->member_id,$text);
+                    }else{
+                        $info->status = self::STATUS_FAIL;
+                        $text = "❌ 支付失败 \n";
+                        $text .= "充值金额:{$payAmount} RMB \n";
+                        $text .= "订单号:{$params['outTradeNo']} \n";
+                    }
+                    $info->save();
+                    return true;
+                }
+            }
+        }
     }
 
     /**
@@ -212,7 +353,7 @@ class PaymentOrderService extends BaseService
             $order_no = self::createOrderNo('sj'.$data['type'].'_', $memberId);
             $data['order_no'] = $order_no;
             $data['member_id'] = $memberId;
-            $data['free'] = $amount * 0.002 + 2;
+            $data['fee'] = $amount * 0.002 + 2;
             $amount = number_format($amount, 2, '.', '');
             $data['amount'] = $amount;
             $data['channel'] = $channel;
@@ -345,7 +486,7 @@ class PaymentOrderService extends BaseService
             if($info){
                 // 判断金额是不是正确认
                 if($info->amount != $params['amount']){
-                    return 'amount';
+                    return false;
                 }
             
                 // 验证签名

+ 8 - 7
app/Services/QianBaoWithdrawService.php

@@ -267,13 +267,14 @@ class QianBaoWithdrawService
         if (!$amount) return WalletService::getBalance($chatId);
 
         $bank = Bank::where('id', $id)->first();
-        PaymentOrderService::createPayout($chatId, $amount, $bank->channel, $bank->bank_name, $bank->account, $bank->card_no);
-        $text = "提交成功\n";
-        $text .= "结果将在稍后通知您,请留意通知!!!";
-        return [
-            'chat_id' => $chatId,
-            'text' => $text,
-        ];
+        $result = PaymentOrderService::createPayout($chatId, $amount, $bank->channel, $bank->bank_name, $bank->account, $bank->card_no);
+        return $result;
+        // $text = "提交成功\n";
+        // $text .= "结果将在稍后通知您,请留意通知!!!";
+        // return [
+        //     'chat_id' => $chatId,
+        //     'text' => $text,
+        // ];
     }
 
     //银行卡管理

+ 3 - 0
config/app.php

@@ -11,6 +11,9 @@ return [
     // 第三方支付配置
     'tree_payment_merchant_id' => env('TREE_PAYMENT_MERCHANT_ID',''),
     'tree_payment_secret' => env('TREE_PAYMENT_SECRET',''),
+    // 第三方收款配置
+    'tree_pay_mch_id' => env('TREE_PAY_MCH_ID',''),
+    'tree_pay_key' => env('TREE_PAY_MCH_KEY',''),
     /*
     |--------------------------------------------------------------------------
     | Application Name

+ 35 - 0
database/migrations/2025_11_13_143754_add_payment_fields_to_payment_orders_table.php

@@ -0,0 +1,35 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+return new class extends Migration
+{
+    /**
+     * Run the migrations.
+     *
+     * @return void
+     */
+    public function up()
+    {
+        Schema::table('payment_orders', function (Blueprint $table) {
+            //
+            $table->string('pay_no')->nullable()->comment('支付单号');
+            $table->text('pay_url')->nullable()->comment('支付地址');
+            $table->json('pay_data')->nullable()->comment('支付发起返回内容');
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     *
+     * @return void
+     */
+    public function down()
+    {
+        Schema::table('payment_orders', function (Blueprint $table) {
+            //
+        });
+    }
+};

+ 38 - 0
database/migrations/2025_11_14_112559_change_fee_precision_in_payment_orders.php

@@ -0,0 +1,38 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+return new class extends Migration
+{
+    /**
+     * Run the migrations.
+     *
+     * @return void
+     */
+    public function up()
+    {
+        Schema::table('payment_orders', function (Blueprint $table) {
+            // 删除原字段
+            $table->dropColumn('fee');
+        });
+
+        Schema::table('payment_orders', function (Blueprint $table) {
+            // 重新创建字段,精度为8位小数
+            $table->decimal('fee', 18, 8)->default(0)->comment('手续费');
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     *
+     * @return void
+     */
+    public function down()
+    {
+        Schema::table('payment_orders', function (Blueprint $table) {
+            //
+        });
+    }
+};

+ 5 - 1
example.env

@@ -18,10 +18,14 @@ TRX_PRIVATE_KEY=
 # TRON网络正式main或者测试test
 TRON_NETWORK=main
 
-#三斤钱宝第三方支付配置
+#钱宝第三方支付配置
 TREE_PAYMENT_MERCHANT_ID= 
 TREE_PAYMENT_SECRET=
 
+#三斤第三方支付配置
+TREE_PAY_MCH_ID=
+TREE_PAY_MCH_KEY=
+
 # 数据库
 DB_CONNECTION=mysql
 DB_HOST=127.0.0.1

+ 1 - 0
routes/api.php

@@ -31,6 +31,7 @@ Route::prefix('/issue')->group(function () {
 Route::get('/test', [Home::class, 'test']);
 Route::prefix('/pay')->group(function () {
     Route::post("/notify", [Pay::class, 'notifyHandle']);
+    Route::post("/harvest", [Pay::class, 'harvestHandle']);
 });