网站接入微信支付后如何实现退款和取消预约?

需求

取消预约分两种情况:

  • 未支付取消订单,直接通知医院取消预约状态并更新相关数据,然后修改平台订单状态
  • 已支付取消订单,退款给用户并在数据库中记录退款记录,通知医院取消预约状态并更新相关数据,然后修改平台订单状态

第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

@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 给就诊人发送短信

}
@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

注意:此步骤只是申请退款,具体退款是否成功,要通过退款查询接口获取,或通过退款回调通知获取。

image-20230318152550929

退款SDK:wechatpay-java/service/src/example/java/com/wechat/pay/java/service/refund at main · wechatpay-apiv3/wechatpay-java · GitHub

image-20230301042206981

1.2、调用退款业务#

OrderInfoServiceImpl

@Resource
private 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

@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);
    }
}
@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

@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);
}
@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

@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);
}
@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

证书和回调报文解密-接口规则

image-20230320135113609

签名验证-接口规则

image-20230320120403904

代码参考:GitHub – wechatpay-apiv3/wechatpay-java: 微信支付 APIv3 的官方 Java Library

image-20230320134319244

2.7、开发回调接口#

创建controller.api包,创建ApiWXPayController类:

解析回调参数、验签、请求内容解密、获取退款结果、记录退款日志

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);
        }
    }
}
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); } } }

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MY09BM4I' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片