********已有新版SDK集成,不需要获取平台证书,加解密,点击下方链接*************
链接:微信APIV3 JAVA SDK集成(JSAPI支付、商家转账到零钱、订单退款、多商户配置)超简单、新鲜,附源码_草不生的博客-CSDN博客
开通过程不做叙述,查看微信官方文档,仅介绍java调用api。
前言:应兄弟们要求,已附上demo,填好配置可直接运行,里面也加入了回查方法,具体的大家慢慢研究,全是哥们儿对着文档一点一点研究出来的,兄弟们顺手点个赞,谢谢
本文先展示发起转账API,后面再讲述发起转账需要的资料以及途中遇到的坑。
1、发起商家转账到零钱方法:
/** *调用API参数准备 当传入姓名的时候需要做敏感信息加解密 */ public HttpResult wechatTransfer(String orderNo, String openid, String userName, Integer amount){ Map<String, Object> postMap = new HashMap<>(10); postMap.put("appid", DouDianConfig.APPID); postMap.put("out_batch_no", orderNo); //该笔批量转账的名称 postMap.put("batch_name", "商户提现"); //转账说明,UTF8编码,最多允许32个字符 postMap.put("batch_remark", username); //转账金额单位为“分”。 总金额 System.out.println("转账总金额"+amount.toString()); 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", orderNo); //转账金额 subMap.put("transfer_amount", amount); //转账备注 subMap.put("transfer_remark", "用户提现(姓名:"+ userName +")"); //用户在直连商户应用下的用户标示 subMap.put("openid", openid); //转账金额 log.info("用户提现(姓名:"+ userName)"); subMap.put("user_name", rsaEncryptOAEP(userName)); list.add(subMap); postMap.put("transfer_detail_list", list); return postTransBatRequest( "https://api.mch.weixin.qq.com/v3/transfer/batches", JSON.toJSONString(postMap)); }
/** * 发起转账请求 (微信所有的接口都需要获取access_token,下面有getToken方法) * @param requestUrl 请求地址 * @param requestJson 请求参数 * @return 响应 */ public HttpResult postTransBatRequest( String requestUrl, String requestJson) { 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", DouDianConfig.WX_SERIAL_NO); //-------------------------核心认证 start----------------------------------------------------------------- String authorization = WechatUtils.getToken("POST","/v3/transfer/batches",requestJson,DouDianConfig.APICLIENT_KEY,DouDianConfig.MCHID,DouDianConfig.SERIAL_NO); // 添加认证信息 httpPost.addHeader("Authorization", "WECHATPAY2-SHA256-RSA2048" + " " + authorization); //---------------------------核心认证 end--------------------------------------------------------------- httpPost.setEntity(new StringEntity(requestJson, "UTF-8")); //发起转账请求 response = httpclient.execute(httpPost); String content = EntityUtils.toString(response.getEntity(), "UTF-8"); log.info("微信转账响应"+content); return new HttpResult(response.getStatusLine().getStatusCode(), content); } catch (Exception e) { e.printStackTrace(); } finally { // 关闭流 } return null; }
/** * 获取微信api v3请求验证头 Authorization * @param method http请求方法 大写 * @param url 请求地址 不含域名 * @param body 请求报文主体 无填写 "" * @param privateKey 请求 * @param mchid 商户号 * @param serialNo 证书序列号 * @return */ public static String getToken (String method, String url, String body, String privateKey, String mchid, String serialNo){ try { String nonceStr = WXPayUtil.generateNonceStr(); long timestamp = System.currentTimeMillis() / 1000; String msg = buildMessage(method, url, timestamp, nonceStr, body); return "mchid=\"" + mchid + "\"," + "nonce_str=\"" + nonceStr + "\"," + "timestamp=\"" + timestamp + "\"," + "serial_no=\"" + serialNo + "\"," + "signature=\"" + sha256withRSA(msg,privateKey) + "\""; } catch (Exception e) { e.printStackTrace(); log.error("[WechatUtils getToken] SHA256withRSA 转义失败 error message:{}",e.getMessage()); } return "fail"; }
/** * 敏感信息加密 (这是最坑的接口,没有之一,加密用到的证书是平台证书!!!不是商家证书) * 后面再叙述平台证书获取 * @param message 需加密信息 * @return 加密之后的信息 */ private String rsaEncryptOAEP(String message) { try { Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-1AndMGF1Padding"); cipher.init(Cipher.ENCRYPT_MODE,getCertificate(new ByteArrayInputStream(DouDianConfig.CIPHERTEXT.getBytes()))); byte[] data = message.getBytes("utf-8"); byte[] cipherdata = cipher.doFinal(data); return Base64.getEncoder().encodeToString(cipherdata); } catch (NoSuchAlgorithmException | NoSuchPaddingException e) { throw new RuntimeException("当前Java环境不支持RSA v1.5/OAEP", e); } catch (InvalidKeyException e) { throw new IllegalArgumentException("无效的证书", e); } catch (IllegalBlockSizeException | BadPaddingException e) { //throw new IllegalBlockSizeException("加密原串的长度不能超过214字节"); throw new BizException("加密原串的长度不能超过214字节"); }catch (Exception e){ e.printStackTrace(); throw new BizException("系统错误"); } }
/** * 证书解析 * @param inputStream 证书数据流 * @return X509 */ public static X509Certificate getCertificate(InputStream inputStream) { try { CertificateFactory cf = CertificateFactory.getInstance("X509"); X509Certificate cert = (X509Certificate) cf.generateCertificate(inputStream); cert.checkValidity(); return cert; } catch (CertificateExpiredException e) { throw new RuntimeException("证书已过期", e); } catch (CertificateNotYetValidException e) { throw new RuntimeException("证书尚未生效", e); } catch (CertificateException e) { throw new RuntimeException("无效的证书", e); } }
2、配置类,除平台证书外的参数请自行在商家平台获取
public class DouDianConfig { /** * 固定值 */ public static final String GRANT_TYPE = "client_credential"; /** * 小程序appid */ public static final String APPID = ""; /** * 小程序密钥 */ public static final String SECRET = ""; /** * 商户号 */ public static final String MCHID = ""; /** * 商户密钥 --- 同 API V3 密钥 */ public static final String KEY = ""; /** * API V3 秘钥 */ public static final String API_V3_KEY = ""; public static final String BODY = ""; /** * 支付分签名方式 */ public static final String SIGN_TYPE = "HMAC-SHA256"; /** * 金额环回说明 */ public static final String REFUND_DESC = ""; /** * 商户api证书序列号 * https://myssl.com/cert_decode.html 证书解析地址 */ public static final String SERIAL_NO = ""; /** * 证书内容 apiclient_cert.pem文件中的内容 除去开头结尾行 * 请求量大为节约资源直接拿出来 */ public static final String PRIVATE_CONTENT = ""; public static final String APICLIENT_KEY = ""; /** * 微信平台证书 5年有效期 通过接口获取 */ public static final String CIPHERTEXT=""; /** * 平台证书序列号 */ public static final String WX_SERIAL_NO=""; }
3、平台证书获取(在转账不需要校验用户姓名的情况下是不需要平台证书的)
/** * 获取微信支付平台证书 */ public static HttpResult getWeChatCertificates(){ CloseableHttpClient httpclient = HttpClients.createDefault(); CloseableHttpResponse response = null; HttpEntity entity = null; try { String requestUrl = "https://api.mch.weixin.qq.com/v3/certificates"; //商户私钥证书 HttpGet httpPost = new HttpGet(requestUrl); // NOTE: 建议指定charset=utf-8。低于4.4.6版本的HttpCore,不能正确的设置字符集,可能导致签名错误 httpPost.addHeader("Content-Type", "application/json"); httpPost.addHeader("Accept", "application/json"); //"55E551E614BAA5A3EA38AE03849A76D8C7DA735A"); httpPost.addHeader("User-Agent", WXPayConstants.USER_AGENT + " " + DouDianConfig.MCHID); //-------------------------核心认证 start----------------------------------------------------------------- String authorization =getToken("GET", "/v3/certificates", "", DouDianConfig.APICLIENT_KEY, DouDianConfig.MCHID, DouDianConfig.SERIAL_NO); // 添加认证信息 httpPost.addHeader("Authorization", "WECHATPAY2-SHA256-RSA2048" + " " + authorization); //发起转账请求 response = httpclient.execute(httpPost); String content = EntityUtils.toString(response.getEntity(), "UTF-8"); System.out.println(content); return new HttpResult(response.getStatusLine().getStatusCode(), content); } catch (Exception e) { e.printStackTrace(); } finally { // 关闭流 } return null; }
上面接口返回的是一串json字符串因为比较懒,而且证书有效期是5年,所以我这边是把请求拿到的值没有直接转成对象,而是把需要的三个参数复制出来:
String ciphertext = ""; String nonce=""; String associated_data =""; EncodeRsa encodeRsa = new EncodeRsa(DouDianConfig.KEY.getBytes()); String s = encodeRsa.decryptToString(associated_data.getBytes(),nonce.getBytes(),ciphertext);
s就是最终得到的平台证书内容了,复制到配置类中就行了;
4、接口回查
不要以为上面的流程走完就结束了,还有最大的一个坑,商家转账到零钱新接口因为是批量操作,接口调起以后并不会直接返回给你成功还是失败,需要进行回查才能知道哪一笔转账是否成功,一般情况下是不会出现失败,但是如果转账时候传入了username,需要校验姓名是否正确的时候,出现失败的可能性就极大的提高了,必须回查,有空再写回查方法,也可以去微信官方文档复制