PHP项目怎样实现订单退款查询?

wen PHP项目 60

PHP项目怎样实现订单退款查询?从核心逻辑到API集成全解析

目录导读

  1. 退款查询的业务场景与核心需求
  2. 技术选型:如何设计退款查询的数据库结构
  3. 核心逻辑实现:PHP处理退款状态的查询与同步
  4. 支付平台API集成:微信、支付宝、银联的退款查询差异
  5. 安全与异常处理:幂等性、重试机制与日志记录
  6. 性能优化:缓存策略与异步查询方案
  7. 常见问题问答(Q&A)

退款查询的业务场景与核心需求

在电商、SaaS订阅、在线教育等PHP项目中,订单退款查询是支付系统的重要组成部分,用户发起退款后,开发者不仅要“接收退款请求”,还需要持续跟踪退款是否成功、失败或正在处理,核心需求包括:

PHP项目怎样实现订单退款查询?

  • 实时性:退款状态需在短时间内同步(如用户支付后反悔,需秒级返回结果)。
  • 一致性:本地数据库状态必须与支付平台保持一致,避免超卖或重复退款。
  • 可追溯:所有退款查询记录需保留,用于对账和纠纷处理。

真实场景示例:用户通过支付宝购买虚拟商品后,因服务问题申请退款,后端需通过支付宝的“退款查询接口”定期轮询,直到获取明确的“退款成功”或“退款失败”状态。


技术选型:如何设计退款查询的数据库结构

退款查询不同于订单查询,它涉及“退款流水”和“原订单”的关联,推荐在MySQL中设计以下两张核心表:

