您的位置:首页 > 移动开发 > 微信开发

Java实现公众号H5 微信支付

2016-09-26 16:37 295 查看
最近由于有微信项目使用到了微信支付这功能,我原本以为跟调用其它js接口一样,非常简单,实则不然,因为微信团队留下了很多天坑,提供的文档全是老版的,下载下来无法使用,导致这个支付功能 害我整整调了一天,全是微信团队留下的坑,有些签名并不是下载事例所说那样,接下来我将分享通过自身努力实现的微信支付成果:

第一步:微信支付配置文件 ConfigUtil

/**

* 微信支付配置文件

* @author Mark

*

*/

public class ConfigUtil {

/**

* 服务号相关信息

*/

public final static String APPID = “”;//服务号的appid

public final static String APP_SECRECT = “”;//服务号的appSecrect

public final static String TOKEN = ” “;//服务号的配置token

public final static String MCH_ID = “”;//开通微信支付分配的商户号

public final static String API_KEY = “”;//商户API密钥 自行去商户平台设置

public final static String SIGN_TYPE = “MD5”;//签名加密方式

//微信支付统一接口的回调action

public final static String NOTIFY_URL = “”; //用于告知微信服务器 调用成功

/**
* 微信基础接口地址
*/
//获取token接口(GET)
public final static String TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET";
//oauth2授权接口(GET)
public final static String OAUTH2_URL = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code";
//刷新access_token接口(GET)
public final static String REFRESH_TOKEN_URL = "https://api.weixin.qq.com/sns/oauth2/refresh_token?appid=APPID&grant_type=refresh_token&refresh_token=REFRESH_TOKEN";
// 菜单创建接口(POST)
public final static String MENU_CREATE_URL = "https://api.weixin.qq.com/cgi-bin/menu/create?access_token=ACCESS_TOKEN";
// 菜单查询(GET)
public final static String MENU_GET_URL = "https://api.weixin.qq.com/cgi-bin/menu/get?access_token=ACCESS_TOKEN";
// 菜单删除(GET)
public final static String MENU_DELETE_URL = "https://api.weixin.qq.com/cgi-bin/menu/delete?access_token=ACCESS_TOKEN";
/**
* 微信支付接口地址
*/
//微信支付统一接口(POST)
public final static String UNIFIED_ORDER_URL = "https://api.mch.weixin.qq.com/pay/unifiedorder";
//微信退款接口(POST)
public final static String REFUND_URL = "https://api.mch.weixin.qq.com/secapi/pay/refund";
//订单查询接口(POST)
public final static String CHECK_ORDER_URL = "https://api.mch.weixin.qq.com/pay/orderquery";
//关闭订单接口(POST)
public final static String CLOSE_ORDER_URL = "https://api.mch.weixin.qq.com/pay/closeorder";
//退款查询接口(POST)
public final static String CHECK_REFUND_URL = "https://api.mch.weixin.qq.com/pay/refundquery";
//对账单接口(POST)
public final static String DOWNLOAD_BILL_URL = "https://api.mch.weixin.qq.com/pay/downloadbill";
//短链接转换接口(POST)
public final static String SHORT_URL = "https://api.mch.weixin.qq.com/tools/shorturl";
//接口调用上报接口(POST)
public final static String REPORT_URL = "https://api.mch.weixin.qq.com/payitil/report";


}

第二步:通用工具类 CommonUtil (用于请求接口)

/**

* 通用工具类

* @author Mark

*/

