laowu 1 settimana fa
parent
commit
4d774689b9

+ 4 - 1
.gitignore

@@ -2,4 +2,7 @@
 .env
 vendor
 storage/logs/*
-storage/framework/*
+storage/framework/*
+AGENTS.md
+resources/docs/*
+.github/*

+ 180 - 0
app/Http/Controllers/Api/SafePayController.php

@@ -0,0 +1,180 @@
+<?php
+
+namespace App\Http\Controllers\Api;
+
+use App\Http\logic\api\SafePayLogic;
+use App\Http\logic\api\SafePayCashierLogic;
+use App\Inter\PayMentInterFace;
+use App\Notification\TelegramBot;
+use App\Services\SafePay;
+use App\Util;
+use App\Utility\SetNXLock;
+use Illuminate\Http\Request;
+use Illuminate\Support\Facades\Redis;
+
+class SafePayController implements PayMentInterFace
+{
+    /**
+     * 代收下单
+     */
+    public function pay_order(
+        $userId,
+        $payAmt,
+        $userName,
+        $userEmail,
+        $userPhone,
+        $GiftsID,
+        $buyIP,
+        $AdId,
+        $eventType,
+        $pay_method = ''
+    ) {
+        $logic = new SafePayLogic();
+        try {
+            $res = $logic->pay_order($userId, $payAmt, $userPhone, $userEmail, $userName, $GiftsID, $buyIP, $AdId, $eventType, $pay_method);
+        } catch (\Exception $exception) {
+            Redis::set("PayErro_SafePay", 1);
+            Redis::expire("PayErro_SafePay", 600);
+            Util::WriteLog('SafePay_error', $exception->getMessage() . json_encode($logic->result ?? []));
+            TelegramBot::getDefault()->sendProgramNotify("SafePay Except ", $exception->getMessage(), $exception);
+            return apiReturnFail($logic->getError());
+        }
+
+        if (!empty($res) && isset($res['code']) && $res['code'] == 200) {
+            // SafePay 成功返回: data.order_data 是支付链接
+            $data = [
+                'content'  => $res['data']['order_data'] ?? '',
+                'money'    => $payAmt,
+                'prdOrdNo' => $res['data']['order_no'] ?? '',
+            ];
+            return apiReturnSuc($data);
+        }
+
+        if ($res == false) {
+            return apiReturnFail($logic->getError() ?: 'Payment failed');
+        }
+
+        // 非200的响应,记录错误
+        Redis::set("PayErro_SafePay", 1);
+        Redis::expire("PayErro_SafePay", 600);
+        $errMsg = $res['message'] ?? 'Unknown error';
+        TelegramBot::getDefault()->sendProgramNotify("SafePay ReturnFail ", $errMsg . " | " . json_encode($res));
+        return apiReturnFail(['web.payment.paytype_error', $errMsg]);
+    }
+
+    /**
+     * 代收异步回调通知
+     * 
+     * SafePay回调格式(POST JSON):
+     * {
+     *   "mer_no": 600000,
+     *   "order_no": "xxx",
+     *   "order_amount": "1000.00",
+     *   "order_reality_amount": "1000.00",
+     *   "pay_type_code": 1301,
+     *   "status": "success",
+     *   "ref_no": "xxx",
+     *   "sign": "xxx"
+     * }
+     */
+    public function notify(Request $request)
+    {
+        $post = $request->all();
+        $payload = json_encode($post);
+
+        Util::WriteLog('SafePay', "SafePay回调订单\n" . $payload);
+
+        $service = new SafePay();
+
+        // 验签(平台公钥)
+        try {
+            $verify = $service->verifySign($post);
+        } catch (\Exception $e) {
+            Util::WriteLog('SafePay', '验签失败:' . $e->getMessage());
+            return 'fail';
+        }
+
+        if (!$verify) {
+            Util::WriteLog('SafePay', '签名错误');
+            return 'fail';
+        }
+
+        $order_sn = $post['order_no'] ?? '';
+        if (empty($order_sn)) {
+            Util::WriteLog('SafePay', '缺少订单号');
+            return 'fail';
+        }
+
+        // 代收回调加锁,防止并发重复处理
+        $lockKey = 'pay_notify_SafePay_' . $order_sn;
+        if (!SetNXLock::getExclusiveLock($lockKey, 60)) {
+            Util::WriteLog('SafePay', '代收回调并发,订单已处理或处理中: ' . $order_sn);
+            return 'ok';
+        }
+
+        $logic = new SafePayLogic();
+        try {
+            $ret = $logic->notify($post);
+            return $ret;
+        } catch (\Exception $exception) {
+            Redis::set("PayErro_SafePay", 1);
+            Redis::expire("PayErro_SafePay", 600);
+            TelegramBot::getDefault()->sendProgramNotify("SafePay 订单回调执行异常 ", json_encode($post), $exception);
+            Util::WriteLog("SafePay_error", $post);
+            return 'fail';
+        } finally {
+            SetNXLock::release($lockKey);
+        }
+    }
+
+    /**
+     * 代付异步回调通知
+     *
+     * SafePay 代付回调格式(POST JSON):
+     * {
+     *   "mer_no": 600000,
+     *   "order_no": "xxx",
+     *   "order_amount": "10.00",
+     *   "order_reality_amount": "10.00",
+     *   "currency": "USD",
+     *   "result": "success",
+     *   "sys_no": "212073",
+     *   "sign": "xxx"
+     * }
+     */
+    public function cash_notify(Request $request)
+    {
+        $post = $request->all();
+        $payload = json_encode($post);
+
+        Util::WriteLog('SafePay', "SafePay 提现回调\n" . $payload);
+
+        $service = new SafePay();
+
+        // 验签(平台公钥)
+        try {
+            $verify = $service->verifySign($post);
+        } catch (\Exception $e) {
+            Util::WriteLog('SafePay', '提现回调验签失败:' . $e->getMessage());
+            return 'FAIL';
+        }
+
+        if (!$verify) {
+            Util::WriteLog('SafePay', '提现回调签名错误');
+            return 'FAIL';
+        }
+
+        $logic = new SafePayCashierLogic();
+        try {
+            return $logic->notify($post);
+        } catch (\Exception $exception) {
+            TelegramBot::getDefault()->sendProgramNotify(
+                "SafePay 提现异步回调执行异常 ",
+                json_encode($post),
+                $exception
+            );
+            Util::WriteLog("SafePay_error", $post);
+            return 'SUCCESS';
+        }
+    }
+}

