提交代码

This commit is contained in:
manghe 2025-03-21 20:21:14 +00:00
parent f9fb3e8916
commit 0fdd3dc6ab
8 changed files with 343 additions and 85 deletions

View File

@ -31,6 +31,7 @@ class Config extends Base
'name' => '默认商户',
'mch_id' => $old_config['mch_id'] ?? '',
'keys' => $old_config['keys'] ?? '',
'order_prefix' => $old_config['order_prefix'] ?? 'MYH',
'weight' => 1
]
]
@ -92,6 +93,23 @@ class Config extends Base
$data = input("post.");
$data['update_time'] = time();
// 处理微信支付商户前缀验证
if ($data['key'] == 'weixinpay_setting' && isset($data['merchants']) && is_array($data['merchants'])) {
// 检查前缀的唯一性
$prefixes = [];
foreach ($data['merchants'] as $index => $merchant) {
if (empty($merchant['order_prefix']) || strlen($merchant['order_prefix']) !== 3) {
return $this->renderError('商户前缀必须是3位字符');
}
if (in_array($merchant['order_prefix'], $prefixes)) {
return $this->renderError('商户前缀"'.$merchant['order_prefix'].'"重复,每个商户的前缀必须唯一');
}
$prefixes[] = $merchant['order_prefix'];
}
}
// 处理同步地址数据格式
if ($data['key'] == 'systemconfig') {
$syncAddresses = [];

View File

@ -4,60 +4,7 @@
<h2 style="text-align: center;margin-top: 20px;">系统设置</h2>
<div class="layui-fluid">
<div class="layui-row layui-col-space15">
<div class="layui-col-md12">
<div class="layui-card">
<div class="layui-card-header">盒子同步地址</div>
<div class="layui-card-body">
<form class="layui-form" action="" lay-filter="component-form-element">
<input type="hidden" name="key" value="{$key}">
<div class="layui-form-item">
<label class="layui-form-label">盒子同步地址</label>
<div class="layui-input-block">
<div id="sync-addresses">
{if isset($data.sync_address) && is_array($data.sync_address)}
{foreach $data.sync_address as $index => $address}
<div class="sync-address-item">
<div class="layui-input-inline" style="width: 200px; margin-right: 10px;">
<input type="text" name="sync_address_names[]"
value="{$address.name|default=''}" autocomplete="off"
class="layui-input" placeholder="地址名称">
</div>
<div class="layui-input-inline" style="width: 450px; margin-right: 10px;">
<input type="text" name="sync_address_urls[]"
value="{$address.sync_address|default=''}" autocomplete="off"
class="layui-input" placeholder="请输入同步地址URL">
</div>
<button type="button"
class="layui-btn layui-btn-danger remove-address">删除</button>
</div>
{/foreach}
{else}
<div class="sync-address-item">
<div class="layui-input-inline" style="width: 200px; margin-right: 10px;">
<input type="text" name="sync_address_names[]" value=""
autocomplete="off" class="layui-input" placeholder="地址名称">
</div>
<div class="layui-input-inline" style="width: 450px; margin-right: 10px;">
<input type="text" name="sync_address_urls[]" value=""
autocomplete="off" class="layui-input" placeholder="请输入同步地址URL">
</div>
<button type="button"
class="layui-btn layui-btn-danger remove-address">删除</button>
</div>
{/if}
</div>
<div style="margin-top: 10px;">
<button type="button" class="layui-btn layui-btn-normal"
id="add-address">添加地址</button>
<button class="layui-btn" lay-submit
lay-filter="component-form-element">保存</button>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
<div class="layui-col-md12">
<div class="layui-card">
@ -126,6 +73,61 @@
</div>
</div>
<div class="layui-col-md12">
<div class="layui-card">
<div class="layui-card-header">盒子同步地址</div>
<div class="layui-card-body">
<form class="layui-form" action="" lay-filter="component-form-element">
<input type="hidden" name="key" value="{$key}">
<div class="layui-form-item">
<label class="layui-form-label">盒子同步地址</label>
<div class="layui-input-block">
<div id="sync-addresses">
{if isset($data.sync_address) && is_array($data.sync_address)}
{foreach $data.sync_address as $index => $address}
<div class="sync-address-item">
<div class="layui-input-inline" style="width: 200px; margin-right: 10px;">
<input type="text" name="sync_address_names[]"
value="{$address.name|default=''}" autocomplete="off"
class="layui-input" placeholder="地址名称">
</div>
<div class="layui-input-inline" style="width: 450px; margin-right: 10px;">
<input type="text" name="sync_address_urls[]"
value="{$address.sync_address|default=''}" autocomplete="off"
class="layui-input" placeholder="请输入同步地址URL">
</div>
<button type="button"
class="layui-btn layui-btn-danger remove-address">删除</button>
</div>
{/foreach}
{else}
<div class="sync-address-item">
<div class="layui-input-inline" style="width: 200px; margin-right: 10px;">
<input type="text" name="sync_address_names[]" value=""
autocomplete="off" class="layui-input" placeholder="地址名称">
</div>
<div class="layui-input-inline" style="width: 450px; margin-right: 10px;">
<input type="text" name="sync_address_urls[]" value=""
autocomplete="off" class="layui-input" placeholder="请输入同步地址URL">
</div>
<button type="button"
class="layui-btn layui-btn-danger remove-address">删除</button>
</div>
{/if}
</div>
<div style="margin-top: 10px;">
<button type="button" class="layui-btn layui-btn-normal"
id="add-address">添加地址</button>
<button class="layui-btn" lay-submit
lay-filter="component-form-element">保存</button>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
<div class="layui-col-md12">
<div class="layui-card">
<div class="layui-card-header">微信公众号设置</div>

View File

@ -14,7 +14,7 @@
<div class="layui-form" wid100 lay-filter="">
<div class="layui-form-item">
<div class="layui-alert layui-bg-gray">
<i class="layui-icon layui-icon-tips"></i> 微信Appid和AppSecret已移至系统设置页面请在那里设置
<i class="layui-icon layui-icon-tips"></i> 微信Appid和AppSecret已移至系统设置页面请在那里设置。ps:权重越高,支付几率越多。订单前缀必填,用于区分哪一个商户号支付的。
</div>
</div>
<input type="hidden" name="key" value="weixinpay_setting" lay-verify="required" class="layui-input">
@ -41,6 +41,12 @@
<input type="text" name="merchants[{$index}][keys]" value="{$merchant.keys}" lay-verify="required" class="layui-input" placeholder="请输入商户密钥">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">订单前缀</label>
<div class="layui-input-inline ggg">
<input type="text" name="merchants[{$index}][order_prefix]" value="{$merchant.order_prefix}" lay-verify="required|prefix" class="layui-input" placeholder="请输入3位订单前缀" maxlength="3">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">权重</label>
<div class="layui-input-inline ggg">
@ -71,6 +77,12 @@
<input type="text" name="merchants[0][keys]" value="{$data.keys}" lay-verify="required" class="layui-input" placeholder="请输入商户密钥">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">订单前缀</label>
<div class="layui-input-inline ggg">
<input type="text" name="merchants[0][order_prefix]" value="{$data.order_prefix|default='MYH'}" lay-verify="required|prefix" class="layui-input" placeholder="请输入3位订单前缀" maxlength="3">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">权重</label>
<div class="layui-input-inline ggg">
@ -101,6 +113,56 @@
<script type="text/javascript">
layui.use(['layer','form','upload','element'], function(){
var $ = layui.$;
var form = layui.form;
// 添加自定义验证规则
form.verify({
prefix: function(value, item){
if(value.length !== 3){
return '订单前缀必须是3位字符';
}
// 检查前缀唯一性
var prefixes = [];
$('input[name$="[order_prefix]"]').each(function() {
var prefix = $(this).val();
if (prefix && prefix !== value) {
prefixes.push(prefix);
}
});
if (prefixes.includes(value)) {
return '商户前缀"' + value + '"重复,每个商户的前缀必须唯一';
}
}
});
// 监听提交
form.on('submit', function(data){
// 验证前缀唯一性
var prefixes = [];
var hasDuplicate = false;
var duplicatePrefix = '';
$('input[name$="[order_prefix]"]').each(function() {
var prefix = $(this).val();
if (prefix) {
if (prefixes.includes(prefix)) {
hasDuplicate = true;
duplicatePrefix = prefix;
return false; // 跳出循环
}
prefixes.push(prefix);
}
});
if (hasDuplicate) {
layer.msg('商户前缀"' + duplicatePrefix + '"重复,每个商户的前缀必须唯一', {icon: 2, anim: 6, time: 2000});
return false;
}
return true;
});
// 添加商户按钮点击事件
$('#add-merchant').on('click', function() {
@ -125,6 +187,12 @@
<input type="text" name="merchants[${index}][keys]" value="" lay-verify="required" class="layui-input" placeholder="请输入商户密钥">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">订单前缀</label>
<div class="layui-input-inline ggg">
<input type="text" name="merchants[${index}][order_prefix]" value="" lay-verify="required|prefix" class="layui-input" placeholder="请输入3位订单前缀" maxlength="3">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">权重</label>
<div class="layui-input-inline ggg">
@ -135,6 +203,7 @@
</div>
`;
$('#merchants-container').append(newItemHtml);
form.render();
});
// 删除商户按钮点击事件(使用事件委托)
@ -157,8 +226,48 @@
});
function check(){
var url="{:url('/admin/update')}";
var $ = layui.$;
var form = layui.form;
// 先进行前缀唯一性验证
var prefixes = [];
var hasDuplicate = false;
var duplicatePrefix = '';
$('input[name$="[order_prefix]"]').each(function() {
var prefix = $(this).val();
if (prefix) {
if (prefixes.includes(prefix)) {
hasDuplicate = true;
duplicatePrefix = prefix;
return false; // 跳出循环
}
prefixes.push(prefix);
}
});
if (hasDuplicate) {
layer.msg('商户前缀"' + duplicatePrefix + '"重复,每个商户的前缀必须唯一', {icon: 2, anim: 6, time: 2000});
return false;
}
// 长度验证
var hasError = false;
$('input[name$="[order_prefix]"]').each(function() {
var prefix = $(this).val();
if (!prefix || prefix.length !== 3) {
hasError = true;
layer.msg('商户前缀必须是3位字符', {icon: 2, anim: 6, time: 2000});
return false; // 跳出循环
}
});
if (hasError) {
return false;
}
// 提交表单
var url="{:url('/admin/update')}";
var load=layer.load(2);
$.post(url,$("form").serialize(),function(data){
if(data.status==1){

View File

@ -38,7 +38,7 @@ class Pay extends Base
$this->merchant = $config['mch_id'];
$this->secretKey = $config['keys'];
} else {
// 小程序使用随机商户
// 初始使用默认配置,具体支付时会根据订单号重新获取商户信息
$wxpayConfig = WxPayHelper::getWxPayConfig();
if (!empty($wxpayConfig['merchant'])) {
$this->appid = $wxpayConfig['appid'];
@ -61,6 +61,35 @@ class Pay extends Base
$this->noticeurl = request()->domain() . '/api/notify/order_notify';#订单回调URL
}
/**
* 根据订单号设置正确的商户配置
* 确保创建订单和支付使用的是同一个商户
*
* @param string $order_num 订单号
* @return bool 是否成功设置商户信息
*/
protected function setMerchantByOrderNum($order_num)
{
if (!$this->ish5() && strpos($order_num, 'MH_') === 0) {
// 提取订单中的商户前缀
$merchant_prefix = WxPayHelper::extractOrderPrefix($order_num);
if (!empty($merchant_prefix)) {
// 根据前缀获取固定的商户配置
$wxpayConfig = WxPayHelper::getFixedWxPayConfig($merchant_prefix);
if (!empty($wxpayConfig['merchant'])) {
// 更新当前实例的商户信息
$this->appid = $wxpayConfig['appid'];
$this->merchant = $wxpayConfig['merchant']['mch_id'];
$this->secretKey = $wxpayConfig['merchant']['keys'];
return true;
}
}
}
return false;
}
//支付
/**
* [pay description]
@ -162,6 +191,9 @@ class Pay extends Base
//支付
public function wxpay($order_num, $money, $title, $openid, $notify)
{
// 根据订单号设置正确的商户配置
$this->setMerchantByOrderNum($order_num);
$openidx = $openid;
if ($this->ish5()) {
$user = User::getInfo(['openid' => $openidx]);
@ -233,6 +265,9 @@ class Pay extends Base
*/
public function wxCreateOrder($order_no, $price, $openid, $body, $attach)
{
// 根据订单号设置正确的商户配置
$this->setMerchantByOrderNum($order_no);
$openidx = $openid;
if ($this->ish5()) {
$user = User::getInfo(['openid' => $openidx]);

View File

@ -263,14 +263,30 @@ if (!function_exists('arraySequence')) {
if (!function_exists('create_order_no')) {
/**
* 生成订单号
* @param string $pre 订单前缀
* @param string $tale 表名
* @param string $field 字段名
* @return string
*/
function create_order_no($pre = 'NO_', $tale = 'order', $field = 'order_num')
{
$order_no = $pre . date('Ymd') . substr(implode(NULL, array_map('ord', str_split(substr(uniqid(), 7, 13), 1))), 0, 8) . str_pad(mt_rand(1, 99999), 5, '0', STR_PAD_LEFT);
// 检查是否包含多商户前缀标记,处理商户随机选择并获取前缀
if (strpos($pre, 'MH_') !== false) {
// 引入微信支付帮助类
$wxpayConfig = \app\common\helper\WxPayHelper::getWxPayConfig();
// 如果存在商户信息并且有前缀,则使用商户前缀
if (!empty($wxpayConfig['merchant']) && !empty($wxpayConfig['merchant']['order_prefix'])) {
$merchant_prefix = $wxpayConfig['merchant']['order_prefix'];
// 在MH_后面添加商户前缀而不是替换MH_
$pre = 'MH_' . $merchant_prefix;
}
}
$order_no = $pre . date('Ymd') . substr(implode('', array_map('ord', str_split(substr(uniqid(), 7, 13), 1))), 0, 8) . str_pad(mt_rand(1, 99999), 5, '0', STR_PAD_LEFT);
$r = Db::name($tale)->field('id')->where([$field => $order_no])->find();
if ($r) {
$order_no = create_order_no($pre);
$order_no = create_order_no($pre, $tale, $field);
}
return $order_no;
}
@ -622,9 +638,8 @@ if (!function_exists('thumbImage')) {
require_once('../vendor/topthink/think-image/src/Image.php');
$image = \think\Image::open($path);
// 按照原图的比例生成一个最大为150*150的缩略图并保存为thumb.png
$image->thumb($width, $height)->radius($radius, $bg)->save($path);
$image->thumb($width, $height)->save($path);
return true;
}
}

View File

@ -67,6 +67,7 @@ class WxPayHelper
'name' => '默认商户',
'mch_id' => $old_config['mch_id'],
'keys' => $old_config['keys'],
'order_prefix' => $old_config['order_prefix'] ?? 'MYH',
'weight' => 1
];
}
@ -85,4 +86,66 @@ class WxPayHelper
'appid' => $appid
];
}
/**
* 获取固定的微信支付配置(基于订单前缀)
*
* @param string $order_prefix 订单前缀,如果提供则返回对应前缀的商户信息
* @return array 包含固定商户信息和appid
*/
public static function getFixedWxPayConfig($order_prefix = '')
{
$wechat_setting = getConfig('wechat_setting');
$weixinpay_setting = getConfig('weixinpay_setting');
// 尝试查找与订单前缀匹配的商户
$merchant = null;
if (!empty($order_prefix) && !empty($weixinpay_setting) && !empty($weixinpay_setting['merchants'])) {
foreach ($weixinpay_setting['merchants'] as $m) {
if (!empty($m['order_prefix']) && $m['order_prefix'] === $order_prefix) {
$merchant = $m;
break;
}
}
}
// 如果没有找到匹配的商户,则使用随机选择
if (empty($merchant)) {
return self::getWxPayConfig();
}
// 获取微信AppID
$appid = '';
if (!empty($wechat_setting) && !empty($wechat_setting['appid'])) {
$appid = $wechat_setting['appid'];
} else {
$old_config = getConfig('weixinpay');
if (!empty($old_config) && !empty($old_config['appid'])) {
$appid = $old_config['appid'];
}
}
return [
'merchant' => $merchant,
'appid' => $appid
];
}
/**
* 从订单号中提取商户前缀
*
* @param string $order_no 订单号(格式如 MH_ABC20240529...)
* @return string|null 商户前缀如果无法提取则返回null
*/
public static function extractOrderPrefix($order_no)
{
if (strpos($order_no, 'MH_') === 0) {
// 尝试提取MH_后的3个字符作为商户前缀
$prefix = substr($order_no, 3, 3);
if (!empty($prefix) && strlen($prefix) === 3) {
return $prefix;
}
}
return null;
}
}

View File

@ -15,8 +15,17 @@ class WechatRefund extends MyController
*/
public function OrderRefund($info)
{
// 使用WxPayHelper获取随机商户配置
$wxpayConfig = WxPayHelper::getWxPayConfig();
// 提取订单号中的商户前缀
$merchant_prefix = null;
if (!empty($info['send_num']) && strpos($info['send_num'], 'MH_') === 0) {
$merchant_prefix = WxPayHelper::extractOrderPrefix($info['send_num']);
}
// 根据订单中的商户前缀获取配置
$wxpayConfig = !empty($merchant_prefix)
? WxPayHelper::getFixedWxPayConfig($merchant_prefix)
: WxPayHelper::getWxPayConfig();
$appid = $wxpayConfig['appid'];
$merchant = $wxpayConfig['merchant']['mch_id'];
@ -27,10 +36,10 @@ class WechatRefund extends MyController
$params['out_refund_no'] = $this->genRandomString(64);
$params['total_fee'] = round($info['freight'] * 100, 2);
$params['refund_fee'] = round($info['freight'] * 100, 2);
$params['sign'] = $this->MakeSign($params);
$params['sign'] = $this->MakeSign($params, $wxpayConfig['merchant']['keys']);
$xml = $this->data_to_xml($params);
$url = 'https://api.mch.weixin.qq.com/secapi/pay/refund';
$tui_info = $this->postXmlCurl($xml, $url);
$tui_info = $this->postXmlCurl($xml, $url, $wxpayConfig['merchant']);
$result = $this->xml_to_data($tui_info);
if ($result['return_code'] === 'SUCCESS' && $result['result_code'] === 'SUCCESS') {
return ['status' => 1, 'msg' => $result];
@ -43,13 +52,17 @@ class WechatRefund extends MyController
/**
* 生成签名
* @return 签名
* @param array $params 签名参数
* @param string $secretKey 可选指定密钥不指定则通过WxPayHelper获取
* @return string 签名
*/
public function MakeSign($params)
public function MakeSign($params, $secretKey = null)
{
// 使用WxPayHelper获取随机商户配置
$wxpayConfig = WxPayHelper::getWxPayConfig();
$secretKey = $wxpayConfig['merchant']['keys'];
// 如果未提供密钥则使用WxPayHelper获取随机商户配置
if (empty($secretKey)) {
$wxpayConfig = WxPayHelper::getWxPayConfig();
$secretKey = $wxpayConfig['merchant']['keys'];
}
//签名步骤一:按字典序排序数组参数
ksort($params);
@ -150,17 +163,19 @@ class WechatRefund extends MyController
*
* @param string $xml 需要post的xml数据
* @param string $url url
* @param bool $useCert 是否需要证书,默认不需要
* @param array|null $merchant 商户配置,如果不传则获取随机商户
* @param int $second url执行超时时间默认30s
* @throws WxPayException
*/
private function postXmlCurl($xml, $url, $second = 30)
private function postXmlCurl($xml, $url, $merchant = null, $second = 30)
{
$path = app()->getRootPath();
// 获取商户配置
$wxpayConfig = WxPayHelper::getWxPayConfig();
$merchant = $wxpayConfig['merchant'];
// 如果未指定商户配置,则使用随机商户配置
if (empty($merchant)) {
$wxpayConfig = WxPayHelper::getWxPayConfig();
$merchant = $wxpayConfig['merchant'];
}
// 优先使用商户配置中的证书路径,如果未设置则使用默认路径
$ssl_cert = isset($merchant['ssl_cert']) && !empty($merchant['ssl_cert'])

View File

@ -24,7 +24,10 @@ return [
'url' => '/admin/user_invite',
'name' => '用户邀请列表',
],
[
'url' => '/admin/sign',
'name' => '用户签到设置',
],
],
],
[
@ -62,7 +65,11 @@ return [
[
'url' => '/admin/goods',
'name' => '盒子列表',
]
],
[
'url' => '/admin/card_shang',
'name' => '抽奖等级设置',
],
],
],
[
@ -215,14 +222,8 @@ return [
'url' => '/admin/wechatofficialaccount',
'name' => '微信公众号',
],
[
'url' => '/admin/sign',
'name' => '签到设置',
],
[
'url' => '/admin/card_shang',
'name' => '抽奖等级设置',
],
[
'url' => '/admin/systemconfig',
'name' => '系统设置',