需求
取消预约分两种情况:
- 未支付取消订单,直接通知医院取消预约状态并更新相关数据,然后修改平台订单状态
- 已支付取消订单,退款给用户并在数据库中记录退款记录,通知医院取消预约状态并更新相关数据,然后修改平台订单状态
第01章-未支付取消预约
1、后端接口#
1.1、Controller#
FrontOrderInfoController中添加接口方法
@ApiOperation("取消预约")@ApiImplicitParam(name = "outTradeNo",value = "订单id", required = true)@GetMapping("/auth/cancelOrder/{outTradeNo}")public Result cancelOrder(@PathVariable("outTradeNo") String outTradeNo, HttpServletRequest request, HttpServletResponse response) {authContextHolder.checkAuth(request, response);orderInfoService.cancelOrder(outTradeNo);return Result.ok().message("预约已取消");}@ApiOperation("取消预约") @ApiImplicitParam(name = "outTradeNo",value = "订单id", required = true) @GetMapping("/auth/cancelOrder/{outTradeNo}") public Result cancelOrder(@PathVariable("outTradeNo") String outTradeNo, HttpServletRequest request, HttpServletResponse response) { authContextHolder.checkAuth(request, response); orderInfoService.cancelOrder(outTradeNo); return Result.ok().message("预约已取消"); }@ApiOperation("取消预约") @ApiImplicitParam(name = "outTradeNo",value = "订单id", required = true) @GetMapping("/auth/cancelOrder/{outTradeNo}") public Result cancelOrder(@PathVariable("outTradeNo") String outTradeNo, HttpServletRequest request, HttpServletResponse response) { authContextHolder.checkAuth(request, response); orderInfoService.cancelOrder(outTradeNo); return Result.ok().message("预约已取消"); }
1.2、Service#
在OrderStatusEnum增加两个状态:
CANCLE_UNREFUND(-2,"取消预约,退款中"),CANCLE_REFUND(-3,"取消预约,已退款"),CANCLE_UNREFUND(-2,"取消预约,退款中"), CANCLE_REFUND(-3,"取消预约,已退款"),CANCLE_UNREFUND(-2,"取消预约,退款中"), CANCLE_REFUND(-3,"取消预约,已退款"),
接口:OrderInfoService
/*** 根据订单号取消订单* @param outTradeNo*/void cancelOrder(String outTradeNo);/** * 根据订单号取消订单 * @param outTradeNo */ void cancelOrder(String outTradeNo);/** * 根据订单号取消订单 * @param outTradeNo */ void cancelOrder(String outTradeNo);
实现:OrderInfoServiceImpl
@Overridepublic void cancelOrder(String outTradeNo) {//获取订单OrderInfo orderInfo = this.selectByOutTradeNo(outTradeNo);//当前时间大于退号时间,不能取消预约DateTime quitTime = new DateTime(orderInfo.getQuitTime());if (quitTime.isBeforeNow()) {throw new GuiguException(ResultCodeEnum.CANCEL_ORDER_NO);}//调用医院端接口,同步数据Map<String, Object> params = new HashMap<>();params.put("hoscode", orderInfo.getHoscode());params.put("hosOrderId", orderInfo.getHosOrderId());params.put("hosScheduleId", orderInfo.getHosScheduleId());params.put("timestamp", HttpRequestHelper.getTimestamp());params.put("sign", HttpRequestHelper.getSign(params, "8af52af00baf6aec434109fc17164aae"));JSONObject jsonResult = HttpRequestHelper.sendRequest(params, "http://localhost:9998/order/updateCancelStatus");if(jsonResult.getInteger("code") != 200) {throw new GuiguException(ResultCodeEnum.CANCEL_ORDER_FAIL);}//是否支付if (orderInfo.getOrderStatus().intValue() == OrderStatusEnum.PAID.getStatus().intValue()) {//已支付,则退款log.info("退款");//wxPayService.refund(outTradeNo);//更改订单状态this.updateStatus(outTradeNo, OrderStatusEnum.CANCLE_UNREFUND.getStatus());}else{//更改订单状态this.updateStatus(outTradeNo, OrderStatusEnum.CANCLE.getStatus());}//TODO 根据医院返回数据,更新排班数量//TODO 给就诊人发送短信}@Override public void cancelOrder(String outTradeNo) { //获取订单 OrderInfo orderInfo = this.selectByOutTradeNo(outTradeNo); //当前时间大于退号时间,不能取消预约 DateTime quitTime = new DateTime(orderInfo.getQuitTime()); if (quitTime.isBeforeNow()) { throw new GuiguException(ResultCodeEnum.CANCEL_ORDER_NO); } //调用医院端接口,同步数据 Map<String, Object> params = new HashMap<>(); params.put("hoscode", orderInfo.getHoscode()); params.put("hosOrderId", orderInfo.getHosOrderId()); params.put("hosScheduleId", orderInfo.getHosScheduleId()); params.put("timestamp", HttpRequestHelper.getTimestamp()); params.put("sign", HttpRequestHelper.getSign(params, "8af52af00baf6aec434109fc17164aae")); JSONObject jsonResult = HttpRequestHelper.sendRequest(params, "http://localhost:9998/order/updateCancelStatus"); if(jsonResult.getInteger("code") != 200) { throw new GuiguException(ResultCodeEnum.CANCEL_ORDER_FAIL); } //是否支付 if (orderInfo.getOrderStatus().intValue() == OrderStatusEnum.PAID.getStatus().intValue()) { //已支付,则退款 log.info("退款"); //wxPayService.refund(outTradeNo); //更改订单状态 this.updateStatus(outTradeNo, OrderStatusEnum.CANCLE_UNREFUND.getStatus()); }else{ //更改订单状态 this.updateStatus(outTradeNo, OrderStatusEnum.CANCLE.getStatus()); } //TODO 根据医院返回数据,更新排班数量 //TODO 给就诊人发送短信 }@Override public void cancelOrder(String outTradeNo) { //获取订单 OrderInfo orderInfo = this.selectByOutTradeNo(outTradeNo); //当前时间大于退号时间,不能取消预约 DateTime quitTime = new DateTime(orderInfo.getQuitTime()); if (quitTime.isBeforeNow()) { throw new GuiguException(ResultCodeEnum.CANCEL_ORDER_NO); } //调用医院端接口,同步数据 Map<String, Object> params = new HashMap<>(); params.put("hoscode", orderInfo.getHoscode()); params.put("hosOrderId", orderInfo.getHosOrderId()); params.put("hosScheduleId", orderInfo.getHosScheduleId()); params.put("timestamp", HttpRequestHelper.getTimestamp()); params.put("sign", HttpRequestHelper.getSign(params, "8af52af00baf6aec434109fc17164aae")); JSONObject jsonResult = HttpRequestHelper.sendRequest(params, "http://localhost:9998/order/updateCancelStatus"); if(jsonResult.getInteger("code") != 200) { throw new GuiguException(ResultCodeEnum.CANCEL_ORDER_FAIL); } //是否支付 if (orderInfo.getOrderStatus().intValue() == OrderStatusEnum.PAID.getStatus().intValue()) { //已支付,则退款 log.info("退款"); //wxPayService.refund(outTradeNo); //更改订单状态 this.updateStatus(outTradeNo, OrderStatusEnum.CANCLE_UNREFUND.getStatus()); }else{ //更改订单状态 this.updateStatus(outTradeNo, OrderStatusEnum.CANCLE.getStatus()); } //TODO 根据医院返回数据,更新排班数量 //TODO 给就诊人发送短信 }
2、前端整合#
2.1、api#
orderInfo.js中添加方法
//取消预约cancelOrder(outTradeNo) {return request({url: `/front/order/orderInfo/auth/cancelOrder/${outTradeNo}`,method: 'get'})},//取消预约 cancelOrder(outTradeNo) { return request({ url: `/front/order/orderInfo/auth/cancelOrder/${outTradeNo}`, method: 'get' }) },//取消预约 cancelOrder(outTradeNo) { return request({ url: `/front/order/orderInfo/auth/cancelOrder/${outTradeNo}`, method: 'get' }) },
2.2、页面#
order/show.vue中添加方法
//取消预约方法cancelOrder() {this.$confirm('确定取消预约吗?', '提示', {confirmButtonText: '确定',cancelButtonText: '取消',type: 'warning',}).then(() => {// 点击确定,远程调用orderInfoApi.cancelOrder(this.orderInfo.outTradeNo).then((response) => {this.$message.success('取消成功')this.init()})})},//取消预约方法 cancelOrder() { this.$confirm('确定取消预约吗?', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning', }).then(() => { // 点击确定,远程调用 orderInfoApi.cancelOrder(this.orderInfo.outTradeNo).then((response) => { this.$message.success('取消成功') this.init() }) }) },//取消预约方法 cancelOrder() { this.$confirm('确定取消预约吗?', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning', }).then(() => { // 点击确定,远程调用 orderInfoApi.cancelOrder(this.orderInfo.outTradeNo).then((response) => { this.$message.success('取消成功') this.init() }) }) },
第02章-已支付取消预约
1、申请退款#
1.1、参考文档#
参考文档:申请退款API
注意:此步骤只是申请退款,具体退款是否成功,要通过退款查询接口获取,或通过退款回调通知获取。
1.2、调用退款业务#
OrderInfoServiceImpl
@Resourceprivate WxPayService wxPayService;@Resource private WxPayService wxPayService;@Resource private WxPayService wxPayService;
//已支付,则退款log.info("退款");wxPayService.refund(outTradeNo);//已支付,则退款 log.info("退款"); wxPayService.refund(outTradeNo);//已支付,则退款 log.info("退款"); wxPayService.refund(outTradeNo);
1.3、退款申请#
接口:WxPayService
/*** 退款* @param outTradeNo*/void refund(String outTradeNo);/** * 退款 * @param outTradeNo */ void refund(String outTradeNo);/** * 退款 * @param outTradeNo */ void refund(String outTradeNo);
实现:WxPayServiceImpl
@Resourceprivate RefundInfoService refundInfoService;@Overridepublic void refund(String outTradeNo) {// 初始化服务RefundService service = new RefundService.Builder().config(rsaAutoCertificateConfig).build();// 调用接口try {//获取订单OrderInfo orderInfo = orderInfoService.selectByOutTradeNo(outTradeNo);CreateRequest request = new CreateRequest();// 调用request.setXxx(val)设置所需参数,具体参数可见Request定义request.setOutTradeNo(outTradeNo);request.setOutRefundNo("TK_" + outTradeNo);AmountReq amount = new AmountReq();//amount.setTotal(orderInfo.getAmount().multiply(new BigDecimal(100)).intValue());amount.setTotal(1L);//1分钱amount.setRefund(1L);amount.setCurrency("CNY");request.setAmount(amount);// 调用接口Refund response = service.create(request);Status status = response.getStatus();// SUCCESS:退款成功(退款申请成功)// CLOSED:退款关闭// PROCESSING:退款处理中// ABNORMAL:退款异常if(Status.CLOSED.equals(status)){throw new GuiguException(ResultCodeEnum.FAIL.getCode(), "退款已关闭,无法退款");}else if(Status.ABNORMAL.equals(status)){throw new GuiguException(ResultCodeEnum.FAIL.getCode(), "退款异常");} else{//SUCCESS:退款成功(退款申请成功) || PROCESSING:退款处理中//记录支退款日志refundInfoService.saveRefundInfo(orderInfo, response);}} catch (HttpException e) { // 发送HTTP请求失败// 调用e.getHttpRequest()获取请求打印日志或上报监控,更多方法见HttpException定义log.error(e.getHttpRequest().toString());throw new GuiguException(ResultCodeEnum.FAIL);} catch (ServiceException e) { // 服务返回状态小于200或大于等于300,例如500// 调用e.getResponseBody()获取返回体打印日志或上报监控,更多方法见ServiceException定义log.error(e.getResponseBody());throw new GuiguException(ResultCodeEnum.FAIL);} catch (MalformedMessageException e) { // 服务返回成功,返回体类型不合法,或者解析返回体失败// 调用e.getMessage()获取信息打印日志或上报监控,更多方法见MalformedMessageException定义log.error(e.getMessage());throw new GuiguException(ResultCodeEnum.FAIL);}}@Resource private RefundInfoService refundInfoService; @Override public void refund(String outTradeNo) { // 初始化服务 RefundService service = new RefundService.Builder().config(rsaAutoCertificateConfig).build(); // 调用接口 try { //获取订单 OrderInfo orderInfo = orderInfoService.selectByOutTradeNo(outTradeNo); CreateRequest request = new CreateRequest(); // 调用request.setXxx(val)设置所需参数,具体参数可见Request定义 request.setOutTradeNo(outTradeNo); request.setOutRefundNo("TK_" + outTradeNo); AmountReq amount = new AmountReq(); //amount.setTotal(orderInfo.getAmount().multiply(new BigDecimal(100)).intValue()); amount.setTotal(1L);//1分钱 amount.setRefund(1L); amount.setCurrency("CNY"); request.setAmount(amount); // 调用接口 Refund response = service.create(request); Status status = response.getStatus(); // SUCCESS:退款成功(退款申请成功) // CLOSED:退款关闭 // PROCESSING:退款处理中 // ABNORMAL:退款异常 if(Status.CLOSED.equals(status)){ throw new GuiguException(ResultCodeEnum.FAIL.getCode(), "退款已关闭,无法退款"); }else if(Status.ABNORMAL.equals(status)){ throw new GuiguException(ResultCodeEnum.FAIL.getCode(), "退款异常"); } else{ //SUCCESS:退款成功(退款申请成功) || PROCESSING:退款处理中 //记录支退款日志 refundInfoService.saveRefundInfo(orderInfo, response); } } catch (HttpException e) { // 发送HTTP请求失败 // 调用e.getHttpRequest()获取请求打印日志或上报监控,更多方法见HttpException定义 log.error(e.getHttpRequest().toString()); throw new GuiguException(ResultCodeEnum.FAIL); } catch (ServiceException e) { // 服务返回状态小于200或大于等于300,例如500 // 调用e.getResponseBody()获取返回体打印日志或上报监控,更多方法见ServiceException定义 log.error(e.getResponseBody()); throw new GuiguException(ResultCodeEnum.FAIL); } catch (MalformedMessageException e) { // 服务返回成功,返回体类型不合法,或者解析返回体失败 // 调用e.getMessage()获取信息打印日志或上报监控,更多方法见MalformedMessageException定义 log.error(e.getMessage()); throw new GuiguException(ResultCodeEnum.FAIL); } }@Resource private RefundInfoService refundInfoService; @Override public void refund(String outTradeNo) { // 初始化服务 RefundService service = new RefundService.Builder().config(rsaAutoCertificateConfig).build(); // 调用接口 try { //获取订单 OrderInfo orderInfo = orderInfoService.selectByOutTradeNo(outTradeNo); CreateRequest request = new CreateRequest(); // 调用request.setXxx(val)设置所需参数,具体参数可见Request定义 request.setOutTradeNo(outTradeNo); request.setOutRefundNo("TK_" + outTradeNo); AmountReq amount = new AmountReq(); //amount.setTotal(orderInfo.getAmount().multiply(new BigDecimal(100)).intValue()); amount.setTotal(1L);//1分钱 amount.setRefund(1L); amount.setCurrency("CNY"); request.setAmount(amount); // 调用接口 Refund response = service.create(request); Status status = response.getStatus(); // SUCCESS:退款成功(退款申请成功) // CLOSED:退款关闭 // PROCESSING:退款处理中 // ABNORMAL:退款异常 if(Status.CLOSED.equals(status)){ throw new GuiguException(ResultCodeEnum.FAIL.getCode(), "退款已关闭,无法退款"); }else if(Status.ABNORMAL.equals(status)){ throw new GuiguException(ResultCodeEnum.FAIL.getCode(), "退款异常"); } else{ //SUCCESS:退款成功(退款申请成功) || PROCESSING:退款处理中 //记录支退款日志 refundInfoService.saveRefundInfo(orderInfo, response); } } catch (HttpException e) { // 发送HTTP请求失败 // 调用e.getHttpRequest()获取请求打印日志或上报监控,更多方法见HttpException定义 log.error(e.getHttpRequest().toString()); throw new GuiguException(ResultCodeEnum.FAIL); } catch (ServiceException e) { // 服务返回状态小于200或大于等于300,例如500 // 调用e.getResponseBody()获取返回体打印日志或上报监控,更多方法见ServiceException定义 log.error(e.getResponseBody()); throw new GuiguException(ResultCodeEnum.FAIL); } catch (MalformedMessageException e) { // 服务返回成功,返回体类型不合法,或者解析返回体失败 // 调用e.getMessage()获取信息打印日志或上报监控,更多方法见MalformedMessageException定义 log.error(e.getMessage()); throw new GuiguException(ResultCodeEnum.FAIL); } }
1.4、记录退款记录#
接口:RefundInfoService
/*** 保存退款记录* @param orderInfo* @param response*/void saveRefundInfo(OrderInfo orderInfo, Refund response);/** * 保存退款记录 * @param orderInfo * @param response */ void saveRefundInfo(OrderInfo orderInfo, Refund response);/** * 保存退款记录 * @param orderInfo * @param response */ void saveRefundInfo(OrderInfo orderInfo, Refund response);
实现:RefundInfoServiceImpl
@Overridepublic void saveRefundInfo(OrderInfo orderInfo, Refund response) {// 保存退款记录RefundInfo refundInfo = new RefundInfo();refundInfo.setOutTradeNo(orderInfo.getOutTradeNo());refundInfo.setOrderId(orderInfo.getId());refundInfo.setPaymentType(PaymentTypeEnum.WEIXIN.getStatus());refundInfo.setTradeNo(response.getOutRefundNo());refundInfo.setTotalAmount(new BigDecimal(response.getAmount().getRefund()));refundInfo.setSubject(orderInfo.getTitle());refundInfo.setRefundStatus(RefundStatusEnum.UNREFUND.getStatus());//退款中baseMapper.insert(refundInfo);}@Override public void saveRefundInfo(OrderInfo orderInfo, Refund response) { // 保存退款记录 RefundInfo refundInfo = new RefundInfo(); refundInfo.setOutTradeNo(orderInfo.getOutTradeNo()); refundInfo.setOrderId(orderInfo.getId()); refundInfo.setPaymentType(PaymentTypeEnum.WEIXIN.getStatus()); refundInfo.setTradeNo(response.getOutRefundNo()); refundInfo.setTotalAmount(new BigDecimal(response.getAmount().getRefund())); refundInfo.setSubject(orderInfo.getTitle()); refundInfo.setRefundStatus(RefundStatusEnum.UNREFUND.getStatus());//退款中 baseMapper.insert(refundInfo); }@Override public void saveRefundInfo(OrderInfo orderInfo, Refund response) { // 保存退款记录 RefundInfo refundInfo = new RefundInfo(); refundInfo.setOutTradeNo(orderInfo.getOutTradeNo()); refundInfo.setOrderId(orderInfo.getId()); refundInfo.setPaymentType(PaymentTypeEnum.WEIXIN.getStatus()); refundInfo.setTradeNo(response.getOutRefundNo()); refundInfo.setTotalAmount(new BigDecimal(response.getAmount().getRefund())); refundInfo.setSubject(orderInfo.getTitle()); refundInfo.setRefundStatus(RefundStatusEnum.UNREFUND.getStatus());//退款中 baseMapper.insert(refundInfo); }
2、退款回调#
前面我们已经申请了退款,具体退款是否成功,要通过退款查询接口获取,或通过退款回调通知获取。这里我们学习退款通知如何实现。
2.1、内网穿透#
资料:资料>微信支付>小米球ngrok.rar
参考小米球使用教程
开通内网穿透服务,获取内网穿透服务地址
2.2、配置回调地址#
将配置文件中的开发参数wxpay.notify-refund-url
的主机地址部分
修改为自己的内网穿透地址,例如:
#退款通知回调地址:申请退款是提交这个参数wxpay.notify-refund-url=http://agxnyzl04y90.ngrok.xiaomiqiu123.top/api/order/wxpay/refunds/notify#退款通知回调地址:申请退款是提交这个参数 wxpay.notify-refund-url=http://agxnyzl04y90.ngrok.xiaomiqiu123.top/api/order/wxpay/refunds/notify#退款通知回调地址:申请退款是提交这个参数 wxpay.notify-refund-url=http://agxnyzl04y90.ngrok.xiaomiqiu123.top/api/order/wxpay/refunds/notify
2.3、配置请求参数#
调用退款申请API时添加参数notify-refund-url
参数。
WxPayServiceImpl类中的refund方法中添加如下参数:
request.setNotifyUrl(wxPayConfig.getNotifyRefundUrl());request.setNotifyUrl(wxPayConfig.getNotifyRefundUrl());request.setNotifyUrl(wxPayConfig.getNotifyRefundUrl());
2.4、更新退款状态#
接口:RefundInfoService
/*** 更新退款状态* @param refundNotification* @param refund*/void updateRefundInfoStatus(RefundNotification refundNotification, RefundStatusEnum refund);/** * 更新退款状态 * @param refundNotification * @param refund */ void updateRefundInfoStatus(RefundNotification refundNotification, RefundStatusEnum refund);/** * 更新退款状态 * @param refundNotification * @param refund */ void updateRefundInfoStatus(RefundNotification refundNotification, RefundStatusEnum refund);
实现:RefundInfoServiceImpl
@Overridepublic void updateRefundInfoStatus(RefundNotification refundNotification, RefundStatusEnum refundStatus) {LambdaQueryWrapper<RefundInfo> queryWrapper = new LambdaQueryWrapper<>();queryWrapper.eq(RefundInfo::getOutTradeNo, outTradeNo);RefundInfo refundInfo = new RefundInfo();refundInfo.setRefundStatus(refundStatus.getStatus());refundInfo.setCallbackContent(refundNotification.toString());refundInfo.setCallbackTime(new Date());baseMapper.update(refundInfo, queryWrapper);}@Override public void updateRefundInfoStatus(RefundNotification refundNotification, RefundStatusEnum refundStatus) { LambdaQueryWrapper<RefundInfo> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(RefundInfo::getOutTradeNo, outTradeNo); RefundInfo refundInfo = new RefundInfo(); refundInfo.setRefundStatus(refundStatus.getStatus()); refundInfo.setCallbackContent(refundNotification.toString()); refundInfo.setCallbackTime(new Date()); baseMapper.update(refundInfo, queryWrapper); }@Override public void updateRefundInfoStatus(RefundNotification refundNotification, RefundStatusEnum refundStatus) { LambdaQueryWrapper<RefundInfo> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(RefundInfo::getOutTradeNo, outTradeNo); RefundInfo refundInfo = new RefundInfo(); refundInfo.setRefundStatus(refundStatus.getStatus()); refundInfo.setCallbackContent(refundNotification.toString()); refundInfo.setCallbackTime(new Date()); baseMapper.update(refundInfo, queryWrapper); }
2.5、引入依赖#
<!--回调验签代码需要--><dependency><groupId>com.squareup.okhttp3</groupId><artifactId>okhttp</artifactId><version>3.14.9</version></dependency><!--回调验签代码需要--> <dependency> <groupId>com.squareup.okhttp3</groupId> <artifactId>okhttp</artifactId> <version>3.14.9</version> </dependency><!--回调验签代码需要--> <dependency> <groupId>com.squareup.okhttp3</groupId> <artifactId>okhttp</artifactId> <version>3.14.9</version> </dependency>
2.6、引入工具类#
将资料目录中的请求参数工具放入service-order微服务的utils包中
资料:资料>微信支付>RequestUtils.java
代码参考:GitHub – wechatpay-apiv3/wechatpay-java: 微信支付 APIv3 的官方 Java Library
2.7、开发回调接口#
创建controller.api包,创建ApiWXPayController类:
解析回调参数、验签、请求内容解密、获取退款结果、记录退款日志
package com.atguigu.syt.order.controller.api;/*** 接收微信发送给服务器的远程回调*/@Api(tags = "微信支付接口")@Controller@RequestMapping("/api/order/wxpay")@Slf4jpublic class ApiWXPayController {@Resourceprivate RefundInfoService refundInfoService;@Resourceprivate OrderInfoService orderInfoService;@Resourceprivate RSAAutoCertificateConfig rsaAutoCertificateConfig;/*** 退款结果通知* 退款状态改变后,微信会把相关退款结果发送给商户。*/@PostMapping("/refunds/notify")public String callback(HttpServletRequest request, HttpServletResponse response){log.info("退款通知执行");Map<String, String> map = new HashMap<>();//应答对象try {/*使用回调通知请求的数据,构建 RequestParam。HTTP 头 Wechatpay-SignatureHTTP 头 Wechatpay-NonceHTTP 头 Wechatpay-TimestampHTTP 头 Wechatpay-SerialHTTP 头 Wechatpay-Signature-TypeHTTP 请求体 body。切记使用原始报文,不要用 JSON 对象序列化后的字符串,避免验签的 body 和原文不一致。*/// 构造 RequestParamString signature = request.getHeader("Wechatpay-Signature");String nonce = request.getHeader("Wechatpay-Nonce");String timestamp = request.getHeader("Wechatpay-Timestamp");String wechatPayCertificateSerialNumber = request.getHeader("Wechatpay-Serial");//请求体String requestBody = RequestUtils.readData(request);RequestParam requestParam = new RequestParam.Builder().serialNumber(wechatPayCertificateSerialNumber).nonce(nonce).signature(signature).timestamp(timestamp).body(requestBody).build();// 初始化 NotificationParserNotificationParser parser = new NotificationParser(rsaAutoCertificateConfig);// 验签、解密并转换成 TransactionRefundNotification refundNotification = parser.parse(requestParam, RefundNotification.class);String orderTradeNo = refundNotification.getOutTradeNo();Status refundStatus = refundNotification.getRefundStatus();if("SUCCESS".equals(refundStatus.toString())){log.info("更新退款记录:已退款");//退款状态refundInfoService.updateRefundInfoStatus(refundNotification, RefundStatusEnum.REFUND);//订单状态orderInfoService.updateStatus(orderTradeNo, OrderStatusEnum.CANCLE_REFUND.getStatus());}//成功应答response.setStatus(200);map.put("code", "SUCCESS");return JSONObject.toJSONString(map);} catch (Exception e) {log.error(ExceptionUtils.getStackTrace(e));//失败应答response.setStatus(500);map.put("code", "ERROR");map.put("message", "失败");return JSONObject.toJSONString(map);}}}package com.atguigu.syt.order.controller.api; /** * 接收微信发送给服务器的远程回调 */ @Api(tags = "微信支付接口") @Controller @RequestMapping("/api/order/wxpay") @Slf4j public class ApiWXPayController { @Resource private RefundInfoService refundInfoService; @Resource private OrderInfoService orderInfoService; @Resource private RSAAutoCertificateConfig rsaAutoCertificateConfig; /** * 退款结果通知 * 退款状态改变后,微信会把相关退款结果发送给商户。 */ @PostMapping("/refunds/notify") public String callback(HttpServletRequest request, HttpServletResponse response){ log.info("退款通知执行"); Map<String, String> map = new HashMap<>();//应答对象 try { /*使用回调通知请求的数据,构建 RequestParam。 HTTP 头 Wechatpay-Signature HTTP 头 Wechatpay-Nonce HTTP 头 Wechatpay-Timestamp HTTP 头 Wechatpay-Serial HTTP 头 Wechatpay-Signature-Type HTTP 请求体 body。切记使用原始报文,不要用 JSON 对象序列化后的字符串,避免验签的 body 和原文不一致。*/ // 构造 RequestParam String signature = request.getHeader("Wechatpay-Signature"); String nonce = request.getHeader("Wechatpay-Nonce"); String timestamp = request.getHeader("Wechatpay-Timestamp"); String wechatPayCertificateSerialNumber = request.getHeader("Wechatpay-Serial"); //请求体 String requestBody = RequestUtils.readData(request); RequestParam requestParam = new RequestParam.Builder() .serialNumber(wechatPayCertificateSerialNumber) .nonce(nonce) .signature(signature) .timestamp(timestamp) .body(requestBody) .build(); // 初始化 NotificationParser NotificationParser parser = new NotificationParser(rsaAutoCertificateConfig); // 验签、解密并转换成 Transaction RefundNotification refundNotification = parser.parse(requestParam, RefundNotification.class); String orderTradeNo = refundNotification.getOutTradeNo(); Status refundStatus = refundNotification.getRefundStatus(); if("SUCCESS".equals(refundStatus.toString())){ log.info("更新退款记录:已退款"); //退款状态 refundInfoService.updateRefundInfoStatus(refundNotification, RefundStatusEnum.REFUND); //订单状态 orderInfoService.updateStatus(orderTradeNo, OrderStatusEnum.CANCLE_REFUND.getStatus()); } //成功应答 response.setStatus(200); map.put("code", "SUCCESS"); return JSONObject.toJSONString(map); } catch (Exception e) { log.error(ExceptionUtils.getStackTrace(e)); //失败应答 response.setStatus(500); map.put("code", "ERROR"); map.put("message", "失败"); return JSONObject.toJSONString(map); } } }package com.atguigu.syt.order.controller.api; /** * 接收微信发送给服务器的远程回调 */ @Api(tags = "微信支付接口") @Controller @RequestMapping("/api/order/wxpay") @Slf4j public class ApiWXPayController { @Resource private RefundInfoService refundInfoService; @Resource private OrderInfoService orderInfoService; @Resource private RSAAutoCertificateConfig rsaAutoCertificateConfig; /** * 退款结果通知 * 退款状态改变后,微信会把相关退款结果发送给商户。 */ @PostMapping("/refunds/notify") public String callback(HttpServletRequest request, HttpServletResponse response){ log.info("退款通知执行"); Map<String, String> map = new HashMap<>();//应答对象 try { /*使用回调通知请求的数据,构建 RequestParam。 HTTP 头 Wechatpay-Signature HTTP 头 Wechatpay-Nonce HTTP 头 Wechatpay-Timestamp HTTP 头 Wechatpay-Serial HTTP 头 Wechatpay-Signature-Type HTTP 请求体 body。切记使用原始报文,不要用 JSON 对象序列化后的字符串,避免验签的 body 和原文不一致。*/ // 构造 RequestParam String signature = request.getHeader("Wechatpay-Signature"); String nonce = request.getHeader("Wechatpay-Nonce"); String timestamp = request.getHeader("Wechatpay-Timestamp"); String wechatPayCertificateSerialNumber = request.getHeader("Wechatpay-Serial"); //请求体 String requestBody = RequestUtils.readData(request); RequestParam requestParam = new RequestParam.Builder() .serialNumber(wechatPayCertificateSerialNumber) .nonce(nonce) .signature(signature) .timestamp(timestamp) .body(requestBody) .build(); // 初始化 NotificationParser NotificationParser parser = new NotificationParser(rsaAutoCertificateConfig); // 验签、解密并转换成 Transaction RefundNotification refundNotification = parser.parse(requestParam, RefundNotification.class); String orderTradeNo = refundNotification.getOutTradeNo(); Status refundStatus = refundNotification.getRefundStatus(); if("SUCCESS".equals(refundStatus.toString())){ log.info("更新退款记录:已退款"); //退款状态 refundInfoService.updateRefundInfoStatus(refundNotification, RefundStatusEnum.REFUND); //订单状态 orderInfoService.updateStatus(orderTradeNo, OrderStatusEnum.CANCLE_REFUND.getStatus()); } //成功应答 response.setStatus(200); map.put("code", "SUCCESS"); return JSONObject.toJSONString(map); } catch (Exception e) { log.error(ExceptionUtils.getStackTrace(e)); //失败应答 response.setStatus(500); map.put("code", "ERROR"); map.put("message", "失败"); return JSONObject.toJSONString(map); } } }