backup: 线上最终版本全量备份
包含所有线上运行的改动: - 代理编辑功能修复(码率/占成/返水率保存) - CORS跨域修复(Pcapi构造函数统一Origin) - 支付通道(ccpay/epay新增) - 代理报表洗码量显示 - AlinPay金额调整 - 代理/会员编辑页面优化 - 多语言更新
This commit is contained in:
@@ -0,0 +1,213 @@
|
||||
<?php
|
||||
namespace pay;
|
||||
|
||||
use think\Db;
|
||||
|
||||
/**
|
||||
* 彩虹易支付(epay)对接类
|
||||
*
|
||||
* 对应线上支付平台:https://pay.g7g7.top/
|
||||
* 官方文档:https://pay.cccyun.cc/
|
||||
*
|
||||
* 支付流程:
|
||||
* 1. buildPayUrl() 构造带签名的 URL,前端 302 跳转到该 URL 发起支付
|
||||
* 2. 用户在易支付页面完成支付(USDT扫码/银行卡/支付宝 等,取决于通道)
|
||||
* 3. 易支付异步 POST notify_url,携带 sign;本端 verifyNotifyCallback() 验签
|
||||
*
|
||||
* 签名规则(MD5):
|
||||
* - 去除 sign、sign_type 字段
|
||||
* - 过滤空值
|
||||
* - 按 key 字典序排序
|
||||
* - 拼接 k=v&k=v(不 urlencode,直接原值)
|
||||
* - 末尾拼接 {商户key}(不是 &key=)
|
||||
* - MD5(str) 得 32 位小写
|
||||
*/
|
||||
class Epay
|
||||
{
|
||||
private $config;
|
||||
private $apiUrl;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->getConfig();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取配置
|
||||
* 优先读 pay_channel 表(和 AlinPay 一致的存储位置),
|
||||
* 回退到 config('pay_list.epay')
|
||||
*/
|
||||
private function getConfig()
|
||||
{
|
||||
$dbConfig = Db::name('pay_channel')->where('key', 'EPAY')->value('value');
|
||||
if ($dbConfig) {
|
||||
$this->config = json_decode($dbConfig, true) ?: [];
|
||||
} else {
|
||||
$this->config = config('pay_list.epay') ?: [];
|
||||
}
|
||||
$this->apiUrl = $this->config['api_url'] ?? 'https://pay.g7g7.top';
|
||||
}
|
||||
|
||||
public function getConfigInfo()
|
||||
{
|
||||
return $this->config;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成签名
|
||||
*
|
||||
* @param array $params
|
||||
* @return string
|
||||
*/
|
||||
public function generateSign(array $params)
|
||||
{
|
||||
unset($params['sign'], $params['sign_type']);
|
||||
|
||||
// 过滤空值(易支付官方要求)
|
||||
$params = array_filter($params, function ($val) {
|
||||
return $val !== '' && $val !== null;
|
||||
});
|
||||
|
||||
ksort($params);
|
||||
|
||||
$parts = [];
|
||||
foreach ($params as $k => $v) {
|
||||
$parts[] = $k . '=' . $v;
|
||||
}
|
||||
$str = implode('&', $parts) . ($this->config['key'] ?? '');
|
||||
|
||||
return md5($str);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证回调签名(时间安全比较)
|
||||
*
|
||||
* @param array $params
|
||||
* @return bool
|
||||
*/
|
||||
public function verifySign(array $params)
|
||||
{
|
||||
$received = $params['sign'] ?? '';
|
||||
$calculated = $this->generateSign($params);
|
||||
return hash_equals($calculated, $received);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造发起支付的 URL(跳转式 submit.php)
|
||||
* 前端收到该 URL 后直接 window.location 跳转即可
|
||||
*
|
||||
* @param string $orderNo 商户订单号
|
||||
* @param string $type 支付方式:alipay|wxpay|qqpay|bank|jdpay|usdt
|
||||
* @param string $amount 金额(元)
|
||||
* @param string $name 商品名称
|
||||
* @param string $notifyUrl 异步通知地址
|
||||
* @param string $returnUrl 同步跳转地址
|
||||
* @param string $param 业务扩展参数(可选,原样回传)
|
||||
* @return string
|
||||
*/
|
||||
public function buildPayUrl($orderNo, $type, $amount, $name, $notifyUrl, $returnUrl = '', $param = '')
|
||||
{
|
||||
$params = [
|
||||
'pid' => strval($this->config['pid'] ?? ''),
|
||||
'type' => $type,
|
||||
'out_trade_no' => strval($orderNo),
|
||||
'notify_url' => $notifyUrl,
|
||||
'return_url' => $returnUrl,
|
||||
'name' => $name,
|
||||
'money' => strval($amount),
|
||||
'param' => $param,
|
||||
'sign_type' => 'MD5',
|
||||
];
|
||||
|
||||
$params['sign'] = $this->generateSign($params);
|
||||
|
||||
$params = array_filter($params, function ($v) {
|
||||
return $v !== '' && $v !== null;
|
||||
});
|
||||
|
||||
return rtrim($this->apiUrl, '/') . '/submit.php?' . http_build_query($params);
|
||||
}
|
||||
|
||||
/**
|
||||
* API 模式下单(mapi.php)
|
||||
*
|
||||
* @param string $clientIp 下单用户的 IP(mapi.php 必填,否则报 "用户IP地址(clientip)不能为空")
|
||||
* @return array|null
|
||||
*/
|
||||
public function createOrderApi($orderNo, $type, $amount, $name, $notifyUrl, $returnUrl = '', $param = '', $clientIp = '')
|
||||
{
|
||||
if ($clientIp === '' || $clientIp === null) {
|
||||
$clientIp = \think\Request::instance()->ip();
|
||||
}
|
||||
$params = [
|
||||
'pid' => strval($this->config['pid'] ?? ''),
|
||||
'type' => $type,
|
||||
'out_trade_no' => strval($orderNo),
|
||||
'notify_url' => $notifyUrl,
|
||||
'return_url' => $returnUrl,
|
||||
'name' => $name,
|
||||
'money' => strval($amount),
|
||||
'param' => $param,
|
||||
'clientip' => strval($clientIp),
|
||||
'sign_type' => 'MD5',
|
||||
];
|
||||
$params['sign'] = $this->generateSign($params);
|
||||
|
||||
$url = rtrim($this->apiUrl, '/') . '/mapi.php';
|
||||
|
||||
addLogToFile('epay API 下单请求:' . $url . ' params=' . json_encode($params, JSON_UNESCAPED_UNICODE), 'epay_request', 'epay');
|
||||
|
||||
$result = httpPost($url, $params);
|
||||
|
||||
addLogToFile('epay API 下单返回:' . json_encode($result, JSON_UNESCAPED_UNICODE), 'epay_response', 'epay');
|
||||
|
||||
if (is_array($result)) return $result;
|
||||
if (is_string($result)) return json_decode($result, true);
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 订单查询
|
||||
*/
|
||||
public function queryOrder($orderNo)
|
||||
{
|
||||
$params = [
|
||||
'act' => 'order',
|
||||
'pid' => strval($this->config['pid'] ?? ''),
|
||||
'out_trade_no' => strval($orderNo),
|
||||
];
|
||||
$params['sign'] = $this->generateSign($params);
|
||||
$params['sign_type'] = 'MD5';
|
||||
|
||||
$url = rtrim($this->apiUrl, '/') . '/api.php?' . http_build_query($params);
|
||||
$resp = httpGet($url);
|
||||
return is_string($resp) ? json_decode($resp, true) : $resp;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理异步回调 — 仅验签+解析状态,不操作余额
|
||||
* 余额结算由 trait 内 epay_recharge_settle() 统一处理
|
||||
*
|
||||
* @param array $data $_POST 或 $_GET
|
||||
* @param int $logId 日志 ID
|
||||
* @return array ['verified' => bool, 'success' => bool, 'data' => array]
|
||||
*/
|
||||
public function verifyNotifyCallback($data, $logId = 0)
|
||||
{
|
||||
if (!$this->verifySign($data)) {
|
||||
addLogToFile('epay 回调验签失败:' . json_encode($data, 320), 'epay_callback_error', 'epay');
|
||||
return ['verified' => false, 'success' => false, 'data' => $data];
|
||||
}
|
||||
|
||||
$orderNo = $data['out_trade_no'] ?? '';
|
||||
if ($logId && $orderNo) {
|
||||
Db::name('pay_callback_log')->where('id', $logId)->update(['unique_no' => $orderNo]);
|
||||
}
|
||||
|
||||
$success = isset($data['trade_status']) && $data['trade_status'] === 'TRADE_SUCCESS';
|
||||
|
||||
addLogToFile('epay 回调验签通过:order=' . $orderNo . ' status=' . ($data['trade_status'] ?? ''), 'epay_callback', 'epay');
|
||||
|
||||
return ['verified' => true, 'success' => $success, 'data' => $data];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user