laowu 1 周之前
父節點
當前提交
b78edf31e5

+ 219 - 0
app/Http/Controllers/Api/SfPayController.php

@@ -0,0 +1,219 @@
+<?php
+
+namespace App\Http\Controllers\Api;
+
+use App\Http\logic\api\SfPayLogic;
+use App\Http\logic\api\SfPayCashierLogic;
+use App\Inter\PayMentInterFace;
+use App\Notification\TelegramBot;
+use App\Services\SfPay;
+use App\Util;
+use App\Utility\SetNXLock;
+use Illuminate\Http\Request;
+use Illuminate\Support\Facades\Redis;
+
+class SfPayController implements PayMentInterFace
+{
+    /**
+     * 代收下单
+     */
+    public function pay_order(
+        $userId,
+        $payAmt,
+        $userName,
+        $userEmail,
+        $userPhone,
+        $GiftsID,
+        $buyIP,
+        $AdId,
+        $eventType,
+        $pay_method = ''
+    ) {
+        $logic = new SfPayLogic();
+        try {
+            $res = $logic->pay_order($userId, $payAmt, $userPhone, $userEmail, $userName, $GiftsID, $buyIP, $AdId, $eventType, $pay_method);
+        } catch (\Exception $exception) {
+            Redis::set("PayErro_SfPay", 1);
+            Redis::expire("PayErro_SfPay", 600);
+            Util::WriteLog('SfPay_error', $exception->getMessage() . json_encode($logic->result ?? []));
+            TelegramBot::getDefault()->sendProgramNotify("SfPay Except ", $exception->getMessage(), $exception);
+            return apiReturnFail($logic->getError());
+        }
+
+        if (!empty($res) && isset($res['code']) && $res['code'] === 0) {
+            // SfPay 成功返回: data.paymentLinkUrl 是支付链接
+            $data = [
+                'content'  => $res['data']['paymentLinkUrl'] ?? '',
+                'money'    => $payAmt,
+                'prdOrdNo' => $res['data']['orderNo'] ?? '',
+            ];
+            return apiReturnSuc($data);
+        }
+
+        if ($res == false) {
+            return apiReturnFail($logic->getError() ?: 'Payment failed');
+        }
+
+        // 非0的响应,记录错误
+        Redis::set("PayErro_SfPay", 1);
+        Redis::expire("PayErro_SfPay", 600);
+        $errMsg = $res['msg'] ?? 'Unknown error';
+        TelegramBot::getDefault()->sendProgramNotify("SfPay ReturnFail ", $errMsg . " | " . json_encode($res) . "$userId");
+        return apiReturnFail(['web.payment.paytype_error', $errMsg]);
+    }
+
+    /**
+     * 代收异步回调通知
+     *
+     * SfPay回调格式(POST JSON,不加密):
+     * {
+     *   "data": {
+     *     "amount": "20.00",
+     *     "orderNo": "...",
+     *     "message": "Success",
+     *     "type": "payment",
+     *     "merchantOrderNo": "...",
+     *     "processedTime": "...",
+     *     "transferMode": "PIX",
+     *     "status": "SUCCESS"
+     *   },
+     *   "signature_n": "..."
+     * }
+     */
+    public function notify(Request $request)
+    {
+        $post = $request->all();
+        $payload = json_encode($post);
+
+        Util::WriteLog('SfPay', "SfPay回调订单\n" . $payload);
+
+        $callbackData = $post['data'] ?? [];
+        $signatureN = $post['signature_n'] ?? '';
+
+        if (empty($callbackData) || empty($signatureN)) {
+            Util::WriteLog('SfPay', '回调数据不完整');
+            return response()->json(['code' => 200]);
+        }
+
+        // 验签
+        $service = new SfPay();
+        if (!$service->verifySign($callbackData, $signatureN)) {
+            Util::WriteLog('SfPay', '签名错误');
+            return response()->json(['code' => 200]);
+        }
+
+        $order_sn = $callbackData['merchantOrderNo'] ?? '';
+        if (empty($order_sn)) {
+            Util::WriteLog('SfPay', '缺少商户订单号');
+            return response()->json(['code' => 200]);
+        }
+
+        // 代收回调加锁,防止并发重复处理
+        $lockKey = 'pay_notify_SfPay_' . $order_sn;
+        if (!SetNXLock::getExclusiveLock($lockKey, 60)) {
+            Util::WriteLog('SfPay', '代收回调并发,订单已处理或处理中: ' . $order_sn);
+            return response()->json(['code' => 200]);
+        }
+
+        $logic = new SfPayLogic();
+        try {
+            $ret = $logic->notify($post);
+            return response()->json(['code' => 200]);
+        } catch (\Exception $exception) {
+            Redis::set("PayErro_SfPay", 1);
+            Redis::expire("PayErro_SfPay", 600);
+            TelegramBot::getDefault()->sendProgramNotify("SfPay 订单回调执行异常 ", json_encode($post), $exception);
+            Util::WriteLog("SfPay_error", $post);
+            return response()->json(['code' => 200]);
+        } finally {
+            SetNXLock::release($lockKey);
+        }
+    }
+
+    /**
+     * 代付异步回调通知
+     *
+     * SfPay代付回调格式(POST JSON,不加密):
+     * {
+     *   "data": {
+     *     "amount": "30.00",
+     *     "orderNo": "...",
+     *     "message": "...",
+     *     "type": "payout",
+     *     "merchantOrderNo": "...",
+     *     "processedTime": "...",
+     *     "processAmount": "0.00",
+     *     "status": "SUCCESS"
+     *   },
+     *   "signature_n": "..."
+     * }
+     */
+    public function cash_notify(Request $request)
+    {
+        $post = $request->all();
+        $payload = json_encode($post);
+
+        Util::WriteLog('SfPay', "SfPay 提现回调\n" . $payload);
+
+        $callbackData = $post['data'] ?? [];
+        $signatureN = $post['signature_n'] ?? '';
+
+        if (empty($callbackData) || empty($signatureN)) {
+            Util::WriteLog('SfPay', '提现回调数据不完整');
+            return response('SUCCESS');
+        }
+
+        // 验签
+        $service = new SfPay();
+        if (!$service->verifySign($callbackData, $signatureN)) {
+            Util::WriteLog('SfPay', '提现回调签名错误');
+            return response('SUCCESS');
+        }
+
+        $logic = new SfPayCashierLogic();
+        try {
+            return $logic->notify($post);
+        } catch (\Exception $exception) {
+            TelegramBot::getDefault()->sendProgramNotify(
+                "SfPay 提现异步回调执行异常 ",
+                json_encode($post),
+                $exception
+            );
+            Util::WriteLog("SfPay_error", $post);
+            return response('SUCCESS');
+        }
+    }
+
+    /**
+     * 代收订单主动查询
+     *
+     * POST /api/sfpay/orderQuery
+     * 用于回调未到达时主动查询订单状态
+     */
+    public function orderQuery(Request $request)
+    {
+        $merchantOrderNo = $request->input('merchantOrderNo', '');
+
+        if (empty($merchantOrderNo)) {
+            return apiReturnFail('merchantOrderNo is required');
+        }
+
+        $logic = new SfPayLogic();
+        try {
+            $res = $logic->orderQuery($merchantOrderNo);
+        } catch (\Exception $e) {
+            Util::WriteLog('SfPay_error', 'orderQuery exception: ' . $e->getMessage());
+            return apiReturnFail('Query failed');
+        }
+
+        if ($res === false) {
+            return apiReturnFail($logic->getError() ?: 'Query failed');
+        }
+
+        if (isset($res['code']) && $res['code'] === 0) {
+            return apiReturnSuc($res['data'] ?? []);
+        }
+
+        return apiReturnFail($res['msg'] ?? 'Query error');
+    }
+}

