微信V3支付签名及退款案例


1、获取token

package com.operation.admin.utils.wx;

import com.alibaba.fastjson.JSON;
import com.operation.admin.utils.log.LogUtils;
import org.springframework.core.io.ClassPathResource;
import org.springframework.util.Base64Utils;

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.cert.X509Certificate;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @Version 1.0.0
 * @Description
 */
public class KeyPairFactory {


    private final static Object lock = new Object();
    //序列号
    public static String serialNo = "";

    public static void main(String[] args)throws Exception {
        //获取别名,通过签名文件获取 keyAlias,keyAlias获取私钥和序列号要用到
        System.out.println("开始===");
        KeyStore keyStore1 = KeyStore.getInstance("PKCS12");
        String keyPath = "C:\\Users\\zwk\\Desktop\\新建文件夹\\apiclient_cert.p12";
        //ClassPathResource resource = new ClassPathResource(keyPath);
        FileInputStream fis = new FileInputStream(new File(keyPath));
        System.out.println("借宿===");
        keyStore1.load(fis, WxPayUtils.mch_id.toCharArray());
        System.out.println("dljfdjlf d");
        System.out.println(JSON.toJSONString(keyStore1.aliases()));
    }


    /**
     * 获取微信验证签名
     * @param keyPath     apiclient_cert.p12证书路径
     * @param url
     * @param timestamp
     * @param nonceStr
     * @param body
     * @return
     */
    public static String getToken(String keyPath,String url, long timestamp, String nonceStr, String body){
        KeyPair keyPair = createPKCS12(keyPath,WxPayUtils.keyAlias,WxPayUtils.mch_id);
        String sign = sign("POST",url,timestamp,nonceStr,body,keyPair);
        String token = token(WxPayUtils.mch_id,nonceStr ,timestamp , serialNo, sign);
        return token;
    }


    /**
     * 获取公私钥.
     *
     * @param keyPath  the key path
     * @param keyAlias the key alias
     * @param keyPass  password  这里的keyPass为商户号 mch_id
     * @return the key pair
     */
    public static KeyPair createPKCS12(String keyPath, String keyAlias, String keyPass) {
        KeyStore store = null;
        try {
            //从项目中读取签名文件
            ClassPathResource resource = new ClassPathResource(keyPath);
            InputStream is = resource.getInputStream();

            //InputStream is = new FileInputStream(new File(keyPath));
            char[] pem = keyPass.toCharArray();
            synchronized (lock) {
                if (store == null) {
                    synchronized (lock) {
                        store = KeyStore.getInstance("PKCS12");
                        store.load(is, pem);
                    }
                }
            }
            X509Certificate certificate = (X509Certificate) store.getCertificate(keyAlias);
            certificate.checkValidity();
            // 证书的序列号 也有用
            serialNo = certificate.getSerialNumber().toString(16).toUpperCase();
            // 证书的 公钥
            PublicKey publicKey = certificate.getPublicKey();
            // 证书的私钥
            PrivateKey storeKey = (PrivateKey) store.getKey(keyAlias, pem);

            return new KeyPair(publicKey, storeKey);

        } catch (Exception e) {
            LogUtils.error("createPKCS12==", e);
        }
        return null;
    }


    /**
     * V3  SHA256withRSA 签名.
     *
     * @param method       请求方法  GET  POST PUT DELETE 等
     * @param canonicalUrl 例如  https://api.mch.weixin.qq.com/v3/pay/transactions/app?version=1 ——> /v3/pay/transactions/app?version=1
     * @param timestamp    当前时间戳   因为要配置到TOKEN 中所以 签名中的要跟TOKEN 保持一致
     * @param nonceStr     随机字符串  要和TOKEN中的保持一致
     * @param body         请求体 GET 为 "" POST 为JSON
     * @param keyPair      商户API 证书解析的密钥对  实际使用的是其中的私钥
     * @return the string
     */

    public static  String sign(String method, String canonicalUrl, long timestamp, String nonceStr, String body, KeyPair keyPair)  {
        try {
            String signatureStr = Stream.of(method, canonicalUrl, String.valueOf(timestamp), nonceStr, body)
                    .collect(Collectors.joining("\n", "", "\n"));
            Signature sign = Signature.getInstance("SHA256withRSA");
            sign.initSign(keyPair.getPrivate());
            sign.update(signatureStr.getBytes(StandardCharsets.UTF_8));
            return Base64Utils.encodeToString(sign.sign());
        }catch (Exception e){
            LogUtils.error("sign==",e );
        }
        return null;

    }

    /**
     * 生成Token.
     *
     * @param mchId 商户号
     * @param nonceStr   随机字符串
     * @param timestamp  时间戳
     * @param serialNo   证书序列号
     * @param signature  签名
     * @return the string
     */
    public  static  String token(String mchId, String nonceStr, long timestamp, String serialNo, String signature) {

        final String TOKEN_PATTERN = "mchid=\"%s\",nonce_str=\"%s\",timestamp=\"%d\",serial_no=\"%s\",signature=\"%s\"";
        // 生成token
        return String.format(TOKEN_PATTERN,
                mchId,
                nonceStr, timestamp, serialNo, signature);
    }







}

