前段时间在公司的开发了一个微信小程序的项目,今天来说一说微信小程序的支付,有很多优秀的文章都说了小程序支付的
开发流程步骤,这里我们推荐一个博主以前就是看他的开发小程序支付:https://github.com/1913045515/weixin
现在来说说我对小程序支付的理解,首先我们在开发文档中找到小程序支付流程,
上面的图是我们支付的流程图,下面是开发要调用接口的顺序,
首先第一步我们获取openid,因为在统一下单接口中的交易类型是JSAPI,是必须要填openid
第二步就是下单了,把我们要下单参数拼接成xml的形式去发送接口,这里主要是签名很重要
签名:
1.签名算法
签名生成的通用步骤如下:
第一步,设所有发送或者接收到的数据为集合M,将集合M内非空参数值的参数按照参数名ASCII码从小到大排序(字典序),使用URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串stringA。
特别注意以下重要规则:
◆ 参数名ASCII码从小到大排序(字典序);
◆ 如果参数的值为空不参与签名;
◆ 参数名区分大小写;
◆ 验证调用返回或微信主动通知签名时,传送的sign参数不参与签名,将生成的签名与该sign值作校验。
◆ 微信接口可能增加字段,验证签名时必须支持增加的扩展字段
第二步,在stringA最后拼接上key=(API密钥的值)得到stringSignTemp字符串,并对stringSignTemp进行MD5运算,再将得到的字符串所有字符转换为大写,得到sign值signValue。
假设传送的参数如下:
appid: wxd930ea5d5a258f4f
mch_id: 10000100
device_info: 1000
body: test
nonce_str: ibuaiVcKdpRxkhJA
第一步:对参数按照key=value的格式,并按照参数名ASCII字典序排序如下:
stringA=”appid=wxd930ea5d5a258f4f&body=test&device_info=1000&mch_id=10000100&nonce_str=ibuaiVcKdpRxkhJA”;
第二步:拼接API密钥:
stringSignTemp=”stringA&key=192006250b4c09247ec02edce69f6a2d”
sign=MD5(stringSignTemp).toUpperCase()=”9A0A8659F005D6984697E2CA0A9CF3B7”
网友的整理:
这样我们就得到了签名
我们下单成功后微信会给我们预支付交易会话标识 这个很主要,拿这个在进行一次签名加密,
因为我们调用支付的接口中需要一个签名,支付成功后就会调用我们下单时所传的回调地址,我们在回调方法中判断用户是否支付成功,进行业务处理
代码实例:
OrderController.java
/** * 下单: * @param openid 用户openid * @param money 金额 * @return map */ @RequestMapping("/createOrder") @ResponseBody public Map<String,String>createOrder(String openid,int money){ logger.info("用户的openid:-------->"+openid+"下单的金额:-------------->"+money); String mch_id= WeChatTool.mch_id; //商户号 String today = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()); String WXPay= WXPayUtil.createCode(8); String out_trade_no=mch_id+today+WXPay;//生成订单号 Map<String,String> result=new HashMap<String,String>(); //去Service层中去生成签名,用户openid out_trade_no订单号 money支付的金额 String formData=orderService.getopenid(openid,out_trade_no,money); //在servlet层中生成签名成功后,把下单所要的参数以xml的格式拼接,发送下单接口 String httpResult = HttpUtils.httpXMLPost(WeChatTool.createOrderUrl,formData); try { //xml转换成Map对象或者值 Map<String, String> resultMap = WXPayUtil.xmlToMap(httpResult); result.put("package", "prepay_id=" + resultMap.get("prepay_id")); //这里是拿下单成功的微信交易号去拼接,因为在下面的接口中必须要这个样子 result.put("nonceStr",resultMap.get("nonce_str")); //随机字符串 } catch (Exception e) { e.printStackTrace(); } String times= WXPayUtil.getCurrentTimestamp()+""; //获取当前时间 result.put("timeStamp",times); //当前时间戳 //生成调用支付接口要的签名 Map<String, String> packageParams = new HashMap<String ,String>(); packageParams.put("appId", WeChatTool.wxspAppid); packageParams.put("signType", WeChatTool.sign_type); packageParams.put("nonceStr",result.get("nonceStr")+""); packageParams.put("timeStamp",times); packageParams.put("package", result.get("package")+"");//商户订单号 String sign=""; try { sign= WXPayUtil.generateSignature(packageParams, WeChatTool.sercet_key); //生成签名: } catch (Exception e) { e.printStackTrace(); } result.put("paySign",sign); logger.info("签名成功----->"+result.get("paySign")); return result; //所有的参数放进map中保存发送到小程序页面中,去调用微信支付接口 }OrderServiceImpl.java:
/** * 统一下单 * @param openid 用户标识 * @param out_trade_no 订单号 * @param total_fee 金额 * @return String */ @Override public String getopenid(String openid,String out_trade_no,int total_fee) { //下单的金额,因为在微信支付中默认是分所以要这样处理 Integer total_fees=total_fee*100; 微信下单的金额是String类型的所以要转换类型 String money=total_fees.toString(); String nonceStr=WXPayUtil.generateUUID(); //设置UUID作为随机字符串 Map<String ,String> map = new HashMap<String ,String>(); map.put("appid",WeChatTool.wxspAppid); //商户appid map.put("mch_id", WeChatTool.mch_id);//商户号 map.put("nonce_str",nonceStr); //随机数 map.put("body","大米");//商户名称 map.put("out_trade_no",out_trade_no);//商户订单号 map.put("total_fee",money);//下单金额 map.put("spbill_create_ip", "127.0.0.1");//终端IP map.put("notify_url",https://xxxx/xxxxx/notify.do);//回调地址 这里的接口必须是在线上用户支付成功才能收到微信发送的信息 map.put("trade_type","JSAPI");//交易类型 map.put("openid",openid+"");//用户openid map.put("sign_type","MD5");//加密类型 String sign=""; try { sign= WXPayUtil.generateSignature(map, WeChatTool.sercet_key); //生成sign签名WeChatTool.sercet_key是商户的支付秘钥 } catch (Exception e) { e.printStackTrace(); } //拼接成xml的格式,这里的参数必须要和上面的一致,并且每次下单的订单号不能一致 String formData="<xml>"; formData += "<appid>"+ WeChatTool.wxspAppid+"</appid>"; formData += "<mch_id>"+ WeChatTool.mch_id+"</mch_id>"; formData += "<nonce_str>"+nonceStr+"</nonce_str>"; formData += "<body>"+WeChatTool.month+"</body>"; formData += "<out_trade_no>"+out_trade_no +"</out_trade_no>"; formData += "<total_fee>"+money+"</total_fee>"; formData += "<spbill_create_ip>"+"127.0.0.1"+"</spbill_create_ip>"; formData += "<notify_url>"+WeChatTool.notify_url+"</notify_url>"; formData += "<trade_type>"+WeChatTool.trade_type+"</trade_type>"; formData += "<openid>"+openid+"</openid>"; //appid formData += "<sign_type>"+WeChatTool.sign_type+"</sign_type>"; formData += "<sign>"+sign+"</sign>"; //签名算法 formData += "</xml>"; return formData; }工具类:
WXPayUtil.java:
import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.w3c.dom.Node;import org.w3c.dom.NodeList;import com.youquan.utli.WXPayConstants.SignType;import javax.crypto.Mac;import javax.crypto.spec.SecretKeySpec;import javax.xml.parsers.DocumentBuilder;import javax.xml.parsers.DocumentBuilderFactory;import javax.xml.transform.OutputKeys;import javax.xml.transform.Transformer;import javax.xml.transform.TransformerFactory;import javax.xml.transform.dom.DOMSource;import javax.xml.transform.stream.StreamResult;import java.io.ByteArrayInputStream;import java.io.InputStream;import java.io.StringWriter;import java.security.MessageDigest;import java.text.ParseException;import java.text.SimpleDateFormat;import java.util.*;/** * <p>User: qrn * <p>Date: 14-1-28 * <p>Version: 1.0 * 描述: 工具类 */public class WXPayUtil { /** * XML格式字符串转换为Map * @param strXML XML字符串 * @return Map XML数据转换后的Map * @see Exception */ public static Map<String, String> xmlToMap(String strXML) throws Exception { try { Map<String, String> data = new HashMap<String, String>(); DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); InputStream stream = new ByteArrayInputStream(strXML.getBytes("UTF-8")); org.w3c.dom.Document doc = documentBuilder.parse(stream); doc.getDocumentElement().normalize(); NodeList nodeList = doc.getDocumentElement().getChildNodes(); for (int idx = 0; idx < nodeList.getLength(); ++idx) { Node node = nodeList.item(idx); if (node.getNodeType() == Node.ELEMENT_NODE) { org.w3c.dom.Element element = (org.w3c.dom.Element) node; data.put(element.getNodeName(), element.getTextContent()); } } try { stream.close(); } catch (Exception ex) { // do nothing } return data; } catch (Exception ex) { WXPayUtil.getLogger().warn("Invalid XML, can not convert to map. Error message: {}. XML content: {}", ex.getMessage(), strXML); throw ex; } } /** * 将Map转换为XML格式的字符串 * @param data Map类型数据 * @return XML格式的字符串 * @see 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("|r", ""); try { writer.close(); } catch (Exception ex) { } return output; } /** * 生成6位或10位随机数 param codeLength(多少位) * @param codeLength 参数 * @return String */ public static String createCode(int codeLength) { String code = ""; for (int i = 0; i < codeLength; i++) { code += (int) (Math.random() * 9); } return code; } /** * 生成带有 sign 的 XML 格式字符串 * @param data Map类型数据 * @param key API密钥 * @see Exception * @return String 含有sign字段的XML */ public static String generateSignedXml(final Map<String, String> data, String key) throws Exception { return generateSignedXml(data, key, SignType.MD5); } /** * 生成带有 sign 的 XML 格式字符串 * * @param data Map类型数据 * @param key API密钥 * @param signType 签名类型 * @see Exception * @return 含有sign字段的XML */ public static String generateSignedXml(final Map<String, String> data, String key, SignType signType) throws Exception { String sign = generateSignature(data, key, signType); data.put(WXPayConstants.FIELD_SIGN, sign); return mapToXml(data); } /** * 判断签名是否正确 * @param xmlStr XML格式数据 * @param key API密钥 * @return boolean 签名是否正确 * @see Exception */ public static boolean isSignatureValid(String xmlStr, String key) throws Exception { Map<String, String> data = xmlToMap(xmlStr); if (!data.containsKey(WXPayConstants.FIELD_SIGN) ) { return false; } String sign = data.get(WXPayConstants.FIELD_SIGN); return generateSignature(data, key).equals(sign); } /** * 判断签名是否正确,必须包含sign字段,否则返回false。使用MD5签名。 * @param data Map类型数据 * @param key API密钥 * @return boolean 签名是否正确 * @see Exception */ public static boolean isSignatureValid(Map<String, String> data, String key) throws Exception { return isSignatureValid(data, key, SignType.MD5); } /** * 判断签名是否正确,必须包含sign字段,否则返回false。 * * @param data Map类型数据 * @param key API密钥 * @param signType 签名方式 * @return boolean 签名是否正确 * @exception Exception */ public static boolean isSignatureValid(Map<String, String> data, String key, SignType signType) throws Exception { if (!data.containsKey(WXPayConstants.FIELD_SIGN) ) { return false; } String sign = data.get(WXPayConstants.FIELD_SIGN); return generateSignature(data, key, signType).equals(sign); } /** * 生成签名 * @param data 待签名数据 * @param key API密钥 * @return String * @see Exception */ public static String generateSignature(final Map<String, String> data, String key) throws Exception { return generateSignature(data, key, SignType.MD5); } /** * 生成签名. 注意,若含有sign_type字段,必须和signType参数保持一致。 * * @param data 待签名数据 * @param key API密钥 * @param signType 签名方式 * @return String 签名 * @see Exception */ public static String generateSignature(final Map<String, String> data, String key, SignType signType) throws Exception { Set<String> keySet = data.keySet(); String[] keyArray = keySet.toArray(new String[keySet.size()]); Arrays.sort(keyArray); StringBuilder sb = new StringBuilder(); for (String k : keyArray) { if (k.equals(WXPayConstants.FIELD_SIGN)) { continue; } if (data.get(k).trim().length() > 0) // 参数值为空,则不参与签名 sb.append(k).append("=").append(data.get(k).trim()).append("&"); } sb.append("key=").append(key); if (SignType.MD5.equals(signType)) { return MD5(sb.toString()).toUpperCase(); } else if (SignType.HMACSHA256.equals(signType)) { return HMACSHA256(sb.toString(), key); } else { throw new Exception(String.format("Invalid sign_type: %s", signType)); } } /** * 获取随机字符串 Nonce Str * * @return String 随机字符串 */ public static String generateNonceStr() { return UUID.randomUUID().toString().replaceAll("-", "").substring(0, 32); } /** * * * @param data 待处理数据 * @return String MD5结果 * @see Exception */ public static String MD5(String data) throws Exception { 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(); } /** * 生成 HMACSHA256 * @param da














