其實微信支付這一塊,真的是個翻來覆去講爛了的話題畦徘,甚至我在去年也寫過一個帖子用來記錄毕籽。但是之所以還要寫這篇文章,主要一來通過最近的使用對微信支付這塊了解的更加深刻井辆,其次也是這次用到了退款的功能关筒,這個算是新鮮點(diǎn),所以想來想去決定再從頭整理一下杯缺。
結(jié)合實際說的話蒸播,我這次項目是小程序,所以支付是略有不同萍肆,我盡量一點(diǎn)點(diǎn)剝開了說袍榆。
第一步:看官網(wǎng)介紹
首先肯定是要從微信的開發(fā)文檔說起,這里附上傳送門:
微信支付開發(fā)文檔——普通商戶版
我個人是覺得微信的文檔越來越好了塘揣,再也不是三年前一提起微信就腦殼痛的時候了包雀。咱們繼續(xù)這個頁面說,雖然都是微信支付亲铡,但是也有不同的方式才写,我上面已經(jīng)說了我在做的這個項目是小程序葡兑,所以毫不猶豫的選擇小程序支付,其實各種支付方式大同小異赞草,尤其是小程序支付走的類型也是JSAPI讹堤,所以兩者沒什么區(qū)別。而且有個小技巧(目前是2020/7/17厨疙,反正現(xiàn)在為止小程序付款是沒有sdk的洲守,但是別的付款方式是有的,所以可以把別的付款方式中的sdk下下來這里用)沾凄。
第二步:引入sdk
當(dāng)然了岖沛,我習(xí)慣性的用maven直接引入依賴。
之前查了下資料搭独,其實能直接引用的有好幾種表現(xiàn)形式,我這里只列出我自己用的:
<dependency>
<groupId>com.github.wxpay</groupId>
<artifactId>wxpay-sdk</artifactId>
<version>0.0.3</version>
</dependency>
說一下這個包廊镜,其實是這么引用還是把sdk中的幾個工具類自己寫出來我覺得都o(jì)k吧牙肝,我也特意和sdk中的代碼對比了下,是一樣的嗤朴。
其實我看了一些網(wǎng)上的demo配椭,我更愿意理解為時間太久了(有的哪怕是新發(fā)布的,但是可能代碼年限很高了)雹姊,居然自己寫各種工具方法的占了一多半股缸,挺費(fèi)解的。所以我這里再次強(qiáng)調(diào):微信已經(jīng)有了非常好用的sdk吱雏,真沒必要自己寫敦姻!
第三步:邏輯流程
其實在有sdk的協(xié)助下,代碼的實現(xiàn)變得很簡單了歧杏。大概的步驟我簡單說一下镰惦。主要也是要對這個支付,回調(diào)等流程心里有數(shù)犬绒。
微信的文檔其實挺好的旺入,就是有點(diǎn)復(fù)雜(可能是我有時候心煩意亂只想速成,所以沒耐心一點(diǎn)點(diǎn)順著圖分析理解)凯力。這里說一下JSAPI/小程序支付的大概流程:
-
預(yù)下單(有了下單的動作茵瘾,計劃好支付多少錢了。然后微信會照著已給的信息去生成那個支付頁面咐鹤,如下圖)
我簡單說下拗秘,這個最上面的支付0.01元的頁面,就是你先告訴微信慷暂,openId是XXX(你自己)的用戶想給XXXX企業(yè)支付0.01元錢聘殖。你先把所有的信息告訴微信晨雳,微信覺得沒問題了(其中檢查你的openid,企業(yè)的情況啥的)就會生成這個彈窗頁面奸腺。
所以說預(yù)下單其實可以理解為信息的提交與校驗餐禁,沒問題了就跳到微信頁面(ps:這個頁面就不屬于前端的控制范圍了,但是微信也提供一些接口可以關(guān)閉啥的突照。不過我們沒做這個操作)帮非。
回調(diào)
剛剛已經(jīng)說了預(yù)下單之后,會彈出一個微信提供的頁面讹蘑,但是到底用戶支付沒支付要怎么判斷呢末盔?這里就用到了回調(diào)。
官網(wǎng)也有關(guān)于回調(diào)地址的詳細(xì)要求座慰,其實本質(zhì)上就是提供一個接口陨舱,微信會把支付的情況告訴你,但是這個接口必須是外網(wǎng)的版仔,必須是https的游盲,這個回調(diào)地址可以在微信設(shè)置。這兩個要求不復(fù)雜蛮粮,但是讓本地沒辦法測試了益缎。反正代碼簡單,所以仔細(xì)點(diǎn)可以一次過然想。退款
這個功能就是某單的錢到了商家的微信賬戶里莺奔,根據(jù)訂單號或者微信流水號就可以退款了。當(dāng)然也有一些限制的变泄,比如微信賬戶要有錢令哟,比如訂單不能超過一年。當(dāng)然了杖刷,因為我們這個業(yè)務(wù)需求都不會涉及這些励饵,所以直接用訂單號退款了。這個差不多就是秒退滑燃,如果有問題直接接口會報錯役听,所以更簡單了。一會兒我會附上代碼表窘。
第四步:代碼實現(xiàn)
終于到了最重要的時候了典予,代碼的實現(xiàn)。
而這個demo代碼在readme里:
然后其實照著這個來做就行。簡單說第一步:配置參數(shù):
需要的參數(shù)有證書昂验,appId捂敌,key艾扮,mchId。下面是我的配置類:
package com.dsyl.util;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.Map.Entry;
import com.github.wxpay.sdk.WXPay;
import com.github.wxpay.sdk.WXPayConfig;
public class PayConfig implements WXPayConfig{
private byte[] certData;
public PayConfig() throws Exception {
File file = new File("C:\\Program Files\\apache-tomcat-8.5.38\\cert\\apiclient_cert.p12");
InputStream certStream = new FileInputStream(file);
this.certData = new byte[(int) file.length()];
certStream.read(this.certData);
certStream.close();
}
public String getAppID() {
return "wx8888888888888";
}
public String getMchID() {
return "XXXXXXXXX";
}
public String getKey() {
return "xxxxxxxxxxxxxxxxxxxxxxxx";
}
public InputStream getCertStream() {
ByteArrayInputStream certBis = new ByteArrayInputStream(this.certData);
return certBis;
}
public int getHttpConnectTimeoutMs() {
return 8000;
}
public int getHttpReadTimeoutMs() {
return 10000;
}
}
第二步就是調(diào)用了占婉。首先說支付的調(diào)用(其實說真的泡嘴,差不多就是readme百分之八十復(fù)制粘貼):
public R pay(String openId,Integer money){
try {
String no = (openId + "-" + money + "-" + System.currentTimeMillis()+"abcdefghigklmn").substring(0,32);
PayConfig config = new PayConfig();
WXPay wxpay = new WXPay(config);
Map<String, String> data = new HashMap<String, String>();
data.put("body", "支付");
data.put("out_trade_no", no);
data.put("openid", openId);
data.put("total_fee", (money*100)+"");
data.put("spbill_create_ip", "127.1.1.1");
data.put("notify_url", "https://www.hongchunyan.com/crowd-funding/wxpay/notify");
data.put("trade_type", "JSAPI");
Map<String, String> resp = wxpay.unifiedOrder(data);
String returnCode = resp.get("return_code");
String resultCode = resp.get("result_code");
String prepay_id = resp.get("prepay_id");
if (!"SUCCESS".equals(returnCode) || !"SUCCESS".equals(resultCode) || prepay_id == null) {
return R.error().put("code", 300).put("data", resp);
}
String packages = "prepay_id=" + prepay_id;
Map<String, String> wxPayMap = new HashMap<String, String>();
wxPayMap.put("appId", config.getAppID());
wxPayMap.put("timeStamp", String.valueOf(System.currentTimeMillis()).substring(0, 10));
wxPayMap.put("nonceStr", WXPayUtil.generateNonceStr());
wxPayMap.put("package", packages);
wxPayMap.put("signType", "MD5");
// 加密串中包括 appId timeStamp nonceStr package signType 5個參數(shù), 通過sdk WXPayUtil類加密,
// 注意, 此處使用 MD5加密 方式
String sign = WXPayUtil.generateSignature(wxPayMap, config.getKey());
Map<String, Object> result = new HashMap<String, Object>();
result.put("prepay_id", prepay_id);
result.put("paySign", sign);
result.putAll(wxPayMap);
return R.ok().put("data", result);
} catch (Exception e) {
e.printStackTrace();
return R.error();
}
}
簡單的說幾點(diǎn):
- 這里的out_trade_no要求是唯一的,其實用uuid也ok逆济,我這里因為想用它存一些業(yè)務(wù)邏輯酌予,所以是自己想要存的東西的拼接(用了時間戳保證唯一性)。
- 只有當(dāng)支付方式是JSAPI(小程序支付也這么填)的時候才需要openId奖慌。不同的支付方式參數(shù)略有不同抛虫。這個稍微注意一下就行了。
- 我返回的這個R其實就是自己封裝的一個返回對象简僧,本質(zhì)就是鍵值對建椰。這個可以自由點(diǎn)。
- 這其中生成sign的方法是sdk里的岛马,網(wǎng)上好多人自己寫了广凸,我比較喜歡拿現(xiàn)成的。
差不多預(yù)下單就這樣了蛛枚。
然后上面有個notify_url就是回調(diào)的接口地址了,必須是外網(wǎng)可訪問的脸哀,必須是https的蹦浦。
下面是回調(diào)接口,他的本質(zhì)就是一個對外的接口撞蜂,所以路徑要對得上:
@RestController
@RequestMapping("/wxpay")
public class WXController {
@PostMapping("/notify")
public String wxnotify(HttpServletRequest request,HttpServletResponse response) {
String xmlBack = "";
try {
BufferedReader br = new BufferedReader(new InputStreamReader(request.getInputStream(),"UTF-8"));
String line = null;
StringBuilder sb = new StringBuilder();
while((line = br.readLine()) != null){
sb.append(line);
}
br.close();
//sb為微信返回的xml盲镶,微信sdk提供轉(zhuǎn)化成map的方法
String notityXml = sb.toString();
System.out.println("接收到的報文:" + notityXml);
Map<String, String> map = WXPayUtil.xmlToMap(notityXml);
WXPay wxpay = new WXPay(new PayConfig());
if (wxpay.isPayResultNotifySignatureValid(map)) {//驗證成功
String return_code = map.get("return_code");//狀態(tài)
String out_trade_no = map.get("out_trade_no");//訂單號
String result_code = map.get("result_code");
//其實這里驗證兩個code都是success就說明充值成功了◎蚬睿可以處理自己的業(yè)務(wù)邏輯了溉贿。
if (!"SUCCESS".equals(return_code) || !"SUCCESS".equals(result_code)) {
System.out.println("充值失敗浦旱!");
//支付失敗的邏輯
xmlBack = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[報文為空]]></return_msg>" + "</xml> ";
return xmlBack;
}
xmlBack = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>" + "<return_msg><![CDATA[SUCCESS]]></return_msg>" + "</xml> ";
return xmlBack;
} else {
System.out.println("<<<<<<<<<<<<<<<<<<<<<微信支付回調(diào)通知簽名錯誤");
xmlBack = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[報文為空]]></return_msg>" + "</xml> ";
return xmlBack;
}
} catch (Exception e) {
System.err.println(e);
System.out.println("微信支付回調(diào)通知失敗");
xmlBack = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[報文為空]]></return_msg>" + "</xml> ";
return xmlBack;
}
}
}
這個給微信的返回值是為了防止重復(fù)回調(diào)宇色。當(dāng)你給微信返回了,微信就知道關(guān)于這個訂單的回調(diào)你收到了颁湖,也就不會再給你發(fā)了(上面代碼中涉及到業(yè)務(wù)邏輯的代碼我都刪了宣蠕,因為刪刪減減的,所以不確定是不是多個括號少個括號啥的甥捺,如果報錯了檢查下代碼吧)抢蚀。
最后還有一個退款,因為當(dāng)時就有結(jié)果了镰禾,所以比較簡單皿曲,我直接附上代碼:
public static Map<String, String> refund(String transactionId,Integer money) throws Exception{
PayConfig config = new PayConfig();
WXPay wxpay = new WXPay(config);
Map<String, String> params = new HashMap<String, String>();
params.put("appid", config.getAppID());
params.put("mch_id", config.getMchID());
params.put("nonce_str", System.currentTimeMillis()+"");
params.put("transaction_id",transactionId);
params.put("out_refund_no", System.currentTimeMillis()+"");
params.put("total_fee", (money*100)+"");
params.put("refund_fee", (money*100)+"");
String sign = WXPayUtil.generateSignature(params, config.getKey());
params.put("sign", sign);
Map<String, String> refund = wxpay.refund(params);
System.out.println(refund);
return refund;
}
注意因為我這里是退全款唱逢,所以兩個fee是一樣的。還有關(guān)于錢轉(zhuǎn)化成分屋休。我第一遍測試坞古,因為一邊乘100.一邊忘記乘了,所以退款失敗了博投,會報錯金額不同绸贡。
微信退款頁介紹
如果遇到什么退款失敗可以去查查失敗的原因,還是很容易理解的毅哗。
然后至此听怕,我這兩天這塊關(guān)于微信支付的業(yè)務(wù)就寫完了,比較簡單虑绵,而且感覺對微信支付的了解也更深了一點(diǎn)尿瞭。
本篇文章就記到這里,如果稍微幫到你了記得點(diǎn)個喜歡點(diǎn)個關(guān)注翅睛,如果有什么問題也歡迎私信或者留言交流声搁。也祝大家工作順順利利!周末愉快捕发!