Tree 1 ay önce
ebeveyn
işleme
e252aa961b

+ 155 - 0
app/Http/Controllers/Api/WDPayController.php

@@ -0,0 +1,155 @@
+<?php
+
+namespace App\Http\Controllers\Api;
+
+use App\Http\logic\api\WDPayLogic;
+use App\Http\logic\api\WDPayCashierLogic;
+use App\Inter\PayMentInterFace;
+use App\Notification\TelegramBot;
+use App\Services\WDPay;
+use App\Services\PayConfig;
+use App\Services\PayUtils;
+use App\Util;
+use Illuminate\Http\Request;
+use Illuminate\Support\Facades\Log;
+use Illuminate\Support\Facades\Redis;
+
+class WDPayController implements PayMentInterFace
+{
+    private $retryTimes = 0;
+
+    public function pay_order($userId, $payAmt, $userName, $userEmail, $userPhone, $GiftsID, $buyIP, $AdId, $eventType, $pay_method = '')
+    {
+        $logic = new WDPayLogic();
+        try {
+            $res = $logic->pay_order($userId, $payAmt, $userPhone, $userEmail, $userName, $GiftsID, $buyIP, $AdId, $eventType, $pay_method);
+        } catch (\Exception $exception) {
+            Redis::set("PayErro_WDPay", 1);
+            Redis::expire("PayErro_WDPay", 600);
+            Util::WriteLog('WDPay_error', $exception->getMessage() . json_encode($logic->result ?? []));
+
+            TelegramBot::getDefault()->sendProgramNotify("WDPay Except ", $exception->getMessage(), $exception);
+            return apiReturnFail($logic->getError());
+        }
+
+        if (!empty($res) && isset($res['code']) && $res['code'] == 0) {
+            $data = [
+                'content' => $res['data']['cashierUrl'],
+                'money' => $payAmt,
+                'prdOrdNo' => $res['data']['mchOrderNo'],
+            ];
+            return apiReturnSuc($data);
+        } else if ($res == false) {
+            return apiReturnFail($logic->getError());
+        } else {
+            if ($this->retryTimes > 0) {
+                Redis::set("PayErro_WDPay", 1);
+                Redis::expire("PayErro_WDPay", 600);
+                TelegramBot::getDefault()->sendProgramNotify("WDPay RetrunFail ", $logic->getError() . " | " . json_encode($res));
+                return apiReturnFail($logic->getError());
+            } else {
+                $this->retryTimes++;
+                return $this->pay_order($userId, $payAmt, $userName, $userEmail, $userPhone, $GiftsID, $buyIP, $AdId, $eventType, $pay_method);
+            }
+        }
+    }
+
+    // 支付异步回调
+    public function notify(Request $request)
+    {
+        // WDPay官方回调格式(POST JSON):
+        // {
+        //     "amount": "4.99",
+        //     "currency": "USD",
+        //     "customerOrderNo": "xxxxxx",
+        //     "orderNo": "c66uNp01892LvA",
+        //     "sign": "2D9AB732083EF62AED9990C96A2BBA17",
+        //     "signType": "MD5",
+        //     "status": "succeeded"  // succeeded=成功, closed=失败
+        // }
+        
+        $post = $request->all();
+        $payload = json_encode($post, JSON_UNESCAPED_UNICODE);
+
+        Util::WriteLog('WDPay', "WDPay支付回调\n" . $payload);
+
+        // 验证签名
+        $service = new WDPay();
+        
+        try {
+            $verify = $service->verifySign($post);
+        } catch (\Exception $e) {
+            Util::WriteLog('WDPay', '验签失败:' . $e->getMessage());
+            return response()->json(['success' => false, 'message' => 'Sign verify failed']);
+        }
+
+        if (!$verify) {
+            Util::WriteLog('WDPay', '签名错误');
+            return response()->json(['success' => false, 'message' => 'Invalid sign']);
+        }
+
+        // 提取商户订单号
+        $order_sn = $post['customerOrderNo'] ?? '';
+        if (empty($order_sn)) {
+            Util::WriteLog('WDPay', '缺少订单号');
+            return response()->json(['success' => false, 'message' => 'Missing customerOrderNo']);
+        }
+
+        $logic = new WDPayLogic();
+        try {
+            $redis = Redis::connection();
+            $ret = $logic->notify($post);
+            if ($ret == 'success') {
+                $redis->set("wdpay_notify_" . $order_sn, $order_sn, 3600 * 24);
+            }
+            return $ret;
+        } catch (\Exception $exception) {
+            Redis::set("PayErro_WDPay", 1);
+            Redis::expire("PayErro_WDPay", 600);
+            TelegramBot::getDefault()->sendProgramNotify("WDPay 订单回调执行 异常 ", json_encode($post), $exception);
+            Util::WriteLog("WDPay_error", $exception->getMessage());
+            return response()->json(['success' => false, 'message' => 'Process failed']);
+        }
+    }
+
+    public function sync_notify(Request $request)
+    {
+        Log::info('WDPay同步回调');
+        echo 'WDPay同步回调';
+    }
+
+    // 提现异步回调
+    public function cash_notify(Request $request)
+    {
+        $post = $request->all();
+        $payload = json_encode($post);
+
+        Util::WriteLog('WDPay', "WDPay cash 异步回调\n" . $payload);
+
+        // ✅ 使用 WDPayOut 配置进行验签
+        $payConfigService = new PayConfig();
+        $config = $payConfigService->getConfig('WDPayOut');
+        $apiKey = $config['key'];
+        
+        try {
+            $verify = PayUtils::verifySign($post, $apiKey);
+        } catch (\Exception $e) {
+            Util::WriteLog('WDPay cash', '验签失败:' . $e->getMessage());
+            return 'fail';
+        }
+
+        if (!$verify) {
+            Util::WriteLog('WDPay cash', '签名错误');
+            return 'fail';
+        }
+
+        $logic = new WDPayCashierLogic();
+        try {
+            return $logic->notify($post);
+        } catch (\Exception $exception) {
+            TelegramBot::getDefault()->sendProgramNotify("WDPay 提现异步回调执行 异常 ", json_encode($post), $exception);
+            Util::WriteLog("WDPay_error", $post);
+            return '{"success":false,"message":"商户自定义出错信息"}';
+        }
+    }
+}