+ 320 - 0
app/Http/logic/api/SafePayCashierLogic.php

@@ -0,0 +1,320 @@
+<?php
+
+namespace App\Http\logic\api;
+
+use App\dao\Estatisticas\RechargeWithDraw;
+use App\Http\helper\NumConfig;
+use App\Inter\CashierInterFace;
+use App\Models\PrivateMail;
+use App\Models\RecordUserDataStatistics;
+use App\Services\SafePay;
+use App\Services\PayConfig;
+use App\Services\StoredProcedure;
+use App\Util;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Log;
+use Illuminate\Support\Facades\Redis;
+
+class SafePayCashierLogic implements CashierInterFace
+{
+    const AGENT = 106; // SafePay代付渠道值
+    protected $agent = 106;
+
+    /**
+     * 提交代付申请
+     */
+    public function payment($RecordID, $amount, $accountName, $phone, $email, $OrderId, $PixNum, $PixType, $IFSCNumber, $BranchBank, $BankNO)
+    {
+        // 查询订单号
+        $query = DB::connection('write')->table('QPAccountsDB.dbo.OrderWithDraw')->where('RecordID', $RecordID)->first();
+        if (!$query) return 'fail';    // 订单不存在
+
+        $payConfigService = new PayConfig();
+        $config = $payConfigService->getConfig('SafePayOut');
+
+        // PixType → bank_type/bank_code 映射(沿用现有系统的PixType约定)
+        $bankTypeMap = [
+            1 => 'CASHAPP',
+            2 => 'PayPal',
+        ];
+
+        $bankType = $bankTypeMap[$PixType] ?? 'CASHAPP';
+
+        // 构建收款账号
+        // CashApp: cashtag,需 $ 前缀
+        if ($PixType == 1) {
+            $account = ($PixNum && strpos($PixNum, '$') !== 0) ? '$' . $PixNum : $PixNum;
+        } elseif ($PixType == 2) {
+            // PayPal: 使用邮箱作为账号
+            $account = $email;
+        } else {
+            $account = $PixNum ?: $email;
+        }
+
+        // 构建代付请求参数
+        $params = [
+            'mer_no'     => $config['mer_no'] ?? '',
+            'order_no'   => $OrderId,
+            'amount'     => number_format($amount / NumConfig::NUM_VALUE, 2, '.', ''),
+            'currency'   => $config['currency'] ?? 'USD',
+            'bank_code'  => $bankType,
+            'bank_type'  => $bankType,
+            'name'       => $accountName ?: 'user',
+            'account'    => $account,
+            'email'      => $email ?: '',
+            'phone'      => $phone ?: '0000000000',
+            'notify_url' => $config['notify_url'] ?? '',
+            'extra'      => json_encode([
+                'userId'    => (string)($query->UserID ?? ''),
+                'firstName' => $accountName ?: '',
+            ]),
+        ];
+
+        // RSA签名
+        $service = new SafePay();
+        $signedParams = $service->sign($params);
+
+        $url = ($config['apiUrl'] ?? 'https://api.safepay.wang') . '/open/api/order/out';
+
+        Log::info('SafePay 提现参数:', $signedParams);
+
+        try {
+            $result = $service->curlPost($url, $signedParams);
+        } catch (\Exception $exception) {
+            Log::info('SafePay 提现请求异常:', [$exception->getMessage()]);
+            Util::WriteLog('SafePay_error', 'SafePay 提现请求异常:' . $exception->getMessage());
+            return 'fail';
+        }
+
+        Log::info('SafePay 提现结果:', [$result ?? "no result"]);
+
+        try {
+            $data = \GuzzleHttp\json_decode($result, true);
+        } catch (\Exception $e) {
+            Util::WriteLog("SafePay_error", [$result, $e->getMessage(), $e->getTraceAsString()]);
+            return 'fail';
+        }
+
+        // SafePay 代付成功响应: code=200, data.sys_no 是系统流水号
+        if (isset($data['code']) && $data['code'] == 200) {
+            return $data;
+        }
+
+        // 同步失败处理:回复玩家金币,标记订单失败
+        if ($query->State == 5) {
+            $msg = 'Liquidation failure';
+            $WithDraw = $query->WithDraw + $query->ServiceFee;
+            $bonus = '30000,' . $WithDraw;
+
+            PrivateMail::failMail($query->UserID, $OrderId, $WithDraw, $msg, $bonus);
+
+            $withdraw_data = [
+                'State'       => 6,
+                'agent'       => 1060,
+                'finishDate'  => now(),
+                'remark'      => json_encode($data)
+            ];
+            DB::connection('write')->table('QPAccountsDB.dbo.OrderWithDraw')
+                ->where('OrderId', $query->OrderId)
+                ->update($withdraw_data);
+
+            $RecordData = ['after_state' => 6, 'update_at' => now()];
+            DB::connection('write')->table('QPAccountsDB.dbo.AccountsRecord')
+                ->where('type', 1)
+                ->where('RecordID', $RecordID)
+                ->update($RecordData);
+        }
+        return 'fail';
+    }
+
+    /**
+     * 代付异步回调处理
+     *
+     * SafePay 代付回调格式(POST JSON):
+     * {
+     *   "mer_no": 600000,
+     *   "order_no": "xxx",
+     *   "order_amount": "10.00",
+     *   "order_reality_amount": "10.00",
+     *   "currency": "USD",
+     *   "result": "success",   // success=成功, fail=失败
+     *   "sys_no": "212073",
+     *   "sign": "xxx"
+     * }
+     */
+    public function notify($post)
+    {
+        if (!is_array($post)) $post = \GuzzleHttp\json_decode($post, true);
+
+        Util::WriteLog('SafePay', 'SafePay 提现回调:' . json_encode($post));
+
+        try {
+            // 判断订单是否存在
+            $OrderId = $post['order_no'] ?? '';
+            $query = DB::connection('write')->table('QPAccountsDB.dbo.OrderWithDraw')
+                ->where('OrderId', $OrderId)
+                ->first();
+
+            if (!$query) {
+                Util::WriteLog('SafePay', '提现订单不存在: ' . $OrderId);
+                return 'SUCCESS';
+            }
+
+            // 订单已完成处理
+            if ($query->State != 5 && $query->State != 7) {
+                Util::WriteLog('SafePay', $OrderId . '_订单状态已完成');
+                return 'SUCCESS';
+            }
+
+            $agentID = DB::connection('write')->table('agent.dbo.admin_configs')
+                ->where('config_value', self::AGENT)
+                ->where('type', 'cash')
+                ->select('id')
+                ->first()->id ?? '';
+
+            $now = now();
+            $notify_data = [
+                'state'      => 1,
+                'finish_at'  => $now,
+                'casOrdNo'   => $post['sys_no'] ?? '',
+                'extra'      => \GuzzleHttp\json_encode($post),
+                'created_at' => $now,
+                'updated_at' => $now,
+                'order_sn'   => $OrderId,
+                'amount'     => $query->WithDraw,
+            ];
+
+            // 判断回调结果: result=success=成功, result=fail=失败
+            $result = $post['result'] ?? '';
+            $orderStatus = 0;
+
+            if ($result === 'success') {
+                $orderStatus = 1; // 成功
+            } elseif ($result === 'fail') {
+                $orderStatus = 2; // 失败
+            }
+
+            if (!$orderStatus) {
+                Util::WriteLog('SafePay', 'SafePay 提现处理中:' . $OrderId);
+                return 'SUCCESS';
+            }
+
+            Util::WriteLog('SafePay', 'SafePay 提现结果:' . $OrderId . '_' . $orderStatus);
+
+            $UserID = $query->UserID;
+            $TakeMoney = $query->WithDraw + $query->ServiceFee;
+
+            $withdraw_data = [];
+
+            switch ($orderStatus) {
+                case 1: // 提现成功
+                    Util::WriteLog('SafePay', 'SafePay提现成功');
+
+                    $withdraw_data = [
+                        'State'      => 2,
+                        'agent'      => $agentID,
+                        'finishDate' => $now
+                    ];
+
+                    // 根据配置计算代付手续费: 费率% * 提现金额 + 固定$
+                    // 例: pay_rate=[0.5,0.3] → 0.5% + $0.3
+                    $payConfigService = new PayConfig();
+                    $outConfig = $payConfigService->getConfig('SafePayOut');
+                    $payRates = $outConfig['pay_rate'] ?? null;
+                    if (is_array($payRates)) {
+                        $payMethod = $query->PixType ?? 1;
+                        $payRate = $payRates[$payMethod] ?? $payRates;
+                        $feePercent = $payRate[0] ?? 0.5;
+                        $feeFixed = $payRate[1] ?? 0.3;
+                        $withdraw_data['withdraw_fee'] = intval(($query->WithDraw * $feePercent) / 100)
+                            + (int)($feeFixed * NumConfig::NUM_VALUE);
+                    }
+
+                    // 增加提现记录
+                    $first = DB::connection('write')->table('QPAccountsDB.dbo.UserTabData')
+                        ->where('UserID', $UserID)->first();
+                    if ($first) {
+                        DB::connection('write')->table('QPAccountsDB.dbo.UserTabData')
+                            ->where('UserID', $UserID)
+                            ->increment('TakeMoney', $TakeMoney);
+                    } else {
+                        DB::connection('write')->table('QPAccountsDB.dbo.UserTabData')
+                            ->insert(['TakeMoney' => $TakeMoney, 'UserID' => $UserID]);
+                        try {
+                            PrivateMail::praiseSendMail($UserID);
+                        } catch (\Exception $e) {
+                            // 忽略邮件发送失败
+                        }
+                    }
+
+                    // 免审的时候,修改免审状态
+                    $withdrawal_position_log = DB::connection('write')
+                        ->table('agent.dbo.withdrawal_position_log')
+                        ->where('order_sn', $OrderId)
+                        ->first();
+                    if ($withdrawal_position_log) {
+                        DB::connection('write')->table('agent.dbo.withdrawal_position_log')
+                            ->where('order_sn', $OrderId)
+                            ->update(['take_effect' => 2, 'update_at' => date('Y-m-d H:i:s')]);
+                    }
+
+                    try {
+                        StoredProcedure::addPlatformData($UserID, 4, $TakeMoney);
+                    } catch (\Exception $exception) {
+                        Util::WriteLog('StoredProcedure', $exception);
+                    }
+
+                    $ServiceFee = $query->ServiceFee;
+                    // 增加用户提现值
+                    RecordUserDataStatistics::updateOrAdd($UserID, $TakeMoney, 0, $ServiceFee);
+
+                    // 数据统计后台 -- 提现记录添加
+                    (new RechargeWithDraw())->withDraw($UserID, $TakeMoney, $withdraw_data['withdraw_fee'] ?? 0, $ServiceFee);
+
+                    $redis = Redis::connection();
+                    $redis->incr('draw_' . date('Ymd') . $UserID);
+                    PrivateMail::successMail($UserID, $OrderId, $TakeMoney);
+                    break;
+
+                case 2: // 提现失败
+                    $msg = 'Encomenda rejeitada pelo banco';
+                    $bonus = '30000,' . $TakeMoney;
+                    PrivateMail::failMail($query->UserID, $OrderId, $TakeMoney, $msg, $bonus);
+
+                    Util::WriteLog('SafePayEmail', [$query->UserID, $OrderId, $TakeMoney, $msg, $bonus]);
+                    $withdraw_data = [
+                        'State'  => 6,
+                        'agent'  => $agentID,
+                        'remark' => $post['result_mes'] ?? ''
+                    ];
+                    $notify_data['state'] = 2;
+                    break;
+            }
+
+            $RecordData = [
+                'before_state' => $query->State,
+                'after_state'  => $withdraw_data['State'] ?? 0,
+                'RecordID'     => $query->RecordID,
+                'update_at'    => date('Y-m-d H:i:s')
+            ];
+
+            // 添加用户提现操作记录
+            DB::connection('write')->table('QPAccountsDB.dbo.AccountsRecord')
+                ->updateOrInsert(['RecordID' => $query->RecordID, 'type' => 1], $RecordData);
+            DB::connection('write')->table('QPAccountsDB.dbo.OrderWithDraw')
+                ->where('OrderId', $query->OrderId)
+                ->update($withdraw_data);
+
+            if (isset($withdraw_data['State']) && $withdraw_data['State'] == 2) {
+                // 单控标签
+                StoredProcedure::user_label($UserID, 2, $TakeMoney);
+            }
+
+            return 'SUCCESS';
+
+        } catch (\Exception $exception) {
+            Util::WriteLog('SafePay', 'SafePay异步业务逻辑处理失败:' . $exception->getMessage());
+            return 'SUCCESS';
+        }
+    }
+}

