SpringBoot微信三方支付/退款等方法

SpringBoot微信三方支付/退款等方法

一、添加依赖
<!--微信支付-->
<dependency>
   <groupId>com.github.binarywang</groupId>
    <artifactId>weixin-java-pay</artifactId>
    <version>4.3.9.B</version>
</dependency>
<dependency>
   <groupId>cn.hutool</groupId>
   <artifactId>hutool-core</artifactId>
   <version>5.3.8</version>
</dependency>
二、添加支付/退款等方法
@Slf4j
public class Wxpay {

    private static final Gson GSON = new GsonBuilder().create();

    /**
     *  支付
     */
    public static WxPayReturnInfoVO prepay(){
        final WxPayUnifiedOrderRequest wxPayUnifiedOrderRequest = WxPayUnifiedOrderRequest.newBuilder()
                //支付人的openid
                .openid("")
                //用户生成的唯一订单编号
                .outTradeNo("")
                //订单金额(单位分)
                .totalFee(1)
                //商品描述
                .body("")
                //可以是区分业务的字段,例如userId,也可以不写
                .attach("")
                //本地ip
                .spbillCreateIp(InetAddress.getLoopbackAddress().getHostAddress())
                //回调的url地址
                .notifyUrl("")
                .build();
        WxPayUnifiedOrderResult wxPayUnifiedOrderResult = null;
        try {
            wxPayUnifiedOrderResult = WxConfig.wxPayService().unifiedOrder(wxPayUnifiedOrderRequest);
        } catch (WxPayException e) {
            e.printStackTrace();
        }
        //组合参数构建支付
        Map<String, String> paySignInfo = new HashMap<>(5);
        String timeStamp = String.valueOf(System.currentTimeMillis() / 1000);
        String nonceStr = String.valueOf(System.currentTimeMillis());
        paySignInfo.put("appId", WxConfig.appId);
        paySignInfo.put("nonceStr", nonceStr);
        paySignInfo.put("timeStamp", timeStamp);
        paySignInfo.put("signType", "MD5");
        paySignInfo.put("package", "prepay_id=" + wxPayUnifiedOrderResult.getPrepayId());
        String[] signInfo = new String[0];
        String paySign = SignUtils.createSign(paySignInfo, "MD5", WxConfig.mch_key, signInfo);

        //组合支付参数
        WxPayReturnInfoVO returnPayInfoVO = new WxPayReturnInfoVO();
        returnPayInfoVO.setAppId(WxConfig.appId);
        returnPayInfoVO.setNonceStr(nonceStr);
        returnPayInfoVO.setPaySign(paySign);
        returnPayInfoVO.setSignType("MD5");
        returnPayInfoVO.setPrepayId(wxPayUnifiedOrderResult.getPrepayId());
        returnPayInfoVO.setTimeStamp(timeStamp);
        return returnPayInfoVO;
    }

    /**
     *  退款
     */
    public static Map<String, String> rebackPay(String orderNo, String transactionId, BigDecimal amount) throws Exception{
        Map<String, String> returnMap = new HashMap<>();
        //微信支付-申请退款请求参数
        WxPayRefundV3Request request = new WxPayRefundV3Request();
        WxPayRefundV3Request.Amount am = new WxPayRefundV3Request.Amount();
        //原订单金额
        am.setTotal(amount.multiply(new BigDecimal(100)).intValue());
        //退款币种,符合ISO 4217标准的三位字母代码,目前只支持人民币:CNY。
        am.setCurrency("CNY");
        //退款金额 注意:退款金额,单位为分,只能为整数,不能超过原订单支付金额。
        am.setRefund(amount.multiply(new BigDecimal(100)).intValue());
        //金额信息
        request.setAmount(am);
        //transaction_id:微信支付订单号
        request.setTransactionId(transactionId);
        //商户订单号
        request.setOutRefundNo("return_"+orderNo);
        request.setNotifyUrl(WxConfig.refund_notify_url);

        //调用微信V3退款API
        WxPayRefundV3Result result = WxConfig.wxPayService().refundV3(request);
        String returnMsg = null;
        String status = result.getStatus();
        switch (status) {
            case "SUCCESS":
                returnMsg = "退款成功";
                break;
            case "CLOSED":
                returnMsg = "退款关闭";
                break;
            case "PROCESSING":
                returnMsg = "退款处理中";
                break;
            case "ABNORMAL":
                returnMsg = "退款异常";
                break;
            default:
                returnMsg = "受理失败";
                break;
        }
        returnMap.put("status", status);
        returnMap.put("returnMsg", returnMsg);

        return returnMap;
    }