public class CommonUtil {

private static Logger log = LoggerFactory.getLogger(CommonUtil.class);

/**

* 发送https请求

* @param requestUrl 请求地址

* @param requestMethod 请求方式(GET、POST)

* @param outputStr 提交的数据

* @return 返回微信服务器响应的信息

*/

public static String httpsRequest(String requestUrl, String requestMethod, String outputStr) {

try {

// 创建SSLContext对象,并使用我们指定的信任管理器初始化

TrustManager[] tm = { new MyX509TrustManager() };

SSLContext sslContext = SSLContext.getInstance(“SSL”, “SunJSSE”);

sslContext.init(null, tm, new java.security.SecureRandom());

// 从上述SSLContext对象中得到SSLSocketFactory对象

SSLSocketFactory ssf = sslContext.getSocketFactory();

URL url = new URL(requestUrl);

HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();

conn.setSSLSocketFactory(ssf);

conn.setDoOutput(true);

conn.setDoInput(true);

conn.setUseCaches(false);

// 设置请求方式(GET/POST)

conn.setRequestMethod(requestMethod);

conn.setRequestProperty(“content-type”, “application/x-www-form-urlencoded”);

// 当outputStr不为null时向输出流写数据

if (null != outputStr) {

OutputStream outputStream = conn.getOutputStream();

// 注意编码格式

outputStream.write(outputStr.getBytes(“UTF-8”));

outputStream.close();

}

// 从输入流读取返回内容

InputStream inputStream = conn.getInputStream();

InputStreamReader inputStreamReader = new InputStreamReader(inputStream, “utf-8”);

BufferedReader bufferedReader = new BufferedReader(inputStreamReader);

String str = null;

StringBuffer buffer = new StringBuffer();

while ((str = bufferedReader.readLine()) != null) {

buffer.append(str);

}

// 释放资源

bufferedReader.close();

inputStreamReader.close();

inputStream.close();

inputStream = null;

conn.disconnect();

return buffer.toString();

} catch (ConnectException ce) {

log.error(“连接超时:{}”, ce);

} catch (Exception e) {

log.error(“https请求异常:{}”, e);

}

return null;

}

public static String urlEncodeUTF8(String source){
String result = source;
try {
result = java.net.URLEncoder.encode(source,"utf-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return result;
}


}

第三步:MD5处理 MD5Util

/**

* MD5处理

* @author Mark

*

*/

public class MD5Util {

private static String byteArrayToHexString(byte b[]) {
StringBuffer resultSb = new StringBuffer();
for (int i = 0; i < b.length; i++)
resultSb.append(byteToHexString(b[i]));

return resultSb.toString();
}

private static String byteToHexString(byte b) {
int n = b;
if (n < 0)
n += 256;
int d1 = n / 16;
int d2 = n % 16;
return hexDigits[d1] + hexDigits[d2];
}

public static String MD5Encode(String origin, String charsetname) {
String resultString = null;
try {
resultString = new String(origin);
MessageDigest md = MessageDigest.getInstance("MD5");
if (charsetname == null || "".equals(charsetname))
resultString = byteArrayToHexString(md.digest(resultString
.getBytes()));
else
resultString = byteArrayToHexString(md.digest(resultString
.getBytes(charsetname)));
} catch (Exception exception) {
}
return resultString;
}

private static final String hexDigits[] = { "0", "1", "2", "3", "4", "5",
"6", "7", "8", "9", "a", "b", "c", "d", "e", "f" };


}

第四步:信任管理器 微信的信任证书 MyX509TrustManager

/**

* 信任管理器

* @author Mark

*/

public class MyX509TrustManager implements X509TrustManager {

// 检查客户端证书
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}

// 检查服务器端证书
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}

// 返回受信任的X509证书数组
public X509Certificate[] getAcceptedIssuers() {
return null;
}


}

第五步:微信H5支付 签名 随机码 时间戳等管理 PayCommonUtil

/**

* 微信H5支付 签名 随机码 时间戳等管理

* @author Mark

*

*/

