由于项目需求老大叫我开发“微信企业付款到零钱”的功能,这之前我做过一些微信支付的相关东西,对我而言微信封装的东西感觉比ALI难过一些。言归正传,在没有找到微信官方的API的情况下就只能借鉴之前写过的各位大佬的了,但是结合自身的水平发现有很多东西还是借鉴的。
开发“微信企业付款到零钱”的功能,首先需要登录微信商户-->产品中心-->查看“企业付款到零钱”是否开通
如果未开通,则需要开通。
2.仔细阅读-->企业付款到零钱API
https://pay.weixin.qq.com/wiki/doc/api/tools/mch_pay.php?chapter=14_1 (必看)
3.证书的准备:java开发需要用到:apiclient_cert.p12证书的,在微信商户平台(pay.weixin.qq.com)–>账户设置–>API安全–>证书中下载的 。
4.关于证书和参数
https://pay.weixin.qq.com/wiki/doc/api/tools/mch_pay.php?chapter=4_3 (建议看看)
5.相关代码
@Controller
@RequestMapping("/transfer")
public class TransferController {
private static final Logger LOG = Logger.getLogger(TransferController.class);
// 获取需要发送的url地址
private static final String TRANSFERS_PAY = "https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers";
// 企业付款查询
private static final String TRANSFERS_PAY_QUERY = "https://api.mch.weixin.qq.com/mmpaymkttransfers/gettransferinfo";
@Autowired
TokenMapper tokenMapper;
/**
* 企业向个人支付转账
*
* @param request
* @param response
* @param openid
* 用户openid
* @param callback
* @throws MyException
*/
@RequestMapping(value = "pay")
@ResponseBody
public Object transferPay(HttpServletRequest request, HttpServletResponse response, String token,BigDecimal amount) throws MyException {
LOG.info("[/transfer/pay]");
Map<Object, Object> map = new HashMap<Object, Object>();
/**
* 这里我是原来微信三方登录时获取openId存入数据库的,所有可以直接读出来
* 根据你的业务逻辑来获取openId
*/
// Map<String, Object> map2 = tokenMapper.queryUserByToken(token);
// String openid = (String) map2.get("openId");
// // 业务判断 openid是否有收款资格
// if (openid == null) {
// throw new MyException("尚未使用过微信支付,无法用微信提现!");
// }
Map<String, String> restmap = null;
try {
Map<String, String> parm = new HashMap<String, String>();
parm.put("mch_appid", ConfigUtil.APPID); // 公众账号appid
parm.put("mchid", ConfigUtil.MCH_ID); // 商户号
parm.put("nonce_str", PayUtil.getNonceStr()); // 随机字符串
parm.put("partner_trade_no", PayUtil.getTransferNo()); // 生成商户订单号
parm.put("openid", openid); // 用户openid
parm.put("check_name", "NO_CHECK"); // 是否验证真实姓名--校验用户姓名选项 OPTION_CHECK
parm.put("re_user_name", "张三"); //收款用户姓名---check_name设置为FORCE_CHECK或OPTION_CHECK,则必填
parm.put("amount", "100"); // 转账金额
parm.put("desc", "测试企业付款到零钱"); // 企业付款描述信息
parm.put("spbill_create_ip", PayUtil.getLocalIp(request)); // Ip地址
parm.put("sign", PayUtil.getSign(parm, ConfigUtil.API_KEY));
String restxml = HttpUtils.posts(TRANSFERS_PAY, XmlUtils.xmlFormat(parm, false));
restmap = XmlUtils.xmlParse(restxml);
} catch (Exception e) {
LOG.error(e.getMessage(), e);
}
if (CollectionUtil.isNotEmpty(restmap) && "SUCCESS".equals(restmap.get("result_code"))) {
LOG.info("转账成功:" + restmap.get("err_code") + ":" + restmap.get("err_code_des"));
Map<String, String> transferMap = new HashMap<>();
transferMap.put("partner_trade_no", restmap.get("partner_trade_no"));// 商户转账订单号
transferMap.put("payment_no", restmap.get("payment_no")); // 微信订单号
transferMap.put("payment_time", restmap.get("payment_time")); // 微信支付成功时间
WebUtil.response(response, WebUtil.packJsonp(null, JSON.toJSONString(
new JsonResult(1, "转账成功", new ResponseData(null, transferMap)), SerializerFeatureUtil.FEATURES)));
} else {
if (CollectionUtil.isNotEmpty(restmap)) {
LOG.info("转账失败:" + restmap.get("err_code") + ":" + restmap.get("err_code_des"));
}
WebUtil.response(response, WebUtil.packJsonp(null,
JSON.toJSONString(new JsonResult(-1, "转账失败", new ResponseData()), SerializerFeatureUtil.FEATURES)));
}
map.put("code", 200);
map.put("msg", "提现成功!");
return map;
}
这里需要着重注意这个方法
String restxml = HttpUtils.posts(TRANSFERS_PAY, XmlUtils.xmlFormat(parm, false));
方法Post进去
/**
* @description 功能描述: post https请求,服务器双向证书验证
* @param url 请求地址
* @param s 参数xml
* @return 请求失败返回null
*/
public static String posts(String url, String s) {
CloseableHttpClient httpClient = null;
HttpPost httpPost = new HttpPost(url);
String body = null;
CloseableHttpResponse response = null;
try {
httpClient = HttpClients.custom()
.setDefaultRequestConfig(REQUEST_CONFIG)
.setSSLSocketFactory(getSSLConnectionSocket())
.build();
httpPost.setEntity(new StringEntity(s, DEFAULT_CHARSET));
response = httpClient.execute(httpPost);
body = EntityUtils.toString(response.getEntity(), DEFAULT_CHARSET);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (response != null) {
try {
response.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (httpClient != null) {
try {
httpClient.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return body;
}
会有httpClient请求,这里是需要配置编码格式,链接超时时间,微信支付ssl证书(就是之前下载的证书)
httpClient = HttpClients.custom().setDefaultRequestConfig(REQUEST_CONFIG)
.setSSLSocketFactory(getSSLConnectionSocket())
.build();
进入配置类,这里的ClassPathResource里表示你证书的放置路径。
public class HttpUtils {
private static final String DEFAULT_CHARSET = "UTF-8";
private static final int CONNECT_TIME_OUT = 5000; //链接超时时间3秒
private static final RequestConfig REQUEST_CONFIG = RequestConfig.custom().setConnectTimeout(CONNECT_TIME_OUT).build();
private static SSLContext wx_ssl_context = null; //微信支付ssl证书
static{
Resource resource = new ClassPathResource("spring/apiclient_cert.p12");
try {
KeyStore keystore = KeyStore.getInstance("PKCS12");
char[] keyPassword = "1494252162".toCharArray(); //证书密码
keystore.load(resource.getInputStream(), keyPassword);
wx_ssl_context = SSLContexts.custom().loadKeyMaterial(keystore, keyPassword).build();
} catch (Exception e) {
e.printStackTrace();
}
}
}
我的路径(注意如果直接放在resources下面就是项目的根路径:ClassPathResource("apiclient_cert.p12")):
照理来说到这里项目就可以正常运行了,但是我在完成这些的后还遇见了另外一个错误:
[2018-04-17 19:01:06][ERROR] - caused by: org.xmlpull.v1.XmlPullParserException: resource not found: /META-INF/services/org.xmlpull.v1.XmlPullParserFactory make sure that parser implementing XmlPull API is available
org.xmlpull.v1.XmlPullParserException: caused by: org.xmlpull.v1.XmlPullParserException: resource not found: /META-INF/services/org.xmlpull.v1.XmlPullParserFactory make sure that parser implementing XmlPull API is available
at org.xmlpull.v1.XmlPullParserFactory.newInstance(XmlPullParserFactory.java:294)
at org.xmlpull.v1.XmlPullParserFactory.newInstance(XmlPullParserFactory.java:259)
at com.ejar.shop.utils.wxpay.XmlUtils.xmlParse(XmlUtils.java:73)
at com.ejar.shop.controller.front.TransferController.transferPay(TransferController.java:83)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.web.method.support.InvocableHandlerMethod.invoke(InvocableHandlerMethod.java:215)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:132)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:110)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandleMethod(RequestMappingHandlerAdapter.java:781)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:721)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:83)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:943)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:877)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:966)
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:868)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:648)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:842)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:729)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:292)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:207)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:240)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:207)
at com.ejar.shop.utils.strideAcross.SimpleCORSFilter.doFilter(SimpleCORSFilter.java:47)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:240)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:207)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:212)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:106)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:502)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:141)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79)
at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:616)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:88)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:509)
at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1104)
at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:684)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1520)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1476)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:745)
四月 17, 2018 7:01:06 下午 org.apache.catalina.core.StandardWrapperValve invoke
严重: Servlet.service() for servlet [shop] in context with path [/shop] threw exception [Request processing failed; nested exception is java.lang.IllegalStateException: getWriter() has already been called for this response] with root cause
java.lang.IllegalStateException: getWriter() has already been called for this response
做为一个小白刚开始确实很懵逼,网上资料也不多,就试着用deBug跑找问题出现在哪里,又在网上找相关资料,最后发现是少了一个kxml2-2.3.0.jar的包,导进去就OK了。
最后是相关API的下载链接:
做为小白第一次写文章,有什么不足之处望各位多多包涵。