+ 315 - 0
app/Http/logic/api/WDPayCashierLogic.php

@@ -0,0 +1,315 @@
+<?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\WDPay;
+use App\Services\PayConfig;
+use App\Services\PayUtils;
+use App\Services\StoredProcedure;
+use App\Util;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Log;
+use Illuminate\Support\Facades\Redis;
+
+class WDPayCashierLogic implements CashierInterFace
+{
+    const AGENT = 100; // WDPay代付渠道值,需要根据实际配置修改
+    protected $agent = 100;
+
+    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';    // 订单不存在
+
+        // 使用WDPay服务类进行签名(WDPayOut配置)
+        $service = new WDPay();
+        $payConfigService = new PayConfig();
+        $config = $payConfigService->getConfig('WDPayOut');
+        $service->config = $config;
+        $service->key = $config['key'] ?? '';
+        
+        // WDPay支付方式映射
+        $wayCode = [
+            1 => 'cashapp',  // CashApp
+            2 => 'paypal'    // PayPal
+        ];
+        
+        // 根据PixType选择账号
+        // PixType: 1=CashApp, 2=PayPal
+        $account = '';
+        if ($PixType == 1) {
+            $account = $PixNum;  // CashApp标签,如: $abcd1234
+        } elseif ($PixType == 2) {
+            $account = $email;   // PayPal邮箱,如: 1111@gmail.com
+        }
+
+        // 构建提现请求参数(按WDPay官方文档 /charging/create-pay-out)
+        $params = [
+            "country" => "US",  // 国家,默认US
+            "currency" => "USD",  // 货币,默认USD
+            "wayCode" => $wayCode[$PixType] ?? 'cashapp',  // cashapp或paypal
+            "account" => $account,  // CashApp标签或PayPal邮箱
+            "username" => $accountName ?: $email,  // 用户名称,没有则用email
+            "amount" => number_format($amount / 100, 2, '.', ''),  // 价格,保留2位小数(元)
+            "platform" => Util::getDeviceType(),  // 设备:android/ios,默认ios
+            "ip" => Util::getClientIp(),  // 用户真实IP
+            "customer" => $config['customer'],  // 商户简称
+            "customerNo" => $config['customerNo'],  // 商户编号
+            "customerOrderNo" => $OrderId,  // 商户自定义订单号
+            "timestamp" => (string)round(microtime(true) * 1000),  // 13位时间戳(毫秒)
+            "callback" => $config['cashNotify'] ?? '',  // 回调地址
+            "signType" => "MD5",  // 传MD5
+        ];
+
+        // 使用WDPay的签名方法
+        $signedParams = $service->sign($params);
+
+        $url = $config['apiUrl'] ?? '';  // /charging/create-pay-out
+
+        Util::WriteLog('WDPay_payout', 'WDPay提现请求: ' . $url . ' | 参数: ' . json_encode($signedParams));
+        
+        try {
+            // 使用独立的 curlPost 方法发送请求
+            $result = $this->curlPost($url, $signedParams);
+        } catch (\Exception $exception) {
+            Util::WriteLog('WDPay_payout_error', '提现请求异常:' . $exception->getMessage());
+            return 'fail';
+        }
+
+        Util::WriteLog('WDPay_payout', 'WDPay提现结果: ' . ($result ?? "no result"));
+
+        try {
+            $data = \GuzzleHttp\json_decode($result, true);
+        } catch (\Exception $e) {
+            Util::WriteLog("WDPay_payout_error", ['解析失败', $result, $e->getMessage()]);
+            return 'fail';
+        }
+
+        // WDPay官方返回格式:{"code": 200, "data": {...}, "message": "success"}
+        if (isset($data['code']) && $data['code'] == 200) {
+            Util::WriteLog('WDPay_payout', "提现订单创建成功: {$OrderId}");
+            return $data;
+        } else {
+            // 提现失败处理
+            Util::WriteLog('WDPay_payout_error', "提现失败: {$OrderId} | " . json_encode($data));
+            
+            if ($query->State == 5) {
+                // 同步失败,发送邮件给玩家,退还金币
+                $msg = $data['message'] ?? 'Transfer failed';
+                $WithDraw = $query->WithDraw + $query->ServiceFee;
+                $bonus = '30000,' . $WithDraw;
+
+                PrivateMail::failMail($query->UserID, $OrderId, $WithDraw, $msg, $bonus);
+
+                $withdraw_data = [
+                    'State' => 6, 
+                    'agent' => $this->agent, 
+                    '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';
+        }
+    }
+
+    public function notify($post)
+    {
+        if (!is_array($post)) $post = \GuzzleHttp\json_decode($post, true);
+        
+        Util::WriteLog('WDPay', 'WDPay 提现回调:' . json_encode($post));
+
+        try {
+            // 判断订单是否存在(WDPay使用customerOrderNo作为商户订单号)
+            $OrderId = $post['customerOrderNo'] ?? '';
+            $query = DB::connection('write')->table('QPAccountsDB.dbo.OrderWithDraw')->where('OrderId', $OrderId)->first();
+
+            if (!$query) {
+                Util::WriteLog('WDPay_payout','提现订单不存在: ' . $OrderId);
+                return '{"msg":"success","code":200}';
+            }
+
+            if ($query->State != 5 && $query->State != 7) {
+                Util::WriteLog('WDPay_payout', $OrderId . '_提现订单状态已完成');
+                return '{"msg":"success","code":200}';
+            }
+
+            $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['orderNo'] ?? '',  // WDPay交易号
+                'extra' => \GuzzleHttp\json_encode($post),
+                'created_at' => $now,
+                'updated_at' => $now,
+                'order_sn' => $OrderId,
+                'amount' => $query->WithDraw,
+            ];
+
+            // 判断订单状态(WDPay提现状态:PAID=成功, REJECTED=失败)
+            $orderStatus = 0;
+            if (isset($post['status'])) {
+                $status = strtoupper($post['status']);
+                if ($status == 'PAID') {
+                    $orderStatus = 1;  // 提现成功
+                } elseif ($status == 'REJECTED') {
+                    $orderStatus = 2;  // 提现失败
+                }
+            }
+
+            if (!$orderStatus) {
+                Util::WriteLog('WDPay_payout', 'WDPay提现处理中:' . $OrderId . ', 状态: ' . ($post['status'] ?? 'unknown'));
+                return '{"msg":"success","code":200}';
+            }
+
+            Util::WriteLog('WDPay_payout', 'WDPay提现回调:' . $OrderId . ', 状态: ' . ($post['status'] ?? '') . ', 处理结果: ' . $orderStatus);
+            
+            $UserID = $query->UserID;
+            $TakeMoney = $query->WithDraw + $query->ServiceFee;
+
+            switch ($orderStatus) {
+                case 1: // 提现成功
+                    Util::WriteLog('WDPay', 'WDPay提现成功');
+
+                    $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);
+
+                    $redis = Redis::connection();
+                    $redis->incr('draw_' . date('Ymd') . $UserID);
+                    break;
+
+                case 2: // 提现失败
+                    $msg = 'Encomenda rejeitada pelo banco';
+                    $bonus = '30000,' . $TakeMoney;
+                    PrivateMail::failMail($query->UserID, $OrderId, $TakeMoney, $msg, $bonus);
+                    
+                    Util::WriteLog('WDPayEmail', [$query->UserID, $OrderId, $TakeMoney, $msg, $bonus]);
+                    $withdraw_data = ['State' => 6, 'agent' => $agentID, 'remark' => @$post['transMsg'] ?: ''];
+                    $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.withdraw_notify')->updateOrInsert(['order_sn' => $OrderId], $notify_data);
+            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);
+            }
+            
+            Util::WriteLog('WDPay_payout', "提现回调处理完成: {$OrderId}");
+            
+            return '{"msg":"success","code":200}';
+
+        } catch (\Exception $exception) {
+            Util::WriteLog('WDPay_payout_error', 'WDPay提现回调处理失败:' . $exception->getMessage() . "\n" . $exception->getTraceAsString());
+            return '{"msg":"error","code":500}';
+        }
+    }
+
+    /**
+     * POST请求方法 - application/x-www-form-urlencoded(WDPay协议)
+     */
+    private function curlPost($url, $payload)
+    {
+        $timeout = 20;
+        // WDPay使用 application/x-www-form-urlencoded 格式
+        $data = http_build_query($payload);
+        
+        $headers = [
+            'Content-Type: application/x-www-form-urlencoded; charset=UTF-8',
+        ];
+
+        $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);
+
+        $result = curl_exec($ch);
+        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
+
+        if (curl_errno($ch)) {
+            $error = curl_error($ch);
+            Util::WriteLog('WDPay_error', 'CURL Error: ' . $error);
+            curl_close($ch);
+            return false;
+        }
+
+        if ($httpCode != 200) {
+            Util::WriteLog('WDPay_error', 'HTTP Code: ' . $httpCode . " | Response: " . $result);
+        }
+
+        curl_close($ch);
+        return $result;
+    }
+}

