最近做一个APP充值的功能 ,用到了微信支付,然后就研究了一下,遇到了好多坑,解决也是浪费了我好长时间,在这吐槽下微信的官方文档,哎,真正理解 最重要, 希望这篇文章能够帮助到用到微信支付的伙伴们!
首先附上微信的官方文档,虽然说写的不是很好,但是也不得不看,而且要仔细的看。
微信官方文档:https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=9_1
我是用的框架是MVC的框架,springboot也是相同的道理,只是穿的衣服不通,人都是一样的,也就是道理都是一样的,介绍不是特别详细,但是重要的每一行代码上都有相应的注释,这是程序员必备的好习惯。
把一些固定的配置最好存放在配置文件中,到时候发生更改,直接修改配置文件就OK了,上代码,哈哈
#微信APP:appId
wx.appIdApp=appid
#APP 商户号
wx.mchIdApp=申请的商户号
#支付签名
wx.paySignKeyApp=商户平台有
#交易类型
wx.tradeTypeApp=APP
package com.platform.utils;
import java.io.UnsupportedEncodingException;
import java.util.ResourceBundle;
/**
* 名称:ResourceUtil
* 描述:参数工具类
*
*/
public class ResourceUtil {
private static ResourceUtil RESOURCE_UTIL = null;
private static ResourceBundle BUNDLE = java.util.ResourceBundle.getBundle("platform");
private ResourceUtil() {
}
/**
* 工厂实现配置文件读取
*
* @param properties 参数
* @return ResourceUtil 工具类
*/
public static ResourceUtil getInstance(String properties) {
if (RESOURCE_UTIL == null) {
RESOURCE_UTIL = new ResourceUtil();
}
if (properties != null) {
BUNDLE = java.util.ResourceBundle.getBundle(properties);
}
return RESOURCE_UTIL;
}
/**
* 工厂实现配置文件读取
*
* @return ResourceUtil
*/
public static ResourceUtil getInstance() {
if (RESOURCE_UTIL == null) {
RESOURCE_UTIL = new ResourceUtil();
}
return RESOURCE_UTIL;
}
/**
* 主要功能:获取配置文件参数
* 注意事项:无
*
* @param name 参数名称
* @return 参数名称对应值
*/
public static String getConfigByName(String name) {
String value = "";
try {
value = new String(BUNDLE.getString(name).getBytes("iso8859-1"), "UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return value;
}
/**
* 主要功能:取得分隔符
* 注意事项:无
*
* @return 分隔符
*/
public static String getSeparator() {
return System.getProperty("file.separator");
}
}
Map<Object, Object> parame = new TreeMap<Object, Object>();
//自定义的订单编号(保证不发生重复就可以)
String orderId=generating(merFlowStatement.getMobile());
//随机字符串(32位)
String nonceStr = CharUtil.getRandomString(32);
//appid
parame.put("appid", ResourceUtil.getConfigByName("wx.appIdApp"));
// 商家账号
parame.put("mch_id", ResourceUtil.getConfigByName("wx.mchIdApp"));
String randomStr = CharUtil.getRandomNum(18).toUpperCase();
// 商品描述
parame.put("body", "商户-充值");
// 随机字符串
parame.put("nonce_str", randomStr);
// 商户订单编号
parame.put("out_trade_no", orderId);
//支付金额
parame.put("total_fee",merFlowStatement.getCashMoney().multiply(new BigDecimal(100)).intValue());
//交易类型APP
parame.put("trade_type", ResourceUtil.getConfigByName("wx.tradeTypeApp"));
parame.put("spbill_create_ip", getClientIp());
// 回调地址
//parame.put("notify_url", ResourceUtil.getConfigByName("wx.notifyUrl"));
parame.put("notify_url", "https://platform.ln1788.com/platform/api/merflowstatement/weChatCallback");
String sign = WechatUtil.arraySign(parame, ResourceUtil.getConfigByName("wx.paySignKeyApp"));
// 数字签证
parame.put("sign", sign);
public String generating(String str) {
//当前时间
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
Date date = new Date();
String time = sdf.format(date);
//手机号后四位
String phone=str.substring(str.length()-4, str.length());
//四位随机数
int num=(int) (Math.random()*9000+1000);
String number="CZO"+time+phone+num;
return number;
}
-向微信发送请求(统一下单接口)
//将map的类型转化成xml(微信需要的就是xml的参数)
String xml = MapUtils.convertMap2Xml(parame);
logger.info("xml:" + xml);
//发送微信统一下单请求
Map<String, Object> resultUn =XmlUtil.xmlStrToMap(WechatUtil.requestOnce(ResourceUtil.getConfigByName("wx.uniformorder"), xml));
// 响应报文
String return_code = MapUtils.getString("return_code", resultUn);
String return_msg = MapUtils.getString("return_msg", resultUn);
if (return_code.equalsIgnoreCase("FAIL")) {
resultObj= toResponsFail("支付失败," + return_msg);
} else if (return_code.equalsIgnoreCase("SUCCESS")) {
// 返回数据
String result_code = MapUtils.getString("result_code", resultUn);
String err_code_des = MapUtils.getString("err_code_des", resultUn);
if (result_code.equalsIgnoreCase("FAIL")) {
resultObj=toResponsFail("支付失败," + err_code_des);
} else if (result_code.equalsIgnoreCase("SUCCESS")) {
//成功执行数据返回前端,以下详细介绍
}
一定看好官方文档,并不是下图中这几个参数,人家是举例,一定要把上图中所有参数参与签名;
String prepay_id = MapUtils.getString("prepay_id", resultUn);
// 先生成paySign 参考https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=7_7&index=5
resultObjOne.put("appid", ResourceUtil.getConfigByName("wx.appIdApp"));
resultObjOne.put("prepayid", prepay_id);
resultObjOne.put("partnerid", MapUtils.getString("mch_id", resultUn));
resultObjOne.put("noncestr", nonceStr);
String timestamp = String.valueOf(new Date().getTime()/1000);
resultObjOne.put("timestamp", Integer.valueOf(timestamp));
resultObjOne.put("package", "Sign=WXPay");
String paySign = WechatUtil.arraySign(resultObjOne, ResourceUtil.getConfigByName("wx.paySignKeyApp"));
resultObjOne.put("sign",paySign);
/**
*操作自己的数据库生成订单流水
*/
//微信订单号
merFlowStatement.setReserveOne(prepay_id);
//类型为充值
merFlowStatement.setType(1);
SysMacroEntity entity=macroDao.queryObject(20);
BigDecimal bigDecimal=new BigDecimal( entity.getValue());
BigDecimal money=(merFlowStatement.getCashMoney()).multiply(bigDecimal);
merFlowStatement.setCashMoney(money);
//付款状态为审核中
merFlowStatement.setCashStatus(1);
merFlowStatement.setOrderNumber(orderId);
merFlowStatementMapper.save(merFlowStatement);
resultObj=toResponsObject(0, "微信统一订单下单成功", resultObjOne);
package com.platform.util.wechat;
import com.alibaba.druid.support.logging.Log;
import com.alibaba.druid.support.logging.LogFactory;
import com.platform.utils.CharUtil;
import com.platform.utils.MapUtils;
import com.platform.utils.ResourceUtil;
import com.platform.utils.XmlUtil;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.BasicHttpClientConnectionManager;
import org.apache.http.util.EntityUtils;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.math.MathContext;
import java.net.URLEncoder;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/**
* Title: 微信工具类
* Description: 微信工具类,通过充值客户端的不同初始化不同的工具类,得到相应微信退款相关的appid和muchid
*
* @author xubo
* @date 2017年6月6日 下午5:05:03
*/
public class WechatUtil {
private static Log logger = LogFactory.getLog(WechatUtil.class);
/**
* 充值客户端类型--微信公众号
*/
public static Integer CLIENTTYPE_WX = 2;
/**
* 充值客户端类型--app
*/
public static Integer CLIENTTYPE_APP = 1;
/**
* 方法描述:根据签名加密请求参数
* 创建时间:2017年6月8日 上午11:28:52
* 作者: xubo
*
* @param
* @return
*/
public static String arraySign(Map<Object, Object> params, String paySignKey) {
boolean encode = false;
Set<Object> keysSet = params.keySet();
Object[] keys = keysSet.toArray();
Arrays.sort(keys);
StringBuffer temp = new StringBuffer();
boolean first = true;
for (Object key : keys) {
if (first) {
first = false;
} else {
temp.append("&");
}
temp.append(key).append("=");
Object value = params.get(key);
String valueString = "";
if (null != value) {
valueString = value.toString();
}
if (encode) {
try {
temp.append(URLEncoder.encode(valueString, "UTF-8"));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
} else {
temp.append(valueString);
}
}
temp.append("&key=");
temp.append(paySignKey);
System.out.println(temp.toString());
String packageSign = MD5.getMessageDigest(temp.toString());
return packageSign;
}
/**
* 请求,只请求一次,不做重试
*
* @param url
* @param data
* @return
* @throws Exception
*/
public static String requestOnce(final String url, String data) throws Exception {
BasicHttpClientConnectionManager connManager;
connManager = new BasicHttpClientConnectionManager(
RegistryBuilder.<ConnectionSocketFactory>create()
.register("http", PlainConnectionSocketFactory.getSocketFactory())
.register("https", SSLConnectionSocketFactory.getSocketFactory())
.build(),
null,
null,
null
);
HttpClient httpClient = HttpClientBuilder.create()
.setConnectionManager(connManager)
.build();
HttpPost httpPost = new HttpPost(url);
RequestConfig requestConfig = RequestConfig.custom()
.setSocketTimeout(5000)
.setConnectTimeout(5000)
.setConnectionRequestTimeout(10000).build();
httpPost.setConfig(requestConfig);
StringEntity postEntity = new StringEntity(data, "UTF-8");
httpPost.addHeader("Content-Type", "text/xml");
httpPost.addHeader("User-Agent", "wxpay sdk java v1.0 " + ResourceUtil.getConfigByName("wx.mchId"));
httpPost.setEntity(postEntity);
HttpResponse httpResponse = httpClient.execute(httpPost);
HttpEntity httpEntity = httpResponse.getEntity();
String reusltObj = EntityUtils.toString(httpEntity, "UTF-8");
logger.info("请求结果:" + reusltObj);
return reusltObj;
}
}
package com.platform.utils;
import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* xml相关的工具类
*
* @author yang.y
*/
@SuppressWarnings("unchecked")
public class XmlUtil {
/**
* xml字符串转换成bean对象
*
* @param xmlStr xml字符串
* @param clazz 待转换的class
* @return 转换后的对象
*/
public static Object xmlStrToBean(String xmlStr, Class clazz) {
Object obj = null;
try {
// 将xml格式的数据转换成Map对象
Map<String, Object> map = xmlStrToMap(xmlStr);
// 将map对象的数据转换成Bean对象
obj = mapToBean(map, clazz);
} catch (Exception e) {
e.printStackTrace();
}
return obj;
}
/**
* 将xml格式的字符串转换成Map对象
*
* @param xmlStr xml格式的字符串
* @return Map对象
* @throws Exception 异常
*/
public static Map<String, Object> xmlStrToMap(String xmlStr) throws Exception {
if (StringUtils.isNullOrEmpty(xmlStr)) {
return null;
}
Map<String, Object> map = new HashMap<String, Object>();
// 将xml格式的字符串转换成Document对象
Document doc = DocumentHelper.parseText(xmlStr);
// 获取根节点
Element root = doc.getRootElement();
// 获取根节点下的所有元素
List children = root.elements();
// 循环所有子元素
if (children != null && children.size() > 0) {
for (int i = 0; i < children.size(); i++) {
Element child = (Element) children.get(i);
map.put(child.getName(), child.getTextTrim());
}
}
return map;
}
/**
* 将xml格式字符串转换成Bean对象
* 多级子节点递归遍历
*
* @param xmlStr
* @param clazz
* @return
* @throws Exception
*/
public static Object xmlStrToJavaBean(String xmlStr, Class clazz) {
if (StringUtils.isNullOrEmpty(xmlStr)) {
return null;
}
Object obj = null;
Map<String, Object> map = new HashMap<String, Object>();
// 将xml格式的字符串转换成Document对象
Document doc;
try {
doc = DocumentHelper.parseText(xmlStr);
// 获取根节点
Element root = doc.getRootElement();
map = elementToMap(root, map);
// 将map对象的数据转换成Bean对象
obj = mapToBean(map, clazz);
} catch (Exception e) {
e.printStackTrace();
}
return obj;
}
/**
* 递归遍历xml子节点,转换Map
*
* @param element
* @param map
* @return
*/
public static Map<String, Object> elementToMap(Element element, Map<String, Object> map) {
if (element == null || map == null)
return null;
List children = element.elements();
if (children != null && children.size() > 0) {
for (int i = 0; i < children.size(); i++) {
Element child = (Element) children.get(i);
if (child.elements() != null && child.elements().size() > 0)
elementToMap(child, map);
else
map.put(child.getName(), child.getTextTrim());
}
}
return map;
}
/**
* 将Map对象通过反射机制转换成Bean对象
*
* @param map 存放数据的map对象
* @param clazz 待转换的class
* @return 转换后的Bean对象
* @throws Exception 异常
*/
public static Object mapToBean(Map<String, Object> map, Class clazz) throws Exception {
Object obj = clazz.newInstance();
if (map != null && map.size() > 0) {
for (Map.Entry<String, Object> entry : map.entrySet()) {
String propertyName = entry.getKey();
Object value = entry.getValue();
String setMethodName = "set" + propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1);
Field field = getClassField(clazz, propertyName);
if (field != null) {
Class fieldTypeClass = field.getType();
value = convertValType(value, fieldTypeClass);
clazz.getMethod(setMethodName, field.getType()).invoke(obj, value);
}
}
}
return obj;
}
/**
* 将Object类型的值,转换成bean对象属性里对应的类型值
*
* @param value Object对象值
* @param fieldTypeClass 属性的类型
* @return 转换后的值
*/
private static Object convertValType(Object value, Class fieldTypeClass) {
Object retVal = null;
if (Long.class.getName().equals(fieldTypeClass.getName())
|| long.class.getName().equals(fieldTypeClass.getName())) {
retVal = Long.parseLong(value.toString());
} else if (Integer.class.getName().equals(fieldTypeClass.getName())
|| int.class.getName().equals(fieldTypeClass.getName())) {
retVal = Integer.parseInt(value.toString());
} else if (Float.class.getName().equals(fieldTypeClass.getName())
|| float.class.getName().equals(fieldTypeClass.getName())) {
retVal = Float.parseFloat(value.toString());
} else if (Double.class.getName().equals(fieldTypeClass.getName())
|| double.class.getName().equals(fieldTypeClass.getName())) {
retVal = Double.parseDouble(value.toString());
} else {
retVal = value;
}
return retVal;
}
/**
* 获取指定字段名称查找在class中的对应的Field对象(包括查找父类)
*
* @param clazz 指定的class
* @param fieldName 字段名称
* @return Field对象
*/
private static Field getClassField(Class clazz, String fieldName) {
if (Object.class.getName().equals(clazz.getName())) {
return null;
}
Field[] declaredFields = clazz.getDeclaredFields();
for (Field field : declaredFields) {
if (field.getName().equals(fieldName)) {
return field;
}
}
Class superClass = clazz.getSuperclass();
if (superClass != null) {// 简单的递归一下
return getClassField(superClass, fieldName);
}
return null;
}
}
@IgnoreAuth
@ApiOperation(value = "微信订单回调接口")
@RequestMapping(value = "/weChatCallback")
@ResponseBody
public void weChatCallback(HttpServletRequest request, HttpServletResponse response) throws IOException {
try {
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=UTF-8");
response.setHeader("Access-Control-Allow-Origin", "*");
InputStream in = request.getInputStream();
ByteArrayOutputStream out = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len = 0;
while ((len = in.read(buffer)) != -1) {
out.write(buffer, 0, len);
}
out.close();
in.close();
//xml数据
String reponseXml = new String(out.toByteArray(), "utf-8");
System.out.println(reponseXml);
WechatRefundApiResult result = (WechatRefundApiResult) XmlUtil.xmlStrToBean(reponseXml, WechatRefundApiResult.class);
String result_code = result.getResult_code();
if (result_code.equalsIgnoreCase("FAIL")) {
//订单编号
String out_trade_no = result.getOut_trade_no();
logger.error("订单" + out_trade_no + "充值失败");
ApiMerFlowStatementEntity entity=new ApiMerFlowStatementEntity();
entity.setOrderNumber(out_trade_no);
ApiMerFlowStatementEntity merFlowStatementEntity=apiMerFlowStatementService.queryObjectByOrderNumber(entity);
//查询账户信息
ApiMerAccountEntity accountEntity= accountService.queryObject(merFlowStatementEntity.getAccountId());
/**
* 充值失败逻辑
*/
response.getWriter().write(setXml("SUCCESS", "OK"));
} else if (result_code.equalsIgnoreCase("SUCCESS")) {
//订单编号
String out_trade_no = result.getOut_trade_no();
logger.error("订单" + out_trade_no + "充值成功");
ApiMerFlowStatementEntity entity=new ApiMerFlowStatementEntity();
entity.setOrderNumber(out_trade_no);
ApiMerFlowStatementEntity merFlowStatementEntity=apiMerFlowStatementService.queryObjectByOrderNumber(entity);
//查询账户信息
ApiMerAccountEntity accountEntity= accountService.queryObject(merFlowStatementEntity.getAccountId());
/**
* 充值成功逻辑
*/
}
response.getWriter().write(setXml("SUCCESS", "OK"));
}
} catch (Exception e) {
e.printStackTrace();
return;
}
}
如果还有问题的宝宝们,可以和我进行交流哦,对于apicloud前端的代码暂时没有,我负责后台开发的,如果你遇到问题也可以和我沟通,我们一起解决,希望正在研究微信支付和将来会研究微信支付的小伙伴,能够帮助到你们,加油哦!