- 1.了解微信H5支付 流程
1、用户在商户侧完成下单,使用微信支付进行支付
2、由商户后台向微信支付发起下单请求(调用统一下单接口)注:交易类型trade_type=MWEB
3、统一下单接口返回支付相关参数给商户后台,如支付跳转url(参数名“mweb_url”),商户通过mweb_url调起微信支付中间页
4、中间页进行H5权限的校验,安全性检查(此处常见错误请见下文)
5、如支付成功,商户后台会接收到微信侧的异步通知
6、用户在微信支付收银台完成支付或取消支付,返回商户页面(默认为返回支付发起页面)
7、商户在展示页面,引导用户主动发起支付结果的查询
8,9、商户后台判断是否接到收微信侧的支付结果通知,如没有,后台调用我们的订单查询接口确认订单状态
10、展示最终的订单支付结果给用户
- 2maven
<dependency>
<groupId>com.thoughtworks.xstream</groupId>
<artifactId>xstream</artifactId>
<version>1.4.8</version>
</dependency>
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.2.2</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.0.1</version>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.10</version>
</dependency>
<dependency>
<groupId>net.sf.json-lib</groupId>
<artifactId>json-lib</artifactId>
<version>2.4</version>
<classifier>jdk15</classifier>
</dependency>
<dependency>
<groupId>org.jdom</groupId>
<artifactId>jdom</artifactId>
<version>2.0.2</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore</artifactId>
<version>4.4.10</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpmime</artifactId>
<version>4.5.2</version>
</dependency>
<dependency>
<groupId>commons-httpclient</groupId>
<artifactId>commons-httpclient</artifactId>
<version>3.1</version>
</dependency>
- 3 controller
//回调地址
String notifyUrl = PropUtil.getString("notifyurl", "", Constant.H5PAY_FILE);
if (notifyUrl.endsWith("/")){
notifyUrl+="h5";
}else{
notifyUrl+="/h5";
}
String APPID = PropUtil.getString("h5payAPPID");//商户号取配置;
String MERID = PropUtil.getString("h5payMERID");//;
String SIGNKEY = "&^$&())^%3124";//;
String h5payurl=PropUtil.getString("h5payurl");
logger.info("APPID="+APPID);
String spbill_create_ip = getIpAddr(request);//生产
// System.out.println("spbill_create_ip="+spbill_create_ip);
logger.info("spbill_create_ip="+spbill_create_ip);
//String spbill_create_ip = "";//测试地址,也就是本地真是ip,用于本地测试用
String scene_info = "{\"h5_info\": {\"type\":\"Wap\",\"wap_url\": \""+h5payurl+"\",\"wap_name\": \"代理购钻\"}}";//我这里是网页入口,app入口参考文档的安卓和ios写法 授权地址 和购买信息
String tradeType = "MWEB";//H5支付标记
String MD5 = "MD5";//虽然官方文档不是必须参数,但是不送有时候会验签失败
JSONObject result = new JSONObject();
String subject = "代理购钻";//前端上送的支付主题
String total_amount = String.valueOf(orderAmount);//前端上送的支付金额
String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
//金额转化为分为单位 微信支付以分为单位
BigDecimal b1 = new BigDecimal(Double.valueOf(total_amount));
BigDecimal b2 = new BigDecimal(Double.toString(0.01));
logger.info("b1:"+b1.toString());
logger.info("b2:"+b2.toString());
BigDecimal b3= new BigDecimal(b1.multiply(b2).doubleValue()).setScale(0, BigDecimal.ROUND_HALF_UP);
logger.info(b3.toString());
String finalmoney = StringUtil.getMoney(b3.toString());//;
logger.info(finalmoney);
int randomNum = (int) (Math.random() * 1999+5000);
/*String out_trade_no = Util.getSysTime("yyyyMMddHHmmss") + randomNum;*/
//随机数
String nonce_str= MD5Utils.getMessageDigest(String.valueOf(new Random().nextInt(10000)).getBytes());
// String nonce_str = RandomStringUtils.randomAlphabetic(10);
//签名数据
StringBuilder sb = new StringBuilder();
sb.append("appid="+APPID);
sb.append("&body="+subject);
sb.append("&mch_id="+MERID);
sb.append("&nonce_str="+nonce_str);
sb.append("¬ify_url="+notifyUrl);
sb.append("&out_trade_no="+out_trade_no);
sb.append("&scene_info="+scene_info);
sb.append("&sign_type="+"MD5");
sb.append("&spbill_create_ip="+spbill_create_ip);
sb.append("&total_fee="+finalmoney);
sb.append("&trade_type="+tradeType);
sb.append("&key="+SIGNKEY);
System.out.println("sb="+sb);
logger.info("sb="+sb);
//签名MD5加密
String sign = MD5Utils.md5Encode(sb.toString()).toUpperCase();
System.out.println("sign="+sign);
logger.info("签名数据:"+sign);
//封装xml报文
String xml="<xml>"+
"<appid>"+ APPID+"</appid>"+
"<mch_id>"+ MERID+"</mch_id>"+
"<nonce_str>"+nonce_str+"</nonce_str>"+
"<sign>"+sign+"</sign>"+
"<body>"+subject+"</body>"+//
"<out_trade_no>"+out_trade_no+"</out_trade_no>"+
"<total_fee>"+finalmoney+"</total_fee>"+//
"<trade_type>"+tradeType+"</trade_type>"+
"<notify_url>"+notifyUrl+"</notify_url>"+
"<sign_type>MD5</sign_type>"+
"<scene_info>"+scene_info+"</scene_info>"+
"<spbill_create_ip>"+spbill_create_ip+"</spbill_create_ip>"+
"</xml>";
String createOrderURL = "https://api.mch.weixin.qq.com/pay/unifiedorder";//微信统一下单接口
String mweb_url = "";
Map map = new HashMap();
logger.info("报文:"+xml);
try {
//预下单 获取接口地址
map = WebUtils.getMwebUrl(createOrderURL, xml);
logger.info("请求返回结果:"+map);
String return_code = (String) map.get("return_code");
String return_msg = (String) map.get("return_msg");
if("SUCCESS".equals(return_code) && "OK".equals(return_msg)){
mweb_url = (String) map.get("mweb_url");//调微信支付接口地址
// System.out.println("mweb_url="+mweb_url);
logger.info("create order url:"+mweb_url);
//操作自己的业务逻辑
OutputUtil.output(MessageBuilder.newInstance()
.builderCodeMessage(1000, "OK").builder("body","代理购钻").builder("pay_info",mweb_url).builder("type","微信支付")
.builder("out_trade_no", orderInfo.getOrderId()).builder("total_fee", orderInfo.getOrderAmount())
, request, response, null, false);
return;
}else{
OutputUtil.output(1002, "购钻失败,请稍后再试", request, response, false);
return;
}
} catch (Exception e) {
logger.error("Exception:" + e.getMessage(), e);
}
- 4工具类
/**
* Project Name:pay-protocol
* File Name:Xml.java
* Package Name:cn.swiftpass.pay.protocol
* Date:2014-8-10下午10:48:21
*
*/
package com.sy.util;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.xml.sax.InputSource;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import java.io.*;
import java.util.*;
/**
* ClassName:Xml
* Function: XML的工具方法
* Date: 2014-8-10 下午10:48:21
* @author
*/
public class XmlUtils {
/** <一句话功能简述>
* <功能详细描述>request转字符串
* @param request
* @return
* @see [类、类#方法、类#成员]
*/
public static String parseRequst(HttpServletRequest request){
String body = "";
try {
ServletInputStream inputStream = request.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));
while(true){
String info = br.readLine();
if(info == null){
break;
}
if(body == null || "".equals(body)){
body = info;
}else{
body += info;
}
}
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return body;
}
public static String parseXML(SortedMap<String, String> parameters) {
StringBuffer sb = new StringBuffer();
sb.append("<xml>");
Set es = parameters.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) && !"appkey".equals(k)) {
sb.append("<" + k + ">" + parameters.get(k) + "</" + k + ">\n");
}
}
sb.append("</xml>");
return sb.toString();
}
/**
* 从request中获得参数Map,并返回可读的Map
*
* @param request
* @return
*/
public static SortedMap getParameterMap(HttpServletRequest request) {
// 参数Map
Map properties = request.getParameterMap();
// 返回值Map
SortedMap returnMap = new TreeMap();
Iterator entries = properties.entrySet().iterator();
Map.Entry entry;
String name = "";
String value = "";
while (entries.hasNext()) {
entry = (Map.Entry) entries.next();
name = (String) entry.getKey();
Object valueObj = entry.getValue();
if(null == valueObj){
value = "";
}else if(valueObj instanceof String[]){
String[] values = (String[])valueObj;
for(int i=0;i<values.length;i++){
value = values[i] + ",";
}
value = value.substring(0, value.length()-1);
}else{
value = valueObj.toString();
}
returnMap.put(name, value.trim());
}
return returnMap;
}
/**
* 转XMLmap
* @author
* @param xmlBytes
* @param charset
* @return
* @throws Exception
*/
public static Map<String, String> toMap(byte[] xmlBytes,String charset) throws Exception{
SAXReader reader = new SAXReader(false);
InputSource source = new InputSource(new ByteArrayInputStream(xmlBytes));
source.setEncoding(charset);
Document doc = reader.read(source);
Map<String, String> params = XmlUtils.toMap(doc.getRootElement());
return params;
}
/**
* 转MAP
* @author
* @param element
* @return
*/
public static Map<String, String> toMap(Element element){
Map<String, String> rest = new HashMap<String, String>();
List<Element> els = element.elements();
for(Element el : els){
rest.put(el.getName().toLowerCase(), el.getTextTrim());
}
return rest;
}
public static String toXml(Map<String, String> params){
StringBuilder buf = new StringBuilder();
List<String> keys = new ArrayList<String>(params.keySet());
Collections.sort(keys);
buf.append("<xml>");
for(String key : keys){
buf.append("<").append(key).append(">");
buf.append("<![CDATA[").append(params.get(key)).append("]]>");
buf.append("</").append(key).append(">\n");
}
buf.append("</xml>");
return buf.toString();
}
}
package com.sy.util;
import com.alibaba.fastjson.JSON;
import com.sy.mainland.util.HttpsUtil;
import com.sy.mainland.util.SHAUtil;
import com.alibaba.fastjson.JSON;
import com.sy.mainland.util.HttpsUtil;
import com.sy.mainland.util.SHAUtil;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.methods.StringRequestEntity;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.RequestEntity;
import java.io.BufferedInputStream;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
public class WebUtils {
public static Map getMwebUrl(String url,String xmlParam){
String jsonStr = null;
HttpClient httpClient = new HttpClient();
Map map = new HashMap();
try {
PostMethod method = null;
StringRequestEntity reqEntity = new StringRequestEntity(xmlParam,"text/json","UTF-8");
method = new PostMethod(url);
method.setRequestEntity(reqEntity);
method.addRequestHeader("Content-Type","application/json;charset=utf-8");
httpClient.executeMethod(method);
StringBuffer resBodyBuf = new StringBuffer();
byte[] responseBody = new byte[1024];
int readCount = 0;
BufferedInputStream is = new BufferedInputStream(method.getResponseBodyAsStream());
while((readCount = is.read(responseBody,0,responseBody.length))!=-1){
resBodyBuf.append(new String(responseBody,0,readCount,"utf-8"));
}
jsonStr = resBodyBuf.toString();
System.out.println(jsonStr);
// map = XmlUtils.parseXmlToList(jsonStr);
map = XmlUtils.toMap(jsonStr.getBytes(), "utf-8");
} catch (Exception e) {
e.printStackTrace();
}
return map;
}
}
package com.sy.util;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
/**
* 采用MD5加密
*
* @author relax
*/
public class MD5Utils {
public static String md5U(String inStr) {
return md5Encode(inStr).toUpperCase();
}
/***
* MD5加密 生成32位md5码
*
* @param 待加密字符串
* @return 返回32位md5码
*/
public static String md5Encode(String inStr) {
MessageDigest md5 = null;
try {
md5 = MessageDigest.getInstance("MD5");
} catch (Exception e) {
System.out.println(e.toString());
e.printStackTrace();
return "";
}
byte[] byteArray;
StringBuffer hexValue = new StringBuffer();
try {
byteArray = inStr.getBytes("UTF-8");
byte[] md5Bytes = md5.digest(byteArray);
for (int i = 0; i < md5Bytes.length; i++) {
int val = ((int) md5Bytes[i]) & 0xff;
if (val < 16) {
hexValue.append("0");
}
hexValue.append(Integer.toHexString(val));
}
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return hexValue.toString();
}
public static String md5For16U(String inStr) {
return md5For16(inStr).toUpperCase();
}
public static String md5For16(String inStr) {
MessageDigest md5 = null;
try {
md5 = MessageDigest.getInstance("MD5");
} catch (Exception e) {
System.out.println(e.toString());
e.printStackTrace();
return "";
}
byte[] byteArray;
StringBuffer hexValue = new StringBuffer();
try {
byteArray = inStr.getBytes("UTF-8");
byte[] md5Bytes = md5.digest(byteArray);
for (int i = 0; i < md5Bytes.length; i++) {
int val = ((int) md5Bytes[i]) & 0xff;
if (val < 16) {
hexValue.append("0");
}
hexValue.append(Integer.toHexString(val));
}
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return hexValue.toString().substring(8, 24);
}
/**
* 测试主函数
*
* @param args
* @throws Exception
*/
public static void main(String args[]) throws Exception {
String str = new String("amigoxiexiexingxing");
System.out.println("原始:" + str);
System.out.println("MD5后:" + md5Encode(str));
}
public final static String getMessageDigest(byte[] buffer) {
char hexDigits[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
try {
MessageDigest mdTemp = MessageDigest.getInstance("MD5");
mdTemp.update(buffer);
byte[] md = mdTemp.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];
}
return new String(str);
} catch (Exception e) {
return null;
}
}
}
/**
* 元转换成分
* @param money
* @return
*/
public static String getMoney(String amount) {
if(amount==null){
return "";
}
// 金额转化为分为单位
String currency = amount.replaceAll("\\$|\\¥|\\,", ""); //处理包含, ¥ 或者$的金额
int index = currency.indexOf(".");
int length = currency.length();
Long amLong = 0l;
if(index == -1){
amLong = Long.valueOf(currency+"00");
}else if(length - index >= 3){
amLong = Long.valueOf((currency.substring(0, index+3)).replace(".", ""));
}else if(length - index == 2){
amLong = Long.valueOf((currency.substring(0, index+2)).replace(".", "")+0);
}else{
amLong = Long.valueOf((currency.substring(0, index+1)).replace(".", "")+"00");
}
return amLong.toString();
}
- 5 回调
@RequestMapping(value = "/notify/h5")
public void weixinPayNotify(HttpServletRequest req, HttpServletResponse resp) throws Exception {
String respString = "fail";
try {
BufferedReader reader = req.getReader();
String line = "";
Map map = new HashMap();
String xml = "<xml><return_code><![CDATA[FAIL]]></xml>";;
JSONObject dataInfo = new JSONObject();
StringBuffer inputString = new StringBuffer();
while ((line = reader.readLine()) != null) {
inputString.append(line);
}
req.getReader().close();
// System.out.println("----接收到的报文---"+inputString.toString());
logger.info("----接收到的报文---"+inputString.toString());
if(inputString.toString().length()>0){
// map = XmlUtils.parseXmlToList(inputString.toString());
map = XmlUtils.toMap(inputString.toString().getBytes(), "utf-8");
}else{
System.out.println("接受微信报文为空");
}
// System.out.println("map="+map);
logger.info("map="+map);
if(map!=null && "SUCCESS".equals(map.get("result_code"))){
//成功的业务。。。
xml = "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>";
}
} catch (Exception e) {
logger.error("Exception:" + e.getMessage(), e);
}finally {
resp.getWriter().write(respString);
resp.getWriter().flush();
resp.getWriter().close();
}
}
注:伸手党仔细看一下
支付常见错误