+ 242 - 0
app/Http/logic/api/WDPayLogic.php

@@ -0,0 +1,242 @@
+<?php
+
+namespace App\Http\logic\api;
+
+use App\dao\Pay\AccountPayInfo;
+use App\dao\Pay\PayController;
+use App\Facade\TableName;
+use App\Http\helper\CreateOrder;
+use App\Http\helper\NumConfig;
+use App\Jobs\Order;
+use App\Notification\TelegramBot;
+use App\Services\OrderServices;
+use App\Services\PayConfig;
+use App\Services\WDPay;
+use App\Services\CreateLog;
+use App\Util;
+use Illuminate\Support\Facades\DB;
+
+class WDPayLogic extends BaseApiLogic
+{
+    public $result;
+
+    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 WDPay();
+        $config = $service->config;
+
+        $order_sn = CreateOrder::order_sn($userId);
+
+        // 生成订单信息(WDPay使用元,但数据库存储还是用分)
+        $logic = new OrderLogic();
+        $amount = (int) round($pay_amount * NumConfig::NUM_VALUE);
+        $logic->orderCreate($order_sn, $amount, 'WDPay', $userId, '', $GiftsID, $AdId, $eventType);
+
+        $payMethods = [
+            1 => 'cashapp',
+            2 => 'paypal',
+            4 => 'applepay',
+        ];
+
+        $wdMethod = @$payMethods[$pay_method];
+
+        // 构建支付请求参数(按WDPay官方文档 /charging/create-pay-in)
+        // 金额格式:元,必须保留2位小数,如 4.99
+        $params = [
+            "country" => "US",  // 国家,默认US
+            "currency" => "USD",  // 货币,默认USD
+            "amount" => number_format($pay_amount, 2, '.', ''),  // 价格,必须保留2位小数
+            "platform" =>Util::getDeviceType(),  // 设备:android/ios
+            "ip" => $buyIP,  // 用户真实IP
+            "username" => $userName ?: 'user' . $userId,  // 用户名称
+            "customer" => $config['customer'],  // 商户简称
+            "customerNo" => $config['customerNo'],  // 商户编号
+            "customerOrderNo" => $order_sn,  // 商户自定义订单号
+            "wayCode" => $wdMethod ? $wdMethod : ($config['wayCode'] ?? "cashapp"),  // cashapp或paypal
+            "timestamp" => (string)round(microtime(true) * 1000),  // 13位时间戳(毫秒)
+            "signType" => "MD5",  // 传MD5
+            "callback" => $config['notify'] ?? '',  // 异步通知地址
+        ];
+        
+        // redirectUrl 是可选参数
+        if (!empty($config['return'])) {
+            $params["redirectUrl"] = $config['return'];  // 支付成功后跳转的页面
+        }
+
+        // 签名
+        $signedParams = $service->sign($params);
+
+        // 生成用户请求信息日志
+        $request_extra = \GuzzleHttp\json_encode($signedParams);
+        CreateLog::pay_request($userPhone, $request_extra, $order_sn, $userEmail, $userId, $userName);
+
+        $url = $service->apiUrl;
+
+        $result = $service->curlPost($url, $signedParams);
+        $rresult = compact('result', 'signedParams', 'url');
+        
+        Util::WriteLog('WDPay', 'WDPay支付请求' . $url . " | " . $request_extra);
+        Util::WriteLog('WDPay', 'WDPay支付结果' . json_encode($rresult));
+        
+        try {
+            $res = \GuzzleHttp\json_decode($result, true);
+        } catch (\Exception $e) {
+            Util::WriteLog("WDPay_error", [$result, $e->getMessage(), $e->getTraceAsString()]);
+            $this->error = 'Payment processing failed';
+            return false;
+        }
+
+        // WDPay官方返回格式:
+        // {"code": 200, "data": {...}, "message": "success"}
+        if (isset($res['code']) && $res['code'] == 200) {
+            // 转换为统一格式供控制器使用
+            return [
+                'code' => 0,  // 统一成功码
+                'data' => [
+                    'cashierUrl' => $res['data']['paylink'] ?? '',  // 支付链接
+                    'mchOrderNo' => $order_sn,  // 商户订单号
+                    'orderNo' => $res['data']['id'] ?? '',  // WDPay订单号
+                    'amount' => $res['data']['amount'] ?? $pay_amount,
+                    'currency' => $res['data']['currency'] ?? 'USD',
+                    'status' => $res['data']['status'] ?? 'initiated',
+                    'paychannel' => $res['data']['paychannel'] ?? '',
+                    'time_created' => $res['data']['time_created'] ?? time(),
+                ]
+            ];
+        } else {
+            $this->error = $res['message'] ?? $res['msg'] ?? 'Payment failed';
+            Util::WriteLog('WDPay_error', 'Payment failed: ' . json_encode($res));
+            return false;
+        }
+    }
+
+    public function notify($post)
+    {
+        try {
+            if (!is_array($post)) $post = \GuzzleHttp\json_decode($post, true);
+
+            Util::WriteLog('WDPay', "WDPay异步回调处理\n" . json_encode($post, JSON_UNESCAPED_UNICODE));
+
+            // WDPay官方回调字段:
+            // customerOrderNo: 商户订单号
+            // orderNo: WDPay订单号
+            // amount: 金额(元)
+            // currency: 货币
+            // status: 订单状态 (succeeded=成功, closed=失败)
+            // signType: MD5
+            // sign: 签名
+            
+            $order_sn = $post['customerOrderNo'] ?? '';  // 商户订单号
+
+            $order = DB::connection('write')->table('agent.dbo.order')->where('order_sn', $order_sn)->first();
+
+            if (!$order) {
+                Util::WriteLog('WDPay', '订单不存在: ' . $order_sn);
+                return response()->json(['success' => false, 'message' => 'Order not found']);
+            }
+
+            // 订单已处理,直接返回成功
+            if ((!empty($order->pay_at) || !empty($order->finished_at))) {
+                Util::WriteLog('WDPay', '订单已处理: ' . $order_sn);
+                return 'success';
+            }
+
+            $body = [
+                'payment_sn' => $post['orderNo'] ?? '',  // WDPay订单号
+                'updated_at' => date('Y-m-d H:i:s'),
+            ];
+
+            $ordStatus = $post['status'] ?? '';  // WDPay状态字段
+            $GiftsID = $order->GiftsID ?: '';
+            $userID = $order->user_id ?: '';
+            $AdId = $order->AdId ?: '';
+            $eventType = $order->eventType ?: '';
+            // WDPay金额是元,需要转换为分
+            $payAmt = floatval($post['amount'] ?? 0);
+
+            // WDPay状态判断:succeeded=成功, closed=失败
+            switch (strtolower($ordStatus)) {
+                case 'succeeded':  // 支付成功(官方状态)
+
+                    $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('WDPay');
+                    $body['payment_fee'] = $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_sn, $GiftsID, $Recharge, $czReason, $give, $cjReason, $AdId, $eventType);
+                        // 成功处理回调
+                        Order::dispatch([$userID, $payAmt, $Score, $favorable_price, $GiftsID, $order_sn]);
+
+                        Util::WriteLog("WDPay", "订单处理成功: {$order_sn}, 用户: {$userID}, 金额: {$payAmt}, 到账: {$Score}");
+
+                    } catch (\Exception $exception) {
+                        Util::WriteLog("WDPay_error", "订单处理异常: " . $exception->getMessage());
+                        throw $exception;
+                    }
+
+                    break;
+                    
+                case 'closed':    // 支付失败(官方状态)
+                    $body['pay_status'] = 2;
+                    Util::WriteLog('WDPay', "订单支付失败: {$order_sn}, 状态: closed");
+                    break;
+                    
+                default:          // 其他状态(不应该出现)
+                    Util::WriteLog('WDPay', "订单未知状态: {$order_sn}, 状态: {$ordStatus}");
+                    return 'success';  // 返回成功
+            }
+
+            $order_up = DB::connection('write')->table('agent.dbo.order')
+                ->where('order_sn', $order_sn)
+                ->update($body);
+
+            if (!$order_up) {
+                Util::WriteLog('WDPay', '订单更新失败: ' . $order_sn);
+                return response()->json(['success' => false, 'message' => 'Update failed']);
+            }
+
+            Util::WriteLog("WDPay", "订单回调处理完成: {$order_sn}");
+
+            return '{"msg":"success","code":200}';
+
+        } catch (\Exception $exception) {
+            Util::WriteLog("WDPay_error", "回调异常: " . $exception->getMessage() . "\n" . $exception->getTraceAsString());
+            throw $exception;
+        }
+    }
+}

