2025-10-17 16:32:16 +08:00
|
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
|
|
namespace addons\shopro\controller;
|
|
|
|
|
|
|
|
|
|
|
|
use addons\shopro\controller\Common;
|
|
|
|
|
|
use addons\shopro\library\ccblife\CcbPaymentService;
|
|
|
|
|
|
use addons\shopro\library\ccblife\CcbOrderService;
|
|
|
|
|
|
use app\admin\model\shopro\order\Order as OrderModel;
|
|
|
|
|
|
use think\Db;
|
|
|
|
|
|
use think\Exception;
|
|
|
|
|
|
use think\Log;
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 建行支付控制器
|
|
|
|
|
|
*
|
|
|
|
|
|
* 功能:
|
|
|
|
|
|
* - 生成支付串
|
|
|
|
|
|
* - 处理支付回调
|
|
|
|
|
|
* - 验证支付结果
|
|
|
|
|
|
*
|
|
|
|
|
|
* @author Billy
|
|
|
|
|
|
* @date 2025-01-16
|
|
|
|
|
|
*/
|
|
|
|
|
|
class Ccbpayment extends Common
|
|
|
|
|
|
{
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 不需要登录的方法 (支付回调不需要登录)
|
|
|
|
|
|
* @var array
|
|
|
|
|
|
*/
|
2025-10-22 20:32:53 +08:00
|
|
|
|
protected $noNeedLogin = ['notify', 'refundNotify'];
|
2025-10-17 16:32:16 +08:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 不需要权限的方法
|
|
|
|
|
|
* @var array
|
|
|
|
|
|
*/
|
|
|
|
|
|
protected $noNeedRight = ['*'];
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 支付服务
|
|
|
|
|
|
* @var CcbPaymentService
|
|
|
|
|
|
*/
|
|
|
|
|
|
private $paymentService;
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 订单服务
|
|
|
|
|
|
* @var CcbOrderService
|
|
|
|
|
|
*/
|
|
|
|
|
|
private $orderService;
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 初始化
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function _initialize()
|
|
|
|
|
|
{
|
|
|
|
|
|
parent::_initialize();
|
|
|
|
|
|
|
|
|
|
|
|
$this->paymentService = new CcbPaymentService();
|
|
|
|
|
|
$this->orderService = new CcbOrderService();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 生成支付串
|
|
|
|
|
|
*
|
|
|
|
|
|
* @return void
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function createPayment()
|
|
|
|
|
|
{
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 1. 获取订单ID
|
|
|
|
|
|
$orderId = $this->request->post('order_id', 0);
|
|
|
|
|
|
|
|
|
|
|
|
if (empty($orderId)) {
|
|
|
|
|
|
$this->error('订单ID不能为空');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 查询订单
|
|
|
|
|
|
$order = OrderModel::where('id', $orderId)
|
|
|
|
|
|
->where('user_id', $this->auth->id)
|
|
|
|
|
|
->find();
|
|
|
|
|
|
|
|
|
|
|
|
if (!$order) {
|
|
|
|
|
|
$this->error('订单不存在');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 3. 检查订单状态
|
|
|
|
|
|
if ($order['status'] != 'unpaid') {
|
|
|
|
|
|
$this->error('订单已支付或已关闭');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-21 14:33:20 +08:00
|
|
|
|
// 4. ✅ 生成支付流水号(统一标识,用于订单推送和支付串生成)
|
|
|
|
|
|
// 格式: PAY + 年月日时分秒(14位) + 随机数(6位) = 23位
|
|
|
|
|
|
$payFlowId = 'PAY' . date('YmdHis') . mt_rand(100000, 999999);
|
|
|
|
|
|
Log::info('[建行支付] 生成支付流水号 pay_flow_id:' . $payFlowId . ' order_id:' . $orderId);
|
|
|
|
|
|
|
|
|
|
|
|
// 5. ✅ 先推送订单到建行生活(步骤2:调用A3341TP01订单推送接口)
|
|
|
|
|
|
// ⚠️ 重要:必须先推送订单,收银台才能校验订单信息
|
|
|
|
|
|
// 根据《5.6.2 业务流程说明》步骤2:由服务方调用订单推送接口向建行生活推送订单信息
|
|
|
|
|
|
try {
|
|
|
|
|
|
$pushResult = $this->orderService->pushOrder($orderId, $payFlowId);
|
|
|
|
|
|
|
|
|
|
|
|
if (!$pushResult['status']) {
|
|
|
|
|
|
// ⚠️ 推送失败必须阻塞支付流程!收银台会找不到订单
|
|
|
|
|
|
Log::error('[建行支付] 订单推送失败(阻塞支付) order_id:' . $orderId . ' error:' . $pushResult['message']);
|
|
|
|
|
|
$this->error('订单推送失败,无法生成支付串: ' . $pushResult['message']);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Log::info('[建行支付] 订单推送成功 order_id:' . $orderId);
|
|
|
|
|
|
|
|
|
|
|
|
} catch (Exception $e) {
|
|
|
|
|
|
// ⚠️ 推送异常必须阻塞支付流程
|
|
|
|
|
|
Log::error('[建行支付] 订单推送异常(阻塞支付) order_id:' . $orderId . ' error:' . $e->getMessage());
|
|
|
|
|
|
$this->error('订单推送异常,无法生成支付串: ' . $e->getMessage());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 6. 生成支付串(步骤3:调用收银台)
|
2025-10-20 15:51:06 +08:00
|
|
|
|
// ⚠️ 注意: generatePaymentString()内部已经完成了以下操作:
|
|
|
|
|
|
// - 更新订单的ccb_pay_flow_id字段
|
|
|
|
|
|
// - 记录支付日志到ccb_payment_log表
|
|
|
|
|
|
// 控制器不应该重复操作,否则会导致数据重复写入!
|
2025-10-21 14:33:20 +08:00
|
|
|
|
$result = $this->paymentService->generatePaymentString($orderId, $payFlowId);
|
2025-10-17 16:32:16 +08:00
|
|
|
|
|
2025-10-17 17:18:15 +08:00
|
|
|
|
if (!$result['status']) {
|
|
|
|
|
|
$this->error('支付串生成失败: ' . $result['message']);
|
2025-10-17 16:32:16 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-21 14:33:20 +08:00
|
|
|
|
// 7. 返回支付串给前端调用收银台
|
2025-10-20 15:51:06 +08:00
|
|
|
|
$this->success('支付串生成成功', $result['data']);
|
2025-10-17 16:32:16 +08:00
|
|
|
|
|
|
|
|
|
|
} catch (Exception $e) {
|
|
|
|
|
|
Log::error('[建行支付] 生成支付串失败 order_id:' . ($orderId ?? 0) . ' error:' . $e->getMessage());
|
|
|
|
|
|
|
|
|
|
|
|
$this->error('生成支付串失败: ' . $e->getMessage());
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2025-10-20 16:36:06 +08:00
|
|
|
|
* 查询订单支付状态 (前端轮询用)
|
2025-10-17 16:32:16 +08:00
|
|
|
|
*
|
2025-10-20 16:36:06 +08:00
|
|
|
|
* ⚠️ 重要说明:
|
|
|
|
|
|
* 本接口只查询订单状态,不执行任何业务逻辑!
|
|
|
|
|
|
* 订单状态由建行异步通知(notify)接口更新,前端只负责轮询查询。
|
|
|
|
|
|
*
|
|
|
|
|
|
* 修改原因:
|
|
|
|
|
|
* 原callback()方法存在严重安全漏洞:
|
|
|
|
|
|
* 1. 前端可伪造支付成功请求
|
|
|
|
|
|
* 2. 与notify()形成双通道,存在竞态条件
|
|
|
|
|
|
* 3. 违反建行标准支付流程
|
|
|
|
|
|
*
|
|
|
|
|
|
* 正确流程:
|
|
|
|
|
|
* 前端调起支付 → 建行处理 → 建行异步通知notify() → 前端轮询本接口查询状态
|
2025-10-17 16:32:16 +08:00
|
|
|
|
*
|
|
|
|
|
|
* @return void
|
|
|
|
|
|
*/
|
2025-10-20 16:36:06 +08:00
|
|
|
|
public function queryPaymentStatus()
|
2025-10-17 16:32:16 +08:00
|
|
|
|
{
|
|
|
|
|
|
try {
|
2025-10-20 16:36:06 +08:00
|
|
|
|
// 1. 获取订单ID
|
|
|
|
|
|
$orderId = $this->request->get('order_id', 0);
|
2025-10-17 16:32:16 +08:00
|
|
|
|
|
|
|
|
|
|
if (empty($orderId)) {
|
|
|
|
|
|
$this->error('订单ID不能为空');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-20 16:36:06 +08:00
|
|
|
|
// 2. 查询订单(只查询,不更新!)
|
|
|
|
|
|
$order = OrderModel::where('id', $orderId)
|
|
|
|
|
|
->where('user_id', $this->auth->id)
|
|
|
|
|
|
->field('id, order_sn, status, paid_time, ccb_pay_flow_id')
|
|
|
|
|
|
->find();
|
2025-10-17 16:32:16 +08:00
|
|
|
|
|
|
|
|
|
|
if (!$order) {
|
|
|
|
|
|
$this->error('订单不存在');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-20 16:36:06 +08:00
|
|
|
|
// 3. 返回订单状态(只读操作,绝不修改数据!)
|
|
|
|
|
|
$this->success('查询成功', [
|
|
|
|
|
|
'order_id' => $order->id,
|
|
|
|
|
|
'order_sn' => $order->order_sn,
|
|
|
|
|
|
'status' => $order->status,
|
|
|
|
|
|
'is_paid' => in_array($order->status, ['paid', 'completed', 'success']),
|
|
|
|
|
|
'paid_time' => $order->paid_time,
|
|
|
|
|
|
'pay_flow_id' => $order->ccb_pay_flow_id,
|
|
|
|
|
|
]);
|
2025-10-17 16:32:16 +08:00
|
|
|
|
|
2025-10-20 16:36:06 +08:00
|
|
|
|
} catch (Exception $e) {
|
|
|
|
|
|
Log::error('[建行支付] 查询订单状态失败 order_id:' . ($orderId ?? 0) . ' error:' . $e->getMessage());
|
2025-10-17 16:32:16 +08:00
|
|
|
|
|
2025-10-20 16:36:06 +08:00
|
|
|
|
$this->error('查询失败: ' . $e->getMessage());
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-10-17 16:32:16 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 建行支付通知 (建行服务器回调)
|
|
|
|
|
|
*
|
|
|
|
|
|
* 说明:
|
|
|
|
|
|
* 建行支付成功后,会向notify_url发送支付通知
|
|
|
|
|
|
* 这是服务器到服务器的回调,需要验签
|
|
|
|
|
|
*
|
2025-10-18 01:16:25 +08:00
|
|
|
|
* ⚠️ 重要:此接口为建行服务器异步回调,必须返回纯文本 'SUCCESS' 或 'FAIL'
|
|
|
|
|
|
*
|
2025-10-20 16:36:06 +08:00
|
|
|
|
* ✅ 正确流程:
|
|
|
|
|
|
* 1. 验证签名
|
|
|
|
|
|
* 2. 更新订单状态(由handleNotify()完成)
|
|
|
|
|
|
* 3. 推送订单到建行外联系统(本方法完成)
|
|
|
|
|
|
* 4. 返回SUCCESS给建行
|
|
|
|
|
|
*
|
2025-10-17 16:32:16 +08:00
|
|
|
|
* @return void
|
|
|
|
|
|
*/
|
2025-10-22 20:32:53 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 建行生活支付通知接口
|
|
|
|
|
|
*
|
|
|
|
|
|
* 📋 接口说明(文档7.1):
|
|
|
|
|
|
* - 建行生活主动推送支付结果
|
|
|
|
|
|
* - 不会附带服务方编号
|
|
|
|
|
|
* - 通过 REMARK2 字段识别服务方
|
|
|
|
|
|
* - SIGN 字段使用商户私钥签名,需用建行公钥验签
|
|
|
|
|
|
*
|
|
|
|
|
|
* @return void
|
|
|
|
|
|
*/
|
2025-10-17 16:32:16 +08:00
|
|
|
|
public function notify()
|
|
|
|
|
|
{
|
|
|
|
|
|
try {
|
2025-10-18 01:16:25 +08:00
|
|
|
|
// 1. 获取原始请求数据
|
|
|
|
|
|
$rawData = file_get_contents('php://input');
|
2025-10-22 20:32:53 +08:00
|
|
|
|
Log::info('[建行支付通知] 收到异步通知: ' . $rawData);
|
2025-10-18 01:16:25 +08:00
|
|
|
|
|
|
|
|
|
|
// 2. 解析POST参数
|
|
|
|
|
|
$params = $this->request->post();
|
|
|
|
|
|
|
|
|
|
|
|
// 3. 如果POST为空,尝试解析原始数据
|
|
|
|
|
|
if (empty($params) && $rawData) {
|
|
|
|
|
|
parse_str($rawData, $params);
|
|
|
|
|
|
}
|
2025-10-17 16:32:16 +08:00
|
|
|
|
|
2025-10-18 01:16:25 +08:00
|
|
|
|
// 4. 记录参数
|
2025-10-22 20:32:53 +08:00
|
|
|
|
Log::info('[建行支付通知] 解析参数: ' . json_encode($params, JSON_UNESCAPED_UNICODE));
|
2025-10-18 01:16:25 +08:00
|
|
|
|
|
|
|
|
|
|
// 5. 验证必需参数
|
|
|
|
|
|
if (empty($params['ORDERID'])) {
|
2025-10-22 20:32:53 +08:00
|
|
|
|
Log::error('[建行支付通知] 缺少ORDERID参数');
|
|
|
|
|
|
exit('FAIL');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (empty($params['SIGN'])) {
|
|
|
|
|
|
Log::error('[建行支付通知] 缺少SIGN签名');
|
|
|
|
|
|
exit('FAIL');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (empty($params['SUCCESS'])) {
|
|
|
|
|
|
Log::error('[建行支付通知] 缺少SUCCESS字段');
|
2025-10-20 16:36:06 +08:00
|
|
|
|
exit('FAIL');
|
2025-10-18 01:16:25 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-22 20:32:53 +08:00
|
|
|
|
// 6. ✅ 验证签名(使用建行公钥验签)
|
|
|
|
|
|
try {
|
|
|
|
|
|
$signature = $params['SIGN'];
|
|
|
|
|
|
$verifyParams = $params; // 复制参数用于验签
|
|
|
|
|
|
|
|
|
|
|
|
// 加载建行公钥配置
|
|
|
|
|
|
$configFile = __DIR__ . '/../config/ccblife.php';
|
|
|
|
|
|
if (!file_exists($configFile)) {
|
|
|
|
|
|
throw new \Exception('建行生活配置文件不存在');
|
|
|
|
|
|
}
|
|
|
|
|
|
$config = include $configFile;
|
|
|
|
|
|
|
|
|
|
|
|
// 验签
|
|
|
|
|
|
$verifyResult = \addons\shopro\library\ccblife\CcbRSA::verifyNotify(
|
|
|
|
|
|
$verifyParams,
|
|
|
|
|
|
$signature,
|
|
|
|
|
|
$config['ccb_public_key'] // 建行公钥
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
if (!$verifyResult) {
|
|
|
|
|
|
Log::error('[建行支付通知] 签名验证失败 ORDERID:' . $params['ORDERID']);
|
|
|
|
|
|
exit('FAIL');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Log::info('[建行支付通知] 签名验证成功 ORDERID:' . $params['ORDERID']);
|
|
|
|
|
|
|
|
|
|
|
|
} catch (\Exception $e) {
|
|
|
|
|
|
Log::error('[建行支付通知] 签名验证异常: ' . $e->getMessage());
|
|
|
|
|
|
exit('FAIL');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 7. 检查支付状态
|
|
|
|
|
|
if ($params['SUCCESS'] !== 'Y') {
|
|
|
|
|
|
Log::warning('[建行支付通知] 支付未成功 ORDERID:' . $params['ORDERID'] . ' SUCCESS:' . $params['SUCCESS']);
|
|
|
|
|
|
exit('SUCCESS'); // ⚠️ 仍然返回SUCCESS,表示通知已接收
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 8. 调用支付服务处理通知(返回订单ID)
|
2025-10-18 01:16:25 +08:00
|
|
|
|
$result = $this->paymentService->handleNotify($params);
|
|
|
|
|
|
|
2025-10-22 20:32:53 +08:00
|
|
|
|
// 9. ✅ 处理成功后更新订单状态到建行(步骤13:调用订单更新接口更新订单状态)
|
2025-10-20 16:36:06 +08:00
|
|
|
|
if ($result['status'] === 'success' && !empty($result['order_id'])) {
|
2025-10-20 17:10:16 +08:00
|
|
|
|
// ⚠️ 只有新支付才更新,已支付的订单跳过更新
|
2025-10-20 16:36:06 +08:00
|
|
|
|
if ($result['already_paid'] === false) {
|
|
|
|
|
|
try {
|
2025-10-20 17:10:16 +08:00
|
|
|
|
// 调用订单更新接口,将订单状态从未支付更新为已支付
|
2025-10-22 20:32:53 +08:00
|
|
|
|
$updateResult = $this->orderService->updateOrderStatus($result['order_id'], '1'); // 1-支付成功
|
2025-10-20 17:10:16 +08:00
|
|
|
|
|
|
|
|
|
|
if ($updateResult['status']) {
|
2025-10-22 20:32:53 +08:00
|
|
|
|
Log::info('[建行支付通知] 订单状态更新成功 order_id:' . $result['order_id']);
|
2025-10-20 17:10:16 +08:00
|
|
|
|
} else {
|
|
|
|
|
|
// ⚠️ 更新失败不影响本地支付状态,记录日志后续补推
|
2025-10-22 20:32:53 +08:00
|
|
|
|
Log::warning('[建行支付通知] 订单状态更新失败(本地已支付) order_id:' . $result['order_id'] . ' error:' . $updateResult['message']);
|
2025-10-20 17:10:16 +08:00
|
|
|
|
}
|
2025-10-20 16:36:06 +08:00
|
|
|
|
} catch (Exception $e) {
|
2025-10-20 17:10:16 +08:00
|
|
|
|
// ⚠️ 更新异常不影响支付成功,记录日志后续补推
|
2025-10-22 20:32:53 +08:00
|
|
|
|
Log::error('[建行支付通知] 订单状态更新异常(本地已支付) order_id:' . $result['order_id'] . ' error:' . $e->getMessage());
|
2025-10-20 16:36:06 +08:00
|
|
|
|
}
|
|
|
|
|
|
} else {
|
2025-10-22 20:32:53 +08:00
|
|
|
|
Log::info('[建行支付通知] 订单已支付且已更新,跳过更新 order_id:' . $result['order_id']);
|
2025-10-20 16:36:06 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-22 20:32:53 +08:00
|
|
|
|
// 10. 返回处理结果
|
2025-10-20 15:29:15 +08:00
|
|
|
|
// ⚠️ 重要: 必须使用exit直接退出,防止ThinkPHP框架追加额外内容
|
|
|
|
|
|
// 建行要求返回纯文本 'SUCCESS' 或 'FAIL',任何额外字符都会导致建行认为通知失败
|
2025-10-20 16:36:06 +08:00
|
|
|
|
$response = ($result['status'] === 'success') ? 'SUCCESS' : 'FAIL';
|
2025-10-22 20:32:53 +08:00
|
|
|
|
Log::info('[建行支付通知] 处理完成,返回: ' . $response);
|
2025-10-18 01:16:25 +08:00
|
|
|
|
|
2025-10-20 15:29:15 +08:00
|
|
|
|
// 直接退出,确保只输出SUCCESS/FAIL
|
2025-10-20 16:36:06 +08:00
|
|
|
|
exit($response);
|
2025-10-17 16:32:16 +08:00
|
|
|
|
|
|
|
|
|
|
} catch (Exception $e) {
|
2025-10-22 20:32:53 +08:00
|
|
|
|
Log::error('[建行支付通知] 处理失败 error:' . $e->getMessage());
|
|
|
|
|
|
|
|
|
|
|
|
// 异常情况也要直接退出
|
|
|
|
|
|
exit('FAIL');
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 建行生活退款操作通知接口
|
|
|
|
|
|
*
|
|
|
|
|
|
* 📋 接口说明(文档7.2):
|
|
|
|
|
|
* - 建行生活主动推送退款操作消息
|
|
|
|
|
|
* - 仅推送少量信息(商户号、订单号、退款时间)
|
|
|
|
|
|
* - ⚠️ 不能用于退款结果判断,需通过 A3341TP03 查询接口获取详细信息
|
|
|
|
|
|
* - SIGN 字段使用商户私钥签名,需用建行公钥验签
|
|
|
|
|
|
*
|
|
|
|
|
|
* @return void
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function refundNotify()
|
|
|
|
|
|
{
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 1. 获取原始请求数据
|
|
|
|
|
|
$rawData = file_get_contents('php://input');
|
|
|
|
|
|
Log::info('[建行退款通知] 收到异步通知: ' . $rawData);
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 解析POST参数
|
|
|
|
|
|
$params = $this->request->post();
|
|
|
|
|
|
|
|
|
|
|
|
// 3. 如果POST为空,尝试解析原始数据
|
|
|
|
|
|
if (empty($params) && $rawData) {
|
|
|
|
|
|
parse_str($rawData, $params);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 4. 记录参数
|
|
|
|
|
|
Log::info('[建行退款通知] 解析参数: ' . json_encode($params, JSON_UNESCAPED_UNICODE));
|
|
|
|
|
|
|
|
|
|
|
|
// 5. 验证必需参数
|
|
|
|
|
|
if (empty($params['ORDERID'])) {
|
|
|
|
|
|
Log::error('[建行退款通知] 缺少ORDERID参数');
|
|
|
|
|
|
exit('FAIL');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (empty($params['SIGN'])) {
|
|
|
|
|
|
Log::error('[建行退款通知] 缺少SIGN签名');
|
|
|
|
|
|
exit('FAIL');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (empty($params['REFUND_DTM'])) {
|
|
|
|
|
|
Log::error('[建行退款通知] 缺少REFUND_DTM退款时间');
|
|
|
|
|
|
exit('FAIL');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 6. ✅ 验证签名(使用建行公钥验签)
|
|
|
|
|
|
try {
|
|
|
|
|
|
$signature = $params['SIGN'];
|
|
|
|
|
|
$verifyParams = $params;
|
|
|
|
|
|
|
|
|
|
|
|
// 加载建行公钥配置
|
|
|
|
|
|
$configFile = __DIR__ . '/../config/ccblife.php';
|
|
|
|
|
|
if (!file_exists($configFile)) {
|
|
|
|
|
|
throw new \Exception('建行生活配置文件不存在');
|
|
|
|
|
|
}
|
|
|
|
|
|
$config = include $configFile;
|
|
|
|
|
|
|
|
|
|
|
|
// 验签
|
|
|
|
|
|
$verifyResult = \addons\shopro\library\ccblife\CcbRSA::verifyNotify(
|
|
|
|
|
|
$verifyParams,
|
|
|
|
|
|
$signature,
|
|
|
|
|
|
$config['ccb_public_key'] // 建行公钥
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
if (!$verifyResult) {
|
|
|
|
|
|
Log::error('[建行退款通知] 签名验证失败 ORDERID:' . $params['ORDERID']);
|
|
|
|
|
|
exit('FAIL');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Log::info('[建行退款通知] 签名验证成功 ORDERID:' . $params['ORDERID']);
|
|
|
|
|
|
|
|
|
|
|
|
} catch (\Exception $e) {
|
|
|
|
|
|
Log::error('[建行退款通知] 签名验证异常: ' . $e->getMessage());
|
|
|
|
|
|
exit('FAIL');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 7. ⚠️ 重要提示:退款通知仅包含少量信息,不能用于退款结果判断
|
|
|
|
|
|
// 需要通过 A3341TP03 订单查询接口获取详细的退款信息
|
|
|
|
|
|
Log::info('[建行退款通知] 退款操作通知 ORDERID:' . $params['ORDERID'] . ' REFUND_DTM:' . $params['REFUND_DTM']);
|
|
|
|
|
|
|
|
|
|
|
|
// 8. 调用订单查询接口获取详细退款信息
|
|
|
|
|
|
try {
|
|
|
|
|
|
$orderSn = $params['ORDERID'];
|
|
|
|
|
|
$refundTime = $params['REFUND_DTM'];
|
|
|
|
|
|
|
|
|
|
|
|
// 计算查询时间范围(退款时间前后1天)
|
|
|
|
|
|
$refundTimestamp = strtotime($refundTime);
|
|
|
|
|
|
$startTime = date('YmdHis', $refundTimestamp - 86400); // 前1天
|
|
|
|
|
|
$endTime = date('YmdHis', $refundTimestamp + 86400); // 后1天
|
|
|
|
|
|
|
|
|
|
|
|
// 查询退款交易详情
|
|
|
|
|
|
$queryResult = $this->orderService->queryOrder(
|
|
|
|
|
|
$orderSn,
|
|
|
|
|
|
$startTime,
|
|
|
|
|
|
$endTime,
|
|
|
|
|
|
1,
|
|
|
|
|
|
'1', // 交易类型:1-退款交易
|
|
|
|
|
|
'00' // 交易状态:00-成功
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
if ($queryResult['status']) {
|
|
|
|
|
|
Log::info('[建行退款通知] 查询退款详情成功: ' . json_encode($queryResult['data'], JSON_UNESCAPED_UNICODE));
|
|
|
|
|
|
|
|
|
|
|
|
// 9. 根据查询结果处理本地订单退款状态
|
|
|
|
|
|
// 查找本地订单
|
|
|
|
|
|
$order = Db::name('shopro_order')
|
|
|
|
|
|
->where('pay_flow_id', $orderSn)
|
|
|
|
|
|
->find();
|
|
|
|
|
|
|
|
|
|
|
|
if ($order) {
|
|
|
|
|
|
// 更新订单退款状态到建行
|
|
|
|
|
|
$updateResult = $this->orderService->updateOrderStatus($order['id'], null, '2'); // 2-已退款
|
|
|
|
|
|
|
|
|
|
|
|
if ($updateResult['status']) {
|
|
|
|
|
|
Log::info('[建行退款通知] 订单退款状态更新成功 order_id:' . $order['id']);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
Log::warning('[建行退款通知] 订单退款状态更新失败 order_id:' . $order['id'] . ' error:' . $updateResult['message']);
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
Log::warning('[建行退款通知] 未找到本地订单 pay_flow_id:' . $orderSn);
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
Log::error('[建行退款通知] 查询退款详情失败: ' . $queryResult['message']);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
} catch (\Exception $e) {
|
|
|
|
|
|
Log::error('[建行退款通知] 查询退款详情异常: ' . $e->getMessage());
|
|
|
|
|
|
// ⚠️ 查询失败不影响通知接收,仍然返回SUCCESS
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 10. 返回处理结果
|
|
|
|
|
|
// ⚠️ 重要: 必须使用exit直接退出,防止ThinkPHP框架追加额外内容
|
|
|
|
|
|
Log::info('[建行退款通知] 处理完成,返回: SUCCESS');
|
|
|
|
|
|
|
|
|
|
|
|
// 直接退出,确保只输出SUCCESS
|
|
|
|
|
|
exit('SUCCESS');
|
|
|
|
|
|
|
|
|
|
|
|
} catch (Exception $e) {
|
|
|
|
|
|
Log::error('[建行退款通知] 处理失败 error:' . $e->getMessage());
|
2025-10-20 15:29:15 +08:00
|
|
|
|
|
|
|
|
|
|
// 异常情况也要直接退出
|
|
|
|
|
|
exit('FAIL');
|
2025-10-17 16:32:16 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|