package com.ruoyi.system.service.impl; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONObject; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.icbc.api.IcbcApiException; import com.icbc.api.IcbcConstants; import com.icbc.api.utils.IcbcSignature; import com.icbc.api.utils.WebUtils; import com.ruoyi.system.config.ICBCConfig; import com.ruoyi.system.enums.BigGiftPackageOrderStatusEnum; import com.ruoyi.system.enums.PhysicalGoodsOrderStatusEnum; import com.ruoyi.system.model.TBigGiftPackageOrder; import com.ruoyi.system.model.TPhysicalGoodsOrder; import com.ruoyi.system.service.*; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.time.LocalDateTime; import java.util.HashMap; import java.util.List; import java.util.Map; /** * 工商银行支付回调服务实现 * * @author mitao * @since 2025-12-16 */ @Service @Slf4j @RequiredArgsConstructor(onConstructor_ = @Lazy) public class ICBCNotifyServiceImpl implements ICBCNotifyService { private final ICBCConfig icbcConfig; private final TBigGiftPackageOrderService bigGiftPackageOrderService; private final TPhysicalGoodsOrderService physicalGoodsOrderService; private final TCommissionTransactionFlowService commissionTransactionFlowService; @Override @Transactional(rollbackFor = Exception.class) public void bigGiftPayNotify(HttpServletRequest request, HttpServletResponse response) { boolean signResult = false; String msgId = null; try { JSONObject bizContent = parseRequest(request); if (bizContent == null) { log.error("【工商银行】大礼包支付回调参数解析失败"); sendResponse(response, false, null); return; } log.info("【工商银行】大礼包支付回调参数:{}", bizContent.toJSONString()); String payOrderCode = bizContent.getString("out_trade_no"); String returnCode = bizContent.getString("return_code"); msgId = bizContent.getString("msg_id"); String orderId = bizContent.getString("order_id"); // 验签 if (validateSignature(request, icbcConfig.getNotifyUrlMethod() + "/big-gift/pay-notify")) { signResult = true; log.info("支付订单号{}【工商银行】大礼包支付回调签名验证成功", payOrderCode); // 根据支付订单号查询所有订单 LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); wrapper.eq(TBigGiftPackageOrder::getPayOrderCode, payOrderCode); List orders = bigGiftPackageOrderService.list(wrapper); if ("0".equals(returnCode)) { log.info("支付订单号{}【工商银行】大礼包支付成功", payOrderCode); if (orders == null || orders.isEmpty()) { log.error("支付订单号{}未找到对应的大礼包订单", payOrderCode); sendResponse(response, signResult, msgId); return; } // 处理每个订单 for (TBigGiftPackageOrder order : orders) { processBigGiftPayment(order, orderId); } } else { log.error("支付订单号{}【工商银行】大礼包支付失败,错误信息:{}", payOrderCode, bizContent.getString("return_msg")); //更新订单状态为已取消 orders.forEach(item->item.setStatus(BigGiftPackageOrderStatusEnum.CANCELED.getCode())); bigGiftPackageOrderService.updateBatchById(orders); } } else { log.error("支付订单号{}【工商银行】大礼包支付回调签名验证失败", payOrderCode); } sendResponse(response, signResult, msgId); } catch (Exception e) { log.error("【工商银行】大礼包支付回调处理失败: {}", e.getMessage(), e); sendResponse(response, false, msgId); } } @Override @Transactional(rollbackFor = Exception.class) public void bigGiftFinalPayNotify(HttpServletRequest request, HttpServletResponse response) { boolean signResult = false; String msgId = null; try { JSONObject bizContent = parseRequest(request); if (bizContent == null) { log.error("【工商银行】大礼包尾款支付回调参数解析失败"); sendResponse(response, false, null); return; } log.info("【工商银行】大礼包尾款支付回调参数:{}", bizContent.toJSONString()); String finalPayOrderCode = bizContent.getString("out_trade_no"); String returnCode = bizContent.getString("return_code"); msgId = bizContent.getString("msg_id"); String orderId = bizContent.getString("order_id"); // 验签 if (validateSignature(request, icbcConfig.getNotifyUrlMethod() + "/big-gift/final-pay-notify")) { signResult = true; log.info("尾款支付订单号{}【工商银行】大礼包尾款支付回调签名验证成功", finalPayOrderCode); if ("0".equals(returnCode)) { log.info("尾款支付订单号{}【工商银行】大礼包尾款支付成功", finalPayOrderCode); // 根据尾款支付订单号查询订单(尾款支付是单个订单) // 注意:这里需要根据实际业务逻辑查询订单,可能需要在订单表中添加尾款支付订单号字段 // 暂时使用订单编码查询 LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); wrapper.eq(TBigGiftPackageOrder::getFinalPaymentSerialNumber, finalPayOrderCode); TBigGiftPackageOrder order = bigGiftPackageOrderService.getOne(wrapper); if (order == null) { log.error("尾款支付订单号{}未找到对应的大礼包订单", finalPayOrderCode); sendResponse(response, signResult, msgId); return; } // 更新订单状态为"完成支付" order.setStatus(BigGiftPackageOrderStatusEnum.PAID.getCode()); order.setFinalPaymentTime(LocalDateTime.now()); order.setFinalPaymentThirdSerialNumber(orderId); order.setPaymentPrice(order.getPaymentPrice().add(order.getFinalPayment())); bigGiftPackageOrderService.updateById(order); log.info("订单{}状态更新为完成支付", order.getCode()); // 尾款支付完成后,必须计算分佣 commissionTransactionFlowService.calculateAndSaveCommission( order.getId(), 1, // 订单类型:1=大礼包 order.getOrderPrice(), order.getAppUserId() ); log.info("订单{}分佣计算完成", order.getCode()); } else { log.error("尾款支付订单号{}【工商银行】大礼包尾款支付失败,错误信息:{}", finalPayOrderCode, bizContent.getString("return_msg")); } } else { log.error("尾款支付订单号{}【工商银行】大礼包尾款支付回调签名验证失败", finalPayOrderCode); } sendResponse(response, signResult, msgId); } catch (Exception e) { log.error("【工商银行】大礼包尾款支付回调处理失败: {}", e.getMessage(), e); sendResponse(response, false, msgId); } } @Override @Transactional(rollbackFor = Exception.class) public void physicalPayNotify(HttpServletRequest request, HttpServletResponse response) { boolean signResult = false; String msgId = null; try { JSONObject bizContent = parseRequest(request); if (bizContent == null) { log.error("【工商银行】实物商品支付回调参数解析失败"); sendResponse(response, false, null); return; } log.info("【工商银行】实物商品支付回调参数:{}", bizContent.toJSONString()); String orderCode = bizContent.getString("out_trade_no"); String returnCode = bizContent.getString("return_code"); msgId = bizContent.getString("msg_id"); String orderId = bizContent.getString("order_id"); // 验签 if (validateSignature(request, icbcConfig.getNotifyUrlMethod() + "/physical/pay-notify")) { signResult = true; log.info("订单{}【工商银行】实物商品支付回调签名验证成功", orderCode); // 根据订单编码查询订单 LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); wrapper.eq(TPhysicalGoodsOrder::getCode, orderCode); TPhysicalGoodsOrder order = physicalGoodsOrderService.getOne(wrapper); if ("0".equals(returnCode)) { log.info("订单{}【工商银行】实物商品支付成功", orderCode); if (order == null) { log.error("订单{}未找到对应的实物商品订单", orderCode); sendResponse(response, signResult, msgId); return; } // 更新订单状态为"完成支付" order.setStatus(PhysicalGoodsOrderStatusEnum.PAID.getCode()); order.setPaymentTime(LocalDateTime.now()); order.setPaymentSerialNumber(orderId); physicalGoodsOrderService.updateById(order); log.info("订单{}状态更新为完成支付", orderCode); } else { log.error("订单{}【工商银行】实物商品支付失败,错误信息:{}", orderCode, bizContent.getString("return_msg")); order.setStatus(PhysicalGoodsOrderStatusEnum.CANCELED.getCode()); physicalGoodsOrderService.updateById(order); } } else { log.error("订单{}【工商银行】实物商品支付回调签名验证失败", orderCode); } sendResponse(response, signResult, msgId); } catch (Exception e) { log.error("【工商银行】实物商品支付回调处理失败: {}", e.getMessage(), e); sendResponse(response, false, msgId); } } /** * 处理大礼包支付(定金或全款) */ private void processBigGiftPayment(TBigGiftPackageOrder order, String orderId) { Integer paymentType = order.getPaymentType(); if (paymentType == 1) { // 订金+尾款模式,更新状态为"已付订金" order.setStatus(BigGiftPackageOrderStatusEnum.DEPOSIT_PAID.getCode()); order.setDepositTime(LocalDateTime.now()); order.setDepositPaymentThirdSerialNumber(orderId); bigGiftPackageOrderService.updateById(order); log.info("订单{}状态更新为已付订金,不计算分佣", order.getCode()); } else if (paymentType == 2) { // 全款模式,更新状态为"完成支付" order.setStatus(BigGiftPackageOrderStatusEnum.PAID.getCode()); order.setDepositTime(LocalDateTime.now()); order.setDepositPaymentThirdSerialNumber(orderId); bigGiftPackageOrderService.updateById(order); log.info("订单{}状态更新为完成支付", order.getCode()); // 判断是否启用生效日 Boolean effectDayFlag = order.getEffectDayFlag(); if (effectDayFlag == null || !effectDayFlag) { // 未启用生效日,立即计算分佣 commissionTransactionFlowService.calculateAndSaveCommission( order.getId(), 1, // 订单类型:1=大礼包 order.getOrderPrice(), order.getAppUserId() ); log.info("订单{}未启用生效日,立即计算分佣", order.getCode()); } else { // 启用生效日,不计算分佣(由定时任务处理) log.info("订单{}启用生效日,延迟计算分佣", order.getCode()); } } } /** * 解析请求中的回调参数 * * @param request HTTP请求对象 * @return 解析后的JSON对象 */ private JSONObject parseRequest(HttpServletRequest request) { try { String bizContentStr = request.getParameter("biz_content"); return JSON.parseObject(bizContentStr); } catch (Exception e) { log.error("【工商银行】解析回调参数失败: {}", e.getMessage()); return null; } } /** * 验证签名 * * @param request 回调请求 * @param path 回调URL路径 * @return 签名验证结果 */ private boolean validateSignature(HttpServletRequest request, String path) { Map params = new HashMap<>(); params.put("from", request.getParameter("from")); params.put("api", request.getParameter("api")); params.put("app_id", request.getParameter("app_id")); params.put("charset", request.getParameter("charset")); params.put("format", request.getParameter("format")); params.put("timestamp", request.getParameter("timestamp")); params.put("biz_content", request.getParameter("biz_content")); params.put("sign_type", request.getParameter("sign_type")); String sign = request.getParameter("sign"); String signStr = WebUtils.buildOrderedSignStr(path, params); try { return IcbcSignature.verify(signStr, IcbcConstants.SIGN_TYPE_RSA, icbcConfig.getApiGwPublicKey(), params.get("charset"), sign); } catch (IcbcApiException e) { log.error("【工商银行】签名验证异常: {}", e.getMessage(), e); return false; } } /** * 发送回调响应 * * @param response HTTP响应对象 * @param checkResult 签名验证结果 * @param msgId 消息ID */ private void sendResponse(HttpServletResponse response, boolean checkResult, String msgId) { String responseContent = buildNotifyResponse(checkResult, msgId); response.setContentType("application/json; charset=utf-8"); try { response.getWriter().write(responseContent); log.info("【工商银行】支付回调应答成功"); } catch (IOException e) { log.error("【工商银行】支付回调应答失败: {}", e.getMessage(), e); } } /** * 构建回调响应内容 * 工行要求保证返回参数的绝对顺序,故直接返回固定字符串不定义对象 * 接口文档地址 https://open.icbc.com.cn/icbc/apip/api_detail.html?apiId=10000000000000137000&baseUrl=%2Fcardbusiness%2Faggregatepay%2Fb2c%2Fonline&resUrl=%2Fconsumepurchase&version=V1&apiName=%E8%81%9A%E5%90%88%E6%94%AF%E4%BB%98B2C%E7%BA%BF%E4%B8%8A%E6%B6%88%E8%B4%B9%E4%B8%8B%E5%8D%95&serviceId=P0067&resourceId=10000000000000009800 * * @param checkResult 签名验证结果 * @param msgId 消息ID * @return 回调响应字符串 */ private String buildNotifyResponse(boolean checkResult, String msgId) { String responseBizContent = checkResult ? "{\"return_code\":0,\"return_msg\":\"success\",\"msg_id\":\"" + msgId + "\"}" : "{\"return_code\":-1,\"return_msg\":\"icbc sign not pass.\"}"; String signStr = "\"response_biz_content\":" + responseBizContent + ",\"sign_type\":\"RSA2\""; try { String sign = IcbcSignature.sign(signStr, IcbcConstants.SIGN_TYPE_RSA2, icbcConfig.getMyPrivateKey(), "UTF-8", ""); return "{" + signStr + ",\"sign\":\"" + sign + "\"}"; } catch (IcbcApiException e) { log.error("【工商银行】回调应答签名失败: {}", e.getMessage(), e); return "{\"response_biz_content\":{\"return_code\":-1,\"return_msg\":\"response sign error\"},\"sign_type\":\"RSA2\",\"sign\":\"\"}"; } } }