+ 4 - 0
app/Services/CashService.php

@@ -4,6 +4,7 @@
 namespace App\Services;
 
 use App\Http\logic\api\WiwiPayCashierLogic;
+use App\Http\logic\api\WDPayCashierLogic;
 
 
 class CashService
@@ -16,6 +17,9 @@ class CashService
             case WiwiPayCashierLogic::AGENT:
                 return new WiwiPayCashierLogic();
 
+            case WDPayCashierLogic::AGENT:
+                return new WDPayCashierLogic();
+
         }
     }
 }

+ 4 - 0
app/Services/PayMentService.php

@@ -10,6 +10,7 @@ use App\Http\Controllers\Api\GooglePayController;
 use App\Http\Controllers\Api\GoopagoController;
 use App\Http\Controllers\Api\SitoBankController;
 use App\Http\Controllers\Api\WiwiPayController;
+use App\Http\Controllers\Api\WDPayController;
 use Illuminate\Support\Facades\DB;
 use Illuminate\Support\Facades\Log;
 use Illuminate\Support\Facades\Redis;
@@ -24,6 +25,9 @@ class PayMentService
             case 'WiwiPay':
                 return new WiwiPayController();
 
+            case 'WDPay':
+                return new WDPayController();
+
 
             case 'apple':
                 return new AppleStorePayController();