    /**
     * 企业付款到用户零钱
     */
    public static void entPay(String openId, BigDecimal amount) {
        String outNo = IdUtil.getSnowflake(0, 0).nextIdStr();
        /**
         * 系统内部业务逻辑
         */
        EntPayRequest wxEntPayRequest = new EntPayRequest();
        wxEntPayRequest.setAppid(WxConfig.appId);
        wxEntPayRequest.setMchId(WxConfig.mch_id);
        // 系统订单id
        wxEntPayRequest.setPartnerTradeNo(outNo);
        // 付款用户的wxopenid
        wxEntPayRequest.setOpenid(openId);
        // 实名校验
        // NO_CHECK:不校验真实姓名
        // FORCE_CHECK:强校验真实姓名(未实名认证的用户会校验失败,无法转账)
        // OPTION_CHECK:针对已实名认证的用户才校验真实姓名(未实名认证用户不校验,可以转账成功)
        wxEntPayRequest.setCheckName("NO_CHECK");
        //付款金额 单位分
        wxEntPayRequest.setAmount(amount.multiply(new BigDecimal(100)).intValue());
        //wxEntPayRequest.setAmount(amount.multiply(new BigDecimal(100)).intValue());
        // 描述
        wxEntPayRequest.setDescription("提现");
        // 调用接口的机器IP地址
        wxEntPayRequest.setSpbillCreateIp("");

        try {
            EntPayResult wxEntPayResult = WxConfig.wxPayService().getEntPayService().entPay(wxEntPayRequest);
            if ("SUCCESS".equals(wxEntPayResult.getResultCode())
                    && "SUCCESS".equals(wxEntPayResult.getReturnCode())) {
                log.info("企业对个人付款成功!\n付款信息:\n" + wxEntPayResult);
                System.out.println("企业对个人付款成功!\n付款信息:\n" + wxEntPayResult);
                /**
                 * 系统内部业务逻辑
                 */
            } else {
                log.error("err_code: " + wxEntPayResult.getErrCode()
                        + "  err_code_des: " + wxEntPayResult.getErrCodeDes());
                System.out.println("提现失败! " + "err_code: " + wxEntPayResult.getErrCode() + "  err_code_des: " + wxEntPayResult.getErrCodeDes());
                /**
                 * 系统内部业务逻辑
                 */
            }
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("提现失败! " + e.getMessage());
        }
    }

    /**
     *  商家转账到零钱
     */
    public static String weixinTransferBat(String openId, int amount) throws Exception {
        log.info("进入商家转账到零钱, openId:{},金额:{}", openId,amount);
        //商户号
        String mchid = WxConfig.mch_id;
        //申请商户号的appid或商户号绑定的appid(企业号corpid即为此appid)
        String appId = WxConfig.appId;
        //用户在直连商户应用下的用户标示
        //String openId = openId;
        //商户证书编号
        String wechatPayserialNo = WxConfig.serial_no;
        //商户证书路径(在你本机测试时放你本机路径中的就可以)
        String privatekeypath = WxConfig.pemUrl;

        Map<String, Object> postMap = new HashMap<>();
        //商家批次单号 长度 1~32
        String outNo = IdUtil.getSnowflake(0, 0).nextIdStr();

        postMap.put("appid", appId);
        postMap.put("out_batch_no", outNo);
        //该笔批量转账的名称
        postMap.put("batch_name", "测试转账");
        //转账说明,UTF8编码,最多允许32个字符
        postMap.put("batch_remark", "测试转账");
        //转账金额单位为“分”。 总金额
        //金额
        postMap.put("total_amount", amount);
        //。转账总笔数
        postMap.put("total_num", 1);


        List<Map> list = new ArrayList<>();
        Map<String, Object> subMap = new HashMap<>(4);
        //商家明细单号
        subMap.put("out_detail_no", outNo);
        //转账金额
        //金额
        subMap.put("transfer_amount", amount);
        //转账备注
        subMap.put("transfer_remark", "明细备注1");
        //用户在直连商户应用下的用户标示
        subMap.put("openid", openId);
        list.add(subMap);
        postMap.put("transfer_detail_list", list);

        //发起转账操作
        String resStr = HttpUtil.postTransBatRequest(
                WxConfig.batches_url,
                GSON.toJson(postMap),
                wechatPayserialNo,
                mchid,
                privatekeypath);
        log.info("商家转账到零钱END, 返回参数:{}",resStr);

        return resStr;
    }
}
三、WxPayReturnInfoVO返回实体类
@Data
public class WxPayReturnInfoVO {

