文件

141 行
5.1 KiB
PHP

此文件含有模棱两可的 Unicode 字符

此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。

<?php
namespace app\api\controller;
use app\common\controller\Api;
use app\common\library\PaymentService;
use app\common\library\WxPayV3Service;
use think\Db;
use think\Log;
use think\Response;
/**
* 微信支付 APIv3 异步通知回调控制器
* 覆盖:JSAPI 交易支付通知 / 原路退款通知 / 商家转账(1005)结果通知
*/
class Wxpay extends Api
{
protected $noNeedLogin = ['notify', 'refundNotify', 'transferNotify'];
protected $noNeedRight = '*';
/**
* 1. 微信支付交易结果异步通知 (/api/wxpay/notify)
*/
public function notify()
{
try {
$parsed = $this->parseAndVerifyCallback();
$eventType = strtoupper($parsed['event_type']);
$data = $parsed['data'];
if ($eventType === 'TRANSACTION.SUCCESS' && !empty($data['out_trade_no'])) {
PaymentService::confirmOrderPay((string)$data['out_trade_no'], $data);
}
return $this->wxResponse(true);
} catch (\Throwable $e) {
Log::error('[Wxpay::notify] ' . $e->getMessage());
return $this->wxResponse(false, $e->getMessage());
}
}
/**
* 2. 微信原路退款结果异步通知 (/api/wxpay/refundNotify)
*/
public function refundNotify()
{
try {
$parsed = $this->parseAndVerifyCallback();
$data = $parsed['data'];
$outRefundNo = trim((string)($data['out_refund_no'] ?? ''));
$outTradeNo = trim((string)($data['out_trade_no'] ?? ''));
$refStatus = strtoupper((string)($data['refund_status'] ?? ''));
if ($outRefundNo === '' && $outTradeNo === '') {
throw new \RuntimeException('退款回调缺少商户退款单号与商户支付单号');
}
$q = Db::name('pay_order');
if ($outRefundNo !== '') {
$q->where('refund_no', $outRefundNo);
} else {
$q->where('out_trade_no', $outTradeNo);
}
$po = $q->find();
if (!$po) {
Log::error("[Wxpay::refundNotify] 未找到本地支付流水单 out_refund_no={$outRefundNo}, out_trade_no={$outTradeNo}");
throw new \RuntimeException('未匹配到本地支付交易流水单');
}
try {
PaymentService::applyWxRefundResult($po, $data);
} catch (\Throwable $refundEx) {
// 若微信已下发明确退款异常/关闭终态,系统已完整落库记录原因并写入告警日志,向微信回传成功避免重复轰炸
if (in_array($refStatus, ['ABNORMAL', 'CLOSED'], true)) {
Log::error("[Wxpay::refundNotify] 退款终态异常已落库 out_trade_no={$po['out_trade_no']}, status={$refStatus}, reason=" . $refundEx->getMessage());
return $this->wxResponse(true);
}
throw $refundEx;
}
return $this->wxResponse(true);
} catch (\Throwable $e) {
Log::error('[Wxpay::refundNotify] ' . $e->getMessage());
return $this->wxResponse(false, $e->getMessage());
}
}
/**
* 3. 微信新版「商家转账」终态结果异步通知 (/api/wxpay/transferNotify)
*/
public function transferNotify()
{
try {
$parsed = $this->parseAndVerifyCallback();
$data = $parsed['data'];
$outBillNo = trim((string)($data['out_bill_no'] ?? ''));
if ($outBillNo === '') {
throw new \RuntimeException('商家转账回调缺少商户转账单号 out_bill_no');
}
$wd = Db::name('withdraw')->where('out_bill_no', $outBillNo)->find();
if (!$wd) {
Log::error("[Wxpay::transferNotify] 未找到本地提现单记录 out_bill_no={$outBillNo}");
throw new \RuntimeException('未找到匹配的本地提现单');
}
PaymentService::syncWithdrawTransfer((int)$wd['id'], $data);
return $this->wxResponse(true);
} catch (\Throwable $e) {
Log::error('[Wxpay::transferNotify] ' . $e->getMessage());
return $this->wxResponse(false, $e->getMessage());
}
}
/**
* 读取原始报文并使用微信支付公钥验签 + AES-256-GCM 解密
*/
protected function parseAndVerifyCallback()
{
$rawBody = file_get_contents('php://input');
if ($rawBody === false || $rawBody === '') {
$rawBody = (string)$this->request->getInput();
}
$headers = $this->request->header();
$wxSvc = new WxPayV3Service();
return $wxSvc->verifyAndDecryptNotify((string)$rawBody, is_array($headers) ? $headers : []);
}
/**
* 输出符合微信支付 APIv3 规范的 HTTP 响应
*/
protected function wxResponse($ok, $msg = '')
{
$code = $ok ? 200 : 500;
$body = [
'code' => $ok ? 'SUCCESS' : 'FAIL',
'message' => $ok ? '成功' : ($msg ?: '处理失败'),
];
return Response::create($body, 'json', $code);
}
}