+ 204 - 0
app/Http/logic/api/SafePayLogic.php

@@ -0,0 +1,204 @@
+<?php
+
+namespace App\Http\logic\api;
+
+use App\dao\Pay\AccountPayInfo;
+use App\dao\Pay\PayController;
+use App\Http\helper\CreateOrder;
+use App\Http\helper\NumConfig;
+use App\Jobs\Order;
+use App\Services\SafePay;
+use App\Services\CreateLog;
+use App\Services\OrderServices;
+use App\Services\PayConfig;
+use App\Util;
+use Illuminate\Support\Facades\DB;
+
+class SafePayLogic extends BaseApiLogic
+{
+    public $result;
+
+    /**
+     * 支付方式到 pay_code 的映射
+     * 1=cashapp, 2=paypal, 4=applepay, 8=googlepay
+     */
+    protected $payCodeMap = [
+        1  => '1301', // 美国 CashApp
+        2  => '1305', // 美国 PayPal
+        4  => '1302', // 美国 ApplePay
+        8  => '1303', // 美国 GooglePay
+    ];
+
+    /**
+     * 代收下单
+     */
+    public function pay_order($userId, $pay_amount, $userPhone, $userEmail, $userName, $GiftsID, $buyIP, $AdId, $eventType, $pay_method = '')
+    {
+        $dao = new AccountPayInfo();
+        [$userPhone, $userName, $userEmail] = $dao->payInfo($userId);
+
+        // 礼包类型验证
+        $PayVerify = new PayController();
+        $pay_amount = $PayVerify->verify($userId, $GiftsID, $pay_amount);
+        if ($PayVerify->verify($userId, $GiftsID, $pay_amount) === false) {
+            $this->error = $PayVerify->getError();
+            return false;
+        }
+        if ($pay_amount < 0) {
+            $this->error = 'Payment error_4';
+            return false;
+        }
+
+        $service = new SafePay();
+        $config = $service->config;
+
+        $order_sn = CreateOrder::order_sn($userId);
+
+        // 生成订单信息
+        $logic = new OrderLogic();
+        $amount = (int) round($pay_amount * NumConfig::NUM_VALUE);
+        $logic->orderCreate($order_sn, $amount, 'SafePay', $userId, $pay_method, $GiftsID, $AdId, $eventType);
+
+        // 确定 pay_code
+        $payCode = $this->payCodeMap[$pay_method] ?? ($config['pay_code'] ?? '1301');
+
+        // 构建支付请求参数
+        $params = [
+            'mer_no'     => $service->merNo,
+            'order_no'   => $order_sn,
+            'amount'     => (string)$pay_amount,
+            'name'       => $userName ?: 'user',
+            'email'      => $userEmail ?: ($userId . '@unknown.com'),
+            'phone'      => $userPhone ?: '0000000000',
+            'currency'   => $config['currency'] ?? 'USD',
+            'pay_code'   => $payCode,
+            'notify_url' => $config['notify_url'] ?? '',
+            'extra'      => json_encode([
+                'clientIP'  => $buyIP,
+                'firstName' => $userName ?: '',
+                'lastName'  => '',
+            ]),
+        ];
+
+        // RSA签名
+        $signedParams = $service->sign($params);
+
+        // 生成用户请求信息日志
+        $request_extra = \GuzzleHttp\json_encode($signedParams);
+        CreateLog::pay_request($userPhone, $request_extra, $order_sn, $userEmail, $userId, $userName);
+
+        $url = $service->apiUrl . '/open/api/order/in';
+
+        $result = $service->curlPost($url, $signedParams);
+        $rresult = compact('result', 'signedParams', 'url');
+
+        Util::WriteLog('SafePay', 'SafePay支付请求' . $url . " | " . $request_extra);
+        Util::WriteLog('SafePay', 'SafePay支付结果' . json_encode($rresult));
+
+        try {
+            $data = \GuzzleHttp\json_decode($result, true);
+        } catch (\Exception $e) {
+            Util::WriteLog("SafePay_error", [$result, $e->getMessage(), $e->getTraceAsString()]);
+            $this->error = 'Payment processing error';
+            return false;
+        }
+
+        return $data;
+    }
+
+    /**
+     * 代收异步回调处理
+     */
+    public function notify($post)
+    {
+        $order_no = $post['order_no'] ?? '';
+
+        // 查询订单信息
+        $order = DB::connection('write')->table('agent.dbo.order')
+            ->where('order_sn', $order_no)
+            ->first();
+
+        if (!$order) {
+            Util::WriteLog('SafePay', '订单不存在: ' . $order_no);
+            return 'ok';
+        }
+
+        // 已处理过则直接返回
+        if (!empty($order->pay_at) || !empty($order->finished_at)) {
+            return 'ok';
+        }
+
+        $status = $post['status'] ?? '';
+        $body = [
+            'payment_sn' => $post['ref_no'] ?? '',
+            'updated_at' => date('Y-m-d H:i:s'),
+        ];
+
+        // status: success=交易成功, 其他=失败/待处理
+        if ($status !== 'success') {
+            // 非成功状态(如 pending/waiting),不处理,等待后续通知
+            Util::WriteLog('SafePay', "支付未完成订单: {$order_no}, status: {$status}");
+            return 'ok';
+        }
+
+        // 支付成功
+        $GiftsID = $order->GiftsID ?: '';
+        $userID = $order->user_id ?: '';
+        $AdId = $order->AdId ?: '';
+        $eventType = $order->eventType ?: '';
+
+        $orderAmount = (float)($post['order_reality_amount'] ?? $post['order_amount'] ?? 0);
+        $payAmt = $orderAmount;
+
+        $body['pay_status'] = 1;
+        $body['pay_at'] = date('Y-m-d H:i:s');
+        $body['finished_at'] = date('Y-m-d H:i:s');
+        $body['amount'] = (int) round($payAmt * NumConfig::NUM_VALUE);
+
+        $config = (new PayConfig())->getConfig('SafePay');
+        $body['payment_fee'] = (int)($body['amount'] * ($config['payin_fee'] ?? 0));
+
+        try {
+            $service = new OrderServices();
+
+            if (intval($order->amount) != $body['amount']) {
+                $body['GiftsID'] = 0;
+                $body['amount'] = (int) round($payAmt * NumConfig::NUM_VALUE);
+                $Recharge = $payAmt;
+                $give = 0;
+                $favorable_price = $Recharge + $give;
+                $czReason = 1;
+                $cjReason = 45;
+            } else {
+                [$give, $favorable_price, $Recharge, $czReason, $cjReason] = $service->getPayInfo(
+                    $GiftsID, $userID, $payAmt
+                );
+            }
+
+            // 增加充值记录
+            [$Score] = $service->addRecord(
+                $userID, $payAmt, $favorable_price, $order_no, $GiftsID,
+                $Recharge, $czReason, $give, $cjReason, $AdId, $eventType,
+                $body['payment_fee'] ?? 0
+            );
+
+            // 异步处理后续任务
+            Order::dispatch([$userID, $payAmt, $Score, $favorable_price, $GiftsID, $order_no]);
+        } catch (\Exception $exception) {
+            Util::WriteLog("SafePay_error", $exception->getMessage());
+        }
+
+        // 更新订单状态
+        $order_up = DB::connection('write')->table('agent.dbo.order')
+            ->where('order_sn', $order_no)
+            ->update($body);
+
+        if (!$order_up) {
+            Util::WriteLog('SafePay', '订单更新失败: ' . $order_no);
+            return 'ok';
+        }
+
+        Util::WriteLog("SafePay", 'success: ' . $order_no);
+        return 'ok';
+    }
+}

