Today now,微信小程序的支付顺利完成。
不要慌,先说几个我在做支付之前解决的问题:
1、我们公司有自己的App,App可以微信登录,所以做小程序的要求是和App的用户打通。简而言之,App的手机号绑定了一个微信号,如果小程序用该微信号授权,可以直接拿到他在App绑定的手机号。这就会用到UnionID来区分用户的唯一性;
具体的调用方法就不多说了,大家看微信的官方连接就可以
接下来
支付start
微信小程序的支付分为四步:
一、获取openId
二、统一下单(服务端先调用一下微信的后台,获取到一个最重要的值 “prepay_id”)
三、拿到服务端返回的六个参数掉起微信并支付(5个参数+sign)
四、支付回调
第一步:获取openId
微信调用openId的官方文档
注:因为openId是用来区分用户的唯一性的,不会轻易改变,所以我把它存到了数据库,不用每次都调用微信接口
第二步:统一下单
统一下单官方文档
把所有必填的参数都填上(非必填的根据自己的需要填写,这里有个注意的地方就是openId是必填的)
代码如下:
public Map<String, Object> preOrder(Order order) {
Map<String, String> params = new HashMap<String, String>();
params.put("appid", "小程序的appID");
params.put("mch_id", "商户号");
params.put("nonce_str", WXPayUtil.generateNonceStr()); // 随机字符串
params.put("body", "yly-"+StringUtils.subString(order.getGoodsName(), 0, 20));//商品描述
params.put("out_trade_no", order.getPayRequestId()); //商户订单号
//微信的金额要转成分
params.put("total_fee", String.valueOf(AmountUtils.mul2Int(Double.valueOf(order.getTrxAmount()), 100,0)));
params.put("spbill_create_ip", order.getRequestIP()); //终端IP
//回调地址
params.put("notify_url", "回调地址");
//交易类型,小程序取值如下:JSAPI
params.put("trade_type", "JSAPI");
//openID,必传
params.put("openid", order.getOpenId());
// 过滤参数值为空的参数
Map<String, String> sPara = WXPayCore.paraFilter(params);
//把数组所有元素,按照“参数=参数值”的模式用“&”字符拼接成字符串 并且拼接上key
//String mySign = WXPayCore.sha256Sign(sPara,WXPayConstants.SECRET_KEY);
String mySign = WXPayCore.md5Sign(sPara,WXPayConstants.MERCHANT_NO_SECRET);
//签名
params.put("sign", mySign);
try {
String paramsXml = WXPayUtil.mapToXml(params);
LOG.info("paramsXml is{"+paramsXml+"}");
String result = HttpUtils.post(WXPayConstants.UNIFIED_ORDER_URL, paramsXml,Constants.CHARSET_UTF8);
LOG.info("result is{"+result+"}");
Map<String, Object> resultMap = XmlUtils.Dom2Map(result);
LOG.info("resultMap is{"+resultMap+"}");
return resultMap;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
用到的工具类放到最后
按照微信要求的方式一步一步来,我遇到一个坑,那就是接口的调用结果报签名错误
首先检查自己的参数是否传错或者有误,可以用微信官方给的校验工具进行校验,
微信官方签名效验
如果检验不通过,那就检查自己的参数。但是呢,我调用了官方的效验工具,效验是通过的,一模一样,当时的我一脸懵逼,微信bug???
经过一系列排查,才发现是我的秘钥错了,我之前就是用的小程序的appSecret而不是商户的api秘钥,
这里的秘钥必须和上面商户号是匹配的,否则会一直报错,换成商户秘钥,立马能够支付成功!!!
第三步:返给前端5个参数+sign,调起支付
小程序调起支付Api官方文档
代码如下:`public Map<String, String> pay(Order order) {
Map<String, String> params = new HashMap<String, String>();
//小程序ID
params.put(“appId”, “小程序的appID”);
//时间戳
params.put(“timeStamp”,String.valueOf(WXPayUtil.getCurrentTimestamp()));
//随机字符串
params.put(“nonceStr”, WXPayUtil.generateNonceStr());
//数据包
params.put(“package”, “prepay_id=”+“统一下单返回的prepay_id”);
//签名方式
params.put(“signType”, WXPayConstants.MD5);
Map<String, String> sPara = WXPayCore.paraFilter(params);
//String mySign = WXPayCore.sha256Sign(sPara,WXPayConstants.SECRET_KEY);
String paySign = WXPayCore.md5Sign(sPara,"商户号的秘钥");
params.put("paySign", paySign);
LOG.info("返给前端调起微信支付的参数为:{"+params+"}");
return params;
}`
调起支付需要的参数如下图:
*这个有个问题需要注意一下:
调起支付是不需要appId这个参数的,但是签名的时候这个参数是必填的
而且统一下单的时候也需要这个参数,是小写的"appid";
这次是驼峰的大写"appId";
这个一定要注意,要不然会报"支付验证签名失败"
第四步:支付回调
支付回调官方文档
回调没什么可说的,就是修改订单状态以及各种逻辑处理,根据自己的实际业务来写回调,但是需要注意以下两点:
下面是用到的工具类:
/**
* 获取随机字符串 Nonce Str
*
* @return String 随机字符串
*/
public static String generateNonceStr() {
return UUID.randomUUID().toString().replaceAll("-", "").substring(0, 32);
}
/**
* 除去数组中的空值和签名参数
*
* @param sArray
* 签名参数组
* @return 去掉空值与签名参数后的新签名参数组
*/
public static Map<String, String> paraFilter(Map<String, String> sArray) {
Map<String, String> result = new HashMap<String, String>();
if (sArray == null || sArray.size() <= 0) {
return result;
}
for (String key : sArray.keySet()) {
String value = sArray.get(key);
if (value == null || value.equals("") || key.equalsIgnoreCase("sign")) {
continue;
}
result.put(key, value);
}
return result;
}
/**
* 将Map转换为XML格式的字符串
*
* @param data
* Map类型数据
* @return XML格式的字符串
* @throws Exception
*/
public static String mapToXml(Map<String, String> data) throws Exception {
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
org.w3c.dom.Document document = documentBuilder.newDocument();
org.w3c.dom.Element root = document.createElement("xml");
document.appendChild(root);
for (String key : data.keySet()) {
String value = data.get(key);
if (value == null) {
value = "";
}
value = value.trim();
org.w3c.dom.Element filed = document.createElement(key);
filed.appendChild(document.createTextNode(value));
root.appendChild(filed);
}
TransformerFactory tf = TransformerFactory.newInstance();
Transformer transformer = tf.newTransformer();
DOMSource source = new DOMSource(document);
transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
StringWriter writer = new StringWriter();
StreamResult result = new StreamResult(writer);
transformer.transform(source, result);
String output = writer.getBuffer().toString(); // .replaceAll("\n|\r",
// "");
try {
writer.close();
} catch (Exception ex) {
}
return output;
}
/**
* xml数据转map
* @param xml
* @return
* @throws DocumentException
* @author Ban Wei
*/
@SuppressWarnings("rawtypes")
public static Map<String, Object> Dom2Map(String xml) throws DocumentException{
Map<String, Object> map = new HashMap<String, Object>();
Document doc= DocumentHelper.parseText(xml);
if(doc == null)
return map;
Element root = doc.getRootElement();
for (Iterator iterator = root.elementIterator(); iterator.hasNext();) {
Element e = (Element) iterator.next();
List list = e.elements();
if(list.size() > 0){
map.put(e.getName(), Dom2Map(e));
}else
map.put(e.getName(), e.getText());
}
return map;
}
/**
* 获取当前时间戳,单位秒
*
* @return
*/
public static long getCurrentTimestamp() {
return System.currentTimeMillis() / 1000;
}
/**
* 把数组所有元素字典排序,并按照“参数=参数值”的模式用“&”字符拼接成字符串
*
* @param params
* 需要排序并参与字符拼接的参数组
* @return 拼接后字符串
*/
public static String createLinkString(Map<String, String> params) {
List<String> keys = new ArrayList<String>(params.keySet());
Collections.sort(keys);
String prestr = "";
for (int i = 0; i < keys.size(); i++) {
String key = keys.get(i);
String value = params.get(key);
prestr = prestr + key + "=" + value + "&";
}
return prestr;
}
/**
* 生成 MD5
*
* @param data
* 待处理数据
* @return MD5结果
* @throws Exception
*/
public static String MD5(String data) throws Exception {
java.security.MessageDigest md = MessageDigest.getInstance("MD5");
byte[] array = md.digest(data.getBytes("UTF-8"));
StringBuilder sb = new StringBuilder();
for (byte item : array) {
sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
}
return sb.toString().toUpperCase();
}
/**
*发送post请求
* @param reqUrl
* @param content
* @param charset
* @return
* @throws IOException
*/
public static String post(String reqUrl, String content, String charset) throws IOException {
URL url = new URL(reqUrl);
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setDoInput(true);
con.setDoOutput(true);
con.setAllowUserInteraction(false);
con.setUseCaches(false);
con.setRequestMethod("POST");
con.setConnectTimeout(30000);// 连接超时 单位毫秒
con.setDoOutput(true);// 是否输入参数
con.setRequestProperty("Content-Type", "application/x-www-form-urlencoded;charset=" + charset);
// con.setRequestProperty("Content-Type","text/plain;charset=" +
// charset);
DataOutputStream dataOutput = new DataOutputStream(con.getOutputStream());
byte[] bytes = content.toString().getBytes(charset);
dataOutput.write(bytes);// 输入参数
dataOutput.flush();
dataOutput.close();
BufferedReader bin = new BufferedReader(new InputStreamReader(con.getInputStream(), charset), SIZE);
StringBuilder result = new StringBuilder();
while (true) {
String line = bin.readLine();
if (line == null) {
break;
} else {
result.append(line);
}
}
bin.close();
return result.toString();
}
到这里,小程序微信支付就大功告成了,是不是很简单呀
我也是第一次写博客,
有写的不对的地方欢迎指导,我也不吝赐教
大家如果有什么别的问题:可以加我的QQ:2454424223
我一定知无不言,言无不尽!!!