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; } }