+ 4 - 0
app/Services/CashService.php

@@ -9,6 +9,7 @@ use App\Http\logic\api\CoinPayCashierLogic;
 use App\Http\logic\api\AiPayCashierLogic;
 use App\Http\logic\api\PagYeepPayCashierLogic;
 use App\Http\logic\api\AiNewPayCashierLogic;
+use App\Http\logic\api\SafePayCashierLogic;
 
 
 class CashService
@@ -36,6 +37,9 @@ class CashService
             case AiNewPayCashierLogic::AGENT:
                 return new AiNewPayCashierLogic();
 
+            case SafePayCashierLogic::AGENT:
+                return new SafePayCashierLogic();
+
         }
     }
 }

+ 4 - 0
app/Services/PayMentService.php

@@ -14,6 +14,7 @@ use App\Http\Controllers\Api\WDPayController;
 use App\Http\Controllers\Api\CoinPayController;
 use App\Http\Controllers\Api\AiPayController;
 use App\Http\Controllers\Api\PagYeepPayController;
+use App\Http\Controllers\Api\SafePayController;
 use App\Http\Controllers\Api\AiNewPayController;
 use Illuminate\Support\Facades\DB;
 use Illuminate\Support\Facades\Log;
@@ -44,6 +45,9 @@ class PayMentService
             case 'AiNewPay':
                 return new AiNewPayController();
 