+ 330 - 0
app/Http/logic/api/SfPayCashierLogic.php

@@ -0,0 +1,330 @@
+<?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\SfPay;
+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 SfPayCashierLogic implements CashierInterFace
+{
+    const AGENT = 108; // SfPay代付渠道值(对应 admin_configs config_value)
+    protected $agent = 108;
+
+    /**
+     * 支付方式位掩码 → SfPay payProduct 映射(代付)
+     * 1=cashapp, 2=paypal
+     */
+    protected $payoutProductMap = [
+        1 => 'USA202', // cashapp
+        2 => 'USA203', // paypal
+    ];
+
+    /**
+     * PixType → 代付方式映射
+     */
+    protected $bankTypeMap = [
+        1 => 'USA202', // cashapp
+        2 => 'USA203', // paypal
+    ];
+
+    /**
+     * 提交代付申请
+     */
+    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('SfPay');
+
+        $service = new SfPay();
+
+        // 确定代付产品
+        $payProduct = $this->payoutProductMap[$PixType] ?? 'USA202';
+
+        // 构建收款账号
+        if ($PixType == 1) {
+            // CashApp: cashtag,需 $ 前缀
+            $account = ($PixNum && strpos($PixNum, '$') !== 0) ? '$' . $PixNum : $PixNum;
+        } elseif ($PixType == 2) {
+            // PayPal: 邮箱
+            $account = $email ?: $PixNum;
+        } else {
+            $account = $PixNum ?: $email;
+        }
+
+        // 构建代付请求参数
+        $params = [
+            'merchantOrderNo'    => $OrderId,
+            'amount'             => (float)number_format($amount / NumConfig::NUM_VALUE, 2, '.', ''),
+            'beneficiaryAccount' => $account,
+            'beneficiaryName'    => $accountName ?: 'user',
+            'countryId'          => 'USA',
+            'payProduct'         => $payProduct,
+            'notifyUrl'          => $config['cash_notify_url'] ?? '',
+            'beneficiaryEmail'   => $email ?: '',
+            'beneficiaryPhoneNo' => $phone ?: '',
+        ];
+
+        Log::info('SfPay 提现参数:', $params);
+
+        $url = $service->apiUrl . '/gateway/payout/init';
+
+        try {
+            $result = $service->curlPost($url, $params);
+        } catch (\Exception $exception) {
+            Log::info('SfPay 提现请求异常:', [$exception->getMessage()]);
+            Util::WriteLog('SfPay_error', 'SfPay 提现请求异常:' . $exception->getMessage());
+            return 'fail';
+        }
+
+        Log::info('SfPay 提现结果:', [$result ?? 'no result']);
+
+        try {
+            $data = \GuzzleHttp\json_decode($result, true);
+        } catch (\Exception $e) {
+            Util::WriteLog("SfPay_error", [$result, $e->getMessage()]);
+            return 'fail';
+        }
+
+        // SfPay 代付成功响应: code=0
+        if (isset($data['code']) && $data['code'] === 0) {
+            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'      => 1080,
+                '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';
+    }
+
+    /**
+     * 代付异步回调处理
+     *
+     * SfPay代付回调格式(POST JSON,不加密):
+     * {
+     *   "data": {
+     *     "amount": "30.00",
+     *     "orderNo": "...",
+     *     "message": "...",
+     *     "type": "payout",
+     *     "merchantOrderNo": "...",
+     *     "processedTime": "...",
+     *     "processAmount": "0.00",
+     *     "status": "SUCCESS"
+     *   },
+     *   "signature_n": "..."
+     * }
+     */
+    public function notify($post)
+    {
+        if (!is_array($post)) {
+            $post = \GuzzleHttp\json_decode($post, true);
+        }
+
+        Util::WriteLog('SfPay', 'SfPay 提现回调:' . json_encode($post));
+
+        try {
+            $callbackData = $post['data'] ?? [];
+            $signatureN = $post['signature_n'] ?? '';
+
+            if (empty($callbackData) || empty($signatureN)) {
+                Util::WriteLog('SfPay', '提现回调数据不完整');
+                return 'SUCCESS';
+            }
+
+            // 验签
+            $service = new SfPay();
+            if (!$service->verifySign($callbackData, $signatureN)) {
+                Util::WriteLog('SfPay', '提现回调签名验证失败');
+                return 'SUCCESS';
+            }
+
+            // 判断订单是否存在
+            $OrderId = $callbackData['merchantOrderNo'] ?? '';
+            $query = DB::connection('write')->table('QPAccountsDB.dbo.OrderWithDraw')
+                ->where('OrderId', $OrderId)
+                ->first();
+
+            if (!$query) {
+                Util::WriteLog('SfPay', '提现订单不存在: ' . $OrderId);
+                return 'SUCCESS';
+            }
+
+            // 订单已完成处理
+            if ($query->State != 5 && $query->State != 7) {
+                Util::WriteLog('SfPay', $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'   => $callbackData['orderNo'] ?? '',
+                'extra'      => \GuzzleHttp\json_encode($post),
+                'created_at' => $now,
+                'updated_at' => $now,
+                'order_sn'   => $OrderId,
+                'amount'     => $query->WithDraw,
+            ];
+
+            // 判断回调结果: SUCCESS=成功, FAILURE=失败
+            $status = $callbackData['status'] ?? '';
+            $orderStatus = 0;
+
+            if ($status === 'SUCCESS') {
+                $orderStatus = 1; // 成功
+            } elseif ($status === 'FAILURE' || $status === 'REVERSED') {
+                $orderStatus = 2; // 失败
+            }
+
+            if (!$orderStatus) {
+                Util::WriteLog('SfPay', 'SfPay 提现处理中:' . $OrderId);
+                return 'SUCCESS';
+            }
+
+            Util::WriteLog('SfPay', 'SfPay 提现结果:' . $OrderId . '_' . $orderStatus);
+
+            $UserID = $query->UserID;
+            $TakeMoney = $query->WithDraw + $query->ServiceFee;
+
+            $withdraw_data = [];
+
+            switch ($orderStatus) {
+                case 1: // 提现成功
+                    Util::WriteLog('SfPay', 'SfPay提现成功');
+
+                    $withdraw_data = [
+                        'State'      => 2,
+                        'agent'      => $agentID,
+                        'finishDate' => $now
+                    ];
+
+                    // 增加提现记录
+                    $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, 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('SfPayEmail', [$query->UserID, $OrderId, $TakeMoney, $msg, $bonus]);
+                    $withdraw_data = [
+                        'State'  => 6,
+                        'agent'  => $agentID,
+                        'remark' => $callbackData['message'] ?? ''
+                    ];
+                    $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('SfPay', 'SfPay异步业务逻辑处理失败:' . $exception->getMessage());
+            return 'SUCCESS';
+        }
+    }
+}

+ 270 - 0
app/Http/logic/api/SfPayLogic.php

@@ -0,0 +1,270 @@
+<?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\SfPay;
+use App\Services\CreateLog;
+use App\Services\OrderServices;
+use App\Services\PayConfig;
+use App\Util;
+use Illuminate\Support\Facades\DB;
+
+class SfPayLogic extends BaseApiLogic
+{
+    public $result;
+
+    /**
+     * 支付方式位掩码 → SfPay payProduct 映射
+     *
+     * 1=cashapp, 2=paypal, 4=applepay, 16=btc(CASH_BTC), 1024=usdt
+     */
+    protected $payProductMap = [
+        1    => 'USA102', // CashApp
+        2    => 'USA107', // PayPal
+        4    => 'USA103', // APPLE_PAY
+        16   => 'USA106', // CASH_BTC
+        1024 => 'USA105', // USDT
+    ];
+
+    /**
+     * 代收下单
+     */
+    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 SfPay();
+        $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, 'SfPay', $userId, $pay_method, $GiftsID, $AdId, $eventType);
+
+        // 确定 payProduct
+        $payProduct = $this->payProductMap[$pay_method] ?? ($config['pay_product'] ?? 'USA102');
+
+        // 构建支付请求参数(明文,后续AES加密)
+        // 必传: merchantOrderNo, amount, payProduct, gameId
+        $params = [
+            'merchantOrderNo' => $order_sn,
+            'amount'          => (float)$pay_amount,
+            'payProduct'      => $payProduct,
+            'gameId'          => (int) ($config['game_id'] ?? 0),
+        ];
+
+        // 可选参数(传了收银台会预填,不传则需要用户手动输入)
+        if (!empty($userName))           $params['customerName']  = $userName;
+        if (!empty($userEmail))          $params['customerEmail'] = $userEmail;
+        if (!empty($userPhone))          $params['customerPhone'] = $userPhone;
+        if (!empty($config['notify_url'])) $params['notifyUrl']   = $config['notify_url'];
+        if (!empty($config['return_url'])) $params['returnUrl']   = $config['return_url'];
+
+        // 可选辅助参数
+        $params['countryId'] = 'USA';  // 货币单位依据countryId
+        $params['userAgent'] = $_SERVER['HTTP_USER_AGENT'] ?? 'Mozilla/5.0';
+        $params['ip']        = $buyIP ?: ($_SERVER['REMOTE_ADDR'] ?? '');
+
+        // 生成用户请求信息日志
+        $request_extra = \GuzzleHttp\json_encode($params);
+        CreateLog::pay_request($userPhone, $request_extra, $order_sn, $userEmail, $userId, $userName);
+
+        $url = $service->apiUrl . '/gateway/payment/init';
+        Util::WriteLog('SfPay', 'SfPay支付请求: ' . $url . ' | ' . $request_extra);
+        $result = $service->curlPost($url, $params);
+        Util::WriteLog('SfPay', 'SfPay支付结果: ' . ($result ?: '空结果'));
+
+        if ($result === false) {
+            $this->error = 'Payment processing error';
+            return false;
+        }
+
+        try {
+            $data = \GuzzleHttp\json_decode($result, true);
+        } catch (\Exception $e) {
+            Util::WriteLog("SfPay_error", [$result, $e->getMessage()]);
+            $this->error = 'Payment processing error';
+            return false;
+        }
+
+        return $data;
+    }
+
+    /**
+     * 代收异步回调处理
+     *
+     * SfPay回调格式(POST JSON,不加密):
+     * {
+     *   "data": {
+     *     "amount": "20.00",
+     *     "orderNo": "...",
+     *     "message": "Success",
+     *     "type": "payment",
+     *     "merchantOrderNo": "...",
+     *     "processedTime": "...",
+     *     "transferMode": "PIX",
+     *     "status": "SUCCESS"
+     *   },
+     *   "signature_n": "..."
+     * }
+     */
+    public function notify($post)
+    {
+        $callbackData = $post['data'] ?? [];
+        $signatureN = $post['signature_n'] ?? '';
+
+        if (empty($callbackData) || empty($signatureN)) {
+            Util::WriteLog('SfPay', '回调数据不完整');
+            return 'ok';
+        }
+
+        // 验签
+        $service = new SfPay();
+        if (!$service->verifySign($callbackData, $signatureN)) {
+            Util::WriteLog('SfPay', '回调签名验证失败');
+            return 'ok';
+        }
+
+        $order_no = $callbackData['merchantOrderNo'] ?? '';
+
+        // 查询订单信息
+        $order = DB::connection('write')->table('agent.dbo.order')
+            ->where('order_sn', $order_no)
+            ->first();
+
+        if (!$order) {
+            Util::WriteLog('SfPay', '订单不存在: ' . $order_no);
+            return 'ok';
+        }
+
+        // 已处理过则直接返回
+        if (!empty($order->pay_at) || !empty($order->finished_at)) {
+            return 'ok';
+        }
+
+        $status = $callbackData['status'] ?? '';
+        $body = [
+            'payment_sn' => $callbackData['orderNo'] ?? '',
+            'updated_at' => date('Y-m-d H:i:s'),
+        ];
+
+        // status: SUCCESS=成功, FAILURE=失败, PENDING=处理中
+        if ($status !== 'SUCCESS') {
+            Util::WriteLog('SfPay', "支付未完成订单: {$order_no}, status: {$status}");
+            return 'ok';
+        }
+
+        // 支付成功
+        $GiftsID = $order->GiftsID ?: '';
+        $userID = $order->user_id ?: '';
+        $AdId = $order->AdId ?: '';
+        $eventType = $order->eventType ?: '';
+
+        $payAmt = (float)($callbackData['amount'] ?? 0);
+
+        $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);
+
+        try {
+            $orderService = 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] = $orderService->getPayInfo(
+                    $GiftsID, $userID, $payAmt
+                );
+            }
+
+            // 增加充值记录
+            [$Score] = $orderService->addRecord(
+                $userID, $payAmt, $favorable_price, $order_no, $GiftsID,
+                $Recharge, $czReason, $give, $cjReason, $AdId, $eventType,
+                0
+            );
+
+            // 异步处理后续任务
+            Order::dispatch([$userID, $payAmt, $Score, $favorable_price, $GiftsID, $order_no]);
+        } catch (\Exception $exception) {
+            Util::WriteLog("SfPay_error", $exception->getMessage());
+        }
+
+        // 更新订单状态
+        $order_up = DB::connection('write')->table('agent.dbo.order')
+            ->where('order_sn', $order_no)
+            ->update($body);
+
+        if (!$order_up) {
+            Util::WriteLog('SfPay', '订单更新失败: ' . $order_no);
+            return 'ok';
+        }
+
+        Util::WriteLog("SfPay", 'success: ' . $order_no);
+        return 'ok';
+    }
+
+    /**
+     * 代收订单查询
+     *
+     * POST /gateway/payment/orderQuery
+     * 请求: {"merchantOrderNo": "xxx"}(AES加密)
+     * 响应: code=0, data={status, amount, orderNo, ...}
+     *
+     * @param string $merchantOrderNo 商户订单号
+     * @return array|false
+     */
+    public function orderQuery($merchantOrderNo)
+    {
+        $service = new SfPay();
+        $params = ['merchantOrderNo' => $merchantOrderNo];
+
+        $url = $service->apiUrl . '/gateway/payment/orderQuery';
+
+        Util::WriteLog('SfPay', 'SfPay订单查询: ' . $url . ' | ' . $merchantOrderNo);
+
+        $result = $service->curlPost($url, $params);
+
+        if ($result === false) {
+            $this->error = 'Query processing error';
+            return false;
+        }
+
+        try {
+            $data = \GuzzleHttp\json_decode($result, true);
+        } catch (\Exception $e) {
+            Util::WriteLog("SfPay_error", [$result, $e->getMessage()]);
+            $this->error = 'Query processing error';
+            return false;
+        }
+
+        return $data;
+    }
+}