+ 190 - 0
app/Services/WDPay.php

@@ -0,0 +1,190 @@
+<?php
+
+namespace App\Services;
+
+use App\Util;
+
+class WDPay
+{
+    public $config;
+    public $key;
+    public $customerNo;
+    public $apiUrl;
+
+    public function __construct()
+    {
+        $payConfigService = new PayConfig();
+        $this->config = $payConfigService->getConfig('WDPay');
+        
+        $this->key = $this->config['key'] ?? '';
+        $this->customerNo = $this->config['customerNo'] ?? '';
+        // WDPay官方接口:/charging/create-pay-in
+        $this->apiUrl = $this->config['apiUrl'] ?? '';
+    }
+
+    /**
+     * 签名 - WDPay专用签名算法
+     * 
+     * 规则:
+     * 1. 按参数名ASCII码从小到大排序(字典序)
+     * 2. 空值不参与签名
+     * 3. sign参数不参与签名
+     * 4. 拼接格式:key1=value1&key2=value2&key=商户密钥
+     * 5. MD5后转大写
+     */
+    public function sign(array $params): array
+    {
+        // 移除sign和空值
+        $signParams = [];
+        foreach ($params as $key => $value) {
+            if ($key !== 'sign' && $value !== null && $value !== '') {
+                // 如果是对象或数组,需要递归处理
+                if (is_array($value) || is_object($value)) {
+                    $signParams[$key] = $this->buildObjectString($value);
+                } else {
+                    $signParams[$key] = $value;
+                }
+            }
+        }
+        
+        // 按ASCII码排序
+        ksort($signParams);
+        
+        // 拼接字符串:key1=value1&key2=value2
+        $stringA = urldecode(http_build_query($signParams));
+        
+        // 拼接商户密钥:stringA&key=商户密钥
+        $stringSignTemp = $stringA . '&key=' . $this->key;
+        
+        // MD5并转大写
+        $sign = strtoupper(md5($stringSignTemp));
+        
+        // 将签名添加到参数中
+        $params['sign'] = $sign;
+        
+        Util::WriteLog('WDPay_sign', "待签名字符串: " . $stringSignTemp);
+        Util::WriteLog('WDPay_sign', "签名结果: " . $sign);
+        
+        return $params;
+    }
+
+    /**
+     * 验签 - WDPay专用验签算法
+     */
+    public function verifySign(array $params): bool
+    {
+        if (!isset($params['sign'])) {
+            return false;
+        }
+        
+        $receivedSign = $params['sign'];
+        
+        // 移除sign和空值
+        $signParams = [];
+        foreach ($params as $key => $value) {
+            if ($key !== 'sign' && $value !== null && $value !== '') {
+                if (is_array($value) || is_object($value)) {
+                    $signParams[$key] = $this->buildObjectString($value);
+                } else {
+                    $signParams[$key] = $value;
+                }
+            }
+        }
+        
+        // 按ASCII码排序
+        ksort($signParams);
+        
+        // 拼接字符串
+        $stringA = urldecode(http_build_query($signParams));
+        
+        // 拼接商户密钥
+        $stringSignTemp = $stringA . '&key=' . $this->key;
+        
+        // MD5并转大写
+        $calculatedSign = strtoupper(md5($stringSignTemp));
+        
+        Util::WriteLog('WDPay_verify', "待验签字符串: " . $stringSignTemp);
+        Util::WriteLog('WDPay_verify', "计算签名: " . $calculatedSign);
+        Util::WriteLog('WDPay_verify', "接收签名: " . $receivedSign);
+        
+        return $calculatedSign === $receivedSign;
+    }
+    
+    /**
+     * 递归处理对象/数组为字符串
+     */
+    private function buildObjectString($data): string
+    {
+        if (is_array($data)) {
+            $parts = [];
+            ksort($data);
+            foreach ($data as $k => $v) {
+                if ($v !== null && $v !== '') {
+                    if (is_array($v) || is_object($v)) {
+                        $parts[] = $k . '=' . $this->buildObjectString($v);
+                    } else {
+                        $parts[] = $k . '=' . $v;
+                    }
+                }
+            }
+            return implode('&', $parts);
+        }
+        return (string)$data;
+    }
+
+    /**
+     * 生成随机字符串
+     */
+    public function getNonceStr($length = 16): string
+    {
+        $chars = "abcdefghijklmnopqrstuvwxyz0123456789";
+        $str = "";
+        for ($i = 0; $i < $length; $i++) {
+            $str .= substr($chars, mt_rand(0, strlen($chars) - 1), 1);
+        }
+        return $str;
+    }
+
+    /**
+     * POST请求 - application/x-www-form-urlencoded
+     */
+    public function curlPost($url, $payload)
+    {
+        $timeout = 20;
+        
+        // WDPay使用 application/x-www-form-urlencoded 格式
+        $data = http_build_query($payload);
+        
+        $headers = [
+            'Content-Type: application/x-www-form-urlencoded; charset=UTF-8',
+        ];
+
+        $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);
+
+        $result = curl_exec($ch);
+        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
+
+        if (curl_errno($ch)) {
+            $error = curl_error($ch);
+            Util::WriteLog('WDPay_error', 'CURL Error: ' . $error);
+            curl_close($ch);
+            return false;
+        }
+
+        if ($httpCode != 200) {
+            Util::WriteLog('WDPay_error', 'HTTP Code: ' . $httpCode . " | Response: " . $result);
+        }
+
+        curl_close($ch);
+        return $result;
+    }
+}

