141 行
5.1 KiB
PHP
141 行
5.1 KiB
PHP
<?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);
|
||
}
|
||
}
|