SpringBoot对接微信小程序支付功能开发(三,退款功能)

接着上一篇: SpringBoot对接微信小程序支付功能开发(二,支付回调功能)

1,申请退款接口调用

    /**
     * 申请退款url
     */
    String V3_REFUNDS = "https://api.mch.weixin.qq.com/v3/refund/domestic/refunds";
  /**
     * @Description: 申请退款
     * @Param: [transactionId] 支付订单号
     * @return: java.lang.String
     * @Author: XQD
     * @Date:2022/8/25 13:25
     */
    @Override
    public String refundsOrder(String transactionId) throws Exception {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectMapper objectMapper = new ObjectMapper();
        // 计算扣除的钱
        ObjectNode rootNode = objectMapper.createObjectNode();
        rootNode.put("transaction_id", transactionId)
                .put("out_refund_no", System.currentTimeMillis() + "")
                .put("funds_account", "AVAILABLE")
                .put("notify_url", WechatPayConstant.REFUNDS_URL);
        rootNode.putObject("amount")
                .put("refund", "退款金额")//必须为整数
                .put("total", "原订单金额")//必须为整数
                .put("currency", "CNY");
        objectMapper.writeValue(bos, rootNode);

        CloseableHttpClient httpClient = WxPayUtil.getHttpClient();
        HttpPost httpPost = new HttpPost(WechatPayConstant.V3_REFUNDS);
        httpPost.addHeader("Accept", "application/json");
        httpPost.addHeader("Content-type", "application/json; charset=utf-8");
        httpPost.setEntity(new StringEntity(bos.toString("UTF-8"), "UTF-8"));
        CloseableHttpResponse response = httpClient.execute(httpPost);
        String bodyAsString = EntityUtils.toString(response.getEntity());
        log.info("申请退款返回报文信息 = {}", bodyAsString);
        JSONObject jsonObject = JSON.parseObject(bodyAsString);
        //返回成功的信息是不会有code值,如果能取到说明申请失败
        String code = jsonObject.getString("code");
        if (StringUtils.isNotBlank(code)) {
            return "ERROR-" + jsonObject.getString("message");
        }
        //退款信息存入数据库...
        return bodyAsString;
    }

2,退款结果回调功能

package com.office.miniapp.controller;

import cn.hutool.core.date.DateTime;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.office.miniapp.constants.ProjectConstant;
import com.office.miniapp.constants.WechatPayConstant;
import com.office.miniapp.utils.HttpClientUtil;
import com.office.miniapp.utils.WxPayUtil;
import lombok.extern.slf4j.Slf4j;

import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;

/**
 * @ClassName: TdRefundsController
 * @Description: TODO
 * @Authror: XQD
 * @Date: 2022/8/25 11:22
 */
@RestController
@RequestMapping("/refunds")
public class TdRefundsController {

