微信支付的业务流程
首先得注册微信公众号和微信商户号,获取公众号appid和AppSecret。商户号和商户秘钥
在公众号设置->功能设置里配置网页授权域名
配置网页授权域名的时候要下载MP_verify_9WfgaBOw4mn4Wgie.txt文件到授权目录下,测试一下能否通过域名/文件名访问。
公众号配置好了以后再配置商户号的授权域名: 在产品中心->开发配置中进行配置
授权域名例如:当前支付页面为
http://www.wentianlong.top/wxpay/js_api_call.php
就必须填写:
http://www.wentianlong.top/wxpay/
总之,在当前支付页面的域名中,倒数第一个 / 为准。
前面配置好以后进行获取微信授权:
关于网页授权的两种scope的区别
1、以snsapi_base为scope发起的网页授权,是用来获取进入页面的用户的openid的,并且是静默授权并自动跳转到回调页的。用户感知的就是直接进入了回调页(往往是业务页面)
2、以snsapi_userinfo为scope发起的网页授权,是用来获取用户的基本信息的。但这种授权需要用户手动同意,并且由于用户同意过,所以无须关注,就可在授权后获取该用户的基本信息。
3、用户管理类接口中的“获取用户基本信息接口”,是在用户和公众号产生消息交互或关注后事件推送后,才能根据用户OpenID来获取用户基本信息。这个接口,包括其他微信接口,都是需要该用户(即openid)关注了公众号后,才能调用成功的。
获取openid http://www.cnblogs.com/liuhongfeng/p/5099149.html
public class wxPayServlet extends BaseController {
/**
* 用户提交支付,获取微信支付订单接口
* @param request
* @param response
* @throws IOException
* @throws ServletException
*/
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
Map<String, Object> data = new HashMap<>();
String GZHID = SystemConstant.APP_ID;// 微信公众号id
String GZHSecret = SystemConstant.APP_SECRET;;// 微信公众号密钥id
String SHHID = SystemConstant.COMMERCIAL_CODE;// 微信支付分配的商户号
String SECRET = SystemConstant.COMMERCIAL_SECRET;// 商户号对应的密钥
// ------1.获取参数信息-------
//商户订单号
String out_trade_no= request.getParameter("state");
//价格
String finalmoney = request.getParameter("money");
//金额转化为分为单位
// String finalmoney = getMoney(money);
System.out.println("1.获取参数信息 : out_trade_no:"+out_trade_no+" finalmoney:"+finalmoney);
finalmoney = "1"; //默认为1分
//------2获取用户的code-------
String openid=null;
openid= snsUserInfo.getOpenId();
if(openid == null){
data.put("code", -1);
data.put("msg", "2获取微信用户的openId数据错误");
}
System.out.println("2.根获取微信用户的openId和access_token: out_trade_no:"+openid);
// ------3.生成预支付订单需要的的package数据-------
//随机数
String nonce_str= MD5Util.getMessageDigest(String.valueOf(new Random().nextInt(10000)).getBytes());
//订单生成的机器 IP
String spbill_create_ip = request.getRemoteAddr();
//交易类型 :jsapi代表微信公众号支付
String trade_type = "JSAPI";
//这里notify_url是 微信处理完支付后的回调的应用系统接口url。
String notify_url ="http://wentianlong.top/WashingMachine/home?method=weixinNotify";
SortedMap<String, String> packageParams = new TreeMap<String, String>();
packageParams.put("appid", GZHID);
packageParams.put("mch_id", SHHID);
packageParams.put("nonce_str", nonce_str);
packageParams.put("body", "money");
packageParams.put("out_trade_no", out_trade_no);
packageParams.put("total_fee", finalmoney);
packageParams.put("spbill_create_ip", spbill_create_ip);
packageParams.put("notify_url", notify_url);
packageParams.put("trade_type", trade_type);
packageParams.put("openid", openid);
packageParams.put("sign_type", "MD5");
System.out.println("3.生成预支付订单需要的的package数据: out_trade_no:"+packageParams.size());
for(Map.Entry<String, String> entry : packageParams.entrySet()){
System.out.println("Key = "+entry.getKey() +".....value ="+entry.getValue());
}
//------4.根据package数据生成预支付订单号的签名sign-------
RequestHandler reqHandler = new RequestHandler(request, response);
reqHandler.init( GZHID, GZHSecret, SECRET);
String sign = reqHandler.createSign(packageParams);
System.out.println("4.根据package数据生成预支付订单号的签名sign:"+sign);
//------5.生成需要提交给统一支付接口https://api.mch.weixin.qq.com/pay/unifiedorder 的xml数据-------
String xml="<xml>"+
"<appid>"+GZHID+"</appid>"+
"<mch_id>"+SHHID+"</mch_id>"+
"<nonce_str>"+nonce_str+"</nonce_str>"+
"<sign>"+sign+"</sign>"+
"<body><![CDATA["+"money"+"]]></body>"+
"<out_trade_no>"+out_trade_no+"</out_trade_no>"+
"<total_fee>"+finalmoney+"</total_fee>"+
"<spbill_create_ip>"+spbill_create_ip+"</spbill_create_ip>"+
"<sign_type>MD5</sign_type>"+
"<notify_url>"+notify_url+"</notify_url>"+
"<trade_type>"+trade_type+"</trade_type>"+
"<openid>"+openid+"</openid>"+
"</xml>";
System.out.println("5.生成需要提交给统一支付接口:"+xml);
System.out.println();
//------6.调用统一支付接口https://api.mch.weixin.qq.com/pay/unifiedorder 生产预支付订单----------
String createOrderURL = "https://api.mch.weixin.qq.com/pay/unifiedorder";
String prepay_id="";
try {
prepay_id = GetWxOrderno.getPayNo(createOrderURL, xml);
if(prepay_id.equals("")){
data.put("code", -1);
data.put("msg", "6,生产预支付订单数据错误");
System.out.println("支付数据错误");
}
} catch (Exception e) {
data.put("code", -1);
data.put("msg", "6,统一支付接口获取预支付订单出错数据错误" + e);
System.out.println("支付数据错误"+e);
}
System.out.println("6.调用统一支付接口https:"+prepay_id);
//------7.将预支付订单的id和其他信息生成签名并一起返回到jsp页面 -------
nonce_str= MD5Util.getMessageDigest(String.valueOf(new Random().nextInt(10000)).getBytes());
SortedMap<String, String> finalpackage = new TreeMap<String, String>();
String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
String packageStr = "prepay_id="+prepay_id;
//String packageStr = prepay_id;
finalpackage.put("appId", GZHID);
finalpackage.put("timeStamp", timestamp);
finalpackage.put("nonceStr", nonce_str);
finalpackage.put("package", packageStr);
finalpackage.put("signType", "MD5");
String finalsign = reqHandler.createSign(finalpackage);
System.out.println("finalpackage size:"+finalpackage.size());
System.out.println("7.将预支付订单的id和其他信息生成签名并一起返回到jsp页面:"+finalsign);
for(Map.Entry<String, String> entry : finalpackage.entrySet()){
System.out.println("Key = "+entry.getKey() +".....value ="+entry.getValue());
}
data.put("appId",GZHID);
data.put("timeStamp",timestamp);
data.put("nonceStr",nonce_str);
data.put("packages",packageStr);
data.put("signType", "MD5");
data.put("paySign",finalsign);
JSONObject jsonObject = JSONObject.fromObject(data);
System.out.println("code:"+data.get("code"));
System.out.println("msg:"+data.get("msg"));
System.out.println(finalsign);
System.out.println(jsonObject);
response.setCharacterEncoding("utf-8");
response.getWriter().write(jsonObject.toString());
}
}
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" };
public 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;
}
}
}
/*
'微信支付服务器签名支付请求请求类
'*/
@SuppressWarnings("rawtypes")
public class RequestHandler {
/** Token获取网关地址地址 */
private String tokenUrl;
/** 预支付网关url地址 */
private String gateUrl;
/** 查询支付通知网关URL */
private String notifyUrl;
/** 商户参数 */
private String appid;
private String appkey;
private String partnerkey;
private String appsecret;
private String key;
/** 请求的参数 */
private SortedMap parameters;
/** Token */
private String Token;
private String charset;
/** debug信息 */
private String debugInfo;
private String last_errcode;
private HttpServletRequest request;
private HttpServletResponse response;
/**
* 初始构造函数。
*
* @return
*/
public RequestHandler(HttpServletRequest request,
HttpServletResponse response) {
this.last_errcode = "0";
this.request = request;
this.response = response;
//this.charset = "GBK";
this.charset = "UTF-8";
this.parameters = new TreeMap();
// 验证notify支付订单网关
notifyUrl = "https://gw.tenpay.com/gateway/simpleverifynotifyid.xml";
}
/**
* 初始化函数。
*/
public void init(String app_id, String app_secret, String partner_key) {
this.last_errcode = "0";
this.Token = "token_";
this.debugInfo = "";
this.appid = app_id;
this.partnerkey = partner_key;
this.appsecret = app_secret;
this.key = partner_key;
}
public void init() {
}
/**
* 获取最后错误号
*/
public String getLasterrCode() {
return last_errcode;
}
/**
*获取入口地址,不包含参数值
*/
public String getGateUrl() {
return gateUrl;
}
/**
* 获取参数值
*
* @param parameter
* 参数名称
* @return String
*/
public String getParameter(String parameter) {
String s = (String) this.parameters.get(parameter);
return (null == s) ? "" : s;
}
//设置密钥
public void setKey(String key) {
this.partnerkey = key;
}
//设置微信密钥
public void setAppKey(String key){
this.appkey = key;
}
// 特殊字符处理
public String UrlEncode(String src) throws UnsupportedEncodingException {
return URLEncoder.encode(src, this.charset).replace("+", "%20");
}
// 获取package的签名包
public String genPackage(SortedMap<String, String> packageParams)
throws UnsupportedEncodingException {
String sign = createSign(packageParams);
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();
sb.append(k + "=" + UrlEncode(v) + "&");
}
// 去掉最后一个&
String packageValue = sb.append("sign=" + sign).toString();
// System.out.println("UrlEncode后 packageValue=" + packageValue);
return packageValue;
}
/**
* 创建md5摘要,规则是:按参数名称a-z排序,遇到空值的参数不参加签名。
*/
public String createSign(SortedMap<String, String> packageParams) {
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 + "&");
}
}
sb.append("key=" + this.getKey());
String sign = MD5Util.MD5Encode(sb.toString(), this.charset)
.toUpperCase();
return sign;
}
/**
* 创建package签名
*/
public boolean createMd5Sign(String signParams) {
StringBuffer sb = new StringBuffer();
Set es = this.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 (!"sign".equals(k) && null != v && !"".equals(v)) {
sb.append(k + "=" + v + "&");
}
}
// 算出摘要
String enc = TenpayUtil.getCharacterEncoding(this.request,
this.response);
String sign = MD5Util.MD5Encode(sb.toString(), enc).toLowerCase();
String tenpaySign = this.getParameter("sign").toLowerCase();
// debug信息
this.setDebugInfo(sb.toString() + " => sign:" + sign + " tenpaySign:"
+ tenpaySign);
return tenpaySign.equals(sign);
}
//输出XML
public String parseXML() {
StringBuffer sb = new StringBuffer();
sb.append("<xml>");
Set es = this.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 +">" + getParameter(k) + "</" + k + ">\n");
}
}
sb.append("</xml>");
return sb.toString();
}
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>";
}
/**
* 设置debug信息
*/
protected void setDebugInfo(String debugInfo) {
this.debugInfo = debugInfo;
}
public void setPartnerkey(String partnerkey) {
this.partnerkey = partnerkey;
}
public String getDebugInfo() {
return debugInfo;
}
public String getKey() {
return key;
}
}
/**
* 微信支付订单处理类
* @author Administrator
*
*/
public class GetWxOrderno
{
public static DefaultHttpClient httpclient;
static
{
httpclient = new DefaultHttpClient();
httpclient = (DefaultHttpClient)HttpClientConnectionManager.getSSLInstance(httpclient);
}
/**
*提交数据到统一支付接口,获取微信生成的统一支付订单号
*@param url
*@param xmlParam
*@return
* @author ex_yangxiaoyi
* @see
*/
public static String getPayNo(String url,String xmlParam){
DefaultHttpClient client = new DefaultHttpClient();
client.getParams().setParameter(ClientPNames.ALLOW_CIRCULAR_REDIRECTS, true);
HttpPost httpost= HttpClientConnectionManager.getPostMethod(url);
String prepay_id = "";
try {
httpost.setEntity(new StringEntity(xmlParam, "UTF-8"));
HttpResponse response = httpclient.execute(httpost);
String jsonStr = EntityUtils.toString(response.getEntity(), "UTF-8");
System.out.println("getPayNo:jsonStr"+jsonStr);
if(jsonStr.indexOf("FAIL")!=-1){
return prepay_id;
}
Map map = doXMLParse(jsonStr);
prepay_id = (String) map.get("prepay_id");
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return prepay_id;
}
/**
*description:获取扫码支付连接
*@param url
*@param xmlParam
*@return
* @author ex_yangxiaoyi
* @see
*/
public static String getCodeUrl(String url,String xmlParam){
DefaultHttpClient client = new DefaultHttpClient();
client.getParams().setParameter(ClientPNames.ALLOW_CIRCULAR_REDIRECTS, true);
HttpPost httpost= HttpClientConnectionManager.getPostMethod(url);
String code_url = "";
try {
httpost.setEntity(new StringEntity(xmlParam, "UTF-8"));
HttpResponse response = httpclient.execute(httpost);
String jsonStr = EntityUtils.toString(response.getEntity(), "UTF-8");
if(jsonStr.indexOf("FAIL")!=-1){
return code_url;
}
Map map = doXMLParse(jsonStr);
code_url = (String) map.get("code_url");
} catch (Exception e) {
e.printStackTrace();
}
return code_url;
}
/**
* 解析xml,返回第一级元素键值对。如果第一级元素有子节点,则此节点的值是子节点的xml数据。
* @param strxml
* @return
* @throws JDOMException
* @throws IOException
*/
public static Map doXMLParse(String strxml) throws Exception {
if(null == strxml || "".equals(strxml)) {
return null;
}
Map m = new HashMap();
InputStream in = String2Inputstream(strxml);
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;
}
/**
* 获取子结点的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(getChildrenText(list));
}
sb.append(value);
sb.append("</" + name + ">");
}
}
return sb.toString();
}
public static InputStream String2Inputstream(String str) {
return new ByteArrayInputStream(str.getBytes());
}
}
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<link rel="stylesheet"href="${pageContext.request.contextPath}/css/drum-wash.css" type="text/css" />
<title>支付订单</title>
<script src="js/jquery-1.11.3.min.js" type="text/javascript"></script>
</head>
<script type="text/javascript">
var appId ;
var paySign ;
var timeStamp ;
var nonceStr ;
var packageStr ;
var signType ;
window.onload = function() {
var mode = GetQueryString("mode");
var mypay = GetQueryString("moneys");
}
function GetQueryString(name) {
var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)");
var r = window.location.search.substr(1).match(reg);
if (r != null)
return unescape(r[2]);
return null;
}
function select(e){
var bgtencenpay = document.getElementById("bgtencenpay");
var tencenpay = document.getElementById("tencenpay");
var bgalipay = document.getElementById("bgalipay");
var alipay = document.getElementById("alipay");
switch(e){
case "tencenpay":
bgtencenpay.style.backgroundColor = "#c0c0c0";
bgalipay.style.backgroundColor = "white";
tencenpay.style.visibility = "visible";
alipay.style.visibility = "hidden";
break;
case "alipay":
bgtencenpay.style.backgroundColor = "white";
bgalipay.style.backgroundColor = "#c0c0c0";
tencenpay.style.visibility = "hidden";
alipay.style.visibility = "visible";
break;
case "pay":
pays();
break;
}
}
function pays(){
var url = url = "${pageContext.request.contextPath}/wxpay&money="+${bean.sum}+"&state="+${bean.uuid};
var moneya =${bean.sum};
var statea = ${bean.uuid};
$.ajax({
url:url, //传输的数据
type: "POST", //请求的方式
dataType:"json",
success:function(data){ //请求成功的处理
if(data.code == -1){
alert(data.msg)
}else{
appId = data.appId;
timeStamp = data.timeStamp;
nonceStr = data.nonceStr;
packageStr = data.packages;
paySign = data.paySign;
signType = data.signType;
pay();
}
},
error:function(XMLHttpRequest, textStatus, errorThrown){
alert("textStatus:"+textStatus+"errorThrown:"+errorThrown);
}
});
}
function pay(){
if (typeof WeixinJSBridge == "undefined"){
if( document.addEventListener ){
document.addEventListener('WeixinJSBridgeReady', onBridgeReady, false);
}else if (document.attachEvent){
document.attachEvent('WeixinJSBridgeReady', onBridgeReady);
document.attachEvent('onWeixinJSBridgeReady', onBridgeReady);
}
}else{
onBridgeReady();
}
}
function onBridgeReady(){
WeixinJSBridge.invoke(
'getBrandWCPayRequest', {
"appId":appId, //公众号名称,由商户传入
"paySign":paySign, //微信签名
"timeStamp":timeStamp, //时间戳,自1970年以来的秒数
"nonceStr":nonceStr , //随机串
"package":packageStr, //预支付交易会话标识
"signType":signType //微信签名方式
},function(res){
if(res.err_msg == "get_brand_wcpay_request:ok"){
alert("微信支付成功!");
}else if(res.err_msg == "get_brand_wcpay_request:cancel"){
alert("用户取消支付!");
}else{
alert("支付失败!"+res.err_msg);
}
});
}
</script>
<body>
<header>
<div class="header-style">
<img src="${pageContext.request.contextPath}/image/back.png"
onclick="javascript:history.back(-1);"> <font>支付订单</font>
</div>
</header>
<div id="main">
<div class="pay_order">
<div class="select" onclick="select('dehydration')" id="dehydration">
<img
src="${pageContext.request.contextPath}/image/drum_order_pink__2x.jpg">
<div class="select-font">
<font>${bean.type}/<font size="2" style="color: gray;">${bean.time}min</font></font>
<font style="color: blue">¥ ${bean.sum}</font>
</div>
</div>
<br> 订单号:${bean.uuid}<br> 创建时间:${bean.data }
</div>
<div style="padding: 5%; margin-top: 10%; background-color:#c0c0c0 " onclick="select('tencenpay')" id="bgtencenpay">
<div class="pay_moder">
<img src="${pageContext.request.contextPath}/image/tencentpay.png" style="width: 100px; height: 30px;">
<img src="${pageContext.request.contextPath}/image/select_icon.png" style="width: 30px; height: 30px;" id="tencenpay">
</div>
</div>
<div style="padding: 5%; margin-top: 10%;" onclick="select('alipay')" id="bgalipay">
<div class="pay_moder" onclick="select(aipay)">
<img src="${pageContext.request.contextPath}/image/alipay.png" style="width: 90px; height: 30px;">
<img src="${pageContext.request.contextPath}/image/select_icon.png" style="width: 30px; height: 30px; visibility:hidden " id="alipay">
</div>
</div>
</div>
<footer onclick="select('pay')">确认支付 ${bean.sum}</footer>
</body>
</html>
统一下单 :https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1
微信内H5调起支付:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_7&index=6
还是要多看文档,集成后遇到许多坑,授权域名错误,生成预支付订单需要加上sign_type,为md5,请求参数写错了等,如果签名都正确,前台出现签名验证失败.那就检查后台代码中的变量名是否和文档中的一致,最好一个一个复制。