backup: 线上最终版本全量备份

包含所有线上运行的改动:
- 代理编辑功能修复(码率/占成/返水率保存)
- CORS跨域修复(Pcapi构造函数统一Origin)
- 支付通道(ccpay/epay新增)
- 代理报表洗码量显示
- AlinPay金额调整
- 代理/会员编辑页面优化
- 多语言更新
This commit is contained in:
OG Backup
2026-06-14 14:19:49 +08:00
parent 490df4c4c5
commit 171f8fce5c
25 changed files with 1570 additions and 247 deletions
+2 -2
View File
@@ -64,8 +64,8 @@ class AlinPay
$config = json_decode($config, true);
$this->config = $config;
// 域名从配置读取,回退默认值
$this->domain = $config['domain'] ?? 'https://alinintermon-api.fantianchuangshi.com';
// 域名从配置读取,回退默认值2026-05 第三方网关由 fantianchuangshi.com 迁至 hypayment.net
$this->domain = $config['domain'] ?? 'https://alinintermon-api.hypayment.net';
}
/**
+132
View File
@@ -0,0 +1,132 @@
<?php
namespace pay;
use think\Db;
class Ccpay
{
private $config;
private $apiUrl;
private $mchNo;
private $paySecret;
public function __construct()
{
$this->config = config('pay_list.ccpay') ?: [];
$this->apiUrl = $this->config['api_url'] ?? '';
$this->mchNo = $this->config['mch_no'] ?? '';
$this->paySecret = $this->config['pay_secret'] ?? '';
}
public function getConfigInfo()
{
return $this->config;
}
public function generateSign(array $params)
{
unset($params['sign']);
ksort($params);
$str = '';
foreach ($params as $k => $v) {
if (is_string($v) && strlen($v) > 0) {
$str .= "{$k}={$v}&";
}
}
$str = substr($str, 0, -1) . '&paySecret=' . $this->paySecret;
return strtoupper(md5($str));
}
public function verifySign(array $params)
{
$received = $params['sign'] ?? '';
if (!$received) return false;
$calculated = $this->generateSign($params);
return hash_equals($calculated, $received);
}
public function createOrder($orderNo, $amount, $notifyUrl, $memberId = 'og_user', $callbackUrl = '')
{
$extra = [
'bankType' => 'OFFLINE',
'cardType' => '0',
'orderPeriod' => '30',
'memberId' => $memberId,
'merchantName' => 'OG',
'notifyUrl' => $notifyUrl,
'callbackUrl' => $callbackUrl,
];
$data = [
'tradeType' => 'cs.pay.submit',
'version' => '2.0',
'channel' => 'union_pay',
'mchNo' => $this->mchNo,
'body' => 'online_pay',
'mchOrderNo' => $orderNo,
'amount' => strval(intval($amount)),
'currency' => 'VND',
'timePaid' => date('YmdHis'),
'remark' => 'online-pay',
];
$signParams = array_merge($data, $extra);
$data['sign'] = $this->generateSign($signParams);
$data['extra'] = json_encode($extra);
addLogToFile('ccpay 下单请求: order=' . $orderNo . ' amount=' . $amount . ' params=' . json_encode($data, JSON_UNESCAPED_UNICODE), 'ccpay_request', 'ccpay');
$result = $this->postForm($this->apiUrl, $data);
addLogToFile('ccpay 下单返回: ' . json_encode($result, JSON_UNESCAPED_UNICODE), 'ccpay_response', 'ccpay');
return $result;
}
public function queryOrder($orderNo)
{
$data = [
'tradeType' => 'cs.order.query',
'version' => '2.0',
'mchNo' => $this->mchNo,
'mchOrderNo' => $orderNo,
];
$data['sign'] = $this->generateSign($data);
return $this->postForm($this->apiUrl, $data);
}
private function postForm($url, $params)
{
$ch = curl_init($url);
$body = '';
foreach ($params as $k => $v) {
if (!is_string($v)) continue;
$body .= $k . '=' . urlencode($v) . '&';
}
$body = rtrim($body, '&');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $body,
CURLOPT_HTTPHEADER => ['Content-Type: application/x-www-form-urlencoded; charset=UTF-8'],
CURLOPT_CONNECTTIMEOUT => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_SSL_VERIFYHOST => false,
]);
$resp = curl_exec($ch);
$err = curl_error($ch);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($resp === false) {
addLogToFile("ccpay curl 失败: code={$code} err={$err}", 'ccpay_error', 'ccpay');
return null;
}
return json_decode($resp, true);
}
}
+213
View File
@@ -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 下单用户的 IPmapi.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];
}
}