订单主表(orders

CREATE TABLE `orders` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `order_no` varchar(64) NOT NULL COMMENT '订单号',
  `total_amount` decimal(10,2) NOT NULL,
  `status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '0待支付1已支付2已退款3部分退款',
  `payment_channel` varchar(32) NOT NULL COMMENT 'alipay/wechat/unionpay',
  PRIMARY KEY (`id`),
  UNIQUE KEY `order_no` (`order_no`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

退款流水表(refund_records

CREATE TABLE `refund_records` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `refund_no` varchar(64) NOT NULL COMMENT '退款单号',
  `order_no` varchar(64) NOT NULL,
  `refund_amount` decimal(10,2) NOT NULL,
  `refund_status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '0待查询1退款中2成功3失败',
  `channel_refund_id` varchar(128) DEFAULT NULL COMMENT '支付平台退款单号',
  `query_times` int(11) NOT NULL DEFAULT '0' COMMENT '已查询次数',
  `last_query_time` datetime DEFAULT NULL,
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  INDEX `order_no` (`order_no`),
  UNIQUE KEY `refund_no` (`refund_no`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

设计要点

  • 退款流水表不直接覆盖原订单状态,而是通过 order_no 关联,支持“部分退款”场景(原订单 status = 3)。
  • query_timeslast_query_time 用于控制轮询次数,防止无限请求。

核心逻辑实现:PHP处理退款状态的查询与同步

1 手动触发查询:用户点击“退款进度”

当用户在订单详情页点击“查看退款进度”时,PHP后端应主动调用一次支付平台接口,并更新本地状态,示例如下:

<?php
class RefundQueryService {
    public function queryRefundStatus($refundNo) {
        $refundRecord = $this->getRefundRecordByRefundNo($refundNo);
        if (!$refundRecord) {
            throw new \Exception('退款记录不存在');
        }
        // 检查查询频率:1分钟内不重复请求
        if (time() - strtotime($refundRecord['last_query_time']) < 60) {
            return ['code' => -1, 'msg' => '请求过于频繁'];
        }
        // 根据支付渠道调用不同适配器
        $result = (new PaymentAdapterFactory())
            ->create($refundRecord['payment_channel'])
            ->queryRefund($refundRecord['order_no'], $refundNo);
        // 更新退款记录
        $this->updateRefundStatus($refundNo, $result);
        return $result;
    }
}

2 异步轮询:定时任务(Cron Job)

对于尚未返回最终状态的退款,使用Linux crontab + PHP CLI 脚本每5分钟扫描一次,核心逻辑:

// refund_cron.php
$pendingRefunds = $db->query("SELECT * FROM refund_records WHERE refund_status = 1 AND query_times < 10");
foreach ($pendingRefunds as $refund) {
    try {
        $result = (new PaymentAdapterFactory())
            ->create($refund['payment_channel'])
            ->queryRefund($refund['order_no'], $refund['refund_no']);
        $db->update('refund_records', [
            'refund_status' => $result['status'],
            'query_times' => $refund['query_times'] + 1,
            'last_query_time' => date('Y-m-d H:i:s')
        ], 'id = ?', [$refund['id']]);
        // 若退款成功,更新订单状态为“已退款”
        if ($result['status'] == 2) {
            $this->updateOrderRefunded($refund['order_no']);
        }
    } catch (\Exception $e) {
        // 记录错误,但不中断后续处理
        $logger->error("退款查询异常: " . $e->getMessage());
    }
}

注意:轮询次数超过10次仍未获取最终状态时,建议人工介入或发送告警。


支付平台API集成:微信、支付宝、银联的退款查询差异

所有支付平台的退款查询接口均要求“签名”与“敏感信息加密”,但参数与返回格式存在差异。

微信支付(v3接口)

  • 请求方式GET https://api.mch.weixin.qq.com/v3/refund/domestic/refunds/{refund_id}
  • 关键参数refund_id(微信退款单号)或 out_refund_no(商户退款单号)
  • 返回示例
    {
    "status": "SUCCESS", // SUCCESS/CLOSED/PROCESSING
    "amount": { "refund": 100 }
    }
  • PHP实现注意事项:需要加载微信支付SDK,计算 Authorization 头中的 MCHID 与签名。

支付宝(alipay.trade.fastpay.refund.query)

  • 请求方式POST(支付宝公共参数 + 业务参数)
  • 关键参数out_request_no(商户退款单号)
  • 返回示例
    <alipay_response>
    <code>10000</code>
    <msg>Success</msg>
    <out_trade_no>商家单号</out_trade_no>
    <refund_status>REFUND_SUCCESS</refund_status>
    </alipay_response>
  • PHP实现注意事项:需使用支付宝SDK的 AopClient,并设置 signTypeRSA2

银联(UnifiedOrder 接口)

  • 接口路径https://gateway.95516.com/gateway/api/backTransReq.do
  • 关键参数orderId(商户订单号),txnTime(原退款发起时间)
  • 返回响应:通过后台异步通知或主动查询 query_merchant_transaction 获取状态。

统一封装建议
创建一个 PaymentAdapter 接口,适配器类各自实现 queryRefund($orderNo, $refundNo) 方法,上层业务代码无需关注支付平台差异。


安全与异常处理:幂等性、重试机制与日志记录

幂等性设计

  • 同一笔退款单号 refund_no 的查询请求,即使支付平台返回成功,本地也需判断是否已更新。
  • 利用数据库唯一索引 refund_no,在插入退款记录时防止重复创建。

重试机制

  • 当支付接口超时或返回 SYSTEM_ERROR 时,应在业务层实现指数退避重试(如第1次等待5秒,第2次等待15秒)。
  • 设定最大重试次数(如10次),超过后标记为“需要人工核查”。

日志记录

  • 每条退款查询请求均写入 refund_query_log 表,包含:
    • 请求ID
    • 支付平台返回的原始报文(脱敏)
    • 耗时(ms)
    • 最终判定的状态码

性能优化:缓存策略与异步查询方案

缓存查询结果

  • 对于已成功的退款状态(如 REFUND_SUCCESS),可使用Redis缓存5分钟,避免重复调用支付接口。
  • 缓存key设计:refund:result:{refund_no}

异步协作

  • 消息队列:用户提交退款后,将“退款查询任务”推入RabbitMQ或Redis队列,Worker进程异步轮询,更新数据库。
  • 并行查询:当日志中有10笔退款需要查询时,使用 curl_multi_exec 或 Guzzle 的并发请求,将总耗时从 10秒 降至 1秒。

数据库优化

  • refund_records 表的 refund_statusquery_times 创建联合索引,加速定时任务扫描。
  • last_query_time 使用范围查询时,注意 EXPLAIN 确认是否走索引。

常见问题问答(Q&A)

Q1:退款查询时,支付平台返回“退款失败”,但用户表示已收到退款,怎么办?
A:首先对比支付平台的“交易流水”与本地数据库 channel_refund_id,若支付平台实际已成功,则可能是异步回调延迟,此时应手动调用支付平台“单笔退款查询接口”,获取最终状态,若确认失败,需引导用户联系支付平台或走人工退款流程。

Q2:部分退款场景下,如何设计退款查询逻辑?
A:原订单 status 需支持“部分退款”状态(如0订单已支付,1部分退款中),退款流水表每行记录一笔部分退款,且每笔退款的 order_no 相同,查询时需累加已退金额,若未达到订单总额,则订单状态保持为“部分退款”,否则改为“已退款”。

Q3:高频轮询是否会导致支付平台封禁API?
A:取决于平台规则,微信支付建议每秒不超过2次请求,支付宝每日限额存在动态调整,最佳实践是:

  • 限制同一商户的退款查询并发数(如使用限流组件 Redis INCR)。
  • 对单个退款单号的轮询间隔不低于30秒。
  • 增加 query_times 判断,达到阈值后自动停止轮询。

Q4:PHP脚本中如何避免因数据库连接超时导致查询中断?
A:在长时间运行的Cron脚本中,手动设置 PDOATTR_TIMEOUTMYSQL_ATTR_CONNECT_TIMEOUT,每处理一定数量退款后(如100条),关闭并重新建立数据库连接,释放内存。

Q5:跨国支付场景下,退款查询需注意什么?
A:需考虑时区问题(支付平台返回的时间是UTC还是本地),汇率波动(退款金额需按发起退款时的汇率计算),以及当地法律对“退款状态同步时限”的要求,建议使用 DateTimeZone 明确转换。


通过本篇文章的目录导读与问答设计,您可以系统化地掌握从数据库设计到支付平台集成、从安全机制到性能优化的完整方案,希望这能成为您PHP项目实现订单退款查询的实用参考。

抱歉,评论功能暂时关闭!