從我入行以來,第一個老板就給我講過,微信支付是個坑。
不過因?yàn)槟菚r候一來公司內(nèi)部有封裝好的方法,按要求調(diào)用就可以了,另外多做小程序,公眾號,web端,所以也沒自己踩什么坑。
但是!沒踩過微信的坑的開發(fā)不算是一個完整的開發(fā)!在經(jīng)歷了一兩年的逍遙以后,我到底還要自己做一次。
首先作為一個后臺,我覺得我要說一句良心話,微信對后臺還是很友好的,并不是很麻煩和坑,此次測試,百分之七十的坑都是前端踩的。
從準(zhǔn)備工作講起。
首先,想要做微信支付,需要的必備參數(shù),appId,mchId,key,回調(diào)路徑等等。然后還需要一個證書,要在電腦上配置。
這些幾乎都是必備的參數(shù),然后還有一些依賴于微信的sdk。
首先來看一看微信官網(wǎng):
微信支付開放平臺
大概講一講,微信支付有幾種,其中包括:
- 掃碼支付:就是用戶提供二維碼,商家掃碼支付。(這種適合收銀臺模式,然后我還沒有用過)
- JSAPI支付:這個就比較魔性了,用戶通過消息或掃描二維碼在微信內(nèi)打開網(wǎng)頁時,可以調(diào)用微信支付完成下單購買的流程。其實(shí)說起來真的很繞,反正就是類似于js的一種(這個是需要openId的)。
- Native支付:目前我覺得最簡單的一種支付,就是生成一個二維碼,用戶掃碼付款(錢數(shù)在生成二維碼時生成,只適合在web端使用)
- APP支付:這個不用多說了吧?就是APP用來拉取微信授權(quán)跳轉(zhuǎn)微信支付頁面的。
- H5支付:這個主要用于觸屏版的手機(jī)瀏覽器請求微信支付的場景??梢苑奖愕膹耐獠繛g覽器喚起微信支付。和jsapi的區(qū)別就是是否在微信內(nèi)部喚起吧。(我沒實(shí)際用過,也不清楚)
- 小程序支付: 沒話講,就這樣。
- 人臉支付: 高大上到讓我從未接觸過。
然后我這次業(yè)務(wù)使用的是Navite和APP支付兩種方式。
SDK的使用
這個有兩種方式,一種就是使用微信官網(wǎng)直接下載的SDK,然后以一個個文件的形式導(dǎo)到你的項(xiàng)目里。
還有一種是git上有一份用于這個的依賴(兩種辦法因?yàn)榘姹静煌允怯胁町惖摹?
<dependency>
<groupId>com.github.wxpay</groupId>
<artifactId>wxpay-sdk</artifactId>
<version>0.0.3</version>
</dependency>
然后懶惰如我,選擇了直接用maven的jar包的導(dǎo)入(中間發(fā)生了一些故事,也把微信官方下載的SDK一個個引入了項(xiàng)目中,后來發(fā)現(xiàn)功能一樣都跑起來了,所以又刪除了)。
注意一點(diǎn),以下的代碼都是在引入上面的依賴的前提下實(shí)現(xiàn)的(中間涉及到版本的差異很大,所以千萬不要弄混了,不然一點(diǎn)微小的區(qū)別能調(diào)N久)
開始真的實(shí)現(xiàn)啦!
1.**配置WXPayConfigImpl**
public class WxPayConfigImpl implements WXPayConfig {
public static String url = "你設(shè)置的回調(diào)接口";
private static WxPayConfigImpl wxPayConfig;
private byte[] certData = null;
public static WxPayConfigImpl getInstance() {
if (wxPayConfig == null) {
synchronized (WxPayConfigImpl.class) {
wxPayConfig = new WxPayConfigImpl();
}
}
return wxPayConfig;
}
public WxPayConfigImpl() {
try {
//這個證書的位置不是瞎雞兒填的,你要在這個路徑真的有一個證書
InputStream is = new FileInputStream("C:\\Windows\\System32\\cert\\apiclient_cert.p12");
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] bs = new byte[1024];
int cnt = -1;
while ((cnt = is.read(bs)) != -1) {
baos.write(bs, 0, cnt);
}
is.close();
certData = baos.toByteArray();
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public String getAppID() {
return "你自己的AppId";
}
@Override
public String getMchID() {
return "你自己的設(shè)備碼";
}
@Override
public String getKey() {
return "你自己的key";
}
@Override
public InputStream getCertStream() {
ByteArrayInputStream certBis;
certBis = new ByteArrayInputStream(this.certData);
return certBis;
}
@Override
public int getHttpConnectTimeoutMs() {
// TODO Auto-generated method stub
return 8000;
}
@Override
public int getHttpReadTimeoutMs() {
// TODO Auto-generated method stub
return 10000;
}
public String getPrimaryDomain() {
return "api.mch.weixin.qq.com";
}
public String getNotifyUrl(){
return url;
}
}
對,一個configImpl就這么簡單的實(shí)現(xiàn)了。其中參數(shù)是證書路徑,一個是appId,設(shè)備碼,key,回調(diào)接口連接是必填項(xiàng)。
Native支付
剛我就說覺得這個是最簡單的一種調(diào)用,反正我是一次成功的。下面是實(shí)現(xiàn)代碼:
public R wxPay(String money, String mk, String title, HttpServletRequest request, HttpServletResponse response) {
try {
if (mk == null) {
return R.error().put("msg", "缺少參數(shù),請查證后再訪問");
} else {
WXPayConfig config = WxWebPayConfigImpl.getInstance();
SortedMap<String, String> paramMap = new TreeMap<String, String>();
paramMap.put("body", title);
paramMap.put("out_trade_no", "C" + System.currentTimeMillis());
paramMap.put("fee_type", "CNY");
paramMap.put("total_fee", "1");
paramMap.put("notify_url", WxPayConfigImpl.url);
paramMap.put("trade_type", "NATIVE");
paramMap.put("sign_type", WXPayConstants.MD5);
String ip = getIpAddr(request);
paramMap.put("spbill_create_ip", ip);
WXPay pay = new WXPay(config);
// 1.統(tǒng)一下單
Map<String, String> resultMap = pay.unifiedOrder(paramMap);
String res = resultMap.get("return_code").toString().trim();
System.out.println(">>>>res==" + res);
System.out.println(resultMap);
if ("SUCCESS".equalsIgnoreCase(res)) {
return R.ok().put("data", resultMap);
} else {
return null;
}
}
} catch (Exception e) {
e.printStackTrace();
return null;
}
}

上圖是整理的一些我覺得的注意點(diǎn),然后點(diǎn)擊下單方法后會返回一個路徑,這個路徑可以打開一個二維碼頁面,因?yàn)槲覀兪乔昂蠖朔蛛x,所以我走到這步確定res是SUCCESS就ok了。
對了,還要注意一點(diǎn),這個錢數(shù)total_fee應(yīng)該是前端傳來的,不過我這為了demo效果,所以統(tǒng)一寫了一分,正常應(yīng)該寫活。
一個完整的微信Navite支付就完成了。
APP支付
剛剛也說了,這個坑百分之七十都是前端的,因?yàn)樾枰裁词裁词裁磁渲弥惖模乙膊恢?,反正就是總錯,但是真正的java代碼還是很簡單的。
public R wxAppPay(String money, String mk, String title, HttpServletRequest request, HttpServletResponse response) {
try {
Double d = Double.parseDouble(money);
Integer dd = (int) (d * 100);
money = String.valueOf(dd);
WXPayConfig config = WxPayConfigImpl.getInstance();
SortedMap<String, String> paramMap = new TreeMap<String, String>();
paramMap.put("body", title);
paramMap.put("out_trade_no", "C" + System.currentTimeMillis());
paramMap.put("fee_type", "CNY");
paramMap.put("total_fee",dd);
paramMap.put("notify_url", WxPayConfigImpl.url);
paramMap.put("trade_type", "APP");
paramMap.put("sign_type", WXPayConstants.MD5);
String ip = getIpAddr(request);
paramMap.put("spbill_create_ip", ip);
WXPay pay = new WXPay(config);
// 1.統(tǒng)一下單
Map<String, String> resultMap = pay.unifiedOrder(paramMap);
String res = resultMap.get("return_code").toString().trim();
System.out.println(">>>>res==" + res);
System.out.println(resultMap);
if ("SUCCESS".equalsIgnoreCase(res)) {
Map<String, String> paramMap1 = new HashMap<String, String>();
paramMap1.put("appid", config.getAppID());
// paramMap1.put("total", money);
paramMap1.put("partnerid", config.getMchID());
paramMap1.put("prepayid", (String) resultMap.get("prepay_id"));
paramMap1.put("package", "Sign=WXPay");
paramMap1.put("noncestr", WXPayUtil.generateNonceStr());
// 本來生成的時間戳是13位,但是ios必須是10位,所以截取了一下
paramMap1.put("timestamp", String.valueOf(System.currentTimeMillis()).toString().substring(0, 10));
String sign2 = WXPayUtil.generateSignature(paramMap1, config.getKey(), SignType.MD5);
paramMap1.put("sign", sign2);
Gson gson = new GsonBuilder().disableHtmlEscaping().create();
String xmlReq = gson.toJson(paramMap1);
return R.ok().put("data", xmlReq);
} else {
return R.error();
}
} catch (Exception e) {
e.printStackTrace();
return R.error();
}
}
我個人覺得這個就比較良心了,起碼金額是寫活了。然后與上一個的區(qū)別就是交易類型app,不過這個前端要配置較多的東西(具體配置啥不太清楚,反正我們項(xiàng)目就這里因?yàn)榍岸松倥渲每偪ㄗ?,卡了一天吧)?strong>貌似配置文件,什么什么證書。什么什么包名要一致,還有不能本地測試,會報錯-100,要打包測試(反正我們做完是這樣的,如果有本地也能測的親歡迎指點(diǎn))。
我這里鄭重聲明,app方式的微信支付,配置就這么多!再有任何報錯百分之九十九都是前端的了。
這個惡心的一點(diǎn)就是報錯不知道原因,很有可能莫名其妙就報個62000,如果兩邊信任度不高并且沒有成功的例子,可能報錯了就會兩邊找錯誤,前端改前端的,后臺改后臺的(對,就是我血淋淋的例子),結(jié)果改了一天多才發(fā)現(xiàn)最初的代碼其實(shí)就一點(diǎn)問題沒有。你能想到多崩潰么?所以希望以后或者別人對自己有點(diǎn)信心吧,反正我上面的代碼是跑通了的。
JSAPI支付
這個還有個小問題,一開始我問領(lǐng)導(dǎo)咱們這個項(xiàng)目用的啥方式,跟我說是JSAPI,所以我傻傻的去找了這個方式的官方文檔,不過因?yàn)楹髞砦腋牧耍圆]有現(xiàn)成的demo,大概講一下區(qū)別:
與APP方式比,也就是交易類型改一下,并且需要一個openId的參數(shù)。這個參數(shù)是微信授權(quán)獲取的,用appid生成的,我反正是作為一個參數(shù)的形式從前臺拿的,然后剩下別的就是一樣的了。
反正兩天的微信支付就這么痛苦的完成了,大多數(shù)錯誤犯得莫名其妙也解決的莫名其妙(因?yàn)槲液蠖藥缀跻恢倍际欠祷豷uccess,都是前端在那 調(diào)啊調(diào))
表白我前端,真的辛苦了,啥啥資料都沒有,各種需要做的沒有列出來,只能錯一點(diǎn)改一點(diǎn)。。還有本地不能調(diào)試簡直坑死了,反正我是真心心疼做微信支付這一塊兒的前端,也有可能是因?yàn)槲覀僡pp 是h5開發(fā),沒有用安卓或者微信?不太清楚。繼續(xù)往下說吧。
微信提現(xiàn)
其實(shí)本質(zhì)上微信的提現(xiàn)就是微信企業(yè)付款到個人用戶。
這個還蠻多要求的,大家可以自己看看官網(wǎng);
微信提現(xiàn)介紹

