微信掃碼支付 模式一 (JSAPI)
這個微信支付是靜態二維碼支付,就是店面貼著一個二維碼,讓消費者自己掃自己輸入金額,自己發起支付的支付方式。
要準備的東西比較麻煩:
1、到微信公眾號平臺設定Oauth2的網頁驗證域名(用於獲取code,code用於拿到發起支付的openId),格式是www.xxxx.com/file1/file2/,不需要https:// 要精確到發起支付頁面的當前路徑
2、配置Oauth2網頁驗證域名的時候,需要下載一個txt,放到發起支付頁面的統計目錄
3、到微信公眾號平臺還是商戶平臺設定支付授權地址(用於發起支付),不會設定可以發郵件到微信工作人員郵箱,申請處理,郵箱要自己挖(這個地址需要有https://)
4、拿到微訊號的appid、mch_id、appscret、key、退款證書、sub_mch_id(服務商需要為子商戶開這個,而前五個都是用服務商自己的就可以了)
5、對於境內微信商戶,Oauth2的網頁驗證域名和支付授權地址必須是通過了國內的ICP備案,不然不能用,對於境外微信商戶則沒有這個要求
以上四個東西都拿到做好了 就可以開始了 (開發文件無敵坑)
一、首先寫個通過Oauth的驗證的頁面(注意redirect_url需要urlencode)
jsapi1.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ page language="java" import="java.net.*,java.io.*,java.text.*,java.util.*,com.demo.*,com.demo.dao.*,com.demo.utils.*,java.sql.*" %>
<%@ page language="java" import="java.net.URLDecoder" %>
<%@ page language="java" import="org.json.JSONObject" %>
<%
request.setCharacterEncoding("UTF-8");
String currCode = request.getParameter("currCode");
String oauth2_url = "";
String wechat_appid = "資料庫讀取出來比較好";
String wechat_appsecret = "資料庫讀取出來比較好" ;
String currCode = "RMB";
String state = currCode+"|"+wechat_appid+"|end";
if(wechat_appid==""){
System.out.println("no wechat appid");
}else{
System.out.println("WECHATappid:"+wechat_appid);
}
String redirect_url = "https://www.myserver.com/jsapi2.jsp";
redirect_url = URLEncoder.encode(redirect_url, "UTF-8");
oauth2_url = "https://open.weixin.qq.com/connect/oauth2/authorize?appid="+wechat_appid+"&redirect_uri="+redirect_url+"&response_type=code&scope=snsapi_base&state="+state+"#wechat_redirect";
%>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title><fmt:message key="pg.eng.payment.payWECHATONL.text.title"/></title>
</head>
<body>
<!-- this page will direct to wechat authorize url and then wechat will redirect to redirect_url with code and state -->
<script language="JavaScript" type="text/javascript">
window.location.href="<%=oauth2_url%>";
</script>
</body>
</html>
二、然後微信會根據上一個頁面的redirect_url跳轉到對應頁面並給出一個openId,在這個頁面可以完成輸入金額的操作(注意要驗證是否微信瀏覽器開啟這個頁面,js裡面那個is_weixin();方法)
jsapi2.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ page language="java" import="java.net.*,java.io.*,java.text.*,java.util.*,com.demo.*,com.demo.dao.*,com.demo.utils.*,java.sql.*" %>
<%@ page language="java" import="java.net.URLDecoder" %>
<%@ page language="java" import="org.json.JSONObject" %>
<%
request.setCharacterEncoding("UTF-8");
String openId = "";
String code = request.getParameter("code");
String state = request.getParameter("state");
System.out.println("code:"+code+",state:"+state);
String[] statespilt = state.split("\\|");
String currCode = statespilt[0];
String wechat_appid = statespilt[1];
String wechat_appsecret = "用wechat_appid查資料庫讀取出來比較好";
String notify_url = "回撥地址";
System.out.println("wechat_appid:"+wechat_appid+",wechat_appsecret:"+wechat_appsecret);
String access_token_url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid="+wechat_appid+"&secret="+wechat_appsecret+"&code="+code+"&grant_type=authorization_code";
System.out.println("access_token_url:"+access_token_url);
String accesscode_rs = "";
accesscode_rs = HttpUtil.postData("",access_token_url);
System.out.println("accesscode_rs"+accesscode_rs);
JSONObject json;
json = new JSONObject(accesscode_rs);
System.out.println("OTTO-------set JSONObject");
openId = json.getString("openid");
System.out.println("OTTO-------openid:"+openId);
%>
<!-- ******************************** input page start *************************************** -->
<html>
<head>
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta http-equiv="Content-Style-Type" content="text/css">
<script language="JavaScript" type="text/javascript">
window.onload=function(){
is_weixin();
}
function is_weixin(){
var ua = navigator.userAgent.toLowerCase();
if(ua.match(/MicroMessenger/i)=="micromessenger") {
} else {
window.location.href="remind.jsp";
return;
}
}
</script>
<title>
input page
</title>
</head>
<body>
<form action="jsapi3.jsp" method="POST" >
<input type="text" id="amount" name="amount" placeholder="input amount">
<input type="submit" id="submit" name="submit" value="submit">
<input type="hidden" id="currCode" name="currCode" value="<%=currCode %>">
<input type="hidden" id="notify_url" name="notify_url" value="<%=notify_url %>">
<input type="hidden" id="wechat_appid" name="wechat_appid" value="<%=wechat_appid %>">
</form>
</body>
</html>
三、根據上一個頁面獲取到的openId和金額、貨幣種類等引數呼叫統一訂單API,然後獲得一個prepay_id,用這個prepay_id放到發起支付的js裡面,微信客戶端就能跳出輸入密碼確認支付的框了,呼叫微信瀏覽器裡面的js方法來發起支付(注意發起支付的js方法,只有在微信瀏覽器裡面才生效)
jsapi3.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ page language="java" import="java.net.*,java.io.*,java.text.*,java.util.*,com.demo.*,com.demo.dao.*,com.demo.utils.*,java.sql.*" %>
<%@ page language="java" import="java.net.URLDecoder" %>
<%@ page language="java" import="org.json.JSONObject" %>
<%
request.setCharacterEncoding("UTF-8");
String wechat_appid = request.getParameter("wechat_appid")==null?"":request.getParameter("wechat_appid") ;
String openid = request.getParameter("openid")==null?"":request.getParameter("openid") ;
String amount = request.getParameter("amount")==null?"0":request.getParameter("amount") ;
String currCode = request.getParameter("currCode")==null?"":request.getParameter("currCode") ;
String notify_url = request.getParameter("notify_url")==null?"":request.getParameter("notify_url") ;
String result = "";
String out_trade_no="";
double orderAmt = Double.parseDouble(amount)*100;
System.out.println("orderAmt:"+orderAmt);
//-----------------------------
//設定支付引數
//-----------------------------
String sign = "";
String UFDODER_URL = "https://api.mch.weixin.qq.com/pay/unifiedorder";
String trade_type = "JSAPI";
String spbill_create_ip = request.getRemoteAddr();
String nonce_str = "";
String notify_url = notify_url;
String body = out_trade_no;
String appid = wechat_appid;
String sub_mch_id = "用wechat_appid查資料庫讀取出來比較好";
String mch_id = "用wechat_appid查資料庫讀取出來比較好";
System.out.println("wechat_appid:"+wechat_appid+",appid:"+appid);
String currTime = Method.getCurrTime();
String time_start = currTime.substring(8, currTime.length());
String strRandom = Method.buildRandom(4) + "";
nonce_str = time_start + strRandom; // Generating random number
String total_fee = String.valueOf((int)orderAmt);
String fee_type = CurrCode.getName(request.getParameter("currCode"));
String key = "用wechat_appid查資料庫讀取出來比較好 微信提供的key";
if("RMB".equals(fee_type))
fee_type = "CNY";
System.out.println("datas:"+"\n"+"key:"+key+"\n"+"appid:"+appid+"\n"+"mch_id:"+mch_id+"\n"+"sub_mch_id:"+sub_mch_id+"\n"+"out_trade_no:"+out_trade_no+"\n"+"fee_type:"+fee_type+"\n"+"total_fee:"+total_fee+"\n"+"body:"+body+"\n"+"spbill_create_ip:"+spbill_create_ip+"\n"+"trade_type:"+trade_type+"\n"+"notify_url:"+notify_url+"\n"+"nonce_str:"+nonce_str);
Map<Object, String> map = new TreeMap<Object, String>();
map.put("appid", appid);//服務商appid
map.put("sub_mch_id", sub_mch_id);//商戶號
map.put("mch_id", mch_id);//服務商號
map.put("nonce_str", nonce_str);//隨機字串
map.put("body", body);//商品描述,用orderId
map.put("out_trade_no", out_trade_no);//商家訂單號
map.put("fee_type", fee_type);//幣種
map.put("total_fee", total_fee);//商品金額,以分為單位
map.put("spbill_create_ip", spbill_create_ip);//使用者公網ip
map.put("notify_url", notify_url);//微信反饋的接收url
map.put("trade_type", trade_type);//公眾號支付用JSAPI
map.put("openid", openid);
sign = Method.createSign("UTF-8", map,key);
map.put("sign", sign);
String requestXML = Method.getRequestXml(map);
Map map1 = null;
try{
String resXml = ServerPost.postByExternalServerPost(requestXML,UFDODER_URL,"UTF-8");
resXml = resXml.substring(1);
System.out.println("resXml:"+resXml);
map1 = XMLUtil.doXMLParse(resXml);
}catch (ConnectException ce){
System.out.println("連線超時:"+ce.getMessage());
}catch (Exception e){
System.out.println("https請求異常:"+e.getMessage());
}
if(map1==null){
System.out.println("map null");
result = "appId:"+appid+",timeStamp: ,nonceStr: ,package:prepay_id= ,signType: ,paySign: ";
}else{
if(!map1.containsKey("prepay_id")||!map1.containsKey("appid")||map1.get("prepay_id")==""){
System.out.println("prepayid error or appid error");
result = "appId:"+appid+",timeStamp: ,nonceStr: ,package:prepay_id= ,signType: ,paySign: ";
}else{
String prepay_timeStamp = Method.getCurrTime();
String prepay_time = currTime.substring(8, currTime.length());
String prepay_strRandom = Method.buildRandom(4) + "";
String prepay_nonce_str = nonce_str;
String prepay_id = map1.get("prepay_id").toString();
String prepay_SignType = "MD5";
Map<Object, String> prepay_map = new TreeMap<Object, String>();
prepay_map.put("appId", appid);
prepay_map.put("timeStamp", prepay_timeStamp);
prepay_map.put("nonceStr", prepay_nonce_str);
prepay_map.put("package", ("prepay_id="+prepay_id));
prepay_map.put("signType", prepay_SignType);
String paySign = Method.createSign("UTF-8", prepay_map,key);
result = "\"appId\":\""+appid+"\",\"timeStamp\":\""+prepay_timeStamp+"\",\"nonceStr\":\""+prepay_nonce_str+"\",\"package\":\"prepay_id="+prepay_id+"\",\"signType\":\""+prepay_SignType+"\",\"paySign\":\""+paySign+"\"";
}
System.out.println("result:"+result);
}
%>
<%if(!("appId:"+appid+",timeStamp: ,nonceStr: ,package:prepay_id= ,signType: ,paySign: ").equals(result)){ %>
<!-- ********************************* payment page ***************************************** -->
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>
<fmt:message key="pg.eng.payment.payWECHATONLResult.text.title"/>
</title>
<link rel="stylesheet" type="text/css" media="all" href="../../css/common.css">
<meta name="viewport" content="width=device-width">
</script>
<script src="../../js/jquery-1.4.2.min.js"></script>
<script type="text/javascript">
$(document).ready(function(){
callpay();
});
</script>
<script>
function close_cancel() {
//根據appid,mch_id,out_trade_no,sub_mch_id呼叫關閉訂單介面 關閉訂單
}
</script>
<script type="text/javascript">
//呼叫微信JS api 支付
function jsApiCall()
{
WeixinJSBridge.invoke(
'getBrandWCPayRequest',{
<%=result%>
},
function(res){
WeixinJSBridge.log(res.err_msg);
if(res.err_msg == "get_brand_wcpay_request:cancel"){
close_cancel();
}else if(res.err_msg == "get_brand_wcpay_request:fail"){
close_cancel();
}
}
);
}
function callpay()
{
if (typeof WeixinJSBridge == "undefined"){
if( document.addEventListener ){
document.addEventListener('WeixinJSBridgeReady', jsApiCall, false);
}else if (document.attachEvent){
document.attachEvent('WeixinJSBridgeReady', jsApiCall);
document.attachEvent('onWeixinJSBridgeReady', jsApiCall);
}
}else{
jsApiCall();
}
}
</script>
<script type="text/javascript">
var start = setInterval(function(){order_status()},1000);
function order_status() {
//查詢自己資料庫 根據狀態對page_SorF賦值 然後提交表單
/*
$.ajax({
type: "POST",
url: "../../WECHATONLINEorderStatusCheck",
dataType: "text",
cache: false,
data: {
out_trade_no : '<%=out_trade_no%>'
},
success: function(data) {
if(data!=null){
var result = eval("(" + data + ")");
var s_result = result.status_result;
if("Accepted"==s_result){
clearInterval(start);
$("#page_SorF").val("successpage");
$("#PayWeChatForm").submit();
}else if("Rejected"==s_result){
clearInterval(start);
$("#page_SorF").val("failpage");
$("#PayWeChatForm").submit();
}else{}
}else{
}
}
});
*/
}
</script>
</head>
<body>
<div id="wrap">
<form name="PayWeChatForm" id="PayWeChatForm" method="post" action="jsapiResult.jsp">
貨幣種類:<%=currCode %>
金額: <%=amount %>
<input type="hidden" id="amount" name="amount" value="<%=amount%>" >
<input type="hidden" id="currCode" name="currCode" value="<%=request.getParameter("currCode")%>" >
<input type="hidden" id="page_SorF" name="page_SorF" value="" >
</form>
</div>
</body>
</html>
<!-- ********************************* payment page ***************************************** -->
<%}else{
%>
<html>
<head></head>
<body>
<div>Message error</div>
</body>
</html>
<%
}%>
四、寫回調(微信會通過回撥把交易結果返回到Notify_url所在的地方)
Notify.jsp
<%@ page language="java" import="java.util.*,com.demo.*,com.demo.utils.*" pageEncoding="UTF-8"%>
<%out.println("success");%>
<%
//for getting wechat respon--------start
String appid = "";
String mch_id = "";
String sub_mch_id = "";
String trade_type = "";
String bank_type = "";
String total_fee = "";
String fee_type = "";
String cash_fee = "";
String cash_fee_type = "";
String transaction_id = "";
String out_trade_no = "";
String bankTxTime = "";
String rate = "";
String return_code = "";
String result_code = "";
//讀取引數
InputStream inputStream ;
StringBuffer sb = new StringBuffer();
inputStream = request.getInputStream();
String s ;
BufferedReader in = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
while ((s = in.readLine()) != null){
sb.append(s);
}
in.close();
inputStream.close();
//解析xml成map
Map<String, String> m = new HashMap<String, String>();
m = XMLUtil.doXMLParse(sb.toString());
//過濾空 設定 TreeMap
Map<Object,String> packageParams = new TreeMap<Object,String>();
Iterator it = m.keySet().iterator();
while (it.hasNext()) {
String parameter = (String) it.next();
String parameterValue = m.get(parameter);
String v = "";
if(null != parameterValue) {
v = parameterValue.trim();
}
packageParams.put(parameter, v);
}
//------------------------------
//處理業務開始
//------------------------------
String resXml = "";
if("SUCCESS".equals((String)packageParams.get("result_code"))){
// 這裡是支付成功
appid = (String)packageParams.get("appid");
mch_id = (String)packageParams.get("mch_id");
sub_mch_id = (String)packageParams.get("sub_mch_id");
trade_type = (String)packageParams.get("trade_type");
bank_type = (String)packageParams.get("bank_type");
total_fee = (String)packageParams.get("total_fee");
fee_type = (String)packageParams.get("fee_type");
cash_fee = (String)packageParams.get("cash_fee");
cash_fee_type = (String)packageParams.get("cash_fee_type");
transaction_id = (String)packageParams.get("transaction_id");
out_trade_no = (String)packageParams.get("out_trade_no");
bankTxTime = (String)packageParams.get("time_end");
rate = (String)packageParams.get("rate");
return_code = (String)packageParams.get("return_code");
result_code = (String)packageParams.get("result_code");
System.out.println("Notify:"+"\n"+"appid:"+appid+"\n"+"mch_id:"+mch_id+"\n"+"sub_mch_id:"+sub_mch_id+"\n"+"trade_type:"+trade_type+"\n"+"bank_type:"+bank_type+"\n"+"total_fee:"+total_fee+"\n"+"fee_type:"+fee_type+"\n"+"cash_fee:"+cash_fee+"\n"+"cash_fee_type:"+cash_fee_type+"\n"+"transaction_id:"+transaction_id+"\n"+"out_trade_no:"+out_trade_no+"\n"+"rate:"+rate+"\n"+"return_code:"+return_code+"\n"+"result_code:"+result_code);
System.out.println("pay success");
//通知微信.非同步確認成功.必寫.不然會一直通知後臺.八次之後就認為交易失敗了.
resXml = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>"
+ "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> ";
} else {
System.out.println("支付失敗,錯誤資訊:" + packageParams.get("err_code"));
resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>"
+ "<return_msg><![CDATA[報文 ></return_msg>" + "</xml> ";
}
//------------------------------
//處理業務完畢
//------------------------------
BufferedOutputStream bos = new BufferedOutputStream(response.getOutputStream());
bos.write(resXml.getBytes());
bos.flush();
bos.close();
//for getting wechat respon--------end
boolean signValidate = false;
// 賬號資訊
String key = "set wechat ket here"; // key
//判斷簽名是否正確
if(Method.isTenpaySign("UTF-8", packageParams,key)) {
signValidate = true;
System.out.println("NOTIFY : " + "tenpay sign success");
} else{
signValidate = false;
System.out.println("NOTIFY : " + "tenpay sign fail");
}
if ( (signValidate && return_code.equals("SUCCESS")) ) {
if (signValidate && return_code.equals("SUCCESS") && result_code.equals("SUCCESS")) {
//對資料庫做交易成功的處理邏輯
} else {
//對資料庫做交易失敗的處理邏輯
}
}
out.clear();
out = pageContext.pushBody();
%>
五、寫結果頁面
jsapiResult.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ page language="java" import="java.net.*,java.io.*,java.text.*,java.util.*,com.demo.*,com.demo.dao.*,com.demo.utils.*,java.sql.*" %>
<%
request.setCharacterEncoding("UTF-8");
String page_SorF = request.getParameter("page_SorF")==null?"failpage":request.getParameter("page_SorF"); //successpage or failpage
String amount = request.getParameter("amount")==null?"":request.getParameter("amount");
String currCode = request.getParameter("currCode")==null?"":request.getParameter("currCode");
%>
<%
if("successpage".equals(page_SorF)){
%>
<!--************************success page start********************************-->
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>
Result page
</title>
<link rel="stylesheet" type="text/css" media="all" href="../../css/common.css">
<meta name="viewport" content="width=device-width">
</script>
</head>
<body>
<div>
<form name="form1" method="post" action="">
貨幣種類:<%=currCode %>
金額: <%=amount %>
</form>
</div>
</body>
</html>
<!--*************************success page end*********************************-->
<%
}else if("failpage".equals(page_SorF)){
%>
<!--************************fail page start********************************-->
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>
fail
</title>
<link rel="stylesheet" type="text/css" media="all" href="../../css/common.css">
<meta name="viewport" content="width=device-width">
</head>
<body>
<form name="failForm" method="post" action="payForm.jsp">
<div>fail</div>
</form>
</body>
</html>
<!--*************************fail page end*********************************-->
<%
}else{
%>
<!--************************fail start********************************-->
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>
failpage value null
</title>
<link rel="stylesheet" type="text/css" media="all" href="../../css/common.css">
<meta name="viewport" content="width=device-width">
</head>
<body>
<form name="failForm" method="post" action="payForm.jsp">
<div>failpage value null</div>
</form>
</body>
</html>
<!--*************************fail end*********************************-->
<%
}
%>
六、寫如果不是微信瀏覽器的時候的提示頁面
remind.jsp
<html>
<head>
<title>Wrong Browser</title>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=0" />
<link rel="stylesheet" type="text/css" href="https://res.wx.qq.com/open/libs/weui/0.4.1/weui.css" />
</head>
<body>
<div class="weui_msg">
<div class="weui_icon_area">
<i class="weui_icon_info weui_icon_msg"></i>
</div>
<div class="weui_text_area">
<h4 class="weui_msg_title">Please open with WeChat</h4>
</div>
</div>
</body>
</html>
總結:程式碼寫出來很快(com.demo.utils.*裡面那些Method.xxx()、XMLUtils.xxx之類的方法,微信demo上有,我之前寫的微信支付貼也有,只是一些生成隨機字元、解析xml之類的方法,自己直接寫也可以,很簡單的),不過需要注意細節(redirect_url需要先urlencode、判斷是否微信瀏覽器、微信賬號的配置、code/openId/prepay_id的獲取和使用),這些細節就是坑,做少了做錯了就走不通。