文件
xionggeshop/addons/shopro_huifu/library/HuifuNotifyHandler.php

290 行
10 KiB
PHP

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

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

<?php
namespace addons\shopro_huifu\library;
use addons\shopro\model\Order;
use think\Db;
use think\Log;
class HuifuNotifyHandler
{
protected $client;
public function __construct()
{
$this->client = new HuifuClient();
}
public function handlePayNotify(array $raw)
{
$sign = HuifuNotifyParser::normalizeSign((string) ($raw['sign'] ?? ''));
$respData = $raw['resp_data'] ?? '';
if (is_array($respData)) {
Log::error('[shopro_huifu] notify resp_data is array, verify may fail');
$respData = json_encode($respData, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
}
$respData = (string) $respData;
if ($respData === '' || $sign === '') {
Log::error('[shopro_huifu] notify missing sign/resp_data');
return $this->ack('', false);
}
if (!$this->client->verifyNotify($respData, $sign)) {
Log::error('[shopro_huifu] notify verify fail, resp_data_len=' . strlen($respData)
. ' sign_len=' . strlen($sign));
return $this->ack('', false);
}
$data = json_decode($respData, true);
if (!$data) {
Log::error('[shopro_huifu] notify resp_data json decode fail');
return $this->ack('', false);
}
$reqSeqId = trim((string) ($data['req_seq_id'] ?? ''));
$huifuId = trim((string) ($data['huifu_id'] ?? ''));
$transStat = trim((string) ($data['trans_stat'] ?? ''));
if ($reqSeqId === '') {
Log::error('[shopro_huifu] notify missing req_seq_id');
return $this->ack('', false);
}
Log::info('[shopro_huifu] notify parsed: req_seq_id=' . $reqSeqId
. ' trans_stat=' . ($transStat ?: '-')
. ' resp_code=' . ($data['resp_code'] ?? '-')
. ' acct_stat=' . ($data['acct_stat'] ?? '-'));
$order = $this->resolveOrder($reqSeqId);
if ($order && $order['status'] > 0) {
$this->doneNotify($huifuId, $reqSeqId, 'pay_completed');
return $this->ack($reqSeqId, true);
}
$paidData = null;
if ($transStat === 'S') {
$paidData = $data;
} else {
$paidData = $this->resolvePaidDataViaQuery($reqSeqId, $data);
if ($paidData) {
Log::info('[shopro_huifu] notify query fallback paid: req_seq_id=' . $reqSeqId);
}
}
if (!$paidData) {
HuifuPayService::savePayExt($order ? $order['id'] : 0, $reqSeqId, [
'trans_stat' => $transStat ?: 'P',
'hf_seq_id' => $data['hf_seq_id'] ?? '',
]);
Log::info('[shopro_huifu] notify pending ack: req_seq_id=' . $reqSeqId
. ' trans_stat=' . ($transStat ?: 'empty'));
return $this->ack($reqSeqId, true);
}
if (!$this->markNotify($huifuId, $reqSeqId, 'pay_completed', $respData)) {
return $this->ack($reqSeqId, true);
}
if (!$order) {
$order = $this->resolveOrder($reqSeqId);
}
if (!$order) {
Log::error('[shopro_huifu] notify order not found: req_seq_id=' . $reqSeqId);
return $this->ack($reqSeqId, false);
}
$payFee = floatval($paidData['trans_amt'] ?? $order['total_fee']);
if (abs($payFee - floatval($order['total_fee'])) > 0.001) {
Log::error('[shopro_huifu] notify amount mismatch: req_seq_id=' . $reqSeqId
. ' notify=' . $payFee . ' order=' . $order['total_fee']);
return $this->ack($reqSeqId, false);
}
$wxFields = HuifuWxNotifyHelper::extract($paidData);
$orderSn = $reqSeqId;
try {
Db::transaction(function () use ($order, $paidData, $payFee, $orderSn, $wxFields) {
HuifuPayService::savePayExt($order['id'], $orderSn, [
'req_date' => $paidData['req_date'] ?? '',
'req_seq_id' => $orderSn,
'hf_seq_id' => $paidData['hf_seq_id'] ?? '',
'trans_stat' => 'S',
'extra' => HuifuWxNotifyHelper::encodeExtra($wxFields),
]);
if ($order['order_sn'] !== $orderSn) {
Db::name('shopro_order')->where('id', $order['id'])->update([
'order_sn' => $orderSn,
'pay_type' => 'huifu_wechat',
]);
}
$notify = [
'order_sn' => $orderSn,
'transaction_id' => HuifuWxNotifyHelper::resolveTransactionId($wxFields),
'notify_time' => date('Y-m-d H:i:s'),
'buyer_email' => $wxFields['wx_sub_openid'] ?: ($paidData['sub_openid'] ?? ''),
'payment_json' => HuifuWxNotifyHelper::buildPaymentJson($paidData, $wxFields),
'pay_fee' => $payFee,
'pay_type' => 'huifu_wechat',
];
$order->paymentProcess($order, $notify);
});
} catch (\Throwable $e) {
Log::error('[shopro_huifu] notify process fail: ' . $e->getMessage());
return $this->ack($reqSeqId, false);
}
$this->doneNotify($huifuId, $reqSeqId, 'pay_completed');
Log::info('[shopro_huifu] notify paid ok: order_id=' . $order['id'] . ' req_seq_id=' . $reqSeqId);
return $this->ack($reqSeqId, true);
}
/**
* 账务/通道通知可能不含 trans_stat=S,以查单二次确认(shared-async-notify)
*/
protected function resolvePaidDataViaQuery($reqSeqId, array $notifyData)
{
if ($this->shouldQueryForPaid($notifyData)) {
try {
$query = (new HuifuQueryService())->queryByOrderSn($reqSeqId);
if (($query['trans_stat'] ?? '') === 'S') {
return $query;
}
Log::info('[shopro_huifu] notify query not paid: req_seq_id=' . $reqSeqId
. ' trans_stat=' . ($query['trans_stat'] ?? ''));
} catch (\Throwable $e) {
Log::error('[shopro_huifu] notify query fallback fail: ' . $e->getMessage());
}
}
return null;
}
protected function shouldQueryForPaid(array $data)
{
$transStat = trim((string) ($data['trans_stat'] ?? ''));
if ($transStat === 'F') {
return false;
}
if ($transStat === 'S') {
return false;
}
$respCode = trim((string) ($data['resp_code'] ?? ''));
if (in_array($respCode, ['00000000', '00000100'], true)) {
return true;
}
$bankMsg = (string) ($data['bank_message'] ?? $data['bank_desc'] ?? '');
if ($bankMsg !== '' && strpos($bankMsg, '成功') !== false) {
return true;
}
if (($data['acct_stat'] ?? '') === 'I') {
return true;
}
return false;
}
public function resolveOrder($reqSeqId)
{
$reqSeqId = trim((string) $reqSeqId);
if ($reqSeqId === '') {
return null;
}
$order = Order::where('order_sn', $reqSeqId)->find();
if ($order) {
return $order;
}
$ext = Db::name('shopro_huifu_pay_ext')->where('req_seq_id', $reqSeqId)->find();
if (!$ext) {
$ext = Db::name('shopro_huifu_pay_ext')->where('order_sn', $reqSeqId)->find();
}
if ($ext && !empty($ext['order_id'])) {
return Order::where('id', $ext['order_id'])->find();
}
return null;
}
public function applyPaidOrder($order, array $data, $orderSn = '')
{
$orderSn = $orderSn ?: trim((string) ($data['req_seq_id'] ?? $order['order_sn']));
$payFee = floatval($data['trans_amt'] ?? $order['total_fee']);
$wxFields = HuifuWxNotifyHelper::extract($data);
Db::transaction(function () use ($order, $data, $payFee, $orderSn, $wxFields) {
HuifuPayService::savePayExt($order['id'], $orderSn, [
'req_date' => $data['req_date'] ?? '',
'req_seq_id' => $orderSn,
'hf_seq_id' => $data['hf_seq_id'] ?? '',
'trans_stat' => 'S',
'extra' => HuifuWxNotifyHelper::encodeExtra($wxFields),
]);
if ($order['order_sn'] !== $orderSn) {
Db::name('shopro_order')->where('id', $order['id'])->update([
'order_sn' => $orderSn,
'pay_type' => 'huifu_wechat',
]);
}
$notify = [
'order_sn' => $orderSn,
'transaction_id' => HuifuWxNotifyHelper::resolveTransactionId($wxFields),
'notify_time' => date('Y-m-d H:i:s'),
'buyer_email' => $wxFields['wx_sub_openid'] ?? '',
'payment_json' => HuifuWxNotifyHelper::buildPaymentJson($data, $wxFields),
'pay_fee' => $payFee,
'pay_type' => 'huifu_wechat',
];
$order->paymentProcess($order, $notify);
});
}
protected function markNotify($huifuId, $reqSeqId, $eventType, $payload)
{
try {
Db::name('shopro_huifu_notify_log')->insert([
'huifu_id' => $huifuId,
'req_seq_id' => $reqSeqId,
'event_type' => $eventType,
'payload' => $payload,
'status' => 0,
'createtime' => time(),
]);
return true;
} catch (\Exception $e) {
$exists = Db::name('shopro_huifu_notify_log')
->where(['huifu_id' => $huifuId, 'req_seq_id' => $reqSeqId, 'event_type' => $eventType])
->find();
if ($exists && intval($exists['status']) === 1) {
return false;
}
// status=0 表示上次处理失败,允许重试
return true;
}
}
protected function doneNotify($huifuId, $reqSeqId, $eventType)
{
Db::name('shopro_huifu_notify_log')
->where(['huifu_id' => $huifuId, 'req_seq_id' => $reqSeqId, 'event_type' => $eventType])
->update(['status' => 1]);
}
protected function ack($reqSeqId, $success)
{
return [
'success' => $success,
'body' => $reqSeqId ? 'RECV_ORD_ID_' . $reqSeqId : '',
'httpCode' => $success ? 200 : 500,
];
}
}