微信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 Maprefund(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; }