退款案例

package com.operation.admin.utils.wx;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.operation.admin.config.ConfigParam;
import com.operation.admin.utils.common.ResultUtils;
import com.operation.admin.utils.common.StringUtils;
import com.operation.admin.utils.http.HttpUtils;
import com.operation.admin.utils.log.LogUtils;

import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

/**
 * 微信支付工具类
 * @Version 1.0.0
 * @Description
 */
public class WxPayUtils {

    //微信退款URL
    public static final String WX_REFUND_URL = "https://api.mch.weixin.qq.com/v3/refund/domestic/refunds";
    //微信退款URI
    public static final String WX_REFUND_URI = "/v3/refund/domestic/refunds";
    //商户号
    public static final String mch_id = "11111112";
    //别名
    public static final String keyAlias = "tenpay certificate";

    //签名文件路径
    public static final String keyPath = "/sign/apiclient_cert.p12";

    //public static final String keyPath = "C:\\Users\\zwk\\Desktop\\新建文件夹\\apiclient_cert.p12";

    //微信退款
    public static Map refund(Map policyInfo){
        try {
            Map reqMap = new HashMap<>();
            //微信商户支付订单号(支付成功后返回的订单号)
            String transaction_id = "420000134220220301231262";
            //商户订单号 (创建支付订单时返回的订单号)
            String out_trade_no = "94895255037012144";
            //商户退款单号
            String out_refund_no = UUID.randomUUID().toString().replace("-","" );
            String reason = "退保退款";
            //退款金额(以分为单位)
            BigDecimal policyFee = (BigDecimal)policyInfo.get("policyFee");
            Integer amountInt = policyFee.multiply(new BigDecimal(100)).intValue();
            Integer refund = 1;
            //交易订单总金额(以分为单位)
            Integer total = 1;
            String runE = ConfigParam.runEnvironment;
            //如果是正式环境的,则使用正常的投保金额
            if(runE.equals("prod")){
                refund = amountInt;
                total = amountInt;
            }
            //币种
            String currency = "CNY";
            Map amount = new HashMap<>();
            amount.put("refund", refund);
            amount.put("total", total);
            amount.put("currency", currency);

            reqMap.put("transaction_id",transaction_id );
            reqMap.put("out_trade_no", out_trade_no);
            reqMap.put("out_refund_no",out_refund_no );
            reqMap.put("reason", reason);
            reqMap.put("amount", amount);

            String reqParam = JSON.toJSONString(reqMap);
            //签名文件
            String nonceStr = UUID.randomUUID().toString().replace("-","" );
            String token = KeyPairFactory.getToken(keyPath, WX_REFUND_URI, System.currentTimeMillis()/1000, nonceStr, reqParam);
            LogUtils.app("微信退款请求参数:"+ reqParam);
            LogUtils.app("微信退款路径:"+WX_REFUND_URL);
            String result = HttpUtils.wxHttpRequest(WX_REFUND_URL, reqParam,token);
            LogUtils.app("微信退款响应消息:"+result);
           return null;

        }catch (Exception e){
            LogUtils.error("WxPayUtils%refund==",e );
        }
        return ResultUtils.paramError("退款失败");
      

    }

    public static void main(String[] args) {
        //refund();
    }


}

http请求   /**

     * 微信支付
     * @param url
     * @param params
     * @param token
     * @return
     */
    public static String wxHttpRequest(String url, String params,String token) {
        HttpPost httpRequest = new HttpPost(url);
        CloseableHttpResponse httpResponse = null;
        try {
            //设置微信支付请求头
            httpRequest.addHeader("Content-Type", "application/json;charset=utf-8");
            httpRequest.addHeader("Connection", "keep-alive");
            httpRequest.addHeader("Accept", "application/json");

httpRequest.addHeader(
"Authorization","WECHATPAY2-SHA256-RSA2048 "+ token ); if (params != null) { httpRequest.setEntity(new StringEntity(params, "UTF-8")); } httpResponse = getHttpClient().execute(httpRequest); int status = httpResponse.getStatusLine().getStatusCode(); if (status >= 200 && status < 300) { return EntityUtils.toString(httpResponse.getEntity()); } else { httpRequest.abort(); String str = EntityUtils.toString(httpResponse.getEntity()); LogUtils.app(str); return str; } } catch (Exception e) { LogUtils.error("HttpUtils%sendPostRequest:", e); } finally { httpRequest.releaseConnection(); try { if (httpResponse != null) { httpResponse.close(); } } catch (IOException e) { LogUtils.error("HttpUtils%sendPostRequest:关闭资源失败", e); } } return null; }