    private String appId;

    private String timeStamp;

    private String nonceStr;

    private String prepayId;

    private String paySign;

    private String signType;

    private String id;
}
四、WxConfig配置类
@Component
public class WxConfig {

    /**小程序appId*/
    public static String appId = "";

    /**小程序的秘钥*/
    public static String miyao = "";

    /**商户号*/
    public static String mch_id = "";

    /**商户支付秘钥V2*/
    public static String mch_key = "";

    /**退款用到*/
    public static String keyPath = "";

    /**商家转账到零钱*/
    public static String pemUrl = "";
    public static String sn = "";

    /**商户证书序列号*/
    public static String serial_no = "";

    /**回调通知地址(需内网穿透测试)*/
    public static String notify_url = "";

    /**交易类型*/
    public static  String trade_type = "JSAPI";

    /**统一下单API接口链接*/
    public static String url = "https://api.mch.weixin.qq.com/pay/unifiedorder";

    /**查询订单API接口链接*/
    public static String query_url = "https://api.mch.weixin.qq.com/pay/orderquery";

    /**退款接口*/
    public static String refund_url = "https://api.mch.weixin.qq.com/v3/refund/domestic/refunds";

    /**退款回调接口*/
    public static String refund_notify_url = "";

    /**商家转账到零钱*/
    public static String batches_url = "https://api.mch.weixin.qq.com/v3/transfer/batches";


