小程序支付流程交互图:

进入小程序,下单,请求下单支付,调用小程序登录API来获取Openid,生成商户订单
1// pages/pay/pay.js 2var app = getApp(); 3Page({ 4data: {}, 5onLoad: function (options) { 6// 页面初始化 options为页面跳转所带来的参数 7}, 8/* 微信支付 */ 9wxpay: function () { 10var that = this; 11//登陆获取code 12wx.login({ 13success: function (res) { 14//获取openid 15that.getOpenId(res.code); 16} 17}); 18}, 19getOpenId: function (code) { 20var that = this; 21wx.request({ 22url: "https://api.weixin.qq.com/sns/jscode2session?appid=小程序appid&secret=小程序Secret&js_code=" + code + "&grant_type=authorization_code", 23data: {}, 24method: 'GET', 25success: function (res) { 26console.log(res.data); 27that.generateOrder(res.data.openid); 28}, 29fail: function () { 30// fail 31}, 32complete: function () { 33// complete 34} 35}) 36}, 37/**生成商户订单 */ 38generateOrder: function (openid) { 39var that = this; 40//统一支付 41wx.request({ 42url: 'http://localhost:9090/weixin/payment.do', 43method: 'GET', 44data: { 45total_fee: '666', //金额,注意以分为单位 46body: '茅台', //产品简单描述 47attach:'广州分店' //附加数据 48}, 49success: function (res) { 50var pay = res.data 51//发起支付 52var timeStamp = pay[0].timeStamp; 53var packages = pay[0].package; 54var paySign = pay[0].paySign; 55var nonceStr = pay[0].nonceStr; 56var param = { "timeStamp": timeStamp, "package": packages, "paySign": paySign, "signType": "MD5", "nonceStr": nonceStr }; 57that.pay(param); 58}, 59}) 60}, 61/* 支付 */ 62pay: function (param) { 63wx.requestPayment({ 64timeStamp: param.timeStamp, 65nonceStr: param.nonceStr, 66package: param.package, 67signType: param.signType, 68paySign: param.paySign, 69success: function (res) { 70wx.navigateBack({ 71delta: 1, // 回退前 delta(默认为1) 页面 72success: function (res) { 73wx.showToast({ 74title: '支付成功', 75icon: 'success', 76duration: 2000 77}) 78}, 79fail: function () { 80// fail 81}, 82complete: function () { 83// complete 84} 85}) 86}, 87fail: function (res) { 88// fail 89console.log("支付失败"); 90}, 91complete: function () { 92// complete 93console.log("pay complete"); 94} 95}) 96} 97}) 98调用支付统一下单API来获取prepay_id,并将小程序调起支付数据需要签名的字段appId,timeStamp,nonceStr,package再次签名
后台代码
1package com.card.mp.controller; 2 3import com.alibaba.fastjson.JSON; 4import com.alibaba.fastjson.JSONObject; 5import com.card.dto.PaymentDto; 6import com.card.framework.utils.*; 7import org.dom4j.Document; 8import org.dom4j.DocumentException; 9import org.dom4j.Element; 10import org.dom4j.io.SAXReader; 11import org.springframework.stereotype.Controller; 12import org.springframework.web.bind.annotation.RequestMapping; 13import org.springframework.web.bind.annotation.RequestParam; 14import org.springframework.web.bind.annotation.ResponseBody; 15 16import javax.servlet.http.HttpServletRequest; 17import java.io.ByteArrayInputStream; 18import java.io.InputStream; 19import java.io.UnsupportedEncodingException; 20import java.text.SimpleDateFormat; 21import java.util.Date; 22import java.util.HashMap; 23import java.util.List; 24import java.util.Map; 25 26@Controller 27public class WeiXinPaymentController extends BaseController { 28private final String mch_id = "填写商户号";//商户号 29private final String spbill_create_ip = "填写终端IP";//终端IP 30private final String notify_url = "域名/weixin/paycallback.do";//通知地址 31private final String trade_type = "JSAPI";//交易类型 32private final String url = "https://api.mch.weixin.qq.com/pay/unifiedorder";//统一下单API接口链接 33private final String key = "&key=填写商户支付密钥"; // 商户支付密钥 34private final String appid = "填写小程序AppId"; 35 36/** 37* 38* @param openId 39* @param total_fee 订单总金额,单位为分。 40* @param body 商品简单描述,该字段请按照规范传递。 例:腾讯充值中心-心悦会员充值 41* @param attach 附加数据,在查询API和支付通知中原样返回,可作为自定义参数使用。 例:广州分店 42* @return 43* @throws UnsupportedEncodingException 44* @throws DocumentException 45*/ 46@RequestMapping("/weixin/payment.do") 47@ResponseBody 48public JSONObject payment(@RequestParam(required = true) String openId, @RequestParam(required = true)String total_fee, @RequestParam(required = false) String body, @RequestParam(required = false) String attach) throws UnsupportedEncodingException, DocumentException { 49JSONObject JsonObject = new JSONObject() ; 50body = new String(body.getBytes("UTF-8"),"ISO-8859-1"); 51String nonce_str = UUIDHexGenerator.generate();//随机字符串 52String today = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()); 53String code = PayUtil.createCode(8); 54String out_trade_no = mch_id + today + code;//商户订单号 55 56String openid = openId;//用户标识 57PaymentDto paymentPo = new PaymentDto(); 58paymentPo.setAppid(appid); 59paymentPo.setMch_id(mch_id); 60paymentPo.setNonce_str(nonce_str); 61String newbody = new String(body.getBytes("ISO-8859-1"),"UTF-8");//以utf-8编码放入paymentPo,微信支付要求字符编码统一采用UTF-8字符编码 62paymentPo.setBody(newbody); 63paymentPo.setOut_trade_no(out_trade_no); 64paymentPo.setTotal_fee(total_fee); 65paymentPo.setSpbill_create_ip(spbill_create_ip); 66paymentPo.setNotify_url(notify_url); 67paymentPo.setTrade_type(trade_type); 68paymentPo.setOpenid(openid); 69// 把请求参数打包成数组 70Map<String, Object> sParaTemp = new HashMap(); 71sParaTemp.put("appid", paymentPo.getAppid()); 72sParaTemp.put("mch_id", paymentPo.getMch_id()); 73sParaTemp.put("nonce_str", paymentPo.getNonce_str()); 74sParaTemp.put("body", paymentPo.getBody()); 75sParaTemp.put("out_trade_no", paymentPo.getOut_trade_no()); 76sParaTemp.put("total_fee",paymentPo.getTotal_fee()); 77sParaTemp.put("spbill_create_ip", paymentPo.getSpbill_create_ip()); 78sParaTemp.put("notify_url",paymentPo.getNotify_url()); 79sParaTemp.put("trade_type", paymentPo.getTrade_type()); 80sParaTemp.put("openid", paymentPo.getOpenid()); 81// 除去数组中的空值和签名参数 82Map sPara = PayUtil.paraFilter(sParaTemp); 83String prestr = PayUtil.createLinkString(sPara); // 把数组所有元素,按照“参数=参数值”的模式用“&”字符拼接成字符串 84 85//MD5运算生成签名 86String mysign = PayUtil.sign(prestr, key, "utf-8").toUpperCase(); 87paymentPo.setSign(mysign); 88//打包要发送的xml 89String respXml = XmlUtil.messageToXML(paymentPo); 90// 打印respXml发现,得到的xml中有“__”不对,应该替换成“_” 91respXml = respXml.replace("__", "_"); 92String param = respXml; 93//String result = SendRequestForUrl.sendRequest(url, param);//发起请求 94String result = PayUtil.httpRequest(url, "POST", param); 95System.out.println("请求微信预支付接口,返回 result:"+result); 96// 将解析结果存储在Map中 97Map map = new HashMap(); 98InputStream in=new ByteArrayInputStream(result.getBytes()); 99// 读取输入流 100SAXReader reader = new SAXReader(); 101Document document = reader.read(in); 102// 得到xml根元素 103Element root = document.getRootElement(); 104// 得到根元素的所有子节点 105List<Element> elementList = root.elements(); 106for (Element element : elementList) { 107map.put(element.getName(), element.getText()); 108} 109// 返回信息 110String return_code = map.get("return_code").toString();//返回状态码 111String return_msg = map.get("return_msg").toString();//返回信息 112String result_code = map.get("result_code").toString;//返回状态码 113 114System.out.println("请求微信预支付接口,返回 code:" + return_code); 115System.out.println("请求微信预支付接口,返回 msg:" + return_msg); 116if("SUCCESS".equals(return_code) && "SUCCESS".equals(result_code)){ 117// 业务结果 118String prepay_id = map.get("prepay_id").toString();//返回的预付单信息 119String nonceStr = UUIDHexGenerator.generate(); 120JsonObject.put("nonceStr", nonceStr); 121JsonObject.put("package", "prepay_id=" + prepay_id); 122Long timeStamp = System.currentTimeMillis() / 1000; 123JsonObject.put("timeStamp", timeStamp + ""); 124String stringSignTemp = "appId=" + appid + "&nonceStr=" + nonceStr + "&package=prepay_id=" + prepay_id + "&signType=MD5&timeStamp=" + timeStamp; 125//再次签名 126String paySign = PayUtil.sign(stringSignTemp, key, "utf-8").toUpperCase(); 127JsonObject.put("paySign", paySign); 128} 129return JsonObject; 130} 131 132 133/** 134* 预支付时填写的 notify_url ,支付成功后的回调接口 135* @param request 136*/ 137@RequestMapping("/weixin/paycallback.do") 138@ResponseBody 139public void paycallback(HttpServletRequest request) { 140try { 141Map<String, Object> dataMap = XmlUtil.parseXML(request); 142System.out.println(JSON.toJSONString(dataMap)); 143//{"transaction_id":"4200000109201805293331420304","nonce_str":"402880e963a9764b0163a979a16e0002","bank_type":"CFT","openid":"oXI6G5Jc4D44y2wixgxE3OPwpDVg","sign":"262978D36A3093ACBE4B55707D6EA7B2","fee_type":"CNY","mch_id":"1491307962","cash_fee":"10","out_trade_no":"14913079622018052909183048768217","appid":"wxa177427bc0e60aab","total_fee":"10","trade_type":"JSAPI","result_code":"SUCCESS","time_end":"20180529091834","is_subscribe":"N","return_code":"SUCCESS"} 144} catch (Exception e) { 145e.printStackTrace(); 146} 147} 148} 149 150后台业务逻辑涉及到的工具类及参数封装类 151XmlUtil 152 153package com.card.framework.utils; 154 155import com.card.dto.PaymentDto; 156import com.thoughtworks.xstream.XStream; 157import com.thoughtworks.xstream.core.util.QuickWriter; 158import com.thoughtworks.xstream.io.HierarchicalStreamWriter; 159import com.thoughtworks.xstream.io.xml.PrettyPrintWriter; 160import com.thoughtworks.xstream.io.xml.XppDriver; 161import org.dom4j.Document; 162import org.dom4j.DocumentException; 163import org.dom4j.Element; 164import org.dom4j.io.SAXReader; 165 166import javax.servlet.http.HttpServletRequest; 167import java.io.IOException; 168import java.io.Writer; 169import java.util.HashMap; 170import java.util.List; 171import java.util.Map; 172 173public class XmlUtil { 174public static Map<String,Object> parseXML(HttpServletRequest request) throws IOException, DocumentException { 175Map<String,Object> map=new HashMap<String,Object>(); 176/* 通过IO获得Document */ 177SAXReader reader = new SAXReader(); 178Document doc = reader.read(request.getInputStream()); 179//得到xml的根节点 180Element root=doc.getRootElement(); 181recursiveParseXML(root,map); 182return map; 183} 184private static void recursiveParseXML(Element root,Map<String,Object> map){ 185//得到根节点的子节点列表 186List<Element> elementList=root.elements(); 187//判断有没有子元素列表 188if(elementList.size()==0){ 189map.put(root.getName(), root.getTextTrim()); 190} 191else{ 192//遍历 193for(Element e:elementList){ 194recursiveParseXML(e,map); 195} 196} 197} 198 199private static XStream xstream = new XStream(new XppDriver() { 200public HierarchicalStreamWriter createWriter(Writer out) { 201return new PrettyPrintWriter(out) { 202// 对所有xml节点都增加CDATA标记 203boolean cdata = true; 204public void startNode(String name, Class clazz) { 205super.startNode(name, clazz); 206} 207protected void writeText(QuickWriter writer, String text) { 208if (cdata) { 209writer.write(text); 210} else { 211writer.write(text); 212} 213} 214}; 215} 216}); 217public static String messageToXML(PaymentDto paymentPo){ 218xstream.alias("xml",PaymentDto.class); 219return xstream.toXML(paymentPo); 220} 221} 222 223PaymentDto //封装支付参数实体 224 225package com.card.dto; 226 227import java.io.Serializable; 228 229public class PaymentDto implements Serializable { 230private String appid;//小程序ID 231private String mch_id;//商户号 232private String device_info;//设备号 233private String nonce_str;//随机字符串 234private String sign;//签名 235private String body;//商品描述 236private String detail;//商品详情 237private String attach;//附加数据 238private String out_trade_no;//商户订单号 239private String fee_type;//货币类型 240private String spbill_create_ip;//终端IP 241private String time_start;//交易起始时间 242private String time_expire;//交易结束时间 243private String goods_tag;//商品标记 244private String total_fee;//总金额 245private String notify_url;//通知地址 246private String trade_type;//交易类型 247private String limit_pay;//指定支付方式 248private String openid;//用户标识 249public String getAppid() { 250return appid; 251} 252public void setAppid(String appid) { 253this.appid = appid; 254} 255public String getMch_id() { 256return mch_id; 257} 258public void setMch_id(String mch_id) { 259this.mch_id = mch_id; 260} 261public String getNonce_str() { 262return nonce_str; 263} 264public void setNonce_str(String nonce_str) { 265this.nonce_str = nonce_str; 266} 267public String getSign() { 268return sign; 269} 270public void setSign(String sign) { 271this.sign = sign; 272} 273public String getBody() { 274return body; 275} 276public void setBody(String body) { 277this.body = body; 278} 279public String getOut_trade_no() { 280return out_trade_no; 281} 282public void setOut_trade_no(String out_trade_no) { 283this.out_trade_no = out_trade_no; 284} 285public String getTotal_fee() { 286return total_fee; 287} 288public void setTotal_fee(String total_fee) { 289this.total_fee = total_fee; 290} 291public String getNotify_url() { 292return notify_url; 293} 294public void setNotify_url(String notify_url) { 295this.notify_url = notify_url; 296} 297public String getTrade_type() { 298return trade_type; 299} 300public void setTrade_type(String trade_type) { 301this.trade_type = trade_type; 302} 303public String getOpenid() { 304return openid; 305} 306public void setOpenid(String openid) { 307this.openid = openid; 308} 309public String getSpbill_create_ip() { 310return spbill_create_ip; 311} 312public void setSpbill_create_ip(String spbill_create_ip) { 313this.spbill_create_ip = spbill_create_ip; 314} 315public String getDevice_info() { 316return device_info; 317} 318public void setDevice_info(String device_info) { 319this.device_info = device_info; 320} 321public String getDetail() { 322return detail; 323} 324public void setDetail(String detail) { 325this.detail = detail; 326} 327public String getAttach() { 328return attach; 329} 330public void setAttach(String attach) { 331this.attach = attach; 332} 333public String getFee_type() { 334return fee_type; 335} 336public void setFee_type(String fee_type) { 337this.fee_type = fee_type; 338} 339public String getTime_start() { 340return time_start; 341} 342public void setTime_start(String time_start) { 343this.time_start = time_start; 344} 345public String getTime_expire() { 346return time_expire; 347} 348public void setTime_expire(String time_expire) { 349this.time_expire = time_expire; 350} 351public String getGoods_tag() { 352return goods_tag; 353} 354public void setGoods_tag(String goods_tag) { 355this.goods_tag = goods_tag; 356} 357public String getLimit_pay() { 358return limit_pay; 359} 360public void setLimit_pay(String limit_pay) { 361this.limit_pay = limit_pay; 362} 363} 364PayUtil 365 366package com.card.framework.utils; 367 368import org.apache.commons.codec.digest.DigestUtils; 369 370import java.io.*; 371import java.net.HttpURLConnection; 372import java.net.URL; 373import java.util.*; 374 375public class PayUtil { 376/** 377* 签名字符串 378* @param text 需要签名的字符串 379* @param key 密钥 380* @param input_charset 编码格式 381* @return 签名结果 382*/ 383public static String sign(String text, String key, String input_charset) { 384text = text + key; 385return DigestUtils.md5Hex(getContentBytes(text, input_charset)); 386} 387/** 388* 签名字符串 389* @param text 需要签名的字符串 390* @param sign 签名结果 391* @param key 密钥 392* @param input_charset 编码格式 393* @return 签名结果 394*/ 395public static boolean verify(String text, String sign, String key, String input_charset) { 396text = text + key; 397String mysign = DigestUtils.md5Hex(getContentBytes(text, input_charset)); 398return mysign.equals(sign); 399} 400/** 401* @param content 402* @param charset 403* @return 404* @throws UnsupportedEncodingException 405*/ 406public static byte[] getContentBytes(String content, String charset) { 407if (charset == null || "".equals(charset)) { 408return content.getBytes(); 409} 410try { 411return content.getBytes(charset); 412} catch (UnsupportedEncodingException e) { 413throw new RuntimeException("MD5签名过程中出现错误,指定的编码集不对,您目前指定的编码集是:" + charset); 414} 415} 416/** 417* 生成6位或10位随机数 param codeLength(多少位) 418* @return 419*/ 420public static String createCode(int codeLength) { 421String code = ""; 422for (int i = 0; i < codeLength; i++) { 423code += (int) (Math.random() * 9); 424} 425return code; 426} 427private static boolean isValidChar(char ch) { 428if ((ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z')) 429return true; 430return (ch >= 0x4e00 && ch <= 0x7fff) || (ch >= 0x8000 && ch <= 0x952f); 431} 432/** 433* 除去数组中的空值和签名参数 434* @param sArray 签名参数组 435* @return 去掉空值与签名参数后的新签名参数组 436*/ 437public static Map paraFilter(Map<String,Object> sArray) { 438Map result = new HashMap(); 439if (sArray == null || sArray.size() <= 0) { 440return result; 441} 442for (String key : sArray.keySet()) { 443String value = (String) sArray.get(key); 444if (value == null || value.equals("") || key.equalsIgnoreCase("sign") 445|| key.equalsIgnoreCase("sign_type")) { 446continue; 447} 448result.put(key, value); 449} 450return result; 451} 452/** 453* 把数组所有元素排序,并按照“参数=参数值”的模式用“&”字符拼接成字符串 454* @param params 需要排序并参与字符拼接的参数组 455* @return 拼接后字符串 456*/ 457public static String createLinkString(Map params) { 458List keys = new ArrayList(params.keySet()); 459Collections.sort(keys); 460String prestr = ""; 461for (int i = 0; i < keys.size(); i++) { 462String key = (String) keys.get(i); 463String value = (String) params.get(key); 464if (i == keys.size() - 1) {// 拼接时,不包括最后一个&字符 465prestr = prestr + key + "=" + value; 466} else { 467prestr = prestr + key + "=" + value + "&"; 468} 469} 470return prestr; 471} 472/** 473* 474* @param requestUrl 请求地址 475* @param requestMethod 请求方法 476* @param outputStr 参数 477*/ 478public static String httpRequest(String requestUrl,String requestMethod,String outputStr){ 479// 创建SSLContext 480StringBuffer buffer=null; 481try{ 482URL url = new URL(requestUrl); 483HttpURLConnection conn = (HttpURLConnection) url.openConnection(); 484conn.setRequestMethod(requestMethod); 485conn.setDoOutput(true); 486conn.setDoInput(true); 487conn.connect(); 488//往服务器端写内容 489if(null !=outputStr){ 490OutputStream os=conn.getOutputStream(); 491os.write(outputStr.getBytes("utf-8")); 492os.close(); 493} 494// 读取服务器端返回的内容 495InputStream is = conn.getInputStream(); 496InputStreamReader isr = new InputStreamReader(is, "utf-8"); 497BufferedReader br = new BufferedReader(isr); 498buffer = new StringBuffer(); 499String line = null; 500while ((line = br.readLine()) != null) { 501buffer.append(line); 502} 503}catch(Exception e){ 504e.printStackTrace(); 505} 506return buffer.toString(); 507} 508public static String urlEncodeUTF8(String source){ 509String result=source; 510try { 511result=java.net.URLEncoder.encode(source, "UTF-8"); 512} catch (UnsupportedEncodingException e) { 513// TODO Auto-generated catch block 514e.printStackTrace(); 515} 516return result; 517} 518} 519UUIDHexGenerator //生成随机数工具类 520 521package com.card.framework.utils; 522 523import java.net.InetAddress; 524 525public class UUIDHexGenerator { 526private static String sep = ""; 527private static final int IP; 528private static short counter = (short) 0; 529private static final int JVM = (int) (System.currentTimeMillis() >>> 8); 530private static UUIDHexGenerator uuidgen = new UUIDHexGenerator(); 531static { 532int ipadd; 533try { 534ipadd = toInt(InetAddress.getLocalHost().getAddress()); 535} catch (Exception e) { 536ipadd = 0; 537} 538IP = ipadd; 539} 540public static UUIDHexGenerator getInstance() { 541return uuidgen; 542} 543public static int toInt(byte[] bytes) { 544int result = 0; 545for (int i = 0; i < 4; i++) { 546result = (result << 8) - Byte.MIN_VALUE + bytes[i]; 547// result = (result << - Byte.MIN_VALUE + (int) bytes); 548} 549return result; 550} 551protected static String format(int intval) { 552String formatted = Integer.toHexString(intval); 553StringBuffer buf = new StringBuffer("00000000"); 554buf.replace(8 - formatted.length(), 8, formatted); 555return buf.toString(); 556} 557protected static String format(short shortval) { 558String formatted = Integer.toHexString(shortval); 559StringBuffer buf = new StringBuffer("0000"); 560buf.replace(4 - formatted.length(), 4, formatted); 561return buf.toString(); 562} 563protected static int getJVM() { 564return JVM; 565} 566protected synchronized static short getCount() { 567if (counter < 0) { 568counter = 0; 569} 570return counter++; 571} 572protected static int getIP() { 573return IP; 574} 575protected static short getHiTime() { 576return (short) (System.currentTimeMillis() >>> 32); 577} 578protected static int getLoTime() { 579return (int) System.currentTimeMillis(); 580} 581public static String generate() { 582return new StringBuffer(36).append(format(getIP())).append(sep).append(format(getJVM())).append(sep) 583.append(format(getHiTime())).append(sep).append(format(getLoTime())).append(sep) 584.append(format(getCount())).toString(); 585} 586/** 587* @param args 588*/ 589public static void main(String[] args) { 590String id=""; 591UUIDHexGenerator uuid = UUIDHexGenerator.getInstance(); 592/* 593for (int i = 0; i < 100; i++) { 594id = uuid.generate(); 595}*/ 596id = generate(); 597System.out.println(id); 598} 599}