+            case 'SafePay':
+                return new SafePayController();
+
             case 'apple':
                 return new AppleStorePayController();
 

+ 187 - 0
app/Services/SafePay.php

@@ -0,0 +1,187 @@
+<?php
+
+namespace App\Services;
+
+use App\Util;
+
+class SafePay
+{
+    public $config;
+    public $merNo;
+    public $apiUrl;
+    public $privateKey;
+    public $platformPublicKey;
+
+    public function __construct()
+    {
+        $payConfigService = new PayConfig();
+        $this->config = $payConfigService->getConfig('SafePay');
+
+        $this->merNo = $this->config['mer_no'] ?? '';
+        $this->apiUrl = $this->config['apiUrl'] ?? 'https://api.safepay.wang';
+        $this->privateKey = $this->config['private_key'] ?? '';
+        $this->platformPublicKey = $this->config['platform_public_key'] ?? '';
+    }
+
+    /**
+     * 构建待签名数据(ASCII升序、QueryString格式、跳过sign和空值)
+     */
+    public function buildSignData(array $params): string
+    {
+        // 移除sign字段和空值
+        $signData = [];
+        foreach ($params as $key => $value) {
+            if ($key === 'sign') {
+                continue;
+            }
+            if ($value === null || $value === '' || (is_array($value) && empty($value))) {
+                continue;
+            }
+            if (is_array($value) || is_object($value)) {
+                $signData[$key] = json_encode($value, JSON_UNESCAPED_UNICODE);
+            } else {
+                $signData[$key] = (string)$value;
+            }
+        }
+
+        // ASCII升序(字典序)
+        ksort($signData);
+
+        // 构建 QueryString: key1=value1&key2=value2
+        $result = '';
+        foreach ($signData as $key => $value) {
+            $result .= '&' . $key . '=' . $value;
+        }
+
+        return substr($result, 1);
+    }
+
+    /**
+     * RSA SHA256 签名(使用商户私钥)
+     *
+     * @param array $params 请求参数
+     * @return array 添加sign后的参数
+     */
+    public function sign(array $params): array
+    {
+        $signData = $this->buildSignData($params);
+        $prvKeyPem = $this->convertPrivateKeyToPem($this->privateKey);
+
+        $signature = '';
+        $signed = openssl_sign($signData, $signature, $prvKeyPem, OPENSSL_ALGO_SHA256);
+
+        if (!$signed) {
+            throw new \Exception('SafePay RSA签名失败');
+        }
+
+        $params['sign'] = base64_encode($signature);
+
+        Util::WriteLog('SafePay_sign', "待签名字符串: " . $signData);
+        Util::WriteLog('SafePay_sign', "签名结果: " . $params['sign']);
+
+        return $params;
+    }
+
+    /**
+     * RSA SHA256 验签(使用平台公钥)
+     *
+     * @param array $params 回调参数(含sign)
+     * @return bool
+     */
+    public function verifySign(array $params): bool
+    {
+        $receivedSign = $params['sign'] ?? '';
+        if (empty($receivedSign)) {
+            Util::WriteLog('SafePay', '验签失败:sign字段为空');
+            return false;
+        }
+
+        $signData = $this->buildSignData($params);
+        $pubKeyPem = $this->convertPublicKeyToPem($this->platformPublicKey);
+
+        $isVerified = openssl_verify(
+            $signData,
+            base64_decode($receivedSign),
+            $pubKeyPem,
+            OPENSSL_ALGO_SHA256
+        );
+
+        Util::WriteLog('SafePay_verify', "验签数据: " . $signData);
+        Util::WriteLog('SafePay_verify', "验签结果: " . ($isVerified === 1 ? '通过' : '失败'));
+
+        return $isVerified === 1;
+    }
+
+    /**
+     * 转换私钥为PEM格式
+     */
+    private function convertPrivateKeyToPem($privateKeyString)
+    {
+        if (strpos($privateKeyString, '-----BEGIN PRIVATE KEY-----') !== false) {
+            return $privateKeyString;
+        }
+        $key = trim($privateKeyString);
+        if (!preg_match('/^-----BEGIN/', $key)) {
+            if (base64_decode($key, true)) {
+                $key = base64_decode($key);
+            }
+            $key = "-----BEGIN PRIVATE KEY-----\n" . chunk_split(base64_encode($key), 64, "\n") . "-----END PRIVATE KEY-----";
+        }
+        return $key;
+    }
+
+    /**
+     * 转换公钥为PEM格式
+     */
+    private function convertPublicKeyToPem($publicKeyString)
+    {
+        if (strpos($publicKeyString, '-----BEGIN PUBLIC KEY-----') !== false) {
+            return $publicKeyString;
+        }
+        $key = trim($publicKeyString);
+        if (!preg_match('/^-----BEGIN/', $key)) {
+            if (base64_decode($key, true)) {
+                $key = base64_decode($key);
+            }
+            $key = "-----BEGIN PUBLIC KEY-----\n" . chunk_split(base64_encode($key), 64, "\n") . "-----END PUBLIC KEY-----";
+        }
+        return $key;
+    }
+
+    /**
+     * POST JSON请求
+     */
+    public function curlPost($url, $payload)
+    {
+        $timeout = 20;
+        $data = json_encode($payload, JSON_UNESCAPED_UNICODE);
+
+        $headers = [
+            'Content-Type: application/json',
+        ];
+
+        $ch = curl_init();
+        curl_setopt($ch, CURLOPT_URL, $url);
+        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
+        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
+        curl_setopt($ch, CURLOPT_POST, 1);
+        curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
+        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout);
+        curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
+        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
+        curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
+
+        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
+        $result = curl_exec($ch);
+
+        if (curl_errno($ch)) {
+            $error = curl_error($ch);
+            Util::WriteLog('SafePay_error', 'CURL Error: ' . $error);
+            curl_close($ch);
+            return false;
+        }
+
+        curl_close($ch);
+        return $result;
+    }
+}

