初始化项目:添加后端代码、ThinkPHP框架、前端资源

This commit is contained in:
amb
2026-09-03 12:42:39 +08:00
commit 482bece22e
3726 changed files with 416708 additions and 0 deletions
@@ -0,0 +1,405 @@
<?php
namespace app\callback\library\app\wxapp;
use think\Log;
use app\common\model\order\Order;
use app\common\model\order\Log as OrderLog;
use app\common\constant\order\Status as OrderStatus;
use app\common\library\pay\VirtualPayService;
/**
* 微信小程序消息推送事件处理器
* 处理虚拟支付相关的4种推送事件:
* 1. xpay_goods_deliver_notify - 道具发货推送(现金购买道具支付成功)
* 2. xpay_coin_pay_notify - 代币支付推送(代币扣减成功)
* 3. xpay_refund_notify - 退款推送(退款完成后)
* 4. xpay_complaint_notify - 用户投诉推送(用户发起投诉)
*/
class Notify
{
/**
* 事件分发处理
* @param string $event 事件类型
* @param array $message 消息内容
* @return array|null 返回自定义响应(iOS退款问询需要),其他事件返回null
* @throws \Exception
*/
public function handle($event, $message)
{
switch ($event) {
case 'xpay_goods_deliver_notify':
$this->handleGoodsDeliver($message);
break;
case 'xpay_coin_pay_notify':
$this->handleCoinPay($message);
break;
case 'xpay_refund_notify':
$this->handleRefund($message);
break;
case 'xpay_complaint_notify':
$this->handleComplaint($message);
break;
case 'xpay_subscribe_ios_refund_query_notify':
return $this->handleIosRefundQuery($message);
default:
Log::warning("[WxappNotify] 未知事件类型: " . $event);
break;
}
return null;
}
/**
* 道具发货推送处理
* 用户通过现金购买道具且支付成功后触发
* 需要通知微信已发货完成(调用notifyProvideGoods
* @param array $message 消息内容
* @return void
*/
private function handleGoodsDeliver($message)
{
$outTradeNo = $message['OutTradeNo'] ?? '';
$openid = $message['OpenId'] ?? '';
$env = $message['Env'] ?? 0;
Log::info("[WxappNotify] 道具发货推送: order_no={$outTradeNo}, openid={$openid}, env={$env}");
if (empty($outTradeNo)) {
Log::error("[WxappNotify] 道具发货推送: 订单号为空");
throw new \Exception('订单号为空');
}
$order = Order::orderNoGetOrder($outTradeNo);
if (!$order) {
Log::error("[WxappNotify] 道具发货推送: 订单不存在 order_no={$outTradeNo}");
throw new \Exception('订单不存在');
}
if ($order['status'] !== OrderStatus::STATUS_UNPAID && $order['status'] !== OrderStatus::STATUS_PAID) {
Log::info("[WxappNotify] 道具发货推送: 订单状态无需处理 status={$order['status']}");
return;
}
if ($order['status'] === OrderStatus::STATUS_UNPAID) {
$notify = [
'order_no' => $outTradeNo,
'transaction_id' => $message['WeChatPayInfo']['TransactionId'] ?? '',
'notify_time' => date('Y-m-d H:i:s'),
'buyer_email' => $openid,
'pay_fee' => isset($message['GoodsInfo']['ActualPrice']) ? $message['GoodsInfo']['ActualPrice'] / 100 : $order['real_price'],
'pay_type' => 'virtual_pay',
];
$notify['payment_json'] = json_encode($message, JSON_UNESCAPED_UNICODE);
Order::paySuccess($order, $notify);
OrderLog::set($outTradeNo, "虚拟支付道具发货通知-订单已完成支付");
}
$this->notifyProvideGoods($outTradeNo, $env);
}
/**
* 代币支付推送处理
* 用户代币扣减成功后触发(代币支付场景,无需再调用notifyProvideGoods
* 参考Pay.php的successHandle方法实现统一的订单支付成功逻辑
* @param array $message 消息内容
* @return void
*/
private function handleCoinPay($message)
{
$outTradeNo = $message['OutTradeNo'] ?? '';
$openid = $message['OpenId'] ?? '';
$env = $message['Env'] ?? 0;
Log::info("[WxappNotify] 代币支付推送: order_no={$outTradeNo}, openid={$openid}, env={$env}");
if (empty($outTradeNo)) {
Log::error("[WxappNotify] 代币支付推送: 订单号为空");
throw new \Exception('订单号为空');
}
$order = Order::orderNoGetOrder($outTradeNo);
if (!$order) {
Log::error("[WxappNotify] 代币支付推送: 订单不存在 order_no={$outTradeNo}");
throw new \Exception('订单不存在');
}
if ($order['status'] !== OrderStatus::STATUS_UNPAID) {
Log::info("[WxappNotify] 代币支付推送: 订单已处理 status={$order['status']}");
return;
}
$coinInfo = $message['CoinInfo'] ?? [];
$wechatPayInfo = $message['WeChatPayInfo'] ?? [];
$payFee = isset($coinInfo['ActualPrice']) ? floatval($coinInfo['ActualPrice']) / 100 : floatval($order['real_price']);
$transactionId = $wechatPayInfo['TransactionId'] ?? '';
$coinQuantity = isset($coinInfo['Quantity']) ? intval($coinInfo['Quantity']) : 0;
$origPrice = isset($coinInfo['OrigPrice']) ? floatval($coinInfo['OrigPrice']) / 100 : 0;
$attach = $coinInfo['Attach'] ?? '';
$notify = [
'order_no' => $outTradeNo,
'transaction_id' => $transactionId,
'notify_time' => date('Y-m-d H:i:s'),
'buyer_email' => $openid,
'pay_fee' => $payFee,
'pay_type' => 'virtual_pay',
'payment_json' => json_encode($message, JSON_UNESCAPED_UNICODE),
'real_pay_price' => $payFee,
'pay_time' => time(),
'coin_quantity' => $coinQuantity,
'orig_price' => $origPrice,
'attach' => $attach,
'env' => intval($env),
];
try {
Order::paySuccess($order, $notify);
OrderLog::set($outTradeNo, "虚拟支付代币扣减通知-订单已完成支付, 代币数量:{$coinQuantity}, 实付金额:{$payFee}");
Log::info("[WxappNotify] 代币支付处理成功: order_no={$outTradeNo}, pay_fee={$payFee}, coin_quantity={$coinQuantity}");
} catch (\Exception $e) {
Log::error("[WxappNotify] 代币支付处理异常: " . $e->getMessage() . " order_no={$outTradeNo}");
OrderLog::set($outTradeNo, "虚拟支付代币支付处理异常:{$e->getMessage()}");
throw $e;
}
}
/**
* 退款推送处理
* 退款完成后触发,根据退款结果更新订单状态和退款记录
* @param array $message 消息内容
* @return void
*/
private function handleRefund($message)
{
$mchOrderId = $message['MchOrderId'] ?? '';
$mchRefundId = $message['MchRefundId'] ?? '';
$wxRefundId = $message['WxRefundId'] ?? '';
$retCode = $message['RetCode'] ?? -1;
$retMsg = $message['RetMsg'] ?? '';
$refundFee = $message['RefundFee'] ?? 0;
$openid = $message['OpenId'] ?? '';
Log::info("[WxappNotify] 退款推送: order_no={$mchOrderId}, mch_refund_id={$mchRefundId}, wx_refund_id={$wxRefundId}, retCode={$retCode}, refundFee={$refundFee}");
if (empty($mchOrderId)) {
Log::error("[WxappNotify] 退款推送: 订单号为空");
throw new \Exception('订单号为空');
}
$order = Order::orderNoGetOrder($mchOrderId);
if (!$order) {
Log::error("[WxappNotify] 退款推送: 订单不存在 order_no={$mchOrderId}");
throw new \Exception('订单不存在');
}
$orderItems = \app\admin\model\order\Item::where(['order_no' => $mchOrderId])->select()->toArray();
if (empty($orderItems)) {
Log::error("[WxappNotify] 退款推送: 订单无商品项 order_no={$mchOrderId}");
throw new \Exception('订单商品不存在');
}
foreach ($orderItems as $item) {
$this->processRefundItem($order, $item, $message, $retCode, $retMsg, $refundFee, $mchRefundId, $wxRefundId, $openid);
}
}
/**
* 处理单个订单商品的退款逻辑
* @param array $order 订单信息
* @param array $item 订单商品信息
* @param array $message 微信推送的完整消息
* @param int $retCode 退款结果码(0-成功)
* @param string $retMsg 退款结果详情
* @param int $refundFee 退款金额(单位:分)
* @param string $mchRefundId 商户退款单号
* @param string $wxRefundId 微信退款单号
* @param string $openid 用户openid
* @return void
*/
private function processRefundItem($order, $item, $message, $retCode, $retMsg, $refundFee, $mchRefundId, $wxRefundId, $openid)
{
$refundLog = \app\admin\model\order\RefundLog::where([
'order_no' => $order['order_no'],
'order_item_id' => $item['id']
])->order('id', 'desc')->find();
if (!$refundLog) {
Log::warning("[WxappNotify] 退款推送: 未找到退款记录,创建新记录 order_no={$order['order_no']}, item_id={$item['id']}");
$refundLog = new \app\admin\model\order\RefundLog();
$refundLog->order_no = $order['order_no'];
$refundLog->order_item_id = $item['id'];
$refundLog->refund_no = $mchRefundId ?: ('WX' . date('YmdHis') . mt_rand(1000, 9999));
$refundLog->pay_fee = $order['real_pay_price'];
$refundLog->refund_fee = $refundFee / 100;
$refundLog->pay_type = $order['pay_type'];
$refundLog->save_equity = 2;
$refundLog->save();
}
if ($retCode === 0) {
try {
\app\admin\model\order\Refund::refundFinish($order, $item, $refundLog);
OrderLog::set($order['order_no'], "虚拟支付退款成功通知-商户退款单号:{$mchRefundId}, 微信退款单号:{$wxRefundId}, 退款金额:{$refundFee}分, 用户:{$openid}");
Log::info("[WxappNotify] 退款处理成功: order_no={$order['order_no']}, item_id={$item['id']}, refund_fee={$refundFee}");
} catch (\Exception $e) {
Log::error("[WxappNotify] 退款处理异常: " . $e->getMessage() . " order_no={$order['order_no']}");
OrderLog::set($order['order_no'], "虚拟支付退款处理异常:{$e->getMessage()}");
}
} else {
$refundLog->status = -1;
$refundLog->save();
OrderLog::set($order['order_no'], "虚拟支付退款失败通知-错误码:{$retCode}, 原因:{$retMsg}, 商户退款单号:{$mchRefundId}");
Log::error("[WxappNotify] 退款失败: order_no={$order['order_no']}, retCode={$retCode}, retMsg={$retMsg}");
}
}
/**
* 用户投诉推送处理
* 用户发起投诉后触发
* @param array $message 消息内容
* @return void
*/
private function handleComplaint($message)
{
$mchOrderId = $message['MchOrderId'] ?? '';
$complaintId = $message['ComplaintId'] ?? '';
$complaintDetail = $message['ComplaintDetail'] ?? '';
Log::info("[WxappNotify] 用户投诉推送: order_no={$mchOrderId}, complaintId={$complaintId}");
$order = null;
if (!empty($mchOrderId)) {
$order = Order::orderNoGetOrder($mchOrderId);
}
Log::warning("[WxappNotify] 用户投诉详情: " . json_encode([
'order_no' => $mchOrderId,
'complaint_id' => $complaintId,
'complaint_detail' => $complaintDetail,
'openid' => $message['OpenId'] ?? '',
'wx_order_id' => $message['WxOrderId'] ?? '',
'complaint_time' => $message['ComplaintTime'] ?? 0,
], JSON_UNESCAPED_UNICODE));
if ($order) {
OrderLog::set($mchOrderId, "用户发起投诉-投诉单号:{$complaintId}, 投诉内容:{$complaintDetail}");
}
}
/**
* 通知微信已发货完成(现金订单)
* @param string $outTradeNo 订单号
* @param int $env 环境(0-正式 1-沙箱)
* @return void
*/
private function notifyProvideGoods($outTradeNo, $env = 0)
{
try {
$virtualPayService = new VirtualPayService();
$result = $virtualPayService->notifyProvideGoods($outTradeNo);
if (isset($result['errcode']) && $result['errcode'] === 0) {
Log::info("[WxappNotify] 通知发货成功: order_no={$outTradeNo}");
} else {
Log::error("[WxappNotify] 通知发货失败: " . json_encode($result));
throw new \Exception('通知发货失败: ' . ($result['errmsg'] ?? '未知错误'));
}
} catch (\Exception $e) {
Log::error("[WxappNotify] 通知发货异常: " . $e->getMessage());
throw $e;
}
}
/**
* iOS退款问询处理
* Apple支付用户申请退款时,微信会推送退款问询,开发者需在3秒内响应
* @param array $message 消息内容
* @return array 响应数据
*/
private function handleIosRefundQuery($message)
{
$refundTime = $message['refund_time'] ?? '';
$orderTime = $message['order_time'] ?? '';
$channelBill = $message['channel_bill'] ?? '';
$bundleId = $message['bundleid'] ?? '';
$productId = $message['product_id'] ?? '';
$pCount = $message['p_count'] ?? '';
$refundRequestReason = $message['refund_request_reason'] ?? '';
$provideStatus = $message['provide_status'] ?? '';
$payOrderId = $message['pay_order_id'] ?? '';
Log::info("[WxappNotify] iOS退款问询: pay_order_id={$payOrderId}, order_time={$orderTime}, refund_time={$refundTime}, channel_bill={$channelBill}, product_id={$productId}, reason={$refundRequestReason}, provide_status={$provideStatus}");
$config = \app\common\model\config\System::getConfig('virtual_pay');
$iosRefundPolicy = $config['ios_refund_policy'] ?? '2';
$resultCode = $this->calculateIosRefundResult($iosRefundPolicy, $orderTime, $payOrderId);
$resultInfo = $resultCode === 0 ? '同意退款' : '拒绝退款';
$evidence = $this->buildIosRefundEvidence($iosRefundPolicy, $orderTime, $payOrderId, $resultCode);
Log::info("[WxappNotify] iOS退款问询响应: pay_order_id={$payOrderId}, policy={$iosRefundPolicy}, result_code={$resultCode}, result_info={$resultInfo}");
return [
'result_code' => $resultCode,
'result_info' => $resultInfo,
'evidence' => $evidence,
];
}
/**
* 根据策略计算iOS退款问询结果
* @param string $policy 策略:1-任何时间拒绝退款,2-任何时间同意退款
* @param string $orderTime 订单时间(Unix时间戳)
* @param string $payOrderId 支付订单号
* @return int 0-建议退款,1-拒绝退款
*/
private function calculateIosRefundResult($policy, $orderTime, $payOrderId)
{
switch ($policy) {
case '1':
return 1;
case '2':
default:
return 0;
}
}
/**
* 构建iOS退款问询决策凭据
* @param string $policy 策略
* @param string $orderTime 订单时间
* @param string $payOrderId 支付订单号
* @param int $resultCode 结果码
* @return string
*/
private function buildIosRefundEvidence($policy, $orderTime, $payOrderId, $resultCode)
{
$policyDesc = [
'1' => '任何时间,建议苹果拒绝退款',
'2' => '任何时间,建议苹果同意退款',
];
$orderTimestamp = intval($orderTime);
$evidence = sprintf(
'策略:%s | 订单号:%s | 订单时间:%s | 决策:%s | 决策时间:%s',
$policyDesc[$policy] ?? '默认策略',
$payOrderId,
$orderTime ? date('Y-m-d H:i:s', $orderTimestamp) : '未知',
$resultCode === 0 ? '同意退款' : '拒绝退款',
date('Y-m-d H:i:s')
);
return $evidence;
}
}