初始化项目:添加后端代码、ThinkPHP框架、前端资源
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user