+ 27 - 1
config/payTest.php

@@ -15,6 +15,32 @@ return [
         'return' => env('APP_URL', '').'/api/wiwipay/return',
         'cashNotify' => env('APP_URL', '').'/api/wiwipay/payout_notify',
         'cash_url' => env('APP_URL', '').'/api/payout/create'
-    ]
+    ],
+
+    // SafePay 支付渠道(RSA SHA256签名)
+    'SafePay' => [
+        'mer_no'             => '601079',
+        'apiUrl'             => 'https://api.safepay.wang',
+        'currency'           => 'USD',
+        'payin_fee'          => 0.1,   // 代收费率 5%
+        'notify_url'         => env('APP_URL', '') . '/api/safepay/notify',
+        // 商户私钥(用于签名)
+        'private_key'        => 'MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAM5tmHRZ2uoA22ZUeJt+9LcE3mPWi8BAzNOgb/ubFZ7h6eezVkUrALNoomEugk4MHHSq+pvLJbBpj12SA4c5Ecav0kcm/sXkaF8Ojt9QNto6kPL/SWDvsRkgX0iwJps5oaYRNu7XGPZNFi+v42C6PmMrYtuFIHcDh0e9Y/MMp+mtAgMBAAECgYBkui+5HptmsdJdJXzQi2uH3tVRrD/3KOeJoHDkOjGbnKsRR34iNDCcWsNZXmNxCBHekAvHd6JFaczuILCeDCfyhkFNJL/HqWbhR/XT29JvdyIz4frRQR9eNyG9L+RXIc6dX3+ZVw2j8sf2IBnB63Y3oY57wqiASccBvMLT5P2GlQJBAOcIYhpTP9G9u1dJ1Y9ehFDz1xrCapV6Twi03FbbOzd5YhVt6pJykafXVpgLnGCiVDYpKmAzW5b3vP6ENmoz/BcCQQDkvIPG+hWAZ/+Rx7u6mT5vxLX35PEuddgW8hNhobYkzllfZx8pHK/Puuf7f0bZlJrT17BqJUGjj/x0PpX6ig7bAkBjOJR5XduAxo1YtZsEUcFdyvtMwMZpn/elc5dVrh3Ge1kGfwhJEO9BOBg2gHYOgmjMQe2zFxt0wLzoSfvcrKrNAkBrLh59fS/YFGM/9xxzSl1kWAOAXinZQGT6E4KbbAACbSKZvqNuSz/ikc0kHrafO6/09gt3IXAJaW29NppmZIyjAkEA4iyI24Pct2USqb1EIhV20jgD+/9CFFJmbZXWd2nBa2CxOxO+oe8cO4GM3YmaWu9A78PCbMl7CzPBgYjDyXXKZA==',
+        // 平台公钥(用于验签回调)
+        'platform_public_key' => 'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCnyULwz2hGQzuxBgazlpR74V0giZv8xAJKJ8DzoBzs8GevOMBAVHY7/lcUs/LiMsZTis7OFDFV6Uqz+kaww9O40P7XZF3Qhh9KzgE+9ehqj6EQOvHEDXnmeakwHP3vCHlfmaToxvj7DUjPOLE+Yvgp89cbj7D0bc8n7YbKYlQHVwIDAQAB',
+    ],
+
+    // SafePay 代付渠道配置(RSA SHA256签名)
+    'SafePayOut' => [
+        'mer_no'             => '601079',
+        'apiUrl'             => 'https://api.safepay.wang',
+        'currency'           => 'USD',
+        'notify_url'         => env('APP_URL', '') . '/api/safepay/payout_notify',
+        'pay_rate'           => [0.5, 0.3],  // 代付手续费: [费率%, 固定$]
+        // 商户私钥(用于签名,与代收共用)
+        'private_key'        => 'MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAM5tmHRZ2uoA22ZUeJt+9LcE3mPWi8BAzNOgb/ubFZ7h6eezVkUrALNoomEugk4MHHSq+pvLJbBpj12SA4c5Ecav0kcm/sXkaF8Ojt9QNto6kPL/SWDvsRkgX0iwJps5oaYRNu7XGPZNFi+v42C6PmMrYtuFIHcDh0e9Y/MMp+mtAgMBAAECgYBkui+5HptmsdJdJXzQi2uH3tVRrD/3KOeJoHDkOjGbnKsRR34iNDCcWsNZXmNxCBHekAvHd6JFaczuILCeDCfyhkFNJL/HqWbhR/XT29JvdyIz4frRQR9eNyG9L+RXIc6dX3+ZVw2j8sf2IBnB63Y3oY57wqiASccBvMLT5P2GlQJBAOcIYhpTP9G9u1dJ1Y9ehFDz1xrCapV6Twi03FbbOzd5YhVt6pJykafXVpgLnGCiVDYpKmAzW5b3vP6ENmoz/BcCQQDkvIPG+hWAZ/+Rx7u6mT5vxLX35PEuddgW8hNhobYkzllfZx8pHK/Puuf7f0bZlJrT17BqJUGjj/x0PpX6ig7bAkBjOJR5XduAxo1YtZsEUcFdyvtMwMZpn/elc5dVrh3Ge1kGfwhJEO9BOBg2gHYOgmjMQe2zFxt0wLzoSfvcrKrNAkBrLh59fS/YFGM/9xxzSl1kWAOAXinZQGT6E4KbbAACbSKZvqNuSz/ikc0kHrafO6/09gt3IXAJaW29NppmZIyjAkEA4iyI24Pct2USqb1EIhV20jgD+/9CFFJmbZXWd2nBa2CxOxO+oe8cO4GM3YmaWu9A78PCbMl7CzPBgYjDyXXKZA==',
+        // 平台公钥(用于验签回调)
+        'platform_public_key' => 'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCnyULwz2hGQzuxBgazlpR74V0giZv8xAJKJ8DzoBzs8GevOMBAVHY7/lcUs/LiMsZTis7OFDFV6Uqz+kaww9O40P7XZF3Qhh9KzgE+9ehqj6EQOvHEDXnmeakwHP3vCHlfmaToxvj7DUjPOLE+Yvgp89cbj7D0bc8n7YbKYlQHVwIDAQAB',
+    ],
 
 ];

+ 4 - 0
routes/api.php

@@ -387,6 +387,10 @@ Route::any('/starpagobd/payout_notify', 'Api\StarpagoBDController@cash_notify');
 Route::any('/pkpay/notify', 'Api\PKpayController@notify');
 Route::any('/pkpay/payout_notify', 'Api\PKpayController@cash_notify');
 
+// SafePay支付渠道
+Route::any('/safepay/notify', 'Api\SafePayController@notify');
+Route::any('/safepay/payout_notify', 'Api\SafePayController@cash_notify');
+
 Route::any('/all2pay/notify', 'Api\ALL2payController@notify');
 Route::any('/all2pay/sync_notify', 'Api\ALL2payController@sync_notify');
 Route::any('/all2pay/cash_notify', 'Api\ALL2payController@cash_notify');