内容简介:基于Java实现微信支付(公众号支付)简单教程
最近公司需求,需要在微信公众号内完成支付,找到官方文档,文档还可以,讲的也挺详细,不过有一个地方很坑爹,就是微信内H5调起支付需要一个签名,而他给出的参考签名方式跟统一下单签名一致,害的我以为,他这个签名就是统一下单那个签名,后面找了很久看了好多博客才明白这个签名是怎么生成的(JS-SDK中微信支付有说明)。弄了半天,汗颜。下面进入正题。
微信支付分为很多种,有刷卡支付,公众号支付,扫码支付,APP支付,H5支付,小程序支付,本教程只讲解公众号支付。主要分三大块来讲解。
1.公众号支付介绍
微信支付只面向已认证客户,需要申请,申请成功后有个商家号,还要一个API密匙即key(微信商户平台(pay.weixin.qq.com)-->账户中心-->账户设置-->API安全-->密钥设置)。而公众号支付是用户在微信中打开商户的H5页面,商户在H5页面通过调用微信支付提供的JSAPI接口调起微信支付模块完成支付。应用场景有:
◆ 用户在微信公众账号内进入商家公众号,打开某个主页面,完成支付
◆ 用户的好友在朋友圈、聊天窗口等分享商家页面连接,用户点击链接打开商家页面,完成支付
◆ 将商户页面转换成二维码,用户扫描二维码后在微信浏览器中打开页面后完成支付
2.开发流程
在这里贴出官方文档地址 https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_1 。有兴趣的可以研究研究,贴出最主要的业务流程图:
啥,看不下去,那简单来说,你要掌握三点:
1.在商家后台设置支付目录, 设置路径:商户平台-->产品中心-->开发配置。填写你项目所在域名
2.在公众号后台 设置授权域名,因为在统一下单接口中要求必传用户openid,而获取openid则需要您在公众平台设置获取openid的域名,只有被设置过的域名才是一个有效的获取openid的域名,否则将获取失败。
3.调通统一下单与 支付结果通知接口(详细看demo)
tip:如果不知道如何获取openid,可以看本人另外一篇博客 微信公众号开发《一》OAuth2.0网页授权认证获取用户的详细信息,实现自动登陆
3.demo展示
demo使用了包:xstream-1.3.1.jar,jdom.jar,用于把数据封装成xml格式与xml解析。首先我们把请求参数封装成类
/** * 统一下单请求参数 * @author lhao * */ public class UnifiedOrderRequest { //变量名 字段名 必填 类型 示例值 描述 private String appid;// 公众账号ID 是 String(32) wxd678efh567hg6787 微信支付分配的公众账号ID(企业号corpid即为此appId) private String mch_id;//商户号 必填 String(32) 1230000109 微信支付分配的商户号 private String device_info; //设备号 否 String(32) 013467007045764 自定义参数,可以为终端设备号(门店号或收银设备ID),PC网页或公众号内支付可以传"WEB" private String nonce_str;//随机字符串 是 String(32) 5K8264ILTKCH16CQ2502SI8ZNMTM67VS 随机字符串,长度要求在32位以内。推荐随机数生成算法 private String sign;//签名 是 String(32) C380BEC2BFD727A4B6845133519F3AD6 通过签名算法计算得出的签名值,详见签名生成算法 private String sign_type;//签名类型 sign_type 否 String(32) HMAC-SHA256 签名类型,默认为MD5,支持HMAC-SHA256和MD5。 private String body;//商品描述 body 是 String(128) 腾讯充值中心-QQ会员充值 商品简单描述,该字段请按照规范传递,具体请见参数规定 private String detail;//商品详情 detail 否 String(6000) 单品优惠字段(暂未上线) private String attach;//附加数据 attach 否 String(127) 深圳分店 附加数据,在查询API和支付通知中原样返回,可作为自定义参数使用。 private String out_trade_no;//商户订单号 out_trade_no 是 String(32) 20150806125346 商户系统内部订单号,要求32个字符内,只能是数字、大小写字母_-|*@ ,且在同一个商户号下唯一。详见商户订单号 private String fee_type;//标价币种 fee_type 否 String(16) CNY 符合ISO 4217标准的三位字母代码,默认人民币:CNY,详细列表请参见货币类型 private String total_fee;//标价金额 total_fee 是 Int 88 订单总金额,单位为分,详见支付金额 private String spbill_create_ip;//终端IP spbill_create_ip 是 String(16) 123.12.12.123 APP和网页支付提交用户端ip,Native支付填调用微信支付API的机器IP。 private String time_start;//交易起始时间 time_start 否 String(14) 20091225091010 订单生成时间,格式为yyyyMMddHHmmss,如2009年12月25日9点10分10秒表示为20091225091010。其他详见时间规则 private String time_expire;//交易结束时间 time_expire 否 String(14) 20091227091010 订单失效时间,格式为yyyyMMddHHmmss,如2009年12月27日9点10分10秒表示为20091227091010。其他详见时间规则 注意:最短失效时间间隔必须大于5分钟 private String goods_tag;//订单优惠标记 goods_tag 否 String(32) WXG 订单优惠标记,使用代金券或立减优惠功能时需要的参数,说明详见代金券或立减优惠 private String notify_url;//通知地址 notify_url 是 String(256) http://www.weixin.qq.com/wxpay/pay.php 异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数。 private String trade_type;//交易类型 trade_type 是 String(16) JSAPI 取值如下:JSAPI,NATIVE,APP等,说明详见参数规定 private String product_id;//商品ID product_id 否 String(32) 12235413214070356458058 trade_type=NATIVE时(即扫码支付),此参数必传。此参数为二维码中包含的商品ID,商户自行定义。 private String limit_pay;//指定支付方式 limit_pay 否 String(32) no_credit 上传此参数no_credit--可限制用户不能使用信用卡支付 private String openid;//用户标识 openid 否 String(128) oUpF8uMuAJO_M2pxb1Q9zNjWeS6o trade_type=JSAPI时(即公众号支付),此参数必传,此参数为微信用户在商户对应appid下的唯一标识。openid如何获取,可参考【获取openid】。企业号请使用【企业号OAuth2.0接口】获取企业号内成员userid,再调用【企业号userid转openid接口】进行转换 //省略get,set方法 }
/** * 统一下单返回参数 * @author lhao * */ public class UnifiedOrderRespose { private String return_code; //返回状态码 private String return_msg; //返回信息 private String appid; //公众账号ID private String mch_id; //商户号 private String device_info; //设备号 private String nonce_str; //随机字符串 private String sign; //签名 private String result_code; //业务结果 private String err_code; //错误代码 private String err_code_des; //错误代码描述 private String trade_type; //交易类型 private String prepay_id; //预支付交易会话标识 private String code_url; //二维码链接 //省略get/set方法 }
/** * 常量 */ public class WXPayConstants { public enum SignType { MD5, HMACSHA256 } public static final String FAIL = "FAIL"; public static final String SUCCESS = "SUCCESS"; public static final String HMACSHA256 = "HMAC-SHA256"; public static final String MD5 = "MD5"; public static final String FIELD_SIGN = "sign"; public static final String FIELD_SIGN_TYPE = "sign_type"; }MD5加密 工具 类,封装参数、请求工具类
import java.security.MessageDigest; /** * MD5工具类 * @author lh */ public class MD5Util { public final static String MD5(String s) { char hexDigits[]={'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'}; try { byte[] btInput = s.getBytes(); // 鑾峰緱MD5鎽樿绠楁硶鐨�MessageDigest 瀵硅�? MessageDigest mdInst = MessageDigest.getInstance("MD5"); // 浣跨敤鎸囧畾鐨勫瓧鑺傛洿鏂版憳瑕�? mdInst.update(btInput); // 鑾峰緱�?�嗘�? byte[] md = mdInst.digest(); // 鎶婂瘑鏂囪浆鎹㈡垚鍗佸叚杩涘埗鐨勫瓧绗︿覆褰㈠紡 int j = md.length; char str[] = new char[j * 2]; int k = 0; for (int i = 0; i < j; i++) { byte byte0 = md[i]; str[k++] = hexDigits[byte0 >>> 4 & 0xf]; str[k++] = hexDigits[byte0 & 0xf]; } String md5Str = new String(str); return md5Str; } catch (Exception e) { e.printStackTrace(); return null; } } }
import java.io.BufferedOutputStream; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.InputStream; import java.io.InputStreamReader; import java.io.StringWriter; import java.io.Writer; import java.net.HttpURLConnection; import java.net.URL; import java.util.*; import java.security.MessageDigest; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import com.thoughtworks.xstream.XStream; import com.thoughtworks.xstream.core.util.QuickWriter; import com.thoughtworks.xstream.io.HierarchicalStreamWriter; import com.thoughtworks.xstream.io.xml.PrettyPrintWriter; import com.thoughtworks.xstream.io.xml.XppDriver; import wp.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 org.apache.commons.lang.RandomStringUtils; import org.jdom.Document; import org.jdom.Element; import org.jdom.input.SAXBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * 支付工具类 * @author lh */ public class WXPayUtil { private static Logger log = LoggerFactory.getLogger(WXPayUtil.class); /** * 生成订单对象信息 * @param orderId 订单号 * @param appId 微信appId * @param mch_id 微信分配的商户ID * @param body 支付介绍主体 * @param price 支付价格(放大100倍) * @param spbill_create_ip 终端IP * @param notify_url 异步直接结果通知接口地址 * @param noncestr * @return */ public static Map<String,Object> createOrderInfo(Map<String, String> requestMap) { //生成订单对象 UnifiedOrderRequest unifiedOrderRequest = new UnifiedOrderRequest(); unifiedOrderRequest.setAppid(requestMap.get("appId"));//公众账号ID unifiedOrderRequest.setBody(requestMap.get("body"));//商品描述 unifiedOrderRequest.setMch_id(requestMap.get("mch_id"));//商户号 unifiedOrderRequest.setNonce_str(requestMap.get("noncestr"));//随机字符串 unifiedOrderRequest.setNotify_url(requestMap.get("notify_url"));//通知地址 unifiedOrderRequest.setOpenid(requestMap.get("userWeixinOpenId")); unifiedOrderRequest.setDetail(requestMap.get("detail"));//详情 unifiedOrderRequest.setOut_trade_no(requestMap.get("out_trade_no"));//商户订单号 unifiedOrderRequest.setSpbill_create_ip(requestMap.get("spbill_create_ip"));//终端IP unifiedOrderRequest.setTotal_fee(requestMap.get("payMoney")); //金额需要扩大100倍:1代表支付时是0.01 unifiedOrderRequest.setTrade_type("JSAPI");//JSAPI--公众号支付、NATIVE--原生扫码支付、APP--app支付 SortedMap<String, String> packageParams = new TreeMap<String, String>(); packageParams.put("appid", unifiedOrderRequest.getAppid()); packageParams.put("body", unifiedOrderRequest.getBody()); packageParams.put("mch_id", unifiedOrderRequest.getMch_id()); packageParams.put("nonce_str", unifiedOrderRequest.getNonce_str()); packageParams.put("notify_url", unifiedOrderRequest.getNotify_url()); packageParams.put("openid", unifiedOrderRequest.getOpenid()); packageParams.put("detail", unifiedOrderRequest.getDetail()); packageParams.put("out_trade_no", unifiedOrderRequest.getOut_trade_no()); packageParams.put("spbill_create_ip", unifiedOrderRequest.getSpbill_create_ip()); packageParams.put("total_fee", unifiedOrderRequest.getTotal_fee()); packageParams.put("trade_type", unifiedOrderRequest.getTrade_type()); try { unifiedOrderRequest.setSign(generateSignature(packageParams,"你的密匙"));//签名 } catch (Exception e) { e.printStackTrace(); } //将订单对象转为xml格式 xstream.alias("xml", UnifiedOrderRequest.class);//根元素名需要是xml System.out.println("封装好的统一下单请求数据:"+xstream.toXML(unifiedOrderRequest).replace("__", "_")); Map<String,Object> responseMap = new HashMap<String,Object>(); responseMap.put("orderInfo_toString", xstream.toXML(unifiedOrderRequest).replace("__", "_")); responseMap.put("unifiedOrderRequest",unifiedOrderRequest); return responseMap; } /** * 生成签名 * @param appid_value * @param mch_id_value * @param productId * @param nonce_str_value * @param trade_type * @param notify_url * @param spbill_create_ip * @param total_fee * @param out_trade_no * @return */ private static String createSign(UnifiedOrderRequest unifiedOrderRequest) { //根据规则创建可 排序 的map集合 SortedMap<String, String> packageParams = new TreeMap<String, String>(); packageParams.put("appid", unifiedOrderRequest.getAppid()); packageParams.put("body", unifiedOrderRequest.getBody()); packageParams.put("mch_id", unifiedOrderRequest.getMch_id()); packageParams.put("nonce_str", unifiedOrderRequest.getNonce_str()); packageParams.put("notify_url", unifiedOrderRequest.getNotify_url()); packageParams.put("out_trade_no", unifiedOrderRequest.getOut_trade_no()); packageParams.put("spbill_create_ip", unifiedOrderRequest.getSpbill_create_ip()); packageParams.put("trade_type", unifiedOrderRequest.getTrade_type()); packageParams.put("total_fee", unifiedOrderRequest.getTotal_fee()); StringBuffer sb = new StringBuffer(); Set es = packageParams.entrySet();//字典序 Iterator it = es.iterator(); while (it.hasNext()) { Map.Entry entry = (Map.Entry) it.next(); String k = (String) entry.getKey(); String v = (String) entry.getValue(); //为空不参与签名、参数名区分大小写 if (null != v && !"".equals(v) && !"sign".equals(k) && !"key".equals(k)) { sb.append(k + "=" + v + "&"); } } //第二步拼接key,key设置路径:微信商户平台(pay.weixin.qq.com)-->账户设置-->API安全-->密钥设置 sb.append("key="+"你的密匙"); String sign = MD5Util.MD5(sb.toString()).toUpperCase();//MD5加密 log.error("方式一生成的签名="+sign); return sign; } private static XStream xstream = new XStream(new XppDriver() { public HierarchicalStreamWriter createWriter(Writer out) { return new PrettyPrintWriter(out) { // 对所有xml节点的转换都增加CDATA标记 boolean cdata = true; String NodeName = ""; @SuppressWarnings("unchecked") public void startNode(String name, Class clazz) { NodeName = name; super.startNode(name, clazz); } protected void writeText(QuickWriter writer, String text) { if (cdata) { if(!NodeName.equals("detail")){ writer.write(text); }else{ writer.write("<![CDATA["); writer.write(text); writer.write("]]>"); } } else { writer.write(text); } } }; } }); //xml解析 public static SortedMap<String, String> doXMLParseWithSorted(String strxml) throws Exception { strxml = strxml.replaceFirst("encoding=\".*\"", "encoding=\"UTF-8\""); if(null == strxml || "".equals(strxml)) { return null; } SortedMap<String,String> m = new TreeMap<String,String>(); InputStream in = new ByteArrayInputStream(strxml.getBytes("UTF-8")); SAXBuilder builder = new SAXBuilder(); Document doc = builder.build(in); Element root = doc.getRootElement(); List list = root.getChildren(); Iterator it = list.iterator(); while(it.hasNext()) { Element e = (Element) it.next(); String k = e.getName(); String v = ""; List children = e.getChildren(); if(children.isEmpty()) { v = e.getTextNormalize(); } else { v = getChildrenText(children); } m.put(k, v); } //关闭流 in.close(); return m; } public static String getChildrenText(List children) { StringBuffer sb = new StringBuffer(); if(!children.isEmpty()) { Iterator it = children.iterator(); while(it.hasNext()) { Element e = (Element) it.next(); String name = e.getName(); String value = e.getTextNormalize(); List list = e.getChildren(); sb.append("<" + name + ">"); if(!list.isEmpty()) { sb.append(getChildrenText(list)); } sb.append(value); sb.append("</" + name + ">"); } } return sb.toString(); } /** * 调统一下单API * @param orderInfo * @return */ public static UnifiedOrderRespose httpOrder(String orderInfo) { String url = "https://api.mch.weixin.qq.com/pay/unifiedorder"; try { HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection(); //加入数据 conn.setRequestMethod("POST"); conn.setDoOutput(true); BufferedOutputStream buffOutStr = new BufferedOutputStream(conn.getOutputStream()); buffOutStr.write(orderInfo.getBytes("UTF-8")); buffOutStr.flush(); buffOutStr.close(); //获取输入流 BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream(), "UTF-8")); String line = null; StringBuffer sb = new StringBuffer(); while((line = reader.readLine())!= null){ sb.append(line); } //将请求返回的内容通过xStream转换为UnifiedOrderRespose对象 xstream.alias("xml", UnifiedOrderRespose.class); UnifiedOrderRespose unifiedOrderRespose = (UnifiedOrderRespose)xstream.fromXML(sb.toString()); return unifiedOrderRespose; } catch (Exception e) { e.printStackTrace(); } return null; } /** * XML格式字符串转换为Map * * @param strXML XML字符串 * @return XML数据转换后的Map * @throws 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格式的字符串 * @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; } /** * 生成带有 sign 的 XML 格式字符串 * * @param data Map类型数据 * @param key API密钥 * @return 含有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 签名类型 * @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 签名是否正确 * @throws 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 签名是否正确 * @throws 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 签名是否正确 * @throws 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 签名 */ 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 签名 */ 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 { log.error("获取签名失败,失败原因:"+String.format("Invalid sign_type: %s", signType)); 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); } /** * Map转xml数据 */ public static String GetMapToXML(Map<String,String> param){ StringBuffer sb = new StringBuffer(); sb.append("<xml>"); for (Map.Entry<String,String> entry : param.entrySet()) { sb.append("<"+ entry.getKey() +">"); sb.append(entry.getValue()); sb.append("</"+ entry.getKey() +">"); } sb.append("</xml>"); return sb.toString(); } /** * 生成 MD5 * @param data 待处理数据 * @return MD5结果 */ 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(); } /** * 生成 HMACSHA256 * @param data 待处理数据 * @param key 密钥 * @return 加密结果 * @throws Exception */ public static String HMACSHA256(String data, String key) throws Exception { Mac sha256_HMAC = Mac.getInstance("HmacSHA256"); SecretKeySpec secret_key = new SecretKeySpec(key.getBytes("UTF-8"), "HmacSHA256"); sha256_HMAC.init(secret_key); byte[] array = sha256_HMAC.doFinal(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(); } /** * 日志 * @return */ public static Logger getLogger() { Logger logger = LoggerFactory.getLogger("wxpay java sdk"); return logger; } /** * 获取当前时间戳,单位秒 * @return */ public static long getCurrentTimestamp() { return System.currentTimeMillis()/1000; } /** * 获取当前时间戳,单位毫秒 * @return */ public static long getCurrentTimestampMs() { return System.currentTimeMillis(); } /** * 生成 uuid, 即用来标识一笔单,也用做 nonce_str * @return */ public static String generateUUID() { return UUID.randomUUID().toString().replaceAll("-", "").substring(0, 32); } /** * 支付签名 * @param timestamp * @param noncestr * @param packages * @return * @throws UnsupportedEncodingException */ public static String paySign(String timestamp, String noncestr,String packages,String appId){ Map<String, String> paras = new HashMap<String, String>(); paras.put("appid", appId); paras.put("timestamp", timestamp); paras.put("noncestr", noncestr); paras.put("package", packages); paras.put("signType", "MD5"); StringBuffer sb = new StringBuffer(); Set es = paras.entrySet();//字典序 Iterator it = es.iterator(); while (it.hasNext()) { Map.Entry entry = (Map.Entry) it.next(); String k = (String) entry.getKey(); String v = (String) entry.getValue(); //为空不参与签名、参数名区分大小写 if (null != v && !"".equals(v) && !"sign".equals(k) && !"key".equals(k)) { sb.append(k + "=" + v + "&"); } } String sign = MD5Util.MD5(sb.toString()).toUpperCase();//MD5加密 return sign; } }一些准备就绪,下面我们看看调用方法
import java.util.HashMap; import java.util.Map; import java.util.SortedMap; import java.util.TreeMap; import javax.servlet.ServletInputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; import com.photovoltaic.model.ResultEnum; import com.photovoltaic.model.UserOrderInfo; import com.photovoltaic.model.UserPayInfo; import com.photovoltaic.util.publicUtil; import com.photovoltaic.weixin.model.TemplateData; import com.photovoltaic.weixin.model.TimedTask; import com.photovoltaic.weixin.model.UnifiedOrderRequest; import com.photovoltaic.weixin.model.UnifiedOrderRespose; import com.photovoltaic.weixin.model.WxTemplate; import com.photovoltaic.weixin.util.WXPayUtil; import com.photovoltaic.weixin.util.WeixinUtil; /** * 微信支付controller * 1.用户发起微信支付,初始化数据、调用统一下单接口。生成JSAPI页面调用的支付参数并签名(paySign,prepay_id,nonceStr,timestamp) * 2.js如果返回Ok,提示支付成功,实际支付结果已收到通知为主。 * 3.在微信支付结果通知中,获取微信提供的最终用户支付结果信息,支付结果等信息更新用户支付记录中 * 4.根据微信支付结果通知中的微信订单号调用查询接口,如果查询是已经支付成功,则发送支付成功模板信息给客户 * @author hl * */ @Controller @RequestMapping(value = "/pay") public class WXPayController extends WeixinBaseController{ private static Logger log = LoggerFactory.getLogger(WXPayController.class); /** * 获取终端IP * @param request * @return */ public static String getIpAddr(HttpServletRequest request) { String ip = request.getHeader( " x-forwarded-for " ); if (ip == null || ip.length() == 0 || " unknown " .equalsIgnoreCase(ip)) { ip = request.getHeader( " Proxy-Client-IP " ); } if (ip == null || ip.length() == 0 || " unknown " .equalsIgnoreCase(ip)) { ip = request.getHeader( " WL-Proxy-Client-IP " ); } if (ip == null || ip.length() == 0 || " unknown " .equalsIgnoreCase(ip)) { ip = request.getRemoteAddr(); } return ip; } /** * 支付初始化 * @param payMoney * @return */ @RequestMapping("/toPayInit") @ResponseBody public Map<String,Object> toPay(@RequestParam(value="payMoney",required=true) String payMoney,@RequestParam(value="userWeixinOpenId",required=true) String userWeixinOpenId){ Map<String,Object> map = new HashMap<>(); String orderId = String.valueOf(WXPayUtil.generateUUID()); String noncestr = WXPayUtil.generateNonceStr(); Map<String,String> requestMap = new HashMap<String, String>(); requestMap.put("appId", "wx3fda6310becfe801"); requestMap.put("userWeixinOpenId",userWeixinOpenId); requestMap.put("out_trade_no",orderId); requestMap.put("mch_id", "商家号"); requestMap.put("payMoney",payMoney); requestMap.put("spbill_create_ip", getIpAddr(request)); requestMap.put("notify_url", "支付结果回调通知路径"); requestMap.put("noncestr", noncestr); requestMap.put("body","一元联系"); requestMap.put("detail","获取电站用户的联系方式"); Map<String,Object> requestInfo = WXPayUtil.createOrderInfo(requestMap); String orderInfo_toString = (String) requestInfo.get("orderInfo_toString"); //判断返回码 UnifiedOrderRespose orderResponse = WXPayUtil.httpOrder(orderInfo_toString);// 调用统一下单接口 //根据微信文档return_code 和result_code都为SUCCESS的时候才会返回code_url if(null!=orderResponse && "SUCCESS".equals(orderResponse.getReturn_code()) && "SUCCESS".equals(orderResponse.getResult_code())){ String timestamp = String.valueOf(WXPayUtil.getCurrentTimestamp()); map.put("timestamp",timestamp); map.put("noncestr",noncestr); UnifiedOrderRequest unifiedOrderRequest = (UnifiedOrderRequest) requestInfo.get("unifiedOrderRequest"); map.put("unifiedOrderRequest",unifiedOrderRequest); SortedMap<String, String> packageParams = new TreeMap<String, String>(); packageParams.put("appId","你的appId"); packageParams.put("signType","MD5"); packageParams.put("nonceStr", noncestr); packageParams.put("timeStamp", timestamp); String packages = "prepay_id="+orderResponse.getPrepay_id(); packageParams.put("package",packages); String sign = null;//这个梗,就是开头说的,弄了半天才弄出来的 try { sign = WXPayUtil.generateSignature(packageParams,"你的密匙"); } catch (Exception e) { map.put("result",-1); e.printStackTrace(); } if(sign!=null && !"".equals(sign)){ map.put("paySign",sign); map.put("result",1); }else{ map.put("result",-1); } map.put("prepay_id",orderResponse.getPrepay_id()); return map; }else{ //不成功 String text = "调用微信支付出错,返回状态码:"+orderResponse.getReturn_code()+",返回信息:"+orderResponse.getReturn_msg(); if(orderResponse.getErr_code()!=null && !"".equals(orderResponse.getErr_code())){ text = text +",错误码:"+orderResponse.getErr_code()+",错误描述:"+orderResponse.getErr_code_des(); } log.error(text); map.put("result",-1); return map; } } /** * 异步回调接口 * @param request * @param response * @throws Exception */ @RequestMapping(value="/paymentNotice",produces="text/html;charset=utf-8") @ResponseBody public String WeixinParentNotifyPage(HttpServletRequest request,HttpServletResponse response) throws Exception{ ServletInputStream instream = request.getInputStream(); StringBuffer sb = new StringBuffer(); int len = -1; byte[] buffer = new byte[1024]; while((len = instream.read(buffer)) != -1){ sb.append(new String(buffer,0,len)); } instream.close(); // log.error("支付通知回调信息:"+sb.toString()); // Map<String,String> map = WXPayUtil.doXMLParseWithSorted(sb.toString());//接受微信的通知参数 Map<String,String> map = WXPayUtil.xmlToMap(sb.toString());//接受微信的回调的通知参数 Map<String,String> return_data = new HashMap<String,String>(); //判断签名是否正确 if(WXPayUtil.isSignatureValid(map, "你的密匙")){ if(map.get("return_code").toString().equals("FAIL")){ return_data.put("return_code", "FAIL"); return_data.put("return_msg", map.get("return_msg")); }else if(map.get("return_code").toString().equals("SUCCESS")){ String result_code = map.get("result_code").toString(); String out_trade_no = map.get("out_trade_no").toString(); //获得你自己的订单详情 UserPayInfo payInfo = wxPayService.getUserPayInfo(out_trade_no); if(payInfo == null){ return_data.put("return_code", "FAIL"); return_data.put("return_msg", "订单不存在"); return WeixinUtil.GetMapToXML(return_data); }else{ //2 已支付(不确定是否支付成功)3 支付完成 4 取消支付 5支付失败 if(result_code.equals("SUCCESS")){//支付成功 //如果订单已经支付直接返回成功 if(payInfo.getPayStatus()==3){ return_data.put("return_code", "SUCCESS"); return_data.put("return_msg", "OK"); return WXPayUtil.GetMapToXML(return_data); }else{ String sign = map.get("sign").toString(); String total_fee = map.get("total_fee").toString();//订单金额 if(!publicUtil.subZeroAndDot3(payInfo.getTotal_fee().toString()).equals(total_fee)){//订单金额是否一致 return_data.put("return_code", "FAIL"); return_data.put("return_msg", "金额异常"); }else{ String time_end = map.get("time_end").toString(); String bank_type = map.get("bank_type").toString(); String settlement_total_fee = map.get("settlement_total_fee"); if(settlement_total_fee==null || "".equals(settlement_total_fee)){ settlement_total_fee = "0"; } payInfo.setSign(sign); payInfo.setResult_code(result_code); payInfo.setPayStatus(3); payInfo.setTime_end(time_end); payInfo.setSettlement_total_fee(settlement_total_fee); payInfo.setBank_type(bank_type); payInfo.setCoupon_fee("0"); int result = wxPayService.updatePayInfo(payInfo); if(result<=0){ return_data.put("return_code", "FAIL"); return_data.put("return_msg", "更新订单失败"); return WeixinUtil.GetMapToXML(return_data); }else{ UserOrderInfo orderInfo = new UserOrderInfo(); orderInfo.setId(payInfo.getOrderId()); orderInfo.setStatus(2); result = wxPayService.updateOrderInfo(orderInfo); if(result<=0){ return_data.put("return_code", "FAIL"); return_data.put("return_msg", "更新订单失败"); return WeixinUtil.GetMapToXML(return_data); }else{ return_data.put("return_code", "SUCCESS"); return_data.put("return_msg", "OK"); return WeixinUtil.GetMapToXML(return_data); } } } } }else{//支付失败,更新支付结果 if(payInfo!=null){ payInfo.setResult_code(result_code); payInfo.setPayStatus(5); payInfo.setErr_code(map.get("err_code").toString()); payInfo.setErr_code_des(map.get("err_code_des").toString()); wxPayService.updatePayInfo(payInfo); } return_data.put("return_code", "FAIL"); return_data.put("return_msg",map.get("return_msg").toString()); return WeixinUtil.GetMapToXML(return_data); } } } }else{ return_data.put("return_code", "FAIL"); return_data.put("return_msg", "签名错误"); } String xml = WXPayUtil.GetMapToXML(return_data); log.error("支付通知回调结果:"+xml); return xml; } }前端唤起微信支付,看jsp代码
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> <%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%> <%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn"%> <c:set var="BS" value="${pageContext.request.contextPath}"></c:set> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>支付页面</title> <script type="text/javascript" src="${BS}/js/jquery-1.7.2.min.js"></script> </head> <input type="hidden" id="weixinOperId" value="用户operId"> <script type="text/javascript"> var phoneWidth = parseInt(window.screen.width); var phoneScale = phoneWidth/640; var ua = navigator.userAgent; if (/Android (\d+\.\d+)/.test(ua)){ var version = parseFloat(RegExp.$1); if(version>2.3){ document.write("<meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0, minimum-scale = 1.0, maximum-scale = "+phoneScale+", target-densitydpi=device-dpi\">"); }else{ document.write("<meta name=\"viewport\" content=\"width=device-width, target-densitydpi=device-dpi\">"); } }else{ document.write("<meta name=\"viewport\" content=\"width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no,target-densitydpi=device-dpi\">"); } function initPay(powerStationId,userId){ if(!is_weixin()){ alert("请在微信客户端打开该链接"); }else if(!weixin_version()){ alert("你微信版本太低,不支持微信支付功能,请先更新你的微信"); }else{ if(typeof WeixinJSBridge == "undefined"){ if( document.addEventListener ){ document.addEventListener('WeixinJSBridgeReady', initPay, false); }else if (document.attachEvent){ document.attachEvent('WeixinJSBridgeReady', initPay); document.attachEvent('onWeixinJSBridgeReady', initPay); } }else{ toPay(powerStationId,userId); } } } function toPay(powerStationId,userId){ $.ajax({ url : "/"+getProjectName()+"/pay/toPayInit", type:"POST", dataType : 'json', // 服务器返回的格式,可以是json或xml等 data:{ payMoney:1, userWeixinOpenId:'用户operId' }, success : function(result) { // 服务器响应成功时的处理函数 if(result.result==1){//插入支付记录 var paySign = result.paySign; var prepay_id = result.prepay_id; var nonceStr = result.noncestr; var timestamp = result.timestamp; var unifiedOrderRequest = result.unifiedOrderRequest; var spbill_create_ip = unifiedOrderRequest.spbill_create_ip; var detail = unifiedOrderRequest.detail; var out_trade_no = unifiedOrderRequest.out_trade_no; $.ajax({ url : "/"+getProjectName()+"/pay/toSavePayInfo", type:"POST", dataType : 'json', // 服务器返回的格式,可以是json或xml等 data:{ spbill_create_ip:spbill_create_ip, detail:detail, out_trade_no:out_trade_no, total_fee:1, powerStationId:powerStationId, userId:userId, order_type:1 }, success : function(result) { // 服务器响应成功时的处理函数 if(result>0){//插入支付记录 onBridgeReady(paySign,prepay_id,nonceStr,timestamp); } }, error : function(data, status, e) { // 服务器响应失败时的处理函数 $.toptip("系统出错,请联系系统运营商", 'error'); } }); }else{ $.toptip("初始化支付接口失败,请联系系统运营商", 'error'); } }, error : function(data, status, e) { // 服务器响应失败时的处理函数 $.toptip("初始化支付接口失败,请联系系统运营商", 'error'); } }); } function onBridgeReady(paySign,prepay_id,nonceStr,timestamp){ var weixinOperId = $("#weixinOperId").val(); WeixinJSBridge.invoke( 'getBrandWCPayRequest', { "appId":weixinOperId, //公众号名称,由商户传入 "timeStamp":timestamp, //时间戳,自1970年以来的秒数 "nonceStr":nonceStr, //随机串 "package":"prepay_id="+prepay_id, "signType":"MD5", //微信签名方式: "paySign":paySign //微信签名 (这个签名获取看后台) }, function(res){ // 使用以上方式判断前端返回,微信团队郑重提示:res.err_msg将在用户支付成功后返回 ok,但并不保证它绝对可靠。 if(res.err_msg == "get_brand_wcpay_request:ok" ) { alert("支付成功", 'success'); }else if(res.err_msg == "get_brand_wcpay_request:cancel" ) { alert("用户取消", 'success'); } } ); } </script> <body> <div id="addressArea" style="min-height:526px;"> <section class="SelectCityWrap" style="width:98%;"> <section class="content"> <div class="nav"> <a class="" nav="nav_1" onclick="initPay(11,1)">马上支付</a> </div> </section> </section> </div> </body> </html>到此全部搞定,3个封装数据类,2个工具类,一个controller,一个jsp调用,jsp结果操作提示是用jQuery WeUI插件,很不错的一个手机网站开发插件。
如有不解或者逻辑不通的地方,欢迎评论区留言。
以上所述就是小编给大家介绍的《基于Java实现微信支付(公众号支付)简单教程》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 公众号支付-从服务号申请到支付调用爬坑
- JeeWx 捷微 3.1 小程序版本发布,支持微信公众号,微信企业号,支付窗
- JeeWx 捷微 V3.3 版本发布,支持微信公众号、微信企业号与支付窗
- 微信公众号开发C#系列-9、多公众号集中管理
- Python微信公众号开发
- 优质的技术公众号这样定义
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。