    @PostMapping("/callback")
    public Object callback(HttpServletRequest request) {
        log.info("【退款通知回调】");
        System.out.println("Wechatpay-Timestamp:" + request.getHeader("Wechatpay-Timestamp"));
        System.out.println("Wechatpay-Nonce:" + request.getHeader("Wechatpay-Nonce"));
        System.out.println("Wechatpay-Signature:" + request.getHeader("Wechatpay-Signature"));
        System.out.println("Wechatpay-Serial:" + request.getHeader("Wechatpay-Serial"));
        Map result = new HashMap();
        result.put("code", "FAIL");
        try {
            StringBuilder signStr = new StringBuilder();
            // 应答时间戳\n
            signStr.append(request.getHeaders("Wechatpay-Timestamp")).append("\n");
            // 应答随机串\n
            signStr.append(request.getHeaders("Wechatpay-Nonce")).append("\n");
            // 应答报文主体\n
            BufferedReader br = request.getReader();
            String str = null;
            StringBuilder builder = new StringBuilder();
            while ((str = br.readLine()) != null) {
                builder.append(str);
            }
            log.info("退款应答报文主体:{}", builder.toString());
            signStr.append(builder.toString()).append("\n");
            // 第一步:验证签名
            if (WxPayUtil.signVerify(request.getHeader("Wechatpay-Serial"), signStr.toString(), request.getHeader("Wechatpay-Signature"))) {
                result.put("message", "sign error");
                return result;
            }
            // 第二步:解密密文
            String decryptOrder = WxPayUtil.decryptOrder(builder.toString());
            log.info("解密后退款信息:{}", decryptOrder);
            JSONObject jsonObject = JSON.parseObject(decryptOrder);
            String mchid = jsonObject.getString("mchid");
            if (!mchid.equals(WechatPayConstant.MCH_ID)) {
                log.error("mchid对不上:{}", mchid);
                result.put("message", "mchid对不上");
                return result;
            }
            //进行业务逻辑处理,修改申请退款时存入数据库的退款状态,检查退款信息是否正确等...
            result.put("code", "SUCCESS");
            result.put("message", "成功");
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

}

3,主动查询退款结果

接口地址

    /**
     * 查询退款订单url
     */
    String V3_OUT_REFUND_NO = "https://api.mch.weixin.qq.com/v3/refund/domestic/refunds/{out_refund_no}";

    /**
     * @Description: 查询退款订单
     * @Param: [outRefundsNo] 商户订单号
     * @return: java.lang.Object
     * @Author: XQD
     * @Date:2022/8/14 21:44
     */
    @Override
    public Object queryRefundsOrder(String outRefundsNo) throws Exception {
        CloseableHttpClient httpClient = WxPayUtil.getHttpClient();
        String v3_out_refund_no = WechatPayConstant.V3_OUT_REFUND_NO.replaceFirst("\\{out_refund_no}", outRefundsNo);
        HttpGet httpGet = new HttpGet(v3_out_refund_no);
        httpGet.addHeader("Accept", "application/json");
        CloseableHttpResponse response = httpClient.execute(httpGet);
        String bodyAsString = EntityUtils.toString(response.getEntity());
        System.out.println(bodyAsString);
        return bodyAsString;
    }

退款结果有两种获取方式,一种是让微信回调接口通知,还有一种就是我们主动查询退款结果,根据业务自行选择,也可以都是用,更保险。
以上只做功能开发介绍,不参与业务
退款功能完成!!!

补充:WxPayUtil

package com.office.miniapp.utils;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.office.miniapp.constants.WechatPayConstant;
import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
import com.wechat.pay.contrib.apache.httpclient.auth.AutoUpdateCertificatesVerifier;
import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Credentials;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Validator;
import com.wechat.pay.contrib.apache.httpclient.util.AesUtil;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import org.apache.http.impl.client.CloseableHttpClient;
import org.springframework.stereotype.Component;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.GeneralSecurityException;
import java.security.PrivateKey;
import java.security.Signature;
import java.util.Base64;

/**
 * @ClassName: WxPayUtil
 * @Description: 微信支付工具类
 * @Authror: XQD
 * @Date: 2022/8/13 23:04
 */
@Component
public class WxPayUtil {

    /**
     * @Description: 获取httpClient
     * @Param: []
     * @return: org.apache.http.impl.client.CloseableHttpClient
     * @Author: XQD
     * @Date:2022/8/13 23:16
     */
    public static CloseableHttpClient getHttpClient() {
        CloseableHttpClient httpClient = null;
        // 加载商户私钥(privateKey:私钥字符串)
        try {
            PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(
                    new ByteArrayInputStream(WechatPayConstant.PRIVATE_KEY.getBytes("utf-8")));
        //使用自动更新的签名验证器,不需要传入证书
        AutoUpdateCertificatesVerifier verifier = new AutoUpdateCertificatesVerifier(
                new WechatPay2Credentials(WechatPayConstant.MCH_ID, new PrivateKeySigner(WechatPayConstant.MCH_SERIAL_NO, merchantPrivateKey)),
                WechatPayConstant.API_V3KEY.getBytes("utf-8"));
        // 初始化httpClient
        httpClient = WechatPayHttpClientBuilder.create()
                .withMerchant(WechatPayConstant.MCH_ID, WechatPayConstant.MCH_SERIAL_NO, merchantPrivateKey)
                .withValidator(new WechatPay2Validator(verifier))
                .build();
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return httpClient;
    }


    /**
     * @Description: 生成签名
     * @Param: [message]
     * @return: java.lang.String
     * @Author: XQD
     * @Date:2022/6/7 16:00
     */
    public static String sign(byte[] message) throws Exception {
        Signature sign = Signature.getInstance("SHA256withRSA");
        PrivateKey privateKey = PemUtil.loadPrivateKey(new ByteArrayInputStream(WechatPayConstant.PRIVATE_KEY.getBytes("utf-8")));
        sign.initSign(privateKey);
        sign.update(message);
        return Base64.getEncoder().encodeToString(sign.sign());
    }


    /**
     * @Description: 验证签名
     * @Param: [serial, message, signature]请求头中带的序列号, 验签名串, 请求头中的应答签名
     * @return: boolean
     * @Author: XQD
     * @Date:2021/9/14 10:36
     */
    public static boolean signVerify(String serial, String message, String signature) {
        AutoUpdateCertificatesVerifier verifier = null;
        try {
            // 加载商户私钥(privateKey:私钥字符串)
            PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(
                    new ByteArrayInputStream(WechatPayConstant.PRIVATE_KEY.getBytes("utf-8")));

            //使用自动更新的签名验证器,不需要传入证书
            verifier = new AutoUpdateCertificatesVerifier(
                    new WechatPay2Credentials(WechatPayConstant.MCH_ID, new PrivateKeySigner(WechatPayConstant.MCH_SERIAL_NO, merchantPrivateKey)),
                    WechatPayConstant.API_V3KEY.getBytes("utf-8"));
            return verifier.verify(serial, message.getBytes("utf-8"), signature);
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return false;
    }

    /**
     * @Description: 解密订单信息
     * @Param: [body] 应答报文主体
     * @return: java.lang.String
     * @Author: XQD
     * @Date:2021/9/14 11:48
     */
    public static String decryptOrder(String body) {
        try {
            AesUtil aesUtil = new AesUtil(WechatPayConstant.API_V3KEY.getBytes("utf-8"));
            ObjectMapper objectMapper = new ObjectMapper();
            JsonNode node = objectMapper.readTree(body);
            JsonNode resource = node.get("resource");
            String ciphertext = resource.get("ciphertext").textValue();
            String associatedData = resource.get("associated_data").textValue();
            String nonce = resource.get("nonce").textValue();
            return aesUtil.decryptToString(associatedData.getBytes("utf-8"), nonce.getBytes("utf-8"), ciphertext);
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (GeneralSecurityException e) {
            e.printStackTrace();
        }
        return "";
    }

}
  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值