public class PayCommonUtil {

/**
* 获取支付随机码
* @return
*/
public static String create_nonce_str() {
return UUID.randomUUID().toString();
}
/**
* 获取微信支付时间戳
* @return
*/
public static String create_timestamp() {
return Long.toString(System.currentTimeMillis() / 1000);
}

/**
* 获取预支付ID时  获取随机码
* @param length
* @return
*/
public static String CreateNoncestr(int length) {
String chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
String res = "";
for (int i = 0; i < length; i++) {
Random rd = new Random();
res += chars.indexOf(rd.nextInt(chars.length() - 1));
}
return res;
}
/**
* 获取预支付ID时  获取随机码
* @param length
* @return
*/
public static String CreateNoncestr() {
String chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
String res = "";
for (int i = 0; i < 16; i++) {
Random rd = new Random();
res += chars.charAt(rd.nextInt(chars.length() - 1));
}
return res;
}

/**
* @author Mark
* @Description:sign签名
* @param characterEncoding 编码格式
* @param parameters 请求参数
* @return
*/
public static String createSign(SortedMap<Object,Object> parameters){
StringBuffer sb = new StringBuffer();
Set es = parameters.entrySet();
Iterator it = es.iterator();
while(it.hasNext()) {
Map.Entry entry = (Map.Entry)it.next();
String k = (String)entry.getKey();
Object v = entry.getValue();
if(null != v && !"".equals(v)
&& !"sign".equals(k) && !"key".equals(k)) {
sb.append(k + "=" + v + "&");
}
}
sb.append("key=" + ConfigUtil.API_KEY);
String sign = MD5Util.MD5Encode(sb.toString(),"UTF-8").toUpperCase();
return sign;
}

/**
* @author Mark
* @Description:将请求参数转换为xml格式的string
* @param parameters  请求参数
* @return
*/
public static String getRequestXml(SortedMap<Object,Object> 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 ("attach".equalsIgnoreCase(k)||"body".equalsIgnoreCase(k)||"sign".equalsIgnoreCase(k)) {
sb.append("<"+k+">"+"<![CDATA["+v+"]]></"+k+">");
}else {
sb.append("<"+k+">"+v+"</"+k+">");
}
}
sb.append("</xml>");
return sb.toString();
}
/**
* @author Mark
* @Description:返回给微信的参数
* @param return_code 返回编码
* @param return_msg  返回信息
* @return
*/
public static String setXML(String return_code, String return_msg) {
return "<xml><return_code><![CDATA[" + return_code
+ "]]></return_code><return_msg><![CDATA[" + return_msg
+ "]]></return_msg></xml>";
}


}

第六步:调用微信H5支付统一下单接口 得到预支付ID WxPayUtil

/**

* 调用微信H5支付统一下单接口 得到预支付ID

* @author Mark

*

*/

public class WxPayUtil {

@SuppressWarnings("unchecked")
public static String unifiedorder(String body,String out_trade_no,String openid) {
SortedMap<Object,Object> parameters = new TreeMap<Object,Object>();
parameters.put("appid", ConfigUtil.APPID);

parameters.put("mch_id", ConfigUtil.MCH_ID);
parameters.put("nonce_str", PayCommonUtil.CreateNoncestr());
parameters.put("body", body);
parameters.put("out_trade_no", out_trade_no);
parameters.put("total_fee", "1");
parameters.put("spbill_create_ip","113.57.246.11");
parameters.put("notify_url", ConfigUtil.NOTIFY_URL);
parameters.put("trade_type", "JSAPI");
parameters.put("openid", openid);
String sign = PayCommonUtil.createSign(parameters);
parameters.put("sign", sign);
String requestXML = PayCommonUtil.getRequestXml(parameters);
System.out.println(requestXML.toString());
String result =CommonUtil.httpsRequest(ConfigUtil.UNIFIED_ORDER_URL, "POST", requestXML);
System.out.println(result.toString());
Map<String, String> map=new HashMap<String, String>();
try {
map = XMLUtil.doXMLParse(result);
} catch (JDOMException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}//解析微信返回的信息,以Map形式存储便于取值
return map.get("prepay_id").toString();
}


}

第七步:xml管理 XMLUtil

/**

* xml管理

* @author Mark

*

*/