+ 4 - 0
app/Services/CashService.php

@@ -10,6 +10,7 @@ use App\Http\logic\api\AiPayCashierLogic;
 use App\Http\logic\api\PagYeepPayCashierLogic;
 use App\Http\logic\api\AiNewPayCashierLogic;
 use App\Http\logic\api\SafePayCashierLogic;
+use App\Http\logic\api\SfPayCashierLogic;
 use App\Http\logic\api\BotImPayCashierLogic;
 
 
@@ -41,6 +42,9 @@ class CashService
             case SafePayCashierLogic::AGENT:
                 return new SafePayCashierLogic();
 
+            case SfPayCashierLogic::AGENT:
+                return new SfPayCashierLogic();
+
             case BotImPayCashierLogic::AGENT:
                 return new BotImPayCashierLogic();
 

+ 4 - 0
app/Services/PayMentService.php

@@ -15,6 +15,7 @@ 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\SfPayController;
 use App\Http\Controllers\Api\AiNewPayController;
 use App\Http\Controllers\Api\BotImPayController;
 use Illuminate\Support\Facades\DB;
@@ -49,6 +50,9 @@ class PayMentService
             case 'SafePay':
                 return new SafePayController();
 
+            case 'SfPay':
+                return new SfPayController();
+
             case 'BotImPay':
                 return new BotImPayController();
 