然后如果以上要求都滿足了的話,就可以測出效果了,因?yàn)槲覀冺?xiàng)目中的提現(xiàn)涉及到很多,比如用戶賬戶余額金額的對比,提現(xiàn)成功減去用戶余額等代碼,所以我這里就不完整的貼方法了。僅僅是把一些必要的步驟提出來:
WXPayConfig config = null;
//這個也是與我做的業(yè)務(wù)相關(guān)的,因?yàn)槲覀冞@兩個APP,也就是兩個APPid啥的,如果你們沒這么麻煩直接new WxPayConfigImpl()就ok 了。
// 5是司機(jī)
if (sysUserEntity.getUserType() == 5) {
config = new WxDriverpayConfigImpl();
// 不是司機(jī)就是企業(yè)
} else {
config = new WxPayConfigImpl();
}
SortedMap<String, String> paramMap = new TreeMap<String, String>();
paramMap.put("mch_appid", config.getAppID());
paramMap.put("mchid", config.getMchID());
paramMap.put("nonce_str", WXPayUtil.generateNonceStr());
paramMap.put("partner_trade_no", "C" + System.currentTimeMillis());
paramMap.put("openid", openId);
paramMap.put("check_name", "NO_CHECK");
paramMap.put("amount", (money * 100) + "");
paramMap.put("desc", "提現(xiàn)");
paramMap.put("spbill_create_ip", getIpAddr(request));
String sign = WXPayUtil.generateSignature(paramMap, config.getKey(), SignType.MD5);
paramMap.put("sign", sign);
WXPay pay = new WXPay(config);
// Map<String,String> resMap = pay.transfer(paramMap);
String url = " https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers";
Map<String, String> resMap;
resMap = pay.processResponseXml(pay.requestWithCert(url, paramMap, config.getHttpConnectTimeoutMs(),
config.getHttpReadTimeoutMs()));
String resultCode = resMap.get("result_code");
if ("SUCCESS".equalsIgnoreCase(resultCode)) {
//走到這里就是提現(xiàn)操作成功了,可以做你自己的業(yè)務(wù)邏輯了、
} else {
String err_code = resMap.get("err_code");
if ("SYSTEMERROR".equalsIgnoreCase(err_code)) {
return R.error().put("msg", err_code);
} else if ("NOTENOUGH".equalsIgnoreCase(err_code)) {
return R.error().put("msg", err_code);
} else {
//在這把兩個常見的錯提了出來,剩下的統(tǒng)一為未知錯誤了,如果做個更好一些可以直接傳錯誤信息。
return R.error().put("msg", "調(diào)用微信提現(xiàn)接口未知錯誤,請聯(lián)系管理員!");
}
至此,一個提現(xiàn)的功能也初步完成了。
尾
其實(shí)我這寫的都不完整,僅僅是對付實(shí)現(xiàn)操作,回調(diào)什么的我都沒寫。因?yàn)椋。。。☆I(lǐng)導(dǎo)神奇的工作分配,我這只實(shí)現(xiàn)操作,然后回調(diào)驗(yàn)證另一個同事寫(我也不知道為什么這么分配),當(dāng)時支付寶也是這么做的,只能笑笑吧。
至此,兩天的難受的不行的微信這塊兒就告一段落,只能說外界盛傳的微信坑名副其實(shí)吧。
然后如果稍微幫到了你麻煩點(diǎn)個喜歡點(diǎn)個關(guān)注,另外如果遇到我沒提到的問題歡迎留言或者私信,我們一起探討???指不定是我踩過的坑呢。也祝大家工作順順利利!