backup: 线上最终版本全量备份
包含所有线上运行的改动: - 代理编辑功能修复(码率/占成/返水率保存) - CORS跨域修复(Pcapi构造函数统一Origin) - 支付通道(ccpay/epay新增) - 代理报表洗码量显示 - AlinPay金额调整 - 代理/会员编辑页面优化 - 多语言更新
This commit is contained in:
@@ -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';
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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