由于项目需求老大叫我开发“微信企业付款到零钱”的功能,这之前我做过一些微信支付的相关东西,对我而言微信封装的东西感觉比ALI难过一些。言归正传,在没有找到微信官方的API的情况下就只能借鉴之前写过的各位大佬的了,但是结合自身的水平发现有很多东西还是借鉴的。

开发“微信企业付款到零钱”的功能,首先需要登录微信商户-->产品中心-->查看“企业付款到零钱”是否开通

企业付款到零钱java开发 企业付钱到零钱_微信支付

如果未开通,则需要开通。

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安全–>证书中下载的 。 

企业付款到零钱java开发 企业付钱到零钱_微信支付_02

企业付款到零钱java开发 企业付钱到零钱_apache_03

企业付款到零钱java开发 企业付钱到零钱_企业付款到零钱java开发_04

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")):

企业付款到零钱java开发 企业付钱到零钱_java_05

照理来说到这里项目就可以正常运行了,但是我在完成这些的后还遇见了另外一个错误:

[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了。

企业付款到零钱java开发 企业付钱到零钱_企业付款到零钱java开发_06

最后是相关API的下载链接:


做为小白第一次写文章,有什么不足之处望各位多多包涵。