    public static WxPayService wxPayService() {
        WxPayConfig payConfig = new WxPayConfig();
        payConfig.setAppId(appId);
        payConfig.setMchId(mch_id);
        payConfig.setMchKey(mch_key);
        payConfig.setKeyPath(keyPath);
        payConfig.setTradeType(trade_type);
        payConfig.setNotifyUrl(notify_url);
        WxPayService wxPayService = new WxPayServiceImpl();
        wxPayService.setConfig(payConfig);
        return wxPayService;
    }
}
五、工具类
5.1、HttpUtil
@Slf4j
public class HttpUtil {
    /**
     * 发起批量转账API 批量转账到零钱
     *
     * @param requestUrl
     * @param requestJson 组合参数
     * @param wechatPayserialNo 商户证书序列号
     * @param mchID4M  商户号
     * @param privatekeypath  商户私钥证书路径
     * @return
     */
    public static String postTransBatRequest(
            String requestUrl,
            String requestJson,
            String wechatPayserialNo,
            String mchID4M,
            String privatekeypath) {
        CloseableHttpClient httpclient = HttpClients.createDefault();
        CloseableHttpResponse response = null;
        HttpEntity entity = null;
        try {
            //商户私钥证书
            HttpPost httpPost = new HttpPost(requestUrl);
            // NOTE: 建议指定charset=utf-8。低于4.4.6版本的HttpCore,不能正确的设置字符集,可能导致签名错误
            httpPost.addHeader("Content-Type", "application/json");
            httpPost.addHeader("Accept", "application/json");
            //"55E551E614BAA5A3EA38AE03849A76D8C7DA735A");
            httpPost.addHeader("Wechatpay-Serial", wechatPayserialNo);
            //-------------------------核心认证 start-----------------------------------------------------------------
            String strToken = VechatPayV3Util.getToken("POST",
                    "/v3/transfer/batches",
                    requestJson,mchID4M,wechatPayserialNo, privatekeypath);

            log.error("微信转账token "+strToken);
            // 添加认证信息
            httpPost.addHeader("Authorization",
                    "WECHATPAY2-SHA256-RSA2048" + " "
                            + strToken);
            //---------------------------核心认证 end---------------------------------------------------------------
            httpPost.setEntity(new StringEntity(requestJson, "UTF-8"));
            //发起转账请求
            response = httpclient.execute(httpPost);
            //获取返回的数据
            entity = response.getEntity();
            log.info("-----getHeaders.Request-ID:"+response.getHeaders("Request-ID"));
            return EntityUtils.toString(entity);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

}
5.2、VechatPayV3Util
@Slf4j
public class VechatPayV3Util {

    @Autowired
    static ResourceLoader resourceLoader;

    /**
     *
     * @param method 请求方法 post
     * @param canonicalUrl 请求地址
     * @param body 请求参数
     * @param merchantId 这里用的商户号
     * @param certSerialNo 商户证书序列号
     * @param keyPath 商户证书地址
     * @return
     * @throws Exception
     */
    public static String getToken(
            String method,
            String canonicalUrl,
            String body,
            String merchantId,
            String certSerialNo,
            String keyPath) throws Exception {
        String signStr = "";
        //获取32位随机字符串
        String nonceStr = getRandomString(32);
        //当前系统运行时间
        long timestamp = System.currentTimeMillis() / 1000;
        if (StringUtils.isEmpty(body)) {
            body = "";
        }
        //签名操作
        String message = buildMessage(method, canonicalUrl, timestamp, nonceStr, body);
        //签名操作
        String signature = sign(message.getBytes("utf-8"), keyPath);
        //组装参数
        signStr = "mchid=\"" + merchantId + "\",timestamp=\"" +  timestamp+ "\",nonce_str=\"" + nonceStr
                + "\",serial_no=\"" + certSerialNo + "\",signature=\"" + signature + "\"";

        return signStr;
    }

    public static String buildMessage(String method, String canonicalUrl, long timestamp, String nonceStr, String body) {
//		String canonicalUrl = url.encodedPath();
//		if (url.encodedQuery() != null) {
//			canonicalUrl += "?" + url.encodedQuery();
//		}
        return method + "\n" + canonicalUrl + "\n" + timestamp + "\n" + nonceStr + "\n" + body + "\n";
    }

    public static String sign(byte[] message, String keyPath) throws Exception {
        Signature sign = Signature.getInstance("SHA256withRSA");

        // apiclient_key.pem存在到resource/cert下
        //Resource resource = resourceLoader.getResource("classpath:/cert/apiclient_key.pem");
        ClassPathResource classPathResource = new ClassPathResource(WxConfig.pemUrl);
        File file = classPathResource.getFile();
        String path = file.getPath();

        // 获取私钥key,实际读取apiclient_key.pem文件信息创建PrivateKey 对象
        PrivateKey privateKey = getPrivateKey(path);
        sign.initSign(privateKey);
        sign.update(message);

        return Base64.encodeBase64String(sign.sign());
    }

    /**
     * 微信支付-前端唤起支付参数-获取商户私钥
     *
     * @param filename 私钥文件路径  (required)
     * @return 私钥对象
     */
    public static PrivateKey getPrivateKey(String filename) throws IOException {

        log.error("签名 证书地址是 "+filename);
        String content = new String(Files.readAllBytes(Paths.get(filename)), "utf-8");
        try {
            String privateKey = content.replace("-----BEGIN PRIVATE KEY-----", "")
                    .replace("-----END PRIVATE KEY-----", "")
                    .replaceAll("\\s+", "");
            //System.out.println("--------privateKey---------:"+privateKey);
            KeyFactory kf = KeyFactory.getInstance("RSA");
            return kf.generatePrivate(
                    new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKey)));
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("当前Java环境不支持RSA", e);
        } catch (InvalidKeySpecException e) {
            throw new RuntimeException("无效的密钥格式");
        }
    }

    /**
     * 获取随机位数的字符串
     * @param length
     * @return
     */
    public static String getRandomString(int length) {
        String base = "abcdefghijklmnopqrstuvwxyz0123456789";
        Random random = new Random();
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < length; i++) {
            int number = random.nextInt(base.length());
            sb.append(base.charAt(number));
        }
        return sb.toString();
    }
}

一个在学习的开发者,勿喷,欢迎交流

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值