getConfig(); } /** * 获取配置信息 */ private function getConfig() { $config = Db::name('pay_channel')->where('key', 'ALIN_VN')->value('value'); $config = json_decode($config, true); $this->config = $config; // 域名从配置读取,回退默认值(2026-05 第三方网关由 fantianchuangshi.com 迁至 hypayment.net) $this->domain = $config['domain'] ?? 'https://alinintermon-api.hypayment.net'; } /** * 生成签名 * 规则:参数按key排序 → key=value&连接(空值不参与) → 拼接key={secret} → MD5 32位小写 * ext字段不参与签名 * * @param array $params * @return string */ public function generateSign(array $params) { // 移除不参与签名的字段 unset($params['sign']); unset($params['ext']); // 过滤空值 $params = array_filter($params, function ($val) { return $val !== '' && $val !== null; }); // 按key排序 ksort($params); // 拼接 $parts = []; foreach ($params as $key => $val) { $parts[] = $key . '=' . $val; } $str = implode('&', $parts); $str .= '&key=' . $this->config['secret']; return md5($str); } /** * 验证回调签名(时间安全比较,防timing attack) * * @param array $params * @return bool */ public function verifySign(array $params) { $receivedSign = $params['sign'] ?? ''; $calculatedSign = $this->generateSign($params); return hash_equals($calculatedSign, $receivedSign); } /** * 发送HTTP请求 * * @param string $url * @param array $data * @return array|null */ private function request($url, $data) { $jsonData = json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); addLogToFile('请求地址:' . $url, 'alin_request', 'alin'); addLogToFile('请求报文:' . $jsonData, 'alin_request', 'alin'); $header = ['Content-Type: application/json']; $result = httpPost($url, $jsonData, $header); addLogToFile('返回结果:' . json_encode($result, JSON_UNESCAPED_UNICODE), 'alin_response', 'alin'); return $result; } /** * 代收下单(用户充值) * * @param string $orderNo 商户订单号 * @param string $amount 金额(元) * @param string $notifyUrl 回调地址 * @param string $desc 订单描述 * @return array|null */ public function createOrder($orderNo, $amount, $notifyUrl, $desc = '') { $params = [ 'app_id' => strval($this->config['app_id']), 'product_id' => strval($this->config['collection_product_id']), // 代收 product_id=11 'out_trade_no' => strval($orderNo), 'notify_url' => strval($notifyUrl), 'amount' => strval($amount), 'time' => intval(time()), 'desc' => strval($desc), ]; $params['sign'] = $this->generateSign($params); $url = $this->domain . '/api/order'; return $this->request($url, $params); } /** * 代收查询 * * @param string $orderNo 商户订单号 * @return array|null */ public function queryOrder($orderNo) { $params = [ 'app_id' => $this->config['app_id'], 'out_trade_no' => $orderNo, 'time' => time(), ]; $params['sign'] = $this->generateSign($params); $url = $this->domain . '/api/order/status'; return $this->request($url, $params); } /** * 代付下单(用户提现) * * @param string $orderNo 商户订单号 * @param string $amount 金额(元) * @param string $accountName 账户名称 * @param string $accountNumber 卡号 * @param string $bankCode 银行编码 * @param string $bankName 银行名称 * @param string $notifyUrl 回调地址 * @param string $desc 订单描述 * @return array|null */ public function createPayment($orderNo, $amount, $accountName, $accountNumber, $bankCode, $bankName, $notifyUrl, $desc = '') { $params = [ 'app_id' => strval($this->config['app_id']), 'product_id' => strval($this->config['payment_product_id']), // 代付 product_id=14 'out_trade_no' => strval($orderNo), 'notify_url' => strval($notifyUrl), 'amount' => strval($amount), 'time' => intval(time()), 'desc' => strval($desc), ]; $params['sign'] = $this->generateSign($params); // ext不参与签名,单独附加 $params['ext'] = [ 'accountName' => $accountName, 'accountNumber' => $accountNumber, 'bankCode' => $bankCode, 'bankName' => $bankName, ]; $url = $this->domain . '/api/payment'; return $this->request($url, $params); } /** * 代付查询 * * @param string $orderNo 商户订单号 * @return array|null */ public function queryPayment($orderNo) { $params = [ 'app_id' => $this->config['app_id'], 'out_trade_no' => $orderNo, 'time' => time(), ]; $params['sign'] = $this->generateSign($params); $url = $this->domain . '/api/payment/status'; return $this->request($url, $params); } /** * 账户查询 * * @return array|null */ public function queryAccount() { $params = [ 'app_id' => $this->config['app_id'], 'time' => time(), ]; $params['sign'] = $this->generateSign($params); $url = $this->domain . '/api/account'; return $this->request($url, $params); } /** * 处理代收回调 — 仅验签+解析状态,不操作余额 * 余额结算由 trait alin_recharge_settle() 统一处理 * * @param array $data 回调数据 * @param int $logId 日志ID * @return array ['verified' => bool, 'trade_status' => int, 'data' => array] */ public function verifyRechargeCallback($data, $logId = 0) { // 验签 if (!$this->verifySign($data)) { addLogToFile('代收回调签名验证失败:' . json_encode($data, 320), 'alin_callback_error', 'alin'); return ['verified' => false, 'trade_status' => 0, 'data' => $data]; } $orderNo = $data['out_trade_no'] ?? ''; if ($logId && $orderNo) { Db::name('pay_callback_log')->where('id', $logId)->update(['unique_no' => $orderNo]); } $tradeStatus = intval($data['trade_status']); addLogToFile('代收回调验签通过:order=' . $orderNo . ' status=' . $tradeStatus, 'alin_callback', 'alin'); return ['verified' => true, 'trade_status' => $tradeStatus, 'data' => $data]; } /** * 处理代付回调(提现回调) * * @param array $data 回调数据 * @param int $logId 日志ID * @return string|void */ public function paymentCallback($data, $logId = 0) { // 验签 if (!$this->verifySign($data)) { addLogToFile('代付回调签名验证失败:' . json_encode($data, 320), 'alin_callback_error', 'alin'); return; } $orderNo = $data['out_trade_no'] ?? ''; if (!$orderNo) return; if ($logId) { Db::name('pay_callback_log')->where('id', $logId)->update(['unique_no' => $orderNo]); } $tradeStatus = intval($data['trade_status']); $order = Db::name('user_withdraw')->where('order_no', $orderNo)->find(); if (!$order) { addLogToFile('代付回调订单不存在:' . $orderNo, 'alin_callback', 'alin'); return 'success'; } // 只处理 AGREE 状态的订单 if ($order['status'] !== 'AGREE') { addLogToFile('代付回调订单状态非AGREE:' . $orderNo . ' status=' . $order['status'], 'alin_callback', 'alin'); return 'success'; } $payTime = strtotime($data['complete_time'] ?? '') ?: time(); if ($tradeStatus === 1) { // 成功 $updateData = [ 'status' => 'SUCCESS', 'pay_time' => $payTime, 'out_trade_no' => $data['trade_no'] ?? '', ]; Db::name('user_withdraw')->where('id', $order['id'])->update($updateData); addLogToFile('代付回调成功:' . $orderNo, 'alin_callback', 'alin'); return 'success'; } elseif ($tradeStatus === 2) { // 失败 — 退回用户余额 Db::startTrans(); try { $user = Db::name('user')->where('id', $order['user_id'])->lock(true)->find(); $newMoney = $user['money'] + $order['money'] + $order['service_fee_money']; Db::name('user')->where('id', $order['user_id'])->update([ 'money' => $newMoney, ]); Db::name('user_withdraw')->where('id', $order['id'])->update([ 'status' => 'FAIL', 'pay_time' => $payTime, ]); Db::commit(); addLogToFile('代付回调失败已退款:' . $orderNo . ' refund=' . ($order['money'] + $order['service_fee_money']), 'alin_callback', 'alin'); return 'success'; } catch (\Exception $e) { Db::rollback(); addLogToFile('代付回调退款异常:' . $e->getMessage(), 'alin_callback_error', 'alin'); return; } } // 处理中(-1),不返回success让网关重试 addLogToFile('代付回调处理中:' . $orderNo . ' status=' . $tradeStatus, 'alin_callback', 'alin'); return; } /** * 获取银行编码列表 * * @return array */ public static function getBankCodes() { return self::$bankCodes; } /** * 获取配置信息(外部使用) * * @return array */ public function getConfigInfo() { return $this->config; } }