微信公眾號開發中遇到的問題——支付(二)
阿新 • • 發佈:2019-02-09
第一次開發微信公眾號,也是第一次接觸微信公眾號的支付,我使用的是jssdk,用h5頁面呼叫的支付,後臺使用的是java。首先宣告,我不是一個憤世嫉俗的人,也不喜歡吐槽,我認為別人提供介面就已經很不錯了,幹嘛要吐槽呢?但是,這一次,我不得不說,微信公眾號支付的文件真是渣!!!你沒有也就算了,既然有了,為何不好好寫?就好像別人問你路該怎麼走,你給別人指了路,但指偏了。下面我記錄一下我這次微信支付開發中遇到的坑,希望能給大家一點幫助。
開始之前,有幾個地方需要配置一下(比較容易遺忘):
1、js介面安全域名
2、微信支付中的開發配置
測試授權目錄:就是測試環境的授權目錄。可能有些人不知道支付怎麼測試,那麼,你需要做兩點:①呼叫支付方法的連結地址。②把該連結地址傳送到測試公眾號的聊天記錄裡面。注意是你自己測試微信公眾號的連結地址中。
測試白名單:測試微信公眾號支付時,把你們自己的微信賬號新增到測試白名單中;
3、我想吐槽一下,我的支付簽名有問題(後來換ios系統測試時彈出框彈出來的),但是我之前用android手機一直測試,都報的是get_brand_wcpay_request:fail或chooseWXPay:fail(取決於你用哪種方法),你根本不知道究竟是什麼錯誤。
4、當我要呼叫支付介面時,開啟商戶的微信公眾號支付文件,發現了一個H5調起的支付API,我本以為這裡的方法應該是chooseWXPay,但是為什麼是function onBridgeReady。。。。。。這又是什麼鬼?徹底把我搞暈了,有沒有?後來查資料才知道,兩個方法的本質是一樣的,新版本的微信都會使用chooseWXPay方法,chooseWXPay方法中封裝了onBridgeReady這東西,但是用onBridgeReady不用引用http://res.wx.qq.com/open/js/jweixin-1.0.0.js檔案,用chooseWXPay方法方法需要引用js檔案方法,因為chooseWXPay方法封裝了onBridgeReady,檢視js檔案就可以知道。
既然是H5頁面呼叫,那麼肯定首先是要看jssdk文件了:
一、繫結域名
這裡有一點建議,繫結的域名最好是你開發用的域名,如果你開發用的是二級域名,那麼就用二級域名,不要用一級域名,因為如果在該一級域名下面還有其他的二級域名,也要開發另一個公眾號,那麼就會遇到問題(我沒有試過,這兩天在網上查資料時看到的)。
二、引入js檔案,在呼叫微信支付的頁面引入http://res.wx.qq.com/open/js/jweixin-1.0.0.js;
三、通過config介面注入許可權驗證配置
wx.config({
debug: true, // 開啟除錯模式,呼叫的所有api的返回值會在客戶端alert出來,若要檢視傳入的引數,可以在pc端開啟,引數資訊會通過log打出,僅在pc端時才會列印。
appId: '', // 必填,公眾號的唯一標識
timestamp: , // 必填,生成簽名的時間戳
nonceStr: '', // 必填,生成簽名的隨機串
signature: '',// 必填,簽名,見附錄1
jsApiList: ['chooseWXPay'] // 必填,需要使用的JS介面列表,所有JS介面列表見附錄2,如果只是支付,只用這一個引數就夠了
});
引數說明:debug,測試環境下開啟,會alert出注入引數是否正確的資訊;其它幾個引數一定要注意大小寫!大小寫!大小寫!繼續向下看,我會給出timestamp,nonceStr和signature幾個引數的演算法;
四、通過ready介面處理成功驗證
wx.ready(function(){
// config資訊驗證後會執行ready方法,所有介面呼叫都必須在config介面獲得結果之後,config是一個客戶端的非同步操作,所以如果需要在頁面載入時就呼叫相關介面,則須把相關介面放在ready函式中呼叫來確保正確執行。對於使用者觸發時才呼叫的介面,則可以直接呼叫,不需要放在ready函式中。
});
wx.error(function(res){
// config資訊驗證失敗會執行error函式,如簽名過期導致驗證失敗,具體錯誤資訊可以開啟config的debug模式檢視,也可以在返回的res引數中檢視,對於SPA可以在這裡更新簽名。
});
五、發起支付
wx.ready(function(){
wx.chooseWXPay({
timestamp: 0, // 支付簽名時間戳,注意微信jssdk中的所有使用timestamp欄位均為小寫。但最新版的支付後臺生成簽名使用的timeStamp欄位名需大寫其中的S字元
nonceStr: '', // 支付簽名隨機串,不長於 32 位
package: '', // 統一支付介面返回的prepay_id引數值,提交格式如:prepay_id=***)
signType: '', // 簽名方式,預設為'SHA1',使用新版支付需傳入'MD5'
paySign: '', // 支付簽名
success: function (res) {
// 支付成功後的回撥函式
},
//如果你按照正常的jQuery邏輯,下面如果傳送錯誤,一定是error,那你就太天真了,當然,jssdk文件中也有提到
fail: function(res) {
//介面呼叫失敗時執行的回撥函式。
},
complete: function(res) {
//介面呼叫完成時執行的回撥函式,無論成功或失敗都會執行。
},
cancel: function(res) {
//使用者點選取消時的回撥函式,僅部分有使用者取消操作的api才會用到。
},
trigger: function(res) {
//監聽Menu中的按鈕點選時觸發的方法,該方法僅支援Menu中的相關介面。
}
});
});
至此,微信公眾號支付的jssdk已經描述完畢,但是不要高興得太早,微信支付的坑才剛剛開始。
1、wx.config中signature的演算法
sign方法的呼叫需要兩個引數,jsapi_ticket和url:
2、chooseWXPay方法中的timestamp,nonceStr和wx.config中的引數是一樣的,signType為MD5
3、chooseWXPay中的package,格式是'prepay_id=${prepayId }',${prepayId }是後臺計算得出的,我用的是java。
4、wx.chooseWXPay方法中的paySign測演算法
5、到這裡,我也必須吐槽一下微信搞了三個類似簽名的東西,wx.config中signature,wx.chooseWXPay中的paySign,和呼叫統一下單介面所用的sign。這三個東西徹底把我搞暈了,演算法描述也不是很清楚,希望我上面寫的能給大家一些幫助。
後續還會持續更新一些微信開發中遇到的其他一些不清不楚的地方。。。。。敬請期待
開始之前,有幾個地方需要配置一下(比較容易遺忘):
1、js介面安全域名
公眾號設定————功能設定
2、微信支付中的開發配置
測試授權目錄:就是測試環境的授權目錄。可能有些人不知道支付怎麼測試,那麼,你需要做兩點:①呼叫支付方法的連結地址。②把該連結地址傳送到測試公眾號的聊天記錄裡面。注意是你自己測試微信公眾號的連結地址中。
測試白名單:測試微信公眾號支付時,把你們自己的微信賬號新增到測試白名單中;
3、我想吐槽一下,我的支付簽名有問題(後來換ios系統測試時彈出框彈出來的),但是我之前用android手機一直測試,都報的是get_brand_wcpay_request:fail或chooseWXPay:fail(取決於你用哪種方法),你根本不知道究竟是什麼錯誤。
4、當我要呼叫支付介面時,開啟商戶的微信公眾號支付文件,發現了一個H5調起的支付API,我本以為這裡的方法應該是chooseWXPay,但是為什麼是function onBridgeReady。。。。。。這又是什麼鬼?徹底把我搞暈了,有沒有?後來查資料才知道,兩個方法的本質是一樣的,新版本的微信都會使用chooseWXPay方法,chooseWXPay方法中封裝了onBridgeReady這東西,但是用onBridgeReady不用引用http://res.wx.qq.com/open/js/jweixin-1.0.0.js檔案,用chooseWXPay方法方法需要引用js檔案方法,因為chooseWXPay方法封裝了onBridgeReady,檢視js檔案就可以知道。
既然是H5頁面呼叫,那麼肯定首先是要看jssdk文件了:
一、繫結域名
這裡有一點建議,繫結的域名最好是你開發用的域名,如果你開發用的是二級域名,那麼就用二級域名,不要用一級域名,因為如果在該一級域名下面還有其他的二級域名,也要開發另一個公眾號,那麼就會遇到問題(我沒有試過,這兩天在網上查資料時看到的)。
二、引入js檔案,在呼叫微信支付的頁面引入http://res.wx.qq.com/open/js/jweixin-1.0.0.js;
三、通過config介面注入許可權驗證配置
wx.config({
debug: true, // 開啟除錯模式,呼叫的所有api的返回值會在客戶端alert出來,若要檢視傳入的引數,可以在pc端開啟,引數資訊會通過log打出,僅在pc端時才會列印。
appId: '', // 必填,公眾號的唯一標識
timestamp: , // 必填,生成簽名的時間戳
nonceStr: '', // 必填,生成簽名的隨機串
signature: '',// 必填,簽名,見附錄1
jsApiList: ['chooseWXPay'] // 必填,需要使用的JS介面列表,所有JS介面列表見附錄2,如果只是支付,只用這一個引數就夠了
});
引數說明:debug,測試環境下開啟,會alert出注入引數是否正確的資訊;其它幾個引數一定要注意大小寫!大小寫!大小寫!繼續向下看,我會給出timestamp,nonceStr和signature幾個引數的演算法;
四、通過ready介面處理成功驗證
wx.ready(function(){
// config資訊驗證後會執行ready方法,所有介面呼叫都必須在config介面獲得結果之後,config是一個客戶端的非同步操作,所以如果需要在頁面載入時就呼叫相關介面,則須把相關介面放在ready函式中呼叫來確保正確執行。對於使用者觸發時才呼叫的介面,則可以直接呼叫,不需要放在ready函式中。
});
wx.error(function(res){
// config資訊驗證失敗會執行error函式,如簽名過期導致驗證失敗,具體錯誤資訊可以開啟config的debug模式檢視,也可以在返回的res引數中檢視,對於SPA可以在這裡更新簽名。
});
五、發起支付
wx.ready(function(){
wx.chooseWXPay({
timestamp: 0, // 支付簽名時間戳,注意微信jssdk中的所有使用timestamp欄位均為小寫。但最新版的支付後臺生成簽名使用的timeStamp欄位名需大寫其中的S字元
nonceStr: '', // 支付簽名隨機串,不長於 32 位
package: '', // 統一支付介面返回的prepay_id引數值,提交格式如:prepay_id=***)
signType: '', // 簽名方式,預設為'SHA1',使用新版支付需傳入'MD5'
paySign: '', // 支付簽名
success: function (res) {
// 支付成功後的回撥函式
},
//如果你按照正常的jQuery邏輯,下面如果傳送錯誤,一定是error,那你就太天真了,當然,jssdk文件中也有提到
fail: function(res) {
//介面呼叫失敗時執行的回撥函式。
},
complete: function(res) {
//介面呼叫完成時執行的回撥函式,無論成功或失敗都會執行。
},
cancel: function(res) {
//使用者點選取消時的回撥函式,僅部分有使用者取消操作的api才會用到。
},
trigger: function(res) {
//監聽Menu中的按鈕點選時觸發的方法,該方法僅支援Menu中的相關介面。
}
});
});
至此,微信公眾號支付的jssdk已經描述完畢,但是不要高興得太早,微信支付的坑才剛剛開始。
1、wx.config中signature的演算法
jssdk文件上面給出了演算法的步驟,但是沒有給一個demo,這裡我給出大家一個signature的demo,大家可以根據這個按照自己的需要進行修改。
首先,我封裝了一個Jssdk物件(該物件封裝了 wx.config中所需要的引數):
<span style="font-family:Microsoft YaHei;">
/**
* 微信js-sdk安全驗證
* */
public class JsSdk {
//隨機數
private String noncestr;
//時間戳
private String timestamp;
//授權url
private String url;
//簽名
private String signature;
public String getNoncestr() {
return noncestr;
}
public void setNoncestr(String noncestr) {
this.noncestr = noncestr;
}
public String getTimestamp() {
return timestamp;
}
public void setTimestamp(String timestamp) {
this.timestamp = timestamp;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getSignature() {
return signature;
}
public void setSignature(String signature) {
this.signature = signature;
}
}
</span>
然後,給Jssdk物件屬性賦值:
<span style="font-family:Microsoft YaHei;">
public static JsSdk sign(String jsapi_ticket, String url) {
JsSdk jsSdk = new JsSdk();
jsSdk.setUrl(url);
String nonce_str = create_nonce_str();
jsSdk.setNoncestr(nonce_str);
String timestamp = create_timestamp();
jsSdk.setTimestamp(timestamp);
String signature = "";
//注意這裡引數名必須全部小寫,且必須有序
String string1 = "jsapi_ticket=" + jsapi_ticket +
"&noncestr=" + nonce_str +
"×tamp=" + timestamp +
"&url=" + url;
try
{
MessageDigest crypt = MessageDigest.getInstance("SHA-1");
crypt.reset();
crypt.update(string1.getBytes("UTF-8"));
signature = byteToHex(crypt.digest());
jsSdk.setSignature(signature);
}
catch (NoSuchAlgorithmException e)
{
jsSdk = null;
e.printStackTrace();
}
catch (UnsupportedEncodingException e)
{
jsSdk = null;
e.printStackTrace();
}
return jsSdk;
}
</span>
生成隨機字串:這裡大家可以選擇其他演算法<span style="font-family:Microsoft YaHei;">
private static String create_nonce_str() {
return UUID.randomUUID().toString().substring(0, 20);
}
</span>
生成時間戳的方法:<span style="font-family:Microsoft YaHei;">
private static String create_timestamp() {
return Long.toString(System.currentTimeMillis() / 1000);
}
</span>
sign方法的呼叫需要兩個引數,jsapi_ticket和url:
jsapi_ticket根據token來獲取,https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=ACCESS_TOKEN&type=jsapi,
url為request.getRequestURL().toString()來獲取,實際上就是呼叫支付方法的完整URL。
2、chooseWXPay方法中的timestamp,nonceStr和wx.config中的引數是一樣的,signType為MD5
3、chooseWXPay中的package,格式是'prepay_id=${prepayId }',${prepayId }是後臺計算得出的,我用的是java。
prepayId的演算法:
<span style="font-family:Microsoft YaHei;">
SortedMap<Object, Object> parameters = new TreeMap<Object,Object>();
parameters.put("appid", appId);//你自己公眾號的appId
parameters.put("mch_id", CacheManager.getValue("MCH_ID"));//此處為商戶號
parameters.put("nonce_str", jssdk.getNoncestr());//封裝在jssdk物件中的noncestr
parameters.put("body", "測試");
String outTraceNo = getOutTradeNo();//生成訂單號的方法,測試時可以隨便指定
log.info("outTraceNo = " + outTraceNo);
parameters.put("out_trade_no", outTraceNo);
parameters.put("total_fee", 1);
parameters.put("spbill_create_ip", CommonUtil.getRealIp(request));//測試時就寫成本機ip即可
parameters.put("notify_url", "");
parameters.put("trade_type", "JSAPI");
parameters.put("openid", openid);
parameters.put("sign", PayCommonUtil.createSign("utf-8", parameters));
String requestXml = PayCommonUtil.getRequestXml(parameters);
log.info("requestXml = " + requestXml);
prepayId = AdvancedUtil.createOrder(requestXml);
</span>
createSign和getRequestXml方法:
<span style="font-family:Microsoft YaHei;">
/**
* @Description:sign簽名
* @param characterEncoding 編碼格式
* @param parameters 請求引數
* @return
*/
@SuppressWarnings("rawtypes")
public static String createSign(String characterEncoding,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=" + CacheManager.getValue("API_KEY"));
String sign = CommonUtil.MD5(sb.toString(), characterEncoding).toUpperCase();
return sign;
}
/**
* @Description:將請求引數轉換為xml格式的string
* @param parameters 請求引數
* @return
*/
@SuppressWarnings("rawtypes")
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 = CommonUtil.getStringNotNullValue(entry.getKey());
String v = CommonUtil.getStringNotNullValue(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();
}
</span>
4、wx.chooseWXPay方法中的paySign測演算法
<span style="font-family:Microsoft YaHei;">
SortedMap<Object, Object> paySignParameters = new TreeMap<Object,Object>();
paySignParameters.put("appId", appId);
paySignParameters.put("nonceStr", jssdk.getNoncestr());
paySignParameters.put("package", "prepay_id=" + prepayId);
paySignParameters.put("signType", "MD5");
paySignParameters.put("timeStamp", jssdk.getTimestamp());
paySign = PayCommonUtil.createSign("utf-8", paySignParameters);
</span>
5、到這裡,我也必須吐槽一下微信搞了三個類似簽名的東西,wx.config中signature,wx.chooseWXPay中的paySign,和呼叫統一下單介面所用的sign。這三個東西徹底把我搞暈了,演算法描述也不是很清楚,希望我上面寫的能給大家一些幫助。
後續還會持續更新一些微信開發中遇到的其他一些不清不楚的地方。。。。。敬請期待