【紧急高危补丁】异次元V3.0 易支付、EPUSDT及码支付插件严重漏洞修复(事关资金,速修!)
异次元V3.0 系统自带的【易支付】、【EPUSDT】以及【码支付】这三个核心支付插件,全部存在极其致命的逻辑漏洞。事关大家的钱袋子,看到这条公告请立刻去修改文件!
🚨 漏洞到底有多严重?
这三个插件在验证异步回调签名时,底层都存在“哈希弱类型碰撞漏洞”。
说人话就是:黑客根本不需要知道你的支付密钥,就能利用这个漏洞一分钱不花,直接伪造订单支付成功,疯狂白嫖你的业务。 更离谱的是,EPUSDT 插件里还留了一行高危的调试代码,会直接把你的核心签名文件暴露在网站目录里!如果不修,你的网站等于大门敞开,资产随时被洗劫一空。
第一处修复:易支付核心签名文件
文件路径:网站根目录/app/Pay/Epay/Impl/Signature.php (请根据你的实际目录查找)
替换为以下代码:
<?php
declare(strict_types=1);
namespace App\Pay\Epay\Impl;
class Signature implements \App\Pay\Signature
{
public static function generateSignature(array $data, string $key): string
{
ksort($data);
$sign = '';
foreach ($data as $k => $v) {
$sign .= $k . '=' . $v . '&';
}
$sign = trim($sign, '&');
return md5($sign . $key);
}
public function verification(array $data, array $config): bool
{
if (!isset($data['sign'])) {
return false;
}
$sign = (string)$data['sign'];
unset($data['sign']);
unset($data['sign_type']);
$generateSignature = self::generateSignature($data, $config['key']);
// 已修复:采用 hash_equals 强校验,彻底杜绝哈希碰撞绕过
if (!hash_equals($generateSignature, $sign)) {
return false;
}
return true;
}
}
小白弄不懂怎么办?
打开宝塔面板,进入你的网站跟目录
![图片[1]-异次元发卡重大支付漏洞修复-云创云工坊](https://www.yuncyun.top/wp-content/uploads/2026/03/1774235258-20260323030738908269-1024x896.webp)
找到app/Pay/Epay/Impl/Signature.php文件
点击编辑
![图片[2]-异次元发卡重大支付漏洞修复-云创云工坊](https://www.yuncyun.top/wp-content/uploads/2026/03/1774235344-20260323030904568428-1024x1004.webp)
删除里面所有代码,填入上面的代码即可
![图片[3]-异次元发卡重大支付漏洞修复-云创云工坊](https://www.yuncyun.top/wp-content/uploads/2026/03/1774235471-20260323031111771481-947x1024.webp)
【紧急安全更新】异次元V3.0 严重支付漏洞(跨通道串单、0元白嫖)修复补丁发布
异次元V3.0 系统中存在几个非常致命的支付业务逻辑漏洞。如果你的站还在跑这个版本并且没打补丁,大概率会被黑产盯上,直接把你薅秃。
本次补丁主要修复了以下几个高危问题:
跨通道串单/伪造回调漏洞:修复了黑客利用劣质或无签名的野鸡支付通道,伪造支付宝/微信成功回调的漏洞。
0元购白嫖漏洞:彻底拦截了通过抓包或特殊手段篡改金额(<=0)直接白嫖卡密和余额的非法操作。
强制安全校验:在系统底层强制接管支付回调签名验证,防止部分第三方支付插件漏写验证逻辑。
废话不多说,修复代码已经整理好了,完全不影响你原本的正常购买和充值逻辑。
低版本异次元可自行比对,或者联系客服付费 修复
修复方法:
直接打开你服务器上的源文件,将以下两段代码全选复制,分别覆盖替换掉原来的文件内容即可。
下面修改版本为3.2.x
文件 1:用户充值模块
文件路径:app/Service/Bind/Recharge.php
<?php
declare(strict_types=1);
namespace App\Service\Bind;
use App\Entity\PayEntity;
use App\Model\Bill;
use App\Model\Config;
use App\Model\OrderOption;
use App\Model\Pay;
use App\Model\User;
use App\Model\UserRecharge;
use App\Service\Order;
use App\Util\Client;
use App\Util\Date;
use App\Util\PayConfig;
use App\Util\Str;
use Illuminate\Database\Capsule\Manager as DB;
use Kernel\Annotation\Inject;
use Kernel\Exception\JSONException;
use Kernel\Exception\RuntimeException;
class Recharge implements \App\Service\Recharge
{
#[Inject]
private Order $order;
/**
* @param User $user
* @return array
* @throws JSONException
* @throws RuntimeException
*/
public function trade(User $user): array
{
$payId = (int)$_POST['pay_id'];//支付方式id
$amount = (float)$_POST['amount'];//充值金额
$rechargeMin = (float)Config::get("recharge_min");
$rechargeMin = $rechargeMin == 0 ? 10 : $rechargeMin;
$rechargeMax = (float)Config::get("recharge_max");
if ($amount < $rechargeMin) {
throw new JSONException("单次最低充值{$rechargeMin}元");
}
if ($amount > $rechargeMax && $rechargeMax > 0 && $rechargeMax > $rechargeMin) {
throw new JSONException("单次最高充值{$rechargeMax}元");
}
$pay = Pay::query()->find($payId);
if (!$pay) {
throw new JSONException("请选择支付方式");
}
if ($pay->recharge != 1) {
throw new JSONException("当前支付方式已停用");
}
//回调地址
$callbackDomain = trim(Config::get("callback_domain"), "/");
$clientDomain = Client::getUrl();
if (!$callbackDomain) {
$callbackDomain = $clientDomain;
}
return Db::transaction(function () use ($user, $pay, $amount, $callbackDomain, $clientDomain) {
$order = new UserRecharge();
$order->trade_no = Str::generateTradeNo();
$order->user_id = $user->id;
$order->amount = $amount;
$order->pay_id = $pay->id;
$order->status = 0;
$order->create_time = Date::current();
$order->create_ip = Client::getAddress();
$class = "\\App\\Pay\\{$pay->handle}\\Impl\\Pay";
if (!class_exists($class)) {
throw new JSONException("该支付方式未实现接口,无法使用");
}
$autoload = BASE_PATH . '/app/Pay/' . $pay->handle . "/Vendor/autoload.php";
if (file_exists($autoload)) {
require($autoload);
}
//增加接口手续费:0.9.6-beta
$order->amount = $order->amount + ($pay->cost_type == 0 ? $pay->cost : $order->amount * $pay->cost);
$order->amount = (float)sprintf("%.2f", (int)(string)($order->amount * 100) / 100);
$payObject = new $class;
$payObject->amount = $order->amount;
$payObject->tradeNo = $order->trade_no;
$payObject->config = PayConfig::config($pay->handle);
$payObject->callbackUrl = $callbackDomain . '/user/api/rechargeNotification/callback.' . $pay->handle;
$payObject->returnUrl = $clientDomain . '/user/recharge/index';
$payObject->clientIp = $order->create_ip;
$payObject->code = $pay->code;
$payObject->handle = $pay->handle;
$trade = $payObject->trade();
if ($trade instanceof PayEntity) {
$order->pay_url = $trade->getUrl();
switch ($trade->getType()) {
case \App\Pay\Pay::TYPE_REDIRECT:
$url = $order->pay_url;
break;
case \App\Pay\Pay::TYPE_LOCAL_RENDER:
$url = '/user/recharge/order.' . $order->trade_no . ".1";
break;
case \App\Pay\Pay::TYPE_SUBMIT:
$url = '/user/recharge/order.' . $order->trade_no . ".2";
break;
}
$order->save();
$option = $trade->getOption();
if (!empty($option)) {
$order->option = json_encode($option);
}
} else {
throw new JSONException("支付方式未部署成功");
}
$order->save();
return ['url' => $url, 'amount' => $order->amount, 'tradeNo' => $order->trade_no];
});
}
/**
* @param string $handle
* @param array $map
* @return string
* @throws JSONException
*/
public function callback(string $handle, array $map): string
{
$callback = $this->order->callbackInitialize($handle, $map);
$json = json_encode($map, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
DB::transaction(function () use ($handle, $map, $callback, $json) {
//获取订单
$order = \App\Model\UserRecharge::query()->where("trade_no", $callback['trade_no'])->first();
if (!$order) {
PayConfig::log($handle, "CALLBACK-RECHARGE", "订单不存在,接受数据:" . $json);
throw new JSONException("order not found");
}
if ($order->status != 0) {
PayConfig::log($handle, "CALLBACK-RECHARGE", "重复通知,当前订单已支付");
throw new JSONException("order status error");
}
if ($order->amount != $callback['amount']) {
PayConfig::log($handle, "CALLBACK-RECHARGE", "订单金额不匹配,接受数据:" . $json);
throw new JSONException("amount error");
}
// --- 核心修复:支付通道防串单严格验证 ---
if (!$order->pay || $order->pay->handle !== $handle) {
PayConfig::log($handle, "CALLBACK-RECHARGE", "支付通道伪造拦截:充值原通道与当前回调通道不符,接受数据:" . $json);
throw new JSONException("pay_handle error");
}
// ---------------------------
//订单更新
$this->orderSuccess($order);
});
return $callback['success'];
}
/**
* @param \App\Model\UserRecharge $recharge
* @throws \Kernel\Exception\JSONException
*/
public function orderSuccess(UserRecharge $recharge): void
{
$recharge->status = 1;
$recharge->pay_time = Date::current();
$recharge->option = null;
//充值
$user = $recharge->user;
if ($user) {
$rechargeWelfareAmount = $this->calcAmount($recharge->amount);
Bill::create($user, $recharge->amount, Bill::TYPE_ADD, "充值", 0); //用户余额
if ($rechargeWelfareAmount > 0) {
Bill::create($user, $rechargeWelfareAmount, Bill::TYPE_ADD, "充值赠送", 0); //用户余额
}
}
$recharge->save();
}
/**
* @param float $amount
* @return float
* @throws \Kernel\Exception\JSONException
*/
public function calcAmount(float $amount): float
{
$price = 0;
$rechargeWelfare = (int)Config::get("recharge_welfare");
if ($rechargeWelfare == 1) {
$list = [];
$rechargeWelfareconfig = explode(PHP_EOL, trim(Config::get("recharge_welfare_config"), PHP_EOL));
foreach ($rechargeWelfareconfig as $item) {
$s = explode('-', $item);
if (count($s) == 2) {
$list[$s[0]] = $s[1];
}
}
krsort($list);
foreach ($list as $k => $v) {
if ($amount >= $k) {
$price = $v;
break;
}
}
}
return (float)$price;
}
}
文件 2:商品订单核心模块
文件路径:app/Service/Bind/Order.php
<?php
declare(strict_types=1);
namespace App\Service\Bind;
use App\Consts\Hook;
use App\Entity\PayEntity;
use App\Model\Bill;
use App\Model\Business;
use App\Model\BusinessLevel;
use App\Model\Card;
use App\Model\Commodity;
use App\Model\CommodityGroup;
use App\Model\Config;
use App\Model\Coupon;
use App\Model\OrderOption;
use App\Model\Pay;
use App\Model\User;
use App\Model\UserCommodity;
use App\Model\UserGroup;
use App\Service\Email;
use App\Service\Shared;
use App\Util\Client;
use App\Util\Date;
use App\Util\Ini;
use App\Util\PayConfig;
use App\Util\Str;
use Illuminate\Database\Capsule\Manager as DB;
use Kernel\Annotation\Inject;
use Kernel\Container\Di;
use Kernel\Exception\JSONException;
use Kernel\Exception\RuntimeException;
use Kernel\Util\Arr;
use Kernel\Util\Context;
use Kernel\Util\Decimal;
class Order implements \App\Service\Order
{
#[Inject]
private Shared $shared;
#[Inject]
private Email $email;
/**
* @param int $owner
* @param int $num
* @param Commodity $commodity
* @param UserGroup|null $group
* @param string|null $race
* @param bool $disableSubstation
* @return float
* @throws JSONException
*/
public function calcAmount(int $owner, int $num, Commodity $commodity, ?UserGroup $group, ?string $race = null, bool $disableSubstation = false): float
{
$premium = 0;
//检测分站价格
$bus = Business::get(Client::getDomain());
if ($bus && !$disableSubstation) {
if ($userCommodity = UserCommodity::getCustom($bus->user_id, $commodity->id)) {
$premium = (float)$userCommodity->premium;
}
}
//解析配置文件
$this->parseConfig($commodity, $group);
$price = $owner == 0 ? $commodity->price : $commodity->user_price;
//禁用任何折扣,直接计算
if ($commodity->level_disable == 1) {
return (int)(string)(($num * ($price + $premium)) * 100) / 100;
}
$userDefinedConfig = Commodity::parseGroupConfig((string)$commodity->level_price, $group);
if ($userDefinedConfig && $userDefinedConfig['amount'] > 0) {
if (!$commodity->race) {
//如果自定义价格成功,那么将覆盖其他价格
$price = $userDefinedConfig['amount'];
}
} elseif ($group) {
//如果没有对应的会员等级解析,那么就直接采用系统折扣
$price = $price - ($price * $group->discount);
}
//判定是race还是普通订单
if (is_array($commodity->race)) {
if (array_key_exists((string)$race, (array)$commodity->category_wholesale)) {
//判定当前race是否可以折扣
$list = $commodity->category_wholesale[$race];
krsort($list);
foreach ($list as $k => $v) {
if ($num >= $k) {
$price = $v;
break;
}
}
}
} else {
//普通订单,直接走批发
$list = (array)$commodity->wholesale;
krsort($list);
foreach ($list as $k => $v) {
if ($num >= $k) {
$price = $v;
break;
}
}
}
$price += $premium; //分站加价
return (int)(string)(($num * $price) * 100) / 100;
}
/**
* @param Commodity|int $commodity
* @param int $num
* @param string|null $race
* @param array|null $sku
* @param int|null $cardId
* @param string|null $coupon
* @param UserGroup|null $group
* @return string
* @throws JSONException
* @throws \ReflectionException
*/
public function valuation(Commodity|int $commodity, int $num = 1, ?string $race = null, ?array $sku = [], ?int $cardId = null, ?string $coupon = null, ?UserGroup $group = null): string
{
if (is_int($commodity)) {
$commodity = Commodity::query()->find($commodity);
}
if (!$commodity) {
throw new JSONException("商品不存在");
}
$commodity = clone $commodity;
//解析配置文件
$this->parseConfig($commodity, $group);
$price = (new Decimal($group ? $commodity->user_price : $commodity->price, 2));
//算出race价格
if (!empty($race) && !empty($commodity->config['category'])) {
$_race = $commodity->config['category'];
if (!isset($_race[$race])) {
throw new JSONException("此商品类型不存在[{$race}]");
}
$price = (new Decimal($_race[$race], 2));
if (is_array($commodity->config['category_wholesale'])) {
if (array_key_exists($race, $commodity->config['category_wholesale'])) {
$list = $commodity->config['category_wholesale'][$race];
krsort($list);
foreach ($list as $k => $v) {
if ($num >= $k) {
$price = (new Decimal($v, 2));
break;
}
}
}
}
} else {
if (is_array($commodity->config['wholesale'])) {
$list = $commodity->config['wholesale'];
krsort($list);
foreach ($list as $k => $v) {
if ($num >= $k) {
$price = (new Decimal($v, 2));
break;
}
}
}
}
//算出sku价格
if (!empty($sku) && !empty($commodity->config['sku'])) {
$_sku = $commodity->config['sku'];
foreach ($sku as $k => $v) {
if (!isset($_sku[$k])) {
throw new JSONException("此SKU不存在[{$k}]");
}
if (!isset($_sku[$k][$v])) {
throw new JSONException("此SKU不存在[{$v}]");
}
$_sku_price = $_sku[$k][$v] ?: 0;
if (is_numeric($_sku_price) && $_sku_price > 0) {
$price = $price->add($_sku_price); //sku加价
}
}
}
//card自选加价
if (!empty($cardId) && $commodity->draft_status == 1 && $num == 1) {
/**
* @var \App\Service\Shop $shop
*/
$shop = Di::inst()->make(\App\Service\Shop::class);
if ($commodity->shared) {
$draft = $this->shared->getDraft($commodity->shared, $commodity->shared_code, $cardId);
$draftPremium = $draft['draft_premium'] > 0 ? $this->shared->AdjustmentAmount($commodity->shared_premium_type, $commodity->shared_premium, $draft['draft_premium']) : 0;
} else {
$draft = $shop->getDraft($commodity, $cardId);
$draftPremium = $draft['draft_premium'];
}
if ($draftPremium > 0) {
$price = $price->add($draftPremium); //卡密独立加价
} else {
$price = $price->add($commodity->draft_premium);
}
}
//禁用任何折扣,直接计算
if ($commodity->level_disable == 1) {
return $price->mul($num)->getAmount();
}
//商品组优惠
if ($group && is_array($group->discount_config)) {
$discountConfig = $group->discount_config;
asort($discountConfig);
$commodityGroups = CommodityGroup::query()->whereIn("id", array_keys($discountConfig))->get();
foreach ($commodityGroups as $commodityGroup) {
if (is_array($commodityGroup->commodity_list) && in_array($commodity->id, $commodityGroup->commodity_list)) {
$price = $price->mul((new Decimal($discountConfig[$commodityGroup->id], 3))->div(100)->getAmount());
break;
}
}
}
//优惠券折扣计算
if (!empty($coupon) && $num == 1) {
$voucher = Coupon::query()->where("code", $coupon)->first();
if (!$voucher) {
throw new JSONException("该优惠券不存在");
}
if ($voucher->owner != $commodity->owner) {
throw new JSONException("该优惠券不存在");
}
if ($voucher->commodity_id != 0 && $voucher->commodity_id != $commodity->id) {
throw new JSONException("该优惠券不属于该商品");
}
//race
if ($voucher->race && $voucher->commodity_id != 0 && $race != $voucher->race) {
throw new JSONException("该优惠券不能抵扣当前商品");
}
//sku
if ($voucher->sku && is_array($voucher->sku) && $voucher->commodity_id != 0) {
if (!is_array($sku)) {
throw new JSONException("此优惠券不适用当前商品");
}
foreach ($voucher->sku as $key => $sk) {
if (!isset($sku[$key])) {
throw new JSONException("此优惠券不适用此SKU");
}
if ($sk != $sku[$key]) {
throw new JSONException("此优惠券不适用此SKU{$sku[$key]}");
}
}
}
//判断该优惠券是否有分类设定
if ($voucher->commodity_id == 0 && $voucher->category_id != 0 && $voucher->category_id != $commodity->category_id) {
throw new JSONException("该优惠券不能抵扣当前商品");
}
if ($voucher->status != 0) {
throw new JSONException("该优惠券已失效");
}
//检测过期时间
if ($voucher->expire_time != null && strtotime($voucher->expire_time) < time()) {
throw new JSONException("该优惠券已过期");
}
//检测面额
if ($voucher->money >= $price->getAmount()) {
return "0";
}
$deduction = $voucher->mode == 0 ? $voucher->money : $price->mul($voucher->money)->getAmount();
$price = $price->sub($deduction);
}
//返回单价
return $price->mul($num)->getAmount();
}
/**
* @param int $commodityId
* @param string|float|int $price
* @param UserGroup|null $group
* @return string
*/
public function getValuationPrice(int $commodityId, string|float|int $price, ?UserGroup $group = null): string
{
$price = new Decimal($price);
//商品组优惠
if ($group && is_array($group->discount_config)) {
$discountConfig = $group->discount_config;
asort($discountConfig);
$commodityGroups = CommodityGroup::query()->whereIn("id", array_keys($discountConfig))->get();
foreach ($commodityGroups as $commodityGroup) {
if (is_array($commodityGroup->commodity_list) && in_array($commodityId, $commodityGroup->commodity_list)) {
$price = $price->mul((new Decimal($discountConfig[$commodityGroup->id], 3))->div(100)->getAmount());
break;
}
}
}
return $price->getAmount();
}
/**
* 解析配置
* @param Commodity $commodity
* @param UserGroup|null $group
* @return void
* @throws JSONException
*/
public function parseConfig(Commodity &$commodity, ?UserGroup $group): void
{
$parseConfig = Ini::toArray((string)$commodity->config);
//用户组解析
$userDefinedConfig = Commodity::parseGroupConfig($commodity->level_price, $group);
if ($userDefinedConfig) {
if (key_exists("category", $userDefinedConfig['config'])) {
//$parseConfig['category'] = array_merge($parseConfig['category'] ?? [], $userDefinedConfig['config']['category']);
$parseConfig['category'] = Arr::override($userDefinedConfig['config']['category'] ?? null, $parseConfig['category'] ?? null);
}
if (key_exists("wholesale", $userDefinedConfig['config'])) {
//$parseConfig['wholesale'] = array_merge($parseConfig['wholesale'] ?? [], $userDefinedConfig['config']['wholesale']);
$parseConfig['wholesale'] = Arr::override($userDefinedConfig['config']['wholesale'] ?? null, $parseConfig['wholesale'] ?? null);
}
if (key_exists("category_wholesale", $userDefinedConfig['config'])) {
//$parseConfig['category_wholesale'] = array_merge($parseConfig['category_wholesale'] ?? [], $userDefinedConfig['config']['category_wholesale']);
$parseConfig['category_wholesale'] = Arr::override($userDefinedConfig['config']['category_wholesale'] ?? null, $parseConfig['category_wholesale'] ?? null);
}
if (key_exists("sku", $userDefinedConfig['config'])) {
//$parseConfig['sku'] = array_merge($parseConfig['sku'] ?? [], $userDefinedConfig['config']['sku']);
$parseConfig['sku'] = Arr::override($userDefinedConfig['config']['sku'] ?? null, $parseConfig['sku'] ?? null);
}
}
$commodity->config = $parseConfig;
$commodity->level_price = null;
}
/**
* @param Commodity $commodity
* @param UserGroup|null $group
* @return array|null
*/
public function userDefinedPrice(Commodity $commodity, ?UserGroup $group): ?array
{
if ($group) {
$levelPrice = (array)json_decode((string)$commodity->level_price, true);
return array_key_exists($group->id, $levelPrice) ? $levelPrice[$group->id] : null;
}
return null;
}
/**
* @param User|null $user
* @param UserGroup|null $userGroup
* @param array $map
* @return array
* @throws JSONException
* @throws RuntimeException
* @throws \ReflectionException
*/
public function trade(?User $user, ?UserGroup $userGroup, array $map): array
{
#CFG begin
$commodityId = (int)$map['item_id'];//商品ID
$contact = (string)$map['contact'];//联系方式
$num = (int)$map['num']; //购买数量
$cardId = (int)$map['card_id'];//预选的卡号ID
$payId = (int)$map['pay_id'];//支付方式id
$device = (int)$map['device'];//设备
$password = (string)$map['password'];//查单密码
$coupon = (string)$map['coupon'];//优惠券
$from = $_COOKIE['promotion_from'] ?? 0;//推广人ID
$owner = $user == null ? 0 : $user->id;
$race = (string)$map['race']; //2022/01/09 新增,商品种类功能
$requestNo = (string)$map['request_no'];
$sku = $map['sku'] ?: null;
#CFG end
if ($user && $user->pid > 0) {
$from = $user->pid;
}
if ($commodityId == 0) {
throw new JSONException("请选择商品");
}
if ($num <= 0) {
throw new JSONException("至少购买1个");
}
/**
* @var Commodity $commodity
*/
$commodity = Commodity::with(['shared'])->find($commodityId);
if (!$commodity) {
throw new JSONException("商品不存在");
}
if ($commodity->status != 1) {
throw new JSONException("当前商品已停售");
}
if ($commodity->only_user == 1 || $commodity->purchase_count > 0) {
if ($owner == 0) {
throw new JSONException("请先登录后再购买哦");
}
}
if ($commodity->minimum > 0 && $num < $commodity->minimum) {
throw new JSONException("本商品最少购买{$commodity->minimum}个");
}
if ($commodity->maximum > 0 && $num > $commodity->maximum) {
throw new JSONException("本商品单次最多购买{$commodity->maximum}个");
}
$widget = [];
//widget
if ($commodity->widget) {
$widgetList = (array)json_decode((string)$commodity->widget, true);
foreach ($widgetList as $item) {
if ($item['regex'] != "") {
if (!preg_match("/{$item['regex']}/", (string)$map[$item['name']])) {
throw new JSONException($item['error']);
}
}
$widget[$item['name']] = [
"value" => $map[$item['name']],
"cn" => $item['cn']
];
}
}
$widget = json_encode($widget, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
//预选卡密
($commodity->draft_status == 1 && $cardId != 0) && $num = 1;
$regx = ['/^1[3456789]\d{9}$/', '/.*(.{2}@.*)$/i', '/[1-9]{1}[0-9]{4,11}/'];
$msg = ['手机', '邮箱', 'QQ号'];
//未登录才检测,登录后无需检测
/**
* @var \App\Service\Shop $shopService
*/
$shopService = Di::inst()->make(\App\Service\Shop::class);
if (!$user) {
if (mb_strlen($contact) < 3) {
throw new JSONException("联系方式不能低于3个字符");
}
//联系方式正则判断
if ($commodity->contact_type != 0) {
if (!preg_match($regx[$commodity->contact_type - 1], $contact)) {
throw new JSONException("您输入的{$msg[$commodity->contact_type - 1]}格式不正确!");
}
}
if ($commodity->password_status == 1 && mb_strlen($password) < 6) {
throw new JSONException("您的设置的密码过于简单,不能低于6位哦");
}
}
if ($commodity->seckill_status == 1) {
if (time() < strtotime($commodity->seckill_start_time)) {
throw new JSONException("抢购还未开始");
}
if (time() > strtotime($commodity->seckill_end_time)) {
throw new JSONException("抢购已结束");
}
}
if ($commodity->shared) {
$stock = $this->shared->getItemStock($commodity->shared, $commodity->shared_code, $race ?: null, $sku ?: []);
} else {
$stock = $shopService->getItemStock($commodity, $race, $sku);
}
if (($stock == 0 || $num > $stock)) {
throw new JSONException("库存不足");
}
if ($commodity->purchase_count > 0 && $owner > 0) {
$orderCount = \App\Model\Order::query()->where("owner", $owner)->where("commodity_id", $commodity->id)->count();
if ($orderCount >= $commodity->purchase_count) {
throw new JSONException("该商品每人只能购买{$commodity->purchase_count}件");
}
}
//计算订单价格
$amount = $this->valuation($commodity, $num, $race, $sku, $cardId, $coupon, $userGroup);
$rebate = 0;
$divideAmount = 0;
//分站相关
$business = Business::get();
if ($business) {
$_user = User::query()->find($business->user_id);
if ($commodity->owner === $business->user_id) {
//自营商品
$_level = BusinessLevel::query()->find($_user->business_level);
$rebate = (new Decimal($amount))->sub((new Decimal($amount))->mul($_level->cost)->getAmount())->getAmount();
} else {
//分站提高价格
$amount = $shopService->getSubstationPrice($commodity, $amount);
$_userGroup = UserGroup::get($_user->recharge);
//分站拿到的具体金额
$rebate = (new Decimal($amount))->sub($this->valuation($commodity, $num, $race, $sku, $cardId, $coupon, $_userGroup))->getAmount();
}
} else {
//主站卖分站的东西
if ($commodity->owner > 0) {
$_user = User::query()->find($commodity->owner);
$_level = BusinessLevel::query()->find($_user->business_level);
$rebate = (new Decimal($amount))->sub((new Decimal($amount))->mul($_level->cost)->getAmount())->getAmount();
}
}
//推广者
if ($from > 0 && $commodity->owner != $from && $owner != $from && (!$business || $business->user_id != $from)) {
//佣金计算
$x_user = User::query()->find($from);
$x_userGroup = UserGroup::get($x_user->recharge);
//推广者具体拿到的金额,计算方法:订单总金额 - 拿货价 = 具体金额
$x_amount = $this->valuation($commodity, $num, $race, $sku, $cardId, $coupon, $x_userGroup);
//先判定该订单是否分站或主站
if ($rebate > 0) {
$x_amount = $shopService->getSubstationPrice($commodity, $x_amount);
//分站
$x_divideAmount = (new Decimal($amount))->sub($x_amount)->getAmount();
if ($rebate > $x_divideAmount) {
//当分站利益大过推广者的时候,才会给推广者进行分成
$rebate = (new Decimal($rebate))->sub($x_divideAmount)->getAmount();
$divideAmount = $x_divideAmount;
}
} else {
$divideAmount = (new Decimal($amount))->sub($x_amount)->getAmount();
}
} else {
$from = 0;
}
$pay = Pay::query()->find($payId);
if (!$pay) {
throw new JSONException("该支付方式不存在");
}
if ($pay->commodity != 1) {
throw new JSONException("当前支付方式已停用,请换个支付方式再进行支付");
}
//回调地址
$callbackDomain = trim(Config::get("callback_domain"), "/");
$clientDomain = Client::getUrl();
if (!$callbackDomain) {
$callbackDomain = $clientDomain;
}
DB::connection()->getPdo()->exec("set session transaction isolation level serializable");
$result = Db::transaction(function () use ($commodity, $rebate, $divideAmount, $business, $sku, $requestNo, $user, $userGroup, $num, $contact, $device, $amount, $owner, $pay, $cardId, $password, $coupon, $from, $widget, $race, $callbackDomain, $clientDomain) {
//生成联系方式
if ($user) {
$contact = Str::generateRandStr(16);
}
if ($requestNo && \App\Model\Order::query()->where("request_no", $requestNo)->first()) {
throw new JSONException("The request ID already exists");
}
$date = Date::current();
$order = new \App\Model\Order();
$order->widget = $widget;
$order->owner = $owner;
$order->trade_no = Str::generateTradeNo();
$order->amount = (new Decimal($amount, 2))->getAmount();
$order->commodity_id = $commodity->id;
$order->pay_id = $pay->id;
$order->create_time = $date;
$order->create_ip = Client::getAddress();
$order->create_device = $device;
$order->status = 0;
$order->contact = trim((string)$contact);
$order->delivery_status = 0;
$order->card_num = $num;
$order->user_id = (int)$commodity->owner;
if ($requestNo) $order->request_no = $requestNo;
if (!empty($race)) $order->race = $race;
if (!empty($sku)) $order->sku = $sku;
if ($commodity->draft_status == 1 && $cardId != 0) $order->card_id = $cardId;
if ($password != "") $order->password = $password;
if ($business) $order->substation_user_id = $business->user_id;
if ($rebate > 0) $order->rebate = $rebate;
if ($from > 0) $order->from = $from;
if ($divideAmount > 0) $order->divide_amount = $divideAmount;
//优惠券
if (!empty($coupon)) {
$voucher = Coupon::query()->where("code", $coupon)->first();
if ($voucher->status != 0) {
throw new JSONException("该优惠券已失效");
}
$voucher->service_time = $date;
$voucher->use_life = $voucher->use_life + 1;
$voucher->life = $voucher->life - 1;
if ($voucher->life <= 0) {
$voucher->status = 1;
}
$voucher->trade_no = $order->trade_no;
$voucher->save();
$order->coupon_id = $voucher->id;
}
$secret = null;
hook(Hook::USER_API_ORDER_TRADE_PAY_BEGIN, $commodity, $order, $pay);
// --- 核心修复:彻底拦截前端一切免费白嫖异常订单 ---
if ($order->amount <= 0) {
PayConfig::log($pay->handle ?? 'system', "ORDER", "尝试创建0元订单被拦截: {$order->trade_no}");
throw new JSONException("非法操作:不支持免费或异常金额订单");
} else {
if ($pay->handle == "#system") {
//余额购买
if ($owner == 0) {
throw new JSONException("您未登录,请先登录后再使用余额支付");
}
$session = User::query()->find($owner);
if (!$session) {
throw new JSONException("用户不存在");
}
if ($session->status != 1) {
throw new JSONException("You have been banned");
}
$parent = $session->parent;
if ($parent && $order->user_id != $from) {
$order->from = $parent->id;
}
//扣钱
Bill::create($session, $order->amount, Bill::TYPE_SUB, "商品下单[{$order->trade_no}]");
//发卡
$order->save();//先将订单保存下来
$secret = $this->orderSuccess($order); //提交订单并且获取到卡密信息
} else {
//开始进行远程下单
$class = "\\App\\Pay\\{$pay->handle}\\Impl\\Pay";
if (!class_exists($class)) {
throw new JSONException("该支付方式未实现接口,无法使用");
}
$autoload = BASE_PATH . '/app/Pay/' . $pay->handle . "/Vendor/autoload.php";
if (file_exists($autoload)) {
require($autoload);
}
//增加接口手续费:0.9.6-beta
$order->pay_cost = $pay->cost_type == 0 ? $pay->cost : (new Decimal($order->amount, 2))->mul($pay->cost)->getAmount();
$order->amount = (new Decimal($order->amount, 2))->add($order->pay_cost)->getAmount();
$payObject = new $class;
$payObject->amount = $order->amount;
$payObject->tradeNo = $order->trade_no;
$payObject->config = PayConfig::config($pay->handle);
$payObject->callbackUrl = $callbackDomain . '/user/api/order/callback.' . $pay->handle;
//判断如果登录
if ($owner == 0) {
$payObject->returnUrl = $clientDomain . '/user/index/query?tradeNo=' . $order->trade_no;
} else {
$payObject->returnUrl = $clientDomain . '/user/personal/purchaseRecord?tradeNo=' . $order->trade_no;
}
$payObject->clientIp = Client::getAddress();
$payObject->code = $pay->code;
$payObject->handle = $pay->handle;
$trade = $payObject->trade();
if ($trade instanceof PayEntity) {
$order->pay_url = $trade->getUrl();
switch ($trade->getType()) {
case \App\Pay\Pay::TYPE_REDIRECT:
$url = $order->pay_url;
break;
case \App\Pay\Pay::TYPE_LOCAL_RENDER:
$url = '/user/pay/order.' . $order->trade_no . ".1";
break;
case \App\Pay\Pay::TYPE_SUBMIT:
$url = '/user/pay/order.' . $order->trade_no . ".2";
break;
}
$order->save();
$option = $trade->getOption();
if (!empty($option)) {
OrderOption::create($order->id, $trade->getOption());
}
} else {
throw new JSONException("支付方式未部署成功");
}
}
}
$order->save();
hook(Hook::USER_API_ORDER_TRADE_AFTER, $commodity, $order, $pay);
return ['url' => $url, 'amount' => $order->amount, 'tradeNo' => $order->trade_no, 'secret' => $secret];
});
$result["stock"] = $shopService->getItemStock($commodity, $race, $sku);
return $result;
}
/**
* 初始化回调
* @throws JSONException
*/
public function callbackInitialize(string $handle, array $map): array
{
$json = json_encode($map, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
$payInfo = PayConfig::info($handle);
$payConfig = PayConfig::config($handle);
$callback = $payInfo['callback'];
$autoload = BASE_PATH . '/app/Pay/' . $handle . "/Vendor/autoload.php";
if (file_exists($autoload)) {
require($autoload);
}
// --- 核心修复:强制启用签名验证和状态验证 ---
if (!isset($callback[\App\Consts\Pay::IS_SIGN]) || !$callback[\App\Consts\Pay::IS_SIGN]) {
PayConfig::log($handle, "CALLBACK", "签名验证未启用,存在安全风险");
throw new JSONException("signature verification is required");
}
if (!isset($callback[\App\Consts\Pay::IS_STATUS]) || !$callback[\App\Consts\Pay::IS_STATUS]) {
PayConfig::log($handle, "CALLBACK", "状态验证未启用,存在安全风险");
throw new JSONException("status verification is required");
}
// ----------------------------------------
//检测签名验证是否开启
if ($callback[\App\Consts\Pay::IS_SIGN]) {
$class = "\\App\\Pay\\{$handle}\\Impl\\Signature";
if (!class_exists($class)) {
PayConfig::log($handle, "CALLBACK", "插件未实现接口");
throw new JSONException("signature not implements interface");
}
$signature = new $class;
Context::set(\App\Consts\Pay::DAFA, $map);
if (!$signature->verification($map, $payConfig)) {
PayConfig::log($handle, "CALLBACK", "签名验证失败,接受数据:" . $json);
throw new JSONException("sign error");
}
$map = Context::get(\App\Consts\Pay::DAFA);
}
//验证状态
if ($callback[\App\Consts\Pay::IS_STATUS]) {
if ($map[$callback[\App\Consts\Pay::FIELD_STATUS_KEY]] != $callback[\App\Consts\Pay::FIELD_STATUS_VALUE]) {
PayConfig::log($handle, "CALLBACK", "状态验证失败,接受数据:" . $json);
throw new JSONException("status error");
}
}
//拿到订单号和金额
return [
"trade_no" => $map[$callback[\App\Consts\Pay::FIELD_ORDER_KEY]],
"amount" => $map[$callback[\App\Consts\Pay::FIELD_AMOUNT_KEY]],
"success" => $callback[\App\Consts\Pay::FIELD_RESPONSE]
];
}
/**
* @param \App\Model\Order $order
* @return string
* @throws JSONException
*/
public function orderSuccess(\App\Model\Order $order): string
{
/**
* @var Commodity $commodity
*/
$commodity = $order->commodity;
$order->pay_time = Date::current();
$order->status = 1;
$shared = $commodity->shared; //获取商品的共享平台
if ($shared) {
//拉取远程平台的卡密发货
$order->secret = $this->shared->trade($shared, $commodity, $order->contact, $order->card_num, (int)$order->card_id, $order->create_device, (string)$order->password, (string)$order->race, $order->sku ?: [], $order->widget, $order->trade_no);
$order->delivery_status = 1;
} else {
//自动发货
if ($commodity->delivery_way == 0) {
//拉取本地的卡密发货
$order->secret = $this->pullCardForLocal($order, $commodity);
$order->delivery_status = 1;
} else {
//手动发货
$order->secret = ($commodity->delivery_message != null && $commodity->delivery_message != "") ? $commodity->delivery_message : '正在发货中,请耐心等待,如有疑问,请联系客服。';
//减少手动库存
if ($commodity->stock >= $order->card_num) {
Commodity::query()->where("id", $commodity->id)->decrement('stock', $order->card_num);
} else {
Commodity::query()->where("id", $commodity->id)->update(['stock' => 0]);
}
}
}
//推广者
if ($order->from > 0 && $order->divide_amount > 0) {
Bill::create($order->from, $order->divide_amount, Bill::TYPE_ADD, "推广分成[$order->trade_no]", 1);
}
if ($order->rebate > 0) {
if ($order->user_id > 0) {
Bill::create($order->user_id, $order->rebate, Bill::TYPE_ADD, "自营商品出售[$order->trade_no]", 1);
} elseif ($order->substation_user_id > 0) {
Bill::create($order->substation_user_id, $order->rebate, Bill::TYPE_ADD, "分站商品出售[$order->trade_no]", 1);
}
}
$order->save();
if ($commodity->contact_type == 2 && $commodity->send_email == 1 && $order->owner == 0) {
try {
$this->email->send($order->contact, "【发货提醒】您购买的卡密发货啦", "您购买的卡密如下:" . $order->secret);
} catch (\Exception|\Error $e) {
}
}
hook(Hook::USER_API_ORDER_PAY_AFTER, $commodity, $order, $order->pay);
return (string)$order->secret;
}
/**
* 拉取本地卡密,需要事务环境执行
* @param \App\Model\Order $order
* @param Commodity $commodity
* @return string
*/
private function pullCardForLocal(\App\Model\Order $order, Commodity $commodity): string
{
$secret = "很抱歉,有人在你付款之前抢走了商品,请联系客服。";
/**
* @var Card $draft
*/
$draft = $order->card;
//指定预选卡密
if ($draft) {
if ($draft->status == 0) {
$secret = $draft->secret;
$draft->purchase_time = $order->pay_time;
$draft->order_id = $order->id;
$draft->status = 1;
$draft->save();
}
return $secret;
}
//取出和订单相同数量的卡密
$direction = match ($commodity->delivery_auto_mode) {
0 => "id asc",
1 => "rand()",
2 => "id desc"
};
$cards = Card::query()->where("commodity_id", $order->commodity_id)->orderByRaw($direction)->where("status", 0);
//判断订单是否存在类别
if ($order->race) {
$cards = $cards->where("race", $order->race);
}
//判断sku存在
if (!empty($order->sku)) {
foreach ($order->sku as $k => $v) {
$cards = $cards->where("sku->{$k}", $v);
}
}
$cards = $cards->limit($order->card_num)->get();
if (count($cards) == $order->card_num) {
$ids = [];
$cardc = '';
foreach ($cards as $card) {
$ids[] = $card->id;
$cardc .= $card->secret . PHP_EOL;
}
try {
//将全部卡密置已销售状态
$rows = Card::query()->whereIn("id", $ids)->update(['purchase_time' => $order->pay_time, 'order_id' => $order->id, 'status' => 1]);
if ($rows != 0) {
$secret = trim($cardc, PHP_EOL);
}
} catch (\Exception $e) {
}
}
return $secret;
}
/**
* @param string $handle
* @param array $map
* @return string
* @throws JSONException
* @throws RuntimeException
* @throws \ReflectionException
*/
public function callback(string $handle, array $map): string
{
$callback = $this->callbackInitialize($handle, $map);
$json = json_encode($map, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
DB::connection()->getPdo()->exec("set session transaction isolation level serializable");
DB::transaction(function () use ($handle, $map, $callback, $json) {
//获取订单
$order = \App\Model\Order::query()->where("trade_no", $callback['trade_no'])->first();
if (!$order) {
PayConfig::log($handle, "CALLBACK", "订单不存在,接受数据:" . $json);
throw new JSONException("order not found");
}
if ($order->status != 0) {
PayConfig::log($handle, "CALLBACK", "重复通知,当前订单已支付");
throw new JSONException("order status error");
}
if ($order->amount != $callback['amount']) {
PayConfig::log($handle, "CALLBACK", "订单金额不匹配,接受数据:" . $json);
throw new JSONException("amount error");
}
// --- 核心修复:支付通道防串单严格验证 ---
if (!$order->pay || $order->pay->handle !== $handle) {
PayConfig::log($handle, "CALLBACK", "支付通道伪造拦截:订单原通道与当前回调通道不符,接受数据:" . $json);
throw new JSONException("pay_handle error");
}
// ---------------------------
//第三方支付订单成功,累计充值
if ($order->owner != 0 && $owner = User::query()->find($order->owner)) {
//累计充值
$owner->recharge = $owner->recharge + $order->amount;
$owner->save();
}
$this->orderSuccess($order);
});
return $callback['success'];
}
/**
* @param User|null $user
* @param UserGroup|null $userGroup
* @param int $cardId
* @param int $num
* @param string $coupon
* @param int|Commodity|null $commodityId
* @param string|null $race
* @param array|null $sku
* @param bool $disableShared
* @return array
* @throws JSONException
* @throws \ReflectionException
*/
public function getTradeAmount(
?User $user,
?UserGroup $userGroup,
int $cardId,
int $num,
string $coupon,
int|Commodity|null $commodityId,
?string $race = null,
?array $sku = [],
bool $disableShared = false
): array
{
if ($num <= 0) {
throw new JSONException("购买数量不能低于1个");
}
if ($commodityId instanceof Commodity) {
$commodity = $commodityId;
} else {
$commodity = Commodity::query()->find($commodityId);
}
if (!$commodity) {
throw new JSONException("商品不存在");
}
if ($commodity->status != 1) {
throw new JSONException("当前商品已停售");
}
$data = [];
$config = Ini::toArray($commodity->config);
if (is_array($config['category']) && !in_array($race, $config['category'])) {
throw new JSONException("宝贝分类选择错误");
}
if (is_array($config['sku'])) {
if (empty($sku) || !is_array($sku)) {
throw new JSONException("请选择SKU");
}
foreach ($config['sku'] as $sk => $ks) {
if (!in_array($sk, $sku)) {
throw new JSONException("请选择{$sk}");
}
if (!in_array($sku[$sk], $ks)) {
throw new JSONException("{$sk}中不存在{$sku[$sk]},请选择正确的SKU");
}
}
}
/**
* @var \App\Service\Shop $shopService
*/
$shopService = Di::inst()->make(\App\Service\Shop::class);
$data['card_count'] = $shopService->getItemStock($commodityId, $race, $sku);
//检测限购数量
if ($commodity->minimum != 0 && $num < $commodity->minimum) {
throw new JSONException("本商品单次最少购买{$commodity->minimum}个");
}
if ($commodity->maximum != 0 && $num > $commodity->maximum) {
throw new JSONException("本商品单次最多购买{$commodity->maximum}个");
}
if ($cardId != 0 && $commodity->draft_status == 1) {
$num = 1;
}
$ow = 0;
if ($user) {
$ow = $user->id;
}
$amount = $this->calcAmount($ow, $num, $commodity, $userGroup, $race);
if ($cardId != 0 && $commodity->draft_status == 1) {
$amount = $amount + $commodity->draft_premium;
}
$couponMoney = 0;
//优惠券
$price = $amount / $num;
if ($coupon != "") {
$voucher = Coupon::query()->where("code", $coupon)->first();
if (!$voucher) {
throw new JSONException("该优惠券不存在");
}
if ($voucher->owner != $commodity->owner) {
throw new JSONException("该优惠券不存在");
}
if ($voucher->commodity_id != 0 && $voucher->commodity_id != $commodity->id) {
throw new JSONException("该优惠券不属于该商品");
}
//race
if ($voucher->race && $voucher->commodity_id != 0) {
if ($race != $voucher->race) {
throw new JSONException("该优惠券不能抵扣当前商品");
}
}
//sku
if ($voucher->sku && is_array($voucher->sku) && $voucher->commodity_id != 0) {
if (!is_array(empty($sku))) {
throw new JSONException("此优惠券不适用当前商品");
}
foreach ($voucher->sku as $key => $sk) {
if (isset($sku[$key])) {
throw new JSONException("此优惠券不适用此SKU");
}
if ($sk != $sku[$key]) {
throw new JSONException("此优惠券不适用此SKU{$sku[$key]}");
}
}
}
//判断该优惠券是否有分类设定
if ($voucher->commodity_id == 0 && $voucher->category_id != 0 && $voucher->category_id != $commodity->category_id) {
throw new JSONException("该优惠券不能抵扣当前商品");
}
if ($voucher->status != 0) {
throw new JSONException("该优惠券已失效");
}
//检测过期时间
if ($voucher->expire_time != null && strtotime($voucher->expire_time) < time()) {
throw new JSONException("该优惠券已过期");
}
//检测面额
if ($voucher->money >= $amount) {
throw new JSONException("该优惠券面额大于订单金额");
}
$deduction = $voucher->mode == 0 ? $voucher->money : (new Decimal($price, 2))->mul($voucher->money)->getAmount();
$amount = (new Decimal($amount))->sub($deduction)->getAmount();
$couponMoney = $deduction;
}
$data ['amount'] = $amount;
$data ['price'] = (new Decimal($price))->getAmount();
$data ['couponMoney'] = (new Decimal($couponMoney))->getAmount();
return $data;
}
/**
* @param Commodity $commodity
* @param string $race
* @param int $num
* @param string $contact
* @param string $password
* @param int|null $cardId
* @param int $userId
* @param string $widget
* @return array
* @throws JSONException
* @throws RuntimeException
* @throws \ReflectionException
*/
public function giftOrder(Commodity $commodity, string $race = "", int $num = 1, string $contact = "", string $password = "", ?int $cardId = null, int $userId = 0, string $widget = "[]"): array
{
return DB::transaction(function () use ($race, $widget, $contact, $password, $num, $cardId, $commodity, $userId) {
//创建订单
$date = Date::current();
$order = new \App\Model\Order();
$order->owner = $userId;
$order->trade_no = Str::generateTradeNo();
$order->amount = 0;
$order->commodity_id = $commodity->id;
$order->card_id = $cardId;
$order->card_num = $num;
$order->pay_id = 1;
$order->create_time = $date;
$order->create_ip = Client::getAddress();
$order->create_device = 0;
$order->status = 0;
$order->password = $password;
$order->contact = trim($contact);
$order->delivery_status = 0;
$order->widget = $widget;
$order->rent = 0;
$order->race = $race;
$order->user_id = $commodity->owner;
$order->save();
$secret = $this->orderSuccess($order);
return [
"secret" => $secret,
"tradeNo" => $order->trade_no
];
});
}
}
3.2.x 版本结束
下面修改版本为3.1.x
/app/Service/Impl/RechargeService.php
<?php
declare(strict_types=1);
namespace App\Service\Impl;
use App\Entity\PayEntity;
use App\Model\Bill;
use App\Model\Config;
use App\Model\OrderOption;
use App\Model\Pay;
use App\Model\User;
use App\Model\UserRecharge;
use App\Service\Order;
use App\Service\Recharge;
use App\Util\Client;
use App\Util\Date;
use App\Util\PayConfig;
use App\Util\Str;
use Illuminate\Database\Capsule\Manager as DB;
use Kernel\Annotation\Inject;
use Kernel\Exception\JSONException;
use Kernel\Exception\RuntimeException;
class RechargeService implements Recharge
{
#[Inject]
private Order $order;
/**
* @param User $user
* @return array
* @throws JSONException
* @throws RuntimeException
*/
public function trade(User $user): array
{
$payId = (int)$_POST['pay_id'];//支付方式id
$amount = (float)$_POST['amount'];//充值金额
$rechargeMin = (float)Config::get("recharge_min");
$rechargeMin = $rechargeMin == 0 ? 10 : $rechargeMin;
$rechargeMax = (float)Config::get("recharge_max");
if ($amount < $rechargeMin) {
throw new JSONException("单次最低充值{$rechargeMin}元");
}
if ($amount > $rechargeMax && $rechargeMax > 0 && $rechargeMax > $rechargeMin) {
throw new JSONException("单次最高充值{$rechargeMax}元");
}
$pay = Pay::query()->find($payId);
if (!$pay) {
throw new JSONException("请选择支付方式");
}
if ($pay->recharge != 1) {
throw new JSONException("当前支付方式已停用");
}
//回调地址
$callbackDomain = trim(Config::get("callback_domain"), "/");
$clientDomain = Client::getUrl();
if (!$callbackDomain) {
$callbackDomain = $clientDomain;
}
return Db::transaction(function () use ($user, $pay, $amount, $callbackDomain, $clientDomain) {
$order = new UserRecharge();
$order->trade_no = Str::generateTradeNo();
$order->user_id = $user->id;
$order->amount = $amount;
$order->pay_id = $pay->id;
$order->status = 0;
$order->create_time = Date::current();
$order->create_ip = Client::getAddress();
$class = "\\App\\Pay\\{$pay->handle}\\Impl\\Pay";
if (!class_exists($class)) {
throw new JSONException("该支付方式未实现接口,无法使用");
}
$autoload = BASE_PATH . '/app/Pay/' . $pay->handle . "/Vendor/autoload.php";
if (file_exists($autoload)) {
require($autoload);
}
//增加接口手续费:0.9.6-beta
$order->amount = $order->amount + ($pay->cost_type == 0 ? $pay->cost : $order->amount * $pay->cost);
$order->amount = (float)sprintf("%.2f", (int)(string)($order->amount * 100) / 100);
$payObject = new $class;
$payObject->amount = $order->amount;
$payObject->tradeNo = $order->trade_no;
$payObject->config = PayConfig::config($pay->handle);
$payObject->callbackUrl = $callbackDomain . '/user/api/rechargeNotification/callback.' . $pay->handle;
$payObject->returnUrl = $clientDomain . '/user/recharge/index';
$payObject->clientIp = $order->create_ip;
$payObject->code = $pay->code;
$payObject->handle = $pay->handle;
$trade = $payObject->trade();
if ($trade instanceof PayEntity) {
$order->pay_url = $trade->getUrl();
switch ($trade->getType()) {
case \App\Pay\Pay::TYPE_REDIRECT:
$url = $order->pay_url;
break;
case \App\Pay\Pay::TYPE_LOCAL_RENDER:
$url = '/user/recharge/order.' . $order->trade_no . ".1";
break;
case \App\Pay\Pay::TYPE_SUBMIT:
$url = '/user/recharge/order.' . $order->trade_no . ".2";
break;
}
$order->save();
$option = $trade->getOption();
if (!empty($option)) {
$order->option = json_encode($option);
}
} else {
throw new JSONException("支付方式未部署成功");
}
$order->save();
return ['url' => $url, 'amount' => $order->amount, 'tradeNo' => $order->trade_no];
});
}
/**
* @param string $handle
* @param array $map
* @return string
* @throws JSONException
*/
public function callback(string $handle, array $map): string
{
$callback = $this->order->callbackInitialize($handle, $map);
$json = json_encode($map, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
DB::transaction(function () use ($handle, $map, $callback, $json) {
//获取订单
$order = \App\Model\UserRecharge::query()->where("trade_no", $callback['trade_no'])->first();
if (!$order) {
PayConfig::log($handle, "CALLBACK-RECHARGE", "订单不存在,接受数据:" . $json);
throw new JSONException("order not found");
}
if ($order->status != 0) {
PayConfig::log($handle, "CALLBACK-RECHARGE", "重复通知,当前订单已支付");
throw new JSONException("order status error");
}
if ($order->amount != $callback['amount']) {
PayConfig::log($handle, "CALLBACK-RECHARGE", "订单金额不匹配,接受数据:" . $json);
throw new JSONException("amount error");
}
// --- 终极修复:支付通道防串单严格验证 ---
if (!$order->pay || $order->pay->handle !== $handle) {
PayConfig::log($handle, "CALLBACK-RECHARGE", "支付通道伪造拦截:充值原通道与当前回调通道不符,接受数据:" . $json);
throw new JSONException("pay_handle error");
}
// ---------------------------
//订单更新
$this->orderSuccess($order);
});
return $callback['success'];
}
/**
* @param \App\Model\UserRecharge $recharge
* @throws \Kernel\Exception\JSONException
*/
public function orderSuccess(UserRecharge $recharge): void
{
$recharge->status = 1;
$recharge->pay_time = Date::current();
$recharge->option = null;
//充值
$user = $recharge->user;
if ($user) {
$rechargeWelfareAmount = $this->calcAmount($recharge->amount);
Bill::create($user, $recharge->amount, Bill::TYPE_ADD, "充值", 0); //用户余额
if ($rechargeWelfareAmount > 0) {
Bill::create($user, $rechargeWelfareAmount, Bill::TYPE_ADD, "充值赠送", 0); //用户余额
}
}
$recharge->save();
}
/**
* @param float $amount
* @return float
* @throws \Kernel\Exception\JSONException
*/
public function calcAmount(float $amount): float
{
$price = 0;
$rechargeWelfare = (int)Config::get("recharge_welfare");
if ($rechargeWelfare == 1) {
$list = [];
$rechargeWelfareconfig = explode(PHP_EOL, trim(Config::get("recharge_welfare_config"), PHP_EOL));
foreach ($rechargeWelfareconfig as $item) {
$s = explode('-', $item);
if (count($s) == 2) {
$list[$s[0]] = $s[1];
}
}
krsort($list);
foreach ($list as $k => $v) {
if ($amount >= $k) {
$price = $v;
break;
}
}
}
return (float)$price;
}
}
/app/Service/Impl/OrderService.php
<?php
declare(strict_types=1);
namespace App\Service\Impl;
use App\Entity\PayEntity;
use App\Model\Bill;
use App\Model\Business;
use App\Model\BusinessLevel;
use App\Model\Card;
use App\Model\Commodity;
use App\Model\Config;
use App\Model\Coupon;
use App\Model\OrderOption;
use App\Model\Pay;
use App\Model\User;
use App\Model\UserCommodity;
use App\Model\UserGroup;
use App\Service\Email;
use App\Service\Order;
use App\Service\Shared;
use App\Util\Client;
use App\Util\Date;
use App\Util\Ini;
use App\Util\PayConfig;
use App\Util\Str;
use App\Util\Validation;
use Illuminate\Database\Capsule\Manager as DB;
use JetBrains\PhpStorm\ArrayShape;
use Kernel\Annotation\Inject;
use Kernel\Exception\JSONException;
use Kernel\Util\Context;
class OrderService implements Order
{
#[Inject]
private Shared $shared;
#[Inject]
private Email $email;
/**
* @param int $owner
* @param int $num
* @param Commodity $commodity
* @param UserGroup|null $group
* @param string|null $race
* @param bool $disableSubstation
* @return float
* @throws JSONException
*/
public function calcAmount(int $owner, int $num, Commodity $commodity, ?UserGroup $group, ?string $race = null, bool $disableSubstation = false): float
{
$premium = 0;
//检测分站价格
$bus = \App\Model\Business::get(Client::getDomain());
if ($bus && !$disableSubstation) {
if ($userCommodity = UserCommodity::getCustom($bus->user_id, $commodity->id)) {
$premium = (float)$userCommodity->premium;
}
}
//解析配置文件
$this->parseConfig($commodity, $group, $owner, 1, $race);
$price = $owner == 0 ? $commodity->price : $commodity->user_price;
//禁用任何折扣,直接计算
if ($commodity->level_disable == 1) {
return (int)(string)(($num * ($price + $premium)) * 100) / 100;
}
$userDefinedConfig = Commodity::parseGroupConfig((string)$commodity->level_price, $group);
if ($userDefinedConfig && $userDefinedConfig['amount'] > 0) {
if (!$commodity->race) {
//如果自定义价格成功,那么将覆盖其他价格
$price = $userDefinedConfig['amount'];
}
} elseif ($group) {
//如果没有对应的会员等级解析,那么就直接采用系统折扣
$price = $price - ($price * $group->discount);
}
//判定是race还是普通订单
if (is_array($commodity->race)) {
if (array_key_exists((string)$race, (array)$commodity->category_wholesale)) {
//判定当前race是否可以折扣
$list = $commodity->category_wholesale[$race];
krsort($list);
foreach ($list as $k => $v) {
if ($num >= $k) {
$price = $v;
break;
}
}
}
} else {
//普通订单,直接走批发
$list = (array)$commodity->wholesale;
krsort($list);
foreach ($list as $k => $v) {
if ($num >= $k) {
$price = $v;
break;
}
}
}
$price += $premium; //分站加价
return (int)(string)(($num * $price) * 100) / 100;
}
/**
* 解析配置
* @param Commodity $commodity
* @param UserGroup|null $group
* @param int $owner
* @param int $num
* @param string|null $race
* @return void
* @throws JSONException
*/
public function parseConfig(Commodity &$commodity, ?UserGroup $group, int $owner = 0, int $num = 1, ?string $race = null): void
{
$parseConfig = Ini::toArray((string)$commodity->config);
//用户组解析
$userDefinedConfig = Commodity::parseGroupConfig($commodity->level_price, $group);
if ($userDefinedConfig) {
if (key_exists("category", $userDefinedConfig['config'])) {
$parseConfig['category'] = $userDefinedConfig['config']['category'];
}
if (key_exists("wholesale", $userDefinedConfig['config'])) {
$parseConfig['wholesale'] = $userDefinedConfig['config']['wholesale'];
}
if (key_exists("category_wholesale", $userDefinedConfig['config'])) {
$parseConfig['category_wholesale'] = $userDefinedConfig['config']['category_wholesale'];
}
}
if (key_exists("category", $parseConfig)) {
$category = $parseConfig['category'];
//将类别数组存到对象中
$commodity->race = $category;
//判断是否传了指定的类别
if ($race) {
if (!key_exists($race, $category)) {
throw new JSONException("商品种类不存在");
}
$commodity->price = $category[$race];
$commodity->user_price = $commodity->price;
} else {
$commodity->price = current($category);
$commodity->user_price = $commodity->price;
}
}
//判定批发配置是否配置,如果配置
if (key_exists("wholesale", $parseConfig)) {
$wholesale = $parseConfig['wholesale'];
if (!empty($wholesale)) {
//将全局批发配置写入到对象中
$commodity->wholesale = $wholesale;
}
}
if (key_exists("category_wholesale", $parseConfig)) {
$categoryWholesale = $parseConfig['category_wholesale'];
if (!empty($categoryWholesale)) {
//将商品种类批发配置写入到对象中
$commodity->category_wholesale = $categoryWholesale;
}
}
//成本参数
if (key_exists("category_factory", $parseConfig)) {
$categoryFactory = $parseConfig['category_factory'];
if (!empty($categoryFactory)) {
$commodity->category_factory = $categoryFactory;
}
}
}
/**
* @param Commodity $commodity
* @param UserGroup|null $group
* @return array|null
*/
public function userDefinedPrice(Commodity $commodity, ?UserGroup $group): ?array
{
if ($group) {
$levelPrice = (array)json_decode((string)$commodity->level_price, true);
return array_key_exists($group->id, $levelPrice) ? $levelPrice[$group->id] : null;
}
return null;
}
/**
* @param User|null $user
* @param UserGroup|null $userGroup
* @param array $map
* @return array
* @throws JSONException
*/
public function trade(?User $user, ?UserGroup $userGroup, array $map): array
{
#CFG begin
$commodityId = (int)$map['commodity_id'];//商品ID
$contact = (string)$map['contact'];//联系方式
$num = (int)$map['num']; //购买数量
$cardId = (int)$map['card_id'];//预选的卡号ID
$payId = (int)$map['pay_id'];//支付方式id
$device = (int)$map['device'];//设备
$password = (string)$map['password'];//查单密码
$coupon = (string)$map['coupon'];//优惠卷
$from = (int)$map['from'];//推广人ID
$owner = $user == null ? 0 : $user->id;
$race = (string)$map['race']; //2022/01/09 新增,商品种类功能
$requestNo = (string)$map['request_no'];
#CFG end
if ($commodityId == 0) {
throw new JSONException("请选择商品在下单");
}
if ($num <= 0) {
throw new JSONException("至少购买1个");
}
$commodity = Commodity::query()->find($commodityId);
if (!$commodity) {
throw new JSONException("商品不存在");
}
if ($commodity->status != 1) {
throw new JSONException("当前商品已停售");
}
if ($commodity->only_user == 1 || $commodity->purchase_count > 0) {
if ($owner == 0) {
throw new JSONException("请先登录后再购买哦");
}
}
if ($commodity->minimum > 0 && $num < $commodity->minimum) {
throw new JSONException("本商品最少购买{$commodity->minimum}个");
}
if ($commodity->maximum > 0 && $num > $commodity->maximum) {
throw new JSONException("本商品单次最多购买{$commodity->maximum}个");
}
$widget = [];
//widget
if ($commodity->widget) {
$widgetList = (array)json_decode((string)$commodity->widget, true);
foreach ($widgetList as $item) {
if ($item['regex'] != "") {
if (!preg_match("/{$item['regex']}/", (string)$map[$item['name']])) {
throw new JSONException($item['error']);
}
}
$widget[$item['name']] = [
"value" => $map[$item['name']],
"cn" => $item['cn']
];
}
}
$widget = json_encode($widget, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
//预选卡密
if ($commodity->draft_status == 1 && $cardId != 0) {
$num = 1;
}
$regx = ['/^1[3456789]\d{9}$/', '/.*(.{2}@.*)$/i', '/[1-9]{1}[0-9]{4,11}/'];
$msg = ['手机', '邮箱', 'QQ号'];
//未登录才检测,登录后无需检测
if (!$user) {
if (mb_strlen($contact) < 3) {
throw new JSONException("联系方式不能低于3个字符");
}
//联系方式正则判断
if ($commodity->contact_type != 0) {
if (!preg_match($regx[$commodity->contact_type - 1], $contact)) {
throw new JSONException("您输入的{$msg[$commodity->contact_type - 1]}格式不正确!");
}
}
if ($commodity->password_status == 1 && mb_strlen($password) < 6) {
throw new JSONException("您的设置的密码过于简单,不能低于6位哦");
}
}
if ($commodity->seckill_status == 1) {
if (time() < strtotime($commodity->seckill_start_time)) {
throw new JSONException("抢购还未开始");
}
if (time() > strtotime($commodity->seckill_end_time)) {
throw new JSONException("抢购已结束");
}
}
//解析配置文件且注入对象
$commodityClone = clone $commodity;
$this->parseConfig($commodityClone, $userGroup, $owner, $num, $race);
if ($commodityClone->race && !key_exists($race, $commodityClone->race)) {
throw new JSONException("请选择商品种类");
}
//成本价
if ($commodityClone->race && $race != "") {
//获取种类成本
$factoryPrice = 0;
if ($commodityClone->category_factory && isset($commodityClone->category_factory[$race])) {
$factoryPrice = (float)$commodityClone->category_factory[$race];
}
} else {
$factoryPrice = $commodity->factory_price;
}
//-------------
$shared = $commodity->shared; //获取商品的共享平台
if ($shared) {
if (!$this->shared->inventoryState($shared, $commodity, $cardId, $num, $race)) {
throw new JSONException("库存不足");
}
} else {
//自动发货,库存检测
if ($commodity->delivery_way == 0) {
$count = Card::query()->where("commodity_id", $commodityId)->where("status", 0);
if ($race) {
$count = $count->where("race", $race);
}
$count = $count->count();
if ($count == 0 || $num > $count) {
throw new JSONException("库存不足");
}
}
}
if ($commodity->purchase_count > 0 && $owner > 0) {
$orderCount = \App\Model\Order::query()->where("owner", $owner)->where("commodity_id", $commodity->id)->count();
if ($orderCount >= $commodity->purchase_count) {
throw new JSONException("该商品每人只能购买{$commodity->purchase_count}件");
}
}
//计算订单基础价格
$amount = $this->calcAmount($owner, $num, $commodity, $userGroup, $race);
//判断预选费用
$pay = Pay::query()->find($payId);
if (!$pay) {
throw new JSONException("该支付方式不存在");
}
if ($pay->commodity != 1) {
throw new JSONException("当前支付方式已停用,请换个支付方式再进行支付");
}
//回调地址
$callbackDomain = trim(Config::get("callback_domain"), "/");
$clientDomain = Client::getUrl();
if (!$callbackDomain) {
$callbackDomain = $clientDomain;
}
DB::connection()->getPdo()->exec("set session transaction isolation level serializable");
return Db::transaction(function () use ($requestNo, $user, $userGroup, $num, $contact, $device, $amount, $owner, $commodity, $pay, $cardId, $password, $coupon, $from, $widget, $race, $shared, $callbackDomain, $clientDomain, $factoryPrice) {
//生成联系方式
if ($user) {
//检测订单频繁
//
$contact = "-";
}
if ($requestNo && \App\Model\Order::query()->where("request_no", $requestNo)->first()) {
throw new JSONException("The request ID already exists");
}
$date = Date::current();
$order = new \App\Model\Order();
$order->widget = $widget;
$order->owner = $owner;
$order->trade_no = Str::generateTradeNo();
$order->amount = $amount;
$order->commodity_id = $commodity->id;
$order->pay_id = $pay->id;
$order->create_time = $date;
$order->create_ip = Client::getAddress();
$order->create_device = $device;
$order->status = 0;
$order->contact = trim((string)$contact);
$order->delivery_status = 0;
$order->card_num = $num;
$order->user_id = (int)$commodity->owner;
$order->rent = $factoryPrice * $num; //成本价
if ($requestNo) {
$order->request_no = $requestNo;
}
if ($race) {
$order->race = $race;
}
if ($from != 0 && $order->user_id != $from && $owner != $from) {
$order->from = $from;
if (($userCommodity = UserCommodity::getCustom($from, $commodity->id)) && Business::get(Client::getDomain())) {
$order->premium = $userCommodity->premium;
}
}
if ($commodity->draft_status == 1 && $cardId != 0) {
if ($shared) {
//加钱
$order->amount = $order->amount + $commodity->draft_premium;
$order->card_id = $cardId;
} else {
$card = Card::query();
if ($race) {
$card = $card->where("race", $race);
}
$card = $card->find($cardId);
if (!$card || $card->status != 0) {
throw new JSONException("该卡已被他人抢走啦");
}
if ($card->commodity_id != $commodity->id) {
throw new JSONException("该卡密不属于这个商品,无法预选" . $commodity->id);
}
//加钱
$order->amount = $order->amount + $commodity->draft_premium;
$order->card_id = $cardId;
}
}
if ($password != "") {
$order->password = $password;
}
//优惠卷
if ($coupon != "") {
$voucher = Coupon::query()->where("code", $coupon)->first();
if (!$voucher) {
throw new JSONException("该优惠卷不存在");
}
if ($voucher->owner != $commodity->owner) {
throw new JSONException("该优惠卷不存在");
}
if ($race && $voucher->commodity_id != 0) {
if ($race != $voucher->race) {
throw new JSONException("该优惠卷不能抵扣当前商品");
}
}
if ($voucher->commodity_id != 0 && $voucher->commodity_id != $commodity->id) {
throw new JSONException("该优惠卷不属于该商品");
}
//判断该优惠卷是否有分类设定
if ($voucher->commodity_id == 0 && $voucher->category_id != 0 && $voucher->category_id != $commodity->category_id) {
throw new JSONException("该优惠卷不能抵扣当前商品");
}
if ($voucher->status != 0) {
throw new JSONException("该优惠卷已失效");
}
//检测过期时间
if ($voucher->expire_time != null && strtotime($voucher->expire_time) < time()) {
throw new JSONException("该优惠卷已过期");
}
//检测面额
if ($voucher->money >= $order->amount) {
throw new JSONException("该优惠卷面额大于订单金额");
}
//进行优惠
$order->amount = $voucher->mode == 0 ? $order->amount - $voucher->money : $order->amount - (($order->amount / $order->card_num) * $voucher->money);
$voucher->service_time = $date;
$voucher->use_life = $voucher->use_life + 1;
$voucher->life = $voucher->life - 1;
if ($voucher->life <= 0) {
$voucher->status = 1;
}
$voucher->trade_no = $order->trade_no;
$voucher->save();
$order->coupon_id = $voucher->id;
}
$secret = null;
$order->amount = (float)sprintf("%.2f", (int)(string)($order->amount * 100) / 100);
hook(\App\Consts\Hook::USER_API_ORDER_TRADE_PAY_BEGIN, $commodity, $order, $pay);
// --- 终极修复:彻底拦截前台用户的 0 元抓包白嫖订单 ---
if ($order->amount <= 0) {
PayConfig::log($pay->handle ?? 'system', "ORDER", "尝试创建0元订单被拦截: {$order->trade_no}");
throw new JSONException("非法操作:不支持免费或异常金额订单");
} else {
if ($pay->handle == "#system") {
//余额购买
if ($owner == 0) {
throw new JSONException("您未登录,请先登录后再使用余额支付");
}
$session = User::query()->find($owner);
if (!$session) {
throw new JSONException("用户不存在");
}
if ($session->status != 1) {
throw new JSONException("You have been banned");
}
$parent = $session->parent;
if ($parent && $order->user_id != $from) {
$order->from = $parent->id;
}
//扣钱
Bill::create($session, $order->amount, Bill::TYPE_SUB, "商品下单[{$order->trade_no}]");
//发卡
$order->save();//先将订单保存下来
$secret = $this->orderSuccess($order); //提交订单并且获取到卡密信息
} else {
//开始进行远程下单
$class = "\\App\\Pay\\{$pay->handle}\\Impl\\Pay";
if (!class_exists($class)) {
throw new JSONException("该支付方式未实现接口,无法使用");
}
$autoload = BASE_PATH . '/app/Pay/' . $pay->handle . "/Vendor/autoload.php";
if (file_exists($autoload)) {
require($autoload);
}
//增加接口手续费:0.9.6-beta
$order->pay_cost = $pay->cost_type == 0 ? $pay->cost : $order->amount * $pay->cost;
$order->amount = $order->amount + $order->pay_cost;
$order->amount = (float)sprintf("%.2f", (int)(string)($order->amount * 100) / 100);
$payObject = new $class;
$payObject->amount = $order->amount;
$payObject->tradeNo = $order->trade_no;
$payObject->config = PayConfig::config($pay->handle);
$payObject->callbackUrl = $callbackDomain . '/user/api/order/callback.' . $pay->handle;
//判断如果登录
if ($owner == 0) {
$payObject->returnUrl = $clientDomain . '/user/index/query?tradeNo=' . $order->trade_no;
} else {
$payObject->returnUrl = $clientDomain . '/user/personal/purchaseRecord?tradeNo=' . $order->trade_no;
}
$payObject->clientIp = Client::getAddress();
$payObject->code = $pay->code;
$payObject->handle = $pay->handle;
$trade = $payObject->trade();
if ($trade instanceof PayEntity) {
$order->pay_url = $trade->getUrl();
switch ($trade->getType()) {
case \App\Pay\Pay::TYPE_REDIRECT:
$url = $order->pay_url;
break;
case \App\Pay\Pay::TYPE_LOCAL_RENDER:
//$base64 = urlencode(base64_encode('type=1&handle=' . $pay->handle . '&code=' . $pay->code . '&tradeNo=' . $order->trade_no));
$url = '/user/pay/order.' . $order->trade_no . ".1";
break;
case \App\Pay\Pay::TYPE_SUBMIT:
$url = '/user/pay/order.' . $order->trade_no . ".2";
break;
}
$order->save();
$option = $trade->getOption();
if (!empty($option)) {
OrderOption::create($order->id, $trade->getOption());
}
} else {
throw new JSONException("支付方式未部署成功");
}
}
}
$order->save();
hook(\App\Consts\Hook::USER_API_ORDER_TRADE_AFTER, $commodity, $order, $pay);
return ['url' => $url, 'amount' => $order->amount, 'tradeNo' => $order->trade_no, 'secret' => $secret];
});
}
/**
* 初始化回调
* @throws JSONException
*/
#[ArrayShape(["trade_no" => "mixed", "amount" => "mixed", "success" => "mixed"])] public function callbackInitialize(string $handle, array $map): array
{
$json = json_encode($map, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
$payInfo = PayConfig::info($handle);
$payConfig = PayConfig::config($handle);
$callback = $payInfo['callback'];
$autoload = BASE_PATH . '/app/Pay/' . $handle . "/Vendor/autoload.php";
if (file_exists($autoload)) {
require($autoload);
}
// --- 终极修复:强制启用签名验证和状态验证 ---
if (!isset($callback[\App\Consts\Pay::IS_SIGN]) || !$callback[\App\Consts\Pay::IS_SIGN]) {
PayConfig::log($handle, "CALLBACK", "签名验证未启用,存在安全风险");
throw new JSONException("signature verification is required");
}
if (!isset($callback[\App\Consts\Pay::IS_STATUS]) || !$callback[\App\Consts\Pay::IS_STATUS]) {
PayConfig::log($handle, "CALLBACK", "状态验证未启用,存在安全风险");
throw new JSONException("status verification is required");
}
// ----------------------------------------
//检测签名验证是否开启
if ($callback[\App\Consts\Pay::IS_SIGN]) {
$class = "\\App\\Pay\\{$handle}\\Impl\\Signature";
if (!class_exists($class)) {
PayConfig::log($handle, "CALLBACK", "插件未实现接口");
throw new JSONException("signature not implements interface");
}
$signature = new $class;
Context::set(\App\Consts\Pay::DAFA, $map);
if (!$signature->verification($map, $payConfig)) {
PayConfig::log($handle, "CALLBACK", "签名验证失败,接受数据:" . $json);
throw new JSONException("sign error");
}
$map = Context::get(\App\Consts\Pay::DAFA);
}
//验证状态
if ($callback[\App\Consts\Pay::IS_STATUS]) {
if ($map[$callback[\App\Consts\Pay::FIELD_STATUS_KEY]] != $callback[\App\Consts\Pay::FIELD_STATUS_VALUE]) {
PayConfig::log($handle, "CALLBACK", "状态验证失败,接受数据:" . $json);
throw new JSONException("status error");
}
}
//拿到订单号和金额
return ["trade_no" => $map[$callback[\App\Consts\Pay::FIELD_ORDER_KEY]], "amount" => $map[$callback[\App\Consts\Pay::FIELD_AMOUNT_KEY]], "success" => $callback[\App\Consts\Pay::FIELD_RESPONSE]];
}
/**
* @throws JSONException
*/
public function orderSuccess(\App\Model\Order $order): string
{
$commodity = $order->commodity;
$order->pay_time = Date::current();
$order->status = 1;
$shared = $commodity->shared; //获取商品的共享平台
if ($shared) {
//拉取远程平台的卡密发货
$order->secret = $this->shared->trade($shared, $commodity, $order->contact, $order->card_num, (int)$order->card_id, $order->create_device, (string)$order->password, (string)$order->race, $order->widget, $order->trade_no);
$order->delivery_status = 1;
} else {
//自动发货
if ($commodity->delivery_way == 0) {
//拉取本地的卡密发货
$order->secret = $this->pullCardForLocal($order, $commodity);
$order->delivery_status = 1;
} else {
//手动发货
$order->secret = ($commodity->delivery_message != null && $commodity->delivery_message != "") ? $commodity->delivery_message : '正在发货中,请耐心等待,如有疑问,请联系客服。';
}
}
//佣金
$merchant = $order->user;
if ($merchant) {
//获取返佣比例
$businessLevel = $merchant->businessLevel;
if ($businessLevel) {
$order->cost = $order->amount * $businessLevel->cost; //手续费
$a1 = $order->amount - $order->cost - $order->pay_cost;
if ($a1 > 0) {
Bill::create($merchant, $a1, Bill::TYPE_ADD, "商品出售[$order->trade_no]", 1);
}
}
}
//真 · 返佣
$promote_1 = $order->promote;
if ($promote_1) {
//检测是否分站
$bus = BusinessLevel::query()->find((int)$promote_1->business_level);
if ($bus) {
//查询该商户的拿货价
$calcAmount = $this->calcAmount($promote_1->id, $order->card_num, $commodity, UserGroup::get($promote_1->recharge), $order->race, true);
//计算差价
if ($order->amount > $calcAmount) {
$rebate = $order->amount - $calcAmount; //差价
$order->premium = $rebate;
$a2 = $rebate - ($order->card_id ? $commodity->draft_premium : 0) - $order->pay_cost;
if ($rebate >= 0.01 && $a2 > 0) {
Bill::create($promote_1, $a2, Bill::TYPE_ADD, "分站返佣", 1);
$order->rebate = $a2;
}
}
//检测到商户等级,进行分站返佣算法 废弃
// $rebate = ($bus->accrual * ($order->amount - $order->premium)) + $order->premium; //20.00
} else {
//推广系统
$promoteRebateV1 = (float)Config::get("promote_rebate_v1"); //3级返佣 0.2
$rebate1 = $promoteRebateV1 * ($order->amount - $order->pay_cost); //20.00
if ($rebate1 >= 0.01) {
$promote_2 = $promote_1->parent; //获取上级
if (!$promote_2) {
//没有上级,直接进行1级返佣
Bill::create($promote_1, $rebate1, Bill::TYPE_ADD, "推广返佣", 1); //反20.00
$order->rebate = $rebate1;
} else {
$_rebate = 0;
//出现上级,开始将返佣的钱继续拆分
$promoteRebateV2 = (float)Config::get("promote_rebate_v2"); // 0.4
$rebate2 = $promoteRebateV2 * $rebate1; //拿走属于第二级百分比返佣 8.00
//先给上级返佣,这里拿掉上级的拿一份
Bill::create($promote_1, $rebate1 - $rebate2, Bill::TYPE_ADD, "推广返佣", 1); // 20-8=12.00
$_rebate += ($rebate1 - $rebate2);
if ($rebate2 > 0.01) { // 8.00
$promote_3 = $promote_2->parent; //获取第二级的上级
if (!$promote_3) {
//没有上级直接进行第二级返佣
Bill::create($promote_2, $rebate2, Bill::TYPE_ADD, "推广返佣", 1); // 8.00
$_rebate += $rebate2;
} else {
//出现上级,继续拆分剩下的佣金
$promoteRebateV3 = (float)Config::get("promote_rebate_v3"); // 0.4
$rebate3 = $promoteRebateV3 * $rebate2; // 8.00 * 0.4 = 3.2
//先给上级反
Bill::create($promote_2, $rebate2 - $rebate3, Bill::TYPE_ADD, "推广返佣", 1); // 8.00 - 3.2 = 4.8
$_rebate += ($rebate2 - $rebate3);
if ($rebate3 > 0.01) {
Bill::create($promote_3, $rebate3, Bill::TYPE_ADD, "推广返佣", 1); // 3.2
$_rebate += $rebate3;
//返佣结束 3.2 + 4.8 + 12 = 20.00
}
}
if ($_rebate > 0.01) {
$order->rebate = $_rebate;
}
}
}
}
}
}
$order->save();
if ($commodity->contact_type == 2 && $commodity->send_email == 1 && $order->owner == 0) {
try {
$this->email->send($order->contact, "【发货提醒】您购买的卡密发货啦", "您购买的卡密如下:" . $order->secret);
} catch (\Exception|\Error $e) {
}
}
hook(\App\Consts\Hook::USER_API_ORDER_PAY_AFTER, $commodity, $order, $order->pay);
return (string)$order->secret;
}
/**
* 拉取本地卡密,需要事务环境执行
* @param \App\Model\Order $order
* @param Commodity $commodity
* @return string
*/
private function pullCardForLocal(\App\Model\Order $order, Commodity $commodity): string
{
$secret = "很抱歉,有人在你付款之前抢走了商品,请联系客服。";
$draft = $order->card;
//指定预选卡密
if ($draft) {
if ($draft->status == 0) {
$secret = $draft->secret;
$draft->purchase_time = $order->pay_time;
$draft->order_id = $order->id;
$draft->status = 1;
$draft->save();
}
return $secret;
}
//取出和订单相同数量的卡密
$direction = match ($commodity->delivery_auto_mode) {
0 => "id asc",
1 => "rand()",
2 => "id desc"
};
$cards = Card::query()->where("commodity_id", $order->commodity_id)->orderByRaw($direction)->where("status", 0);
//判断订单是否存在类别
if ($order->race) {
$cards = $cards->where("race", $order->race);
}
$cards = $cards->limit($order->card_num)->get();
if (count($cards) == $order->card_num) {
$ids = [];
$cardc = '';
foreach ($cards as $card) {
$ids[] = $card->id;
$cardc .= $card->secret . PHP_EOL;
}
try {
//将全部卡密置已销售状态
$rows = Card::query()->whereIn("id", $ids)->update(['purchase_time' => $order->pay_time, 'order_id' => $order->id, 'status' => 1]);
if ($rows != 0) {
$secret = trim($cardc, PHP_EOL);
}
} catch (\Exception $e) {
}
}
return $secret;
}
/**
* @param string $handle
* @param array $map
* @return string
* @throws JSONException
*/
public function callback(string $handle, array $map): string
{
$callback = $this->callbackInitialize($handle, $map);
$json = json_encode($map, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
DB::connection()->getPdo()->exec("set session transaction isolation level serializable");
DB::transaction(function () use ($handle, $map, $callback, $json) {
//获取订单
$order = \App\Model\Order::query()->where("trade_no", $callback['trade_no'])->first();
if (!$order) {
PayConfig::log($handle, "CALLBACK", "订单不存在,接受数据:" . $json);
throw new JSONException("order not found");
}
if ($order->status != 0) {
PayConfig::log($handle, "CALLBACK", "重复通知,当前订单已支付");
throw new JSONException("order status error");
}
if ($order->amount != $callback['amount']) {
PayConfig::log($handle, "CALLBACK", "订单金额不匹配,接受数据:" . $json);
throw new JSONException("amount error");
}
// --- 终极修复:支付通道防串单严格验证 ---
if (!$order->pay || $order->pay->handle !== $handle) {
PayConfig::log($handle, "CALLBACK", "支付通道伪造拦截:订单原通道与当前回调通道不符,接受数据:" . $json);
throw new JSONException("pay_handle error");
}
// ---------------------------
//第三方支付订单成功,累计充值
if ($order->owner != 0 && $owner = User::query()->find($order->owner)) {
//累计充值
$owner->recharge = $owner->recharge + $order->amount;
$owner->save();
}
$this->orderSuccess($order);
});
return $callback['success'];
}
/**
* @param User|null $user
* @param UserGroup|null $userGroup
* @param int $cardId
* @param int $num
* @param string $coupon
* @param int|Commodity|null $commodityId
* @param string|null $race
* @param bool $disableShared
* @return array
* @throws JSONException
*/
#[ArrayShape(["amount" => "mixed", "price" => "float|int", "couponMoney" => "float|int"])] public function getTradeAmount(?User $user, ?UserGroup $userGroup, int $cardId, int $num, string $coupon, int|Commodity|null $commodityId, ?string $race = null, bool $disableShared = false): array
{
if ($num <= 0) {
throw new JSONException("购买数量不能低于1个");
}
if ($commodityId instanceof Commodity) {
$commodity = $commodityId;
} else {
$commodity = Commodity::query()->find($commodityId);
}
if (!$commodity) {
throw new JSONException("商品不存在");
}
if ($commodity->status != 1) {
throw new JSONException("当前商品已停售");
}
$data = [];
if ($commodity->delivery_way == 0 && ($commodity->shared_id == null || $commodity->shared_id == 0)) {
if ($race) {
$data['card_count'] = Card::query()->where("commodity_id", $commodity->id)->where("status", 0)->where("race", $race)->count();
}
} elseif ($commodity->shared_id != 0) {
//查远程平台的库存
$shared = \App\Model\Shared::query()->find($commodity->shared_id);
if ($shared && !$disableShared) {
$inventory = $this->shared->inventory($shared, $commodity, (string)$race);
$data['card_count'] = $inventory['count'];
}
}
//检测限购数量
if ($commodity->minimum != 0 && $num < $commodity->minimum) {
throw new JSONException("本商品单次最少购买{$commodity->minimum}个");
}
if ($commodity->maximum != 0 && $num > $commodity->maximum) {
throw new JSONException("本商品单次最多购买{$commodity->maximum}个");
}
if ($cardId != 0 && $commodity->draft_status == 1) {
$num = 1;
}
$ow = 0;
if ($user) {
$ow = $user->id;
}
$amount = $this->calcAmount($ow, $num, $commodity, $userGroup, $race);
if ($cardId != 0 && $commodity->draft_status == 1) {
$amount = $amount + $commodity->draft_premium;
}
$couponMoney = 0;
//优惠卷
$price = $amount / $num;
if ($coupon != "") {
$voucher = Coupon::query()->where("code", $coupon)->first();
if (!$voucher) {
throw new JSONException("该优惠卷不存在");
}
if ($voucher->owner != $commodity->owner) {
throw new JSONException("该优惠卷不存在");
}
if ($race && $voucher->commodity_id != 0) {
if ($race != $voucher->race) {
throw new JSONException("该优惠卷不能抵扣当前商品");
}
}
if ($voucher->commodity_id != 0 && $voucher->commodity_id != $commodity->id) {
throw new JSONException("该优惠卷不属于该商品");
}
//判断该优惠卷是否有分类设定
if ($voucher->commodity_id == 0 && $voucher->category_id != 0 && $voucher->category_id != $commodity->category_id) {
throw new JSONException("该优惠卷不能抵扣当前商品");
}
if ($voucher->status != 0) {
throw new JSONException("该优惠卷已失效");
}
//检测过期时间
if ($voucher->expire_time != null && strtotime($voucher->expire_time) < time()) {
throw new JSONException("该优惠卷已过期");
}
//检测面额
if ($voucher->money >= $amount) {
throw new JSONException("该优惠卷面额大于订单金额");
}
$deduction = $voucher->mode == 0 ? $voucher->money : $price * $voucher->money;
$amount = $amount - $deduction;
$couponMoney = $deduction;
}
$data ['amount'] = sprintf("%.2f", (int)(string)($amount * 100) / 100);
$data ['price'] = sprintf("%.2f", (int)(string)($price * 100) / 100);
$data ['couponMoney'] = sprintf("%.2f", (int)(string)($couponMoney * 100) / 100);
return $data;
}
/**
* @param Commodity $commodity
* @param string $race
* @param int $num
* @param string $contact
* @param string $password
* @param int|null $cardId
* @param int $userId
* @param string $widget
* @return array
* @throws JSONException
*/
public function giftOrder(Commodity $commodity, string $race = "", int $num = 1, string $contact = "", string $password = "", ?int $cardId = null, int $userId = 0, string $widget = "[]"): array
{
return DB::transaction(function () use ($race, $widget, $contact, $password, $num, $cardId, $commodity, $userId) {
//创建订单
$date = Date::current();
$order = new \App\Model\Order();
$order->owner = $userId;
$order->trade_no = Str::generateTradeNo();
// 这里系统后台手动赠送的 0 元订单不受刚才 trade() 前台方法拦截影响
$order->amount = 0;
$order->commodity_id = $commodity->id;
$order->card_id = $cardId;
$order->card_num = $num;
$order->pay_id = 1;
$order->create_time = $date;
$order->create_ip = Client::getAddress();
$order->create_device = 0;
$order->status = 0;
$order->password = $password;
$order->contact = trim($contact);
$order->delivery_status = 0;
$order->widget = $widget;
$order->rent = 0;
$order->race = $race;
$order->user_id = $commodity->owner;
$order->save();
$secret = $this->orderSuccess($order);
return [
"secret" => $secret,
"tradeNo" => $order->trade_no
];
});
}
}
参考上面图片教程进行替换修改代码
异次元V3.0支付地址总被偷偷篡改为别人的?
网站跑得好好的,突然发现这两天一分钱没进账。登进后台一看,心脏骤停——易支付的商户ID的收款地址,全被黑客偷偷换成他自己的了!
你赶紧把密码改了、把木马清了,结果没过两天,地址又被篡改了。简直防不胜防。
特别是很多用 异次元V3.0 系统的老哥,这套系统因为是纯代码运行,没有用到数据库,核心配置全在文件里。黑客只要拿到了任意一个能修改文件的权限(甚至是搞到了你防掉线的 Cookie),就能随时进出你的系统改地址。
今天不扯虚的,给大家分享一个我实战摸索出来的硬核防篡改大招。
这招高端点叫“文件完整性监控与自愈”,通俗点讲就是“关门放看门狗”。既然黑客喜欢偷偷改我们那几个核心的支付配置文件,那我们就把正确的配置藏在一个他绝对摸不到的“安全屋”里。然后让宝塔面板每隔1分钟去巡逻一次,只要发现线上的文件被动了一个标点符号,瞬间用安全屋里的备份给他强行覆盖回去!
黑客前脚刚偷摸改完,后脚不到 60 秒系统就自动复原了,让他连一单都偷不走。
下面是针对异次元V3.0的极简保姆级教程:
第一步:建立“安全屋”(隔离备份)
为了防黑客,我们千万别把备份文件放在网站的 wwwroot 目录下,要把它建在 Web 无法直接访问的地方。
打开你的服务器终端(SSH),建一个隔离目录:
输入并回车
mkdir -p /www/safe_config
![图片[4]-异次元发卡重大支付漏洞修复-云创云工坊](https://www.yuncyun.top/wp-content/uploads/2026/03/1774236383-20260323032623453908-919x1024.webp)
然后,去异次元后台把你的 易支付接口全都配置成正确的。接着,把这两个核心文件复制到“安全屋”里当做母版(注意:下面路径里的 你的域名记得换成你自己的实际网站目录名):
cp /www/wwwroot/你的域名/app/Pay/Epay/Config/Config.php /www/safe_config/Epay_Config.php
![图片[5]-异次元发卡重大支付漏洞修复-云创云工坊](https://www.yuncyun.top/wp-content/uploads/2026/03/1774236520-20260323032840830029-885x1024.webp)
第二步:编写“看门狗”巡逻脚本
在安全屋里写一个 PHP 脚本来做实时比对。新建并编辑 /www/safe_config/watchdog.php 文件
宝塔面板进入/www/safe_config/目录,点击新建-选择新建空白文本输入文件名watchdog.php点击保存
![图片[6]-异次元发卡重大支付漏洞修复-云创云工坊](https://www.yuncyun.top/wp-content/uploads/2026/03/1774236717-20260323033157784401-1024x582.webp)
贴入以下代码(同样记得把里面的域名路径改成你自己的):
<?php
// 看门狗:实时校验并恢复被篡改的支付配置
// 配置映射: '安全母版文件' => '线上运行的实际文件'
$filesToWatch = [
'/www/safe_config/Epay_Config.php' => '/www/wwwroot/你的域名.com/app/Pay/Epay/Config/Config.php',
'/www/safe_config/Usdt_Config.php' => '/www/wwwroot/你的域名.com/app/Plugin/Usdt/Config/Config.php'
];
$logFile = '/www/safe_config/watchdog_alert.log';
foreach ($filesToWatch as $safeFile => $liveFile) {
if (!file_exists($safeFile) || !file_exists($liveFile)) {
continue;
}
// 核心逻辑:用 MD5 校验文件内容。只要线上文件被改了哪怕一个空格,MD5值立马就不一样了
if (md5_file($safeFile) !== md5_file($liveFile)) {
// 发现篡改,瞬间用母版强行覆盖恢复!
copy($safeFile, $liveFile);
// 留个底,记录案发时间,方便事后去 Nginx 日志里抓黑客 IP
$time = date('Y-m-d H:i:s');
$logMsg = "[{$time}] 🚨 警告:支付配置被篡改,已成功实施自动恢复 -> {$liveFile}\n";
file_put_contents($logFile, $logMsg, FILE_APPEND);
}
}
echo "巡逻完毕,支付接口安全。\n";
?>
![图片[7]-异次元发卡重大支付漏洞修复-云创云工坊](https://www.yuncyun.top/wp-content/uploads/2026/03/1774236761-20260323033241417823-1024x896.webp)
第三步:挂上 1 分钟定时任务
脚本写好了,接下来就交给宝塔面板去干苦力。
登录宝塔后台,点击左侧的 “计划任务”。
任务类型选 “Shell 脚本”。
任务名称随便填,比如:异次元支付防篡改。
执行周期选 “N分钟” -> “1 分钟”。
脚本内容框里填入保存:
/usr/bin/php /www/safe_config/watchdog.php
![图片[8]-异次元发卡重大支付漏洞修复-云创云工坊](https://www.yuncyun.top/wp-content/uploads/2026/03/1774236862-20260323033422098468-1024x655.webp)
![图片[9]-异次元发卡重大支付漏洞修复-云创云工坊](https://www.yuncyun.top/wp-content/uploads/2026/03/1774236914-20260323033514572029-1024x835.webp)
进阶大招:如果你的服务器够牛逼,直接按“秒级”防御!
(宝塔默认最低是1分钟,如果你想实现“每秒”巡逻,请在脚本内容框里填入以下代码就不要填上面的代码了):
#!/bin/bash
step=1 # 间隔的秒数,1表示每秒执行一次
for (( i = 0; i < 60; i=(i+step) )); do
/usr/bin/php /www/safe_config/watchdog.php
sleep $step
done
![图片[10]-异次元发卡重大支付漏洞修复-云创云工坊](https://www.yuncyun.top/wp-content/uploads/2026/03/1774237084-20260323033804036896-1024x882.webp)
这样设置后,宝塔虽然是每 1 分钟启动一次任务,但任务启动后会在 60 秒内不断循环,每隔 1 秒去比对一次文件。黑客就算有单身300年的手速,也别想从你这里抢走一单。
点击添加,完事!
总结
这招对付那种“阴魂不散”的自动改钱包地址脚本有奇效。与其天天提心吊胆去查到底哪里有 0day 漏洞,不如直接用这种强行维持状态的流氓办法,直接断了黑产的财路。
⚠️ 唯一要注意的是: 以后你自己如果真要更换 USDT 钱包地址或者易支付商户,记得要去 /www/safe_config/ 里面改母版文件,不然你自己改的也会被系统一巴掌拍回去
本教程内容来自异次元官方公告,本文只提供易支付接口修复教程以供本站的易支付接口用户使用



暂无评论内容