+ 133 - 0
app/Services/SfPay.php

@@ -0,0 +1,133 @@
+<?php
+
+namespace App\Services;
+
+use App\Util;
+
+class SfPay
+{
+    public $config;
+    public $merchantKey;
+    public $aesKey;
+    public $aesIv;
+    public $apiUrl;
+
+    public function __construct()
+    {
+        $payConfigService = new PayConfig();
+        $this->config = $payConfigService->getConfig('SfPay');
+
+        $this->merchantKey = $this->config['merchant_key'] ?? '';
+        $this->aesKey      = $this->config['aes_key'] ?? '';
+        $this->aesIv       = $this->config['aes_iv'] ?? '';
+        $this->apiUrl      = $this->config['apiUrl'] ?? 'https://sfgateway.sforest.io';
+    }
+
+    /**
+     * AES/CBC/PKCS5Padding 加密
+     *
+     * @param string $str 待加密JSON字符串
+     * @return string base64编码的密文
+     */
+    public function encrypt($str)
+    {
+        return base64_encode(openssl_encrypt($str, 'AES-128-CBC', $this->aesKey, OPENSSL_RAW_DATA, $this->aesIv));
+    }
+
+    /**
+     * AES/CBC/PKCS5Padding 解密
+     *
+     * @param string $encrypted base64编码的密文
+     * @return string 解密后的JSON字符串
+     */
+    public function decrypt($encrypted)
+    {
+        return openssl_decrypt(base64_decode($encrypted), 'AES-128-CBC', $this->aesKey, OPENSSL_RAW_DATA, $this->aesIv);
+    }
+
+    /**
+     * 验证回调签名
+     *
+     * signature_n = MD5(merchantKey + message + amount + status + merchantOrderNo + orderNo + aeskey)
+     *
+     * 注意:如果amount为空,则拼接"null"字符串(代付回调可能出现amount为空的情况)
+     *
+     * @param array $data 回调data参数
+     * @param string $signatureN 签名值
+     * @return bool
+     */
+    public function verifySign($data, $signatureN)
+    {
+        $message        = $data['message'] ?? '';
+        $amount         = $data['amount'] ?? '';
+        $status         = $data['status'] ?? '';
+        $merchantOrderNo = $data['merchantOrderNo'] ?? '';
+        $orderNo        = $data['orderNo'] ?? '';
+
+        // 代付回调中amount为空时,拼接"null"字符串
+        if ($amount === '' || $amount === null) {
+            $amount = 'null';
+        }
+
+        $signStr = $this->merchantKey . $message . $amount . $status . $merchantOrderNo . $orderNo . $this->aesKey;
+
+        $expectedSign = md5($signStr);
+
+        Util::WriteLog('SfPay', "待签名字符串: {$signStr}");
+        Util::WriteLog('SfPay', "期望签名: {$expectedSign}, 实际签名: {$signatureN}");
+
+        return strtolower($expectedSign) === strtolower($signatureN);
+    }
+
+    /**
+     * POST JSON请求(SfPay格式:data字段AES加密,merchant_key放在header)
+     *
+     * @param string $url 请求地址
+     * @param array $params 待加密的请求参数
+     * @return string|false
+     */
+    public function curlPost($url, $params)
+    {
+        $timeout = 30;
+        $jsonStr = json_encode($params, JSON_UNESCAPED_UNICODE);
+
+        // AES加密
+        $encrypted = $this->encrypt($jsonStr);
+        $body = json_encode(['data' => $encrypted], JSON_UNESCAPED_UNICODE);
+
+        $headers = [
+            'Content-Type: application/json;charset=UTF-8',
+            'merchant_key: ' . $this->merchantKey,
+        ];
+
+        Util::WriteLog('SfPay', "SfPay请求URL: {$url}");
+        Util::WriteLog('SfPay', "SfPay请求明文: {$jsonStr}");
+        Util::WriteLog('SfPay', "SfPay请求密文: {$body}");
+
+        $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, $body);
+        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout);
+        curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
+        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
+        curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
+
+        $result = curl_exec($ch);
+
+        if (curl_errno($ch)) {
+            $error = curl_error($ch);
+            Util::WriteLog('SfPay_error', 'CURL Error: ' . $error);
+            curl_close($ch);
+            return false;
+        }
+
+        curl_close($ch);
+
+        Util::WriteLog('SfPay', "SfPay响应: {$result}");
+
+        return $result;
+    }
+}