+ 47 - 0
app/Util.php

@@ -180,6 +180,40 @@ class Util {
         return $configs;
 
     }
+
+
+    /**
+     * 获取客户端真实 IP 地址
+     *
+     * @return string
+     */
+    public static function getClientIp(): string
+    {
+        $ip = '';
+
+        // 1. Cloudflare 等 CDN 转发的原始 IP
+        if (!empty($_SERVER['HTTP_CF_CONNECTING_IP'])) {
+            $ip = $_SERVER['HTTP_CF_CONNECTING_IP'];
+        } // 2. 代理服务器传递的真实 IP(可能有多个,用逗号分隔)
+        elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
+            $ips = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
+            $ip = trim($ips[0]); // 取第一个非空 IP
+        } // 3. 部分代理使用的头
+        elseif (!empty($_SERVER['HTTP_X_REAL_IP'])) {
+            $ip = $_SERVER['HTTP_X_REAL_IP'];
+        } // 4. 最后回退到远程地址
+        elseif (!empty($_SERVER['REMOTE_ADDR'])) {
+            $ip = $_SERVER['REMOTE_ADDR'];
+        }
+
+        // 校验合法性
+        if (!filter_var($ip, FILTER_VALIDATE_IP)) {
+            $ip = '0.0.0.0';
+        }
+
+        return $ip;
+    }
+
     public static function getPackageByURL($ihaveparam=null){
         $configs=self::getPackConfigs();
         $packInfos=[];
@@ -374,6 +408,19 @@ class Util {
         return $data;
     }
 
+    public static function getDeviceType(): string
+    {
+        // 获取 UA(统一转为小写)
+        $ua = strtolower($_SERVER['HTTP_USER_AGENT'] ?? '');
+
+        if (strpos($ua, 'iphone') !== false || strpos($ua, 'ipad') !== false || strpos($ua, 'ipod') !== false) {
+            return 'ios';
+        }
+
+        // 默认返回 Android(包括 Android 和 其他)
+        return 'android';
+    }
+
     public static function jsonp( $data, $callback = 'callback' ) {
         @header( 'Content-Type: application/json' );
         @header( "Expires:-1" );

+ 25 - 1
config/pay.php

@@ -38,5 +38,29 @@ return [
         'payout_fee' => 0.15,
         'cashNotify' => env('APP_URL', '').'/api/wiwipay/payout_notify',
         'cash_url' => env('APP_URL', '').'/api/payout/create'
-    ]
+    ],
+    'WDPay' => [
+        'key' => '62b9986350334c29b415103e16ac52f8',
+        'customer' => 'usslot777',
+        'customerNo' => '1027007522',
+        'apiUrl' => 'https://pay.wonderspay.com/charging/create-pay-in',
+        'currency' => 'USD',
+        'wayCode' => 'cashapp',
+        'signType' => 'MD5',
+        'payin_fee' => 0.15,
+        'notify' => env('APP_URL', '').'/api/wdpay/notify',
+        'return' => env('APP_URL', '').'/api/wdpay/return',
+    ],
+    'WDPayOut' => [
+        'key' => '3d904b04cb504aedb78a3e2913efa2a0',
+        'customer' => 'usslot777',
+        'customerNo' => '1027017801',
+        'apiUrl' => 'https://pay.wonderspay.com/charging/create-pay-out',
+        'currency' => 'USD',
+        'wayCode' => 'cashapp',
+        'signType' => 'MD5',
+        'payin_fee' => 0.15,
+        'notify' => env('APP_URL', '').'/api/payout_notify/notify',
+        'return' => env('APP_URL', '').'/api/wdpay/return',
+    ],
 ];

+ 5 - 0
routes/api.php

@@ -291,6 +291,11 @@ Route::any('/wiwipay/notify', 'Api\WiwiPayController@notify');
 Route::any('/wiwipay/payout_notify', 'Api\WiwiPayController@cash_notify');
 Route::any('/wiwipay/return', 'Api\WiwiPayController@sync_notify');
 
+// WDPay支付渠道
+Route::any('/wdpay/notify', 'Api\WDPayController@notify');
+Route::any('/wdpay/payout_notify', 'Api\WDPayController@cash_notify');
+Route::any('/wdpay/return', 'Api\WDPayController@sync_notify');
+
 
 Route::any('/russia/notify', 'Api\RussiaPayController@notify');
 Route::any('/russia/cash_notify', 'Api\RussiaPayController@cash_notify');