public class XMLUtil {

/**

* 解析xml,返回第一级元素键值对。如果第一级元素有子节点,则此节点的值是子节点的xml数据。

* @param strxml

* @return

* @throws JDOMException

* @throws IOException

*/

public static Map doXMLParse(String strxml) throws JDOMException, IOException {

strxml = strxml.replaceFirst(“encoding=\”.*\”“, “encoding=\”UTF-8\”“);

if(null == strxml || "".equals(strxml)) {
return null;
}

Map m = new HashMap();

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 = XMLUtil.getChildrenText(children);
}

m.put(k, v);
}

//关闭流
in.close();

return m;
}

/**
* 获取子结点的xml
* @param children
* @return String
*/
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(XMLUtil.getChildrenText(list));
}
sb.append(value);
sb.append("</" + name + ">");
}
}

return sb.toString();
}


}

第八步:接下来是Controller中调用,方式如下:

@ApiOperation(value = “微信支付调用”, httpMethod = “POST”)

@RequestMapping(“/couponsConfirm”)

public String couponsConfirm(Model m,@RequestParam(“openid”)String openid,@RequestParam(“orderNo”)String orderNo) {

//openid可通过微信高级接口oath2.0网页授权接口获取到用户信息,此接口本文中就不提供了,如果有需要,请留言。

m.addAttribute(“openid”, openid);

//orderNo是你的商品订单号,自行生成的随机订单号,但是要保证随机性,不能有重复订单号。

m.addAttribute(“orderNo”, orderNo);

String timeStamp=PayCommonUtil.create_timestamp();
String nonceStr=PayCommonUtil.create_nonce_str();
m.addAttribute("appid", ConfigUtil.APPID);
m.addAttribute("timestamp", timeStamp);
m.addAttribute("nonceStr", nonceStr);
m.addAttribute("openid",openid);

String prepayId=WxPayUtil.unifiedorder("外卖下单",orderNo, openid);


// String userAgent = request.getHeader(“user-agent”);

// char agent = userAgent.charAt(userAgent.indexOf(“MicroMessenger”)+15);

// m.addAttribute(“agent”, new String(new char[]{agent}));//微信版本号,用于前面提到的判断用户手机微信的版本是否是5.0以上版本。

SortedMap<Object,Object> signParams = new TreeMap<Object,Object>();
signParams.put("appId", ConfigUtil.APPID);
signParams.put("nonceStr",nonceStr);
signParams.put("package", "prepay_id="+prepayId);
signParams.put("timeStamp", timeStamp);
signParams.put("signType", "MD5");

// 生成支付签名,要采用URLENCODER的原始值进行SHA1算法!
String sign= PayCommonUtil.createSign(signParams);

m.addAttribute("paySign", sign);

m.addAttribute("packageValue", "prepay_id="+prepayId);

return "跳转到你的支付页面";
}


最后一步:页面调用

WeixinJSBridge.invoke(‘getBrandWCPayRequest’,{

“appId”:(“#appid”).val(),
“timeStamp”:(“#timestamp”).val(),

“nonceStr”:(“#nonceStr”).val(),
“package”:(“#packageValue”).val(),

“signType”:”MD5”,

“paySign”:$(“#paySign”).val()

},function(res){

WeixinJSBridge.log(res.err_msg);

if(res.err_msg == “get_brand_wcpay_request:ok”){

alert(“支付成功”);

}else if(res.err_msg == “get_brand_wcpay_request:cancel”){

alert(“取消支付!”)

}else{
alert("支付失败!")
}


到此,所有需要的代码都已提供,各位在借鉴的时候,记得也要做些内容修改,ConfigUtil中的一些参数,需要各位自行提供。其它的都可以直接使用,微信支付整体并不难,难点就在于微信签名,很多人调试微信支付,都被这个微信签名给难倒了,这也是微信团队的留下的一天坑。

**最后一句:如果各位在借鉴中,发现缺失什么,可以私信找我要,也可以留言。如果觉得我写的好,就来犒劳我一下吧
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  java 微信 支付