+ 13 - 0
config/payTest.php

@@ -61,6 +61,19 @@ return [
         'platform_public_key' => 'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDHCMT8mq2XZPoLii5wYVgg9NlMZadXhwyxJZ5duAX4hxXT1OiPpHkj2PpNyMhcBhn+O8p4FjWGUrTRoL29b0X/IlEuGD+u6QosimqYta4l6S47tNyTUqh9zrPmlXn6qow1JY9rL2eSs30NdvB0oFcitSsn68kNcRUDZ9FGtOZiRQIDAQAB',
     ],
 
+    // SfPay 支付渠道(AES-CBC加密,仅代收)
+    'SfPay' => [
+        'merchant_key'       => 'pJhgp872bGKk//IU',
+        'aes_key'            => 'tGzRUVea/qdushrA',
+        'aes_iv'             => 'cpUMwwyrUwSDQiiR',
+        'apiUrl'             => 'https://sfgateway.sforest.io',
+        'currency'           => 'USD',
+        'game_id'             => env('SFPAY_GAMEID', '26656'),
+        'notify_url'         => env('APP_URL', '') . '/api/sfpay/notify',
+        'return_url'         => env('APP_URL', '') . '/api/sfpay/notify',
+        'cash_notify_url'    => env('APP_URL', '') . '/api/sfpay/payout_notify',
+    ],
+
     // BotImPay 代付渠道配置(RSA SHA256签名)
     'BotImPayOut' => [
         'mer_no'             => '3005',

+ 5 - 0
routes/api.php

@@ -391,6 +391,11 @@ Route::any('/pkpay/payout_notify', 'Api\PKpayController@cash_notify');
 Route::any('/safepay/notify', 'Api\SafePayController@notify');
 Route::any('/safepay/payout_notify', 'Api\SafePayController@cash_notify');
 
+// SfPay支付渠道
+Route::any('/sfpay/notify', 'Api\SfPayController@notify');
+Route::any('/sfpay/payout_notify', 'Api\SfPayController@cash_notify');
+Route::any('/sfpay/orderQuery', 'Api\SfPayController@orderQuery');
+
 // BotImPay支付渠道
 Route::any('/botimpay/notify', 'Api\BotImPayController@notify');
 Route::any('/botimpay/payout_notify', 'Api\BotImPayController@cash_notify');