使用本模組,可輕鬆實現支付寶支付、微信支付對接,從而專注於業務,無需關心第三方邏輯。
模組完全獨立,無支付寶、微信SDK依賴。
基於Spring Boot。
依賴Redis。
支付寶:電腦網站支付、手機網站支付、掃碼支付、APP支付。
微信:電腦網站支付(同掃碼支付)、手機網站支付(微信外H5支付)、掃碼支付、APP支付、JSAPI支付(微信內H5支付)。
統一支付方法。
非同步回撥封裝。
訂單狀態查詢。
退款。
公對私轉賬。
請確保支付寶、微信帳號已經申請了相應業務、許可權
模組整合
只需要簡單的、非侵入式的配置,即可整合到專案中。
新增模組到Maven專案中
父專案中新增pay-spring-boot
模組依賴(pom.xml):
1 <modules> 2 ... 3 <module>pay-spring-boot</module> 4 ... 5 </modules>
修改pay-spring-boot
的父專案(pom.xml):
1 <parent> 2 <groupId>yourself parent groupId</groupId> 3 <artifactId>yourself parent artifactId</artifactId> 4 <version>yourself parent version</version> 5 </parent>
支付憑證
在application.yml
(或application-*.yml
,視專案具體情況而定)中新增如下配置:
pay: wx: appid: wx1d96c6yxxc0d192a mchid: 1519719912 key: A6nvI8Xp6A6nvI8Xp6A6nvI8Xp6 notifyURL: https://xxx.com/wxpay/notify certPath: /data/cert/wx/apiclient_cert.p12 certPassword: 1517923901 ali: appid: 2019138363328891 privateKey: MIIEuwIBADANBgkqhkiG9w... notifyURL: https://xxx.com/alipay/notify
關於配置項的具體含義參考AliPayConfig
、WxPayConfig
兩個類,裡邊有詳細說明。
注入Redis連線池
在專案中建立一個Redis連線池工廠實現類,名稱無所謂,必須實現RedisResourceFactory
介面,然後新增@ResourceFactoryComponent
註解,例如:
1 @ResourceFactoryComponent 2 public class DefaultRedisResourceFactory implements RedisResourceFactory { 3 @Override 4 public JedisPool getJedisPool() { 5 /* 6 框架不關心JedisPool是怎麼來的 7 這裡的RedisService是我自己實現的服務 8 根據專案實際情況換成你自己的實現 9 */ 10 return RedisService.getPool(); 11 } 12 }
如何支付
一般來說,專案中會有不同的支付場景,比如:購買商品、充值等支付業務。
現在用商品購買舉例,通過這個例子展示如何使用框架。
建立支付介面卡
支付介面卡是支付模組和資料訪問層的橋樑,介面卡將支付結果抽象成支付成功(doPaySuccess)、支付失敗(doPayFail)、退款成功(doRefundSuccess)、退款失敗(doRefundFail)四種情況,代表了四個狀態。
建議不同的場景使用不同的介面卡,不要所有業務邏輯都放一起。
建立訂單的操作,也建議放在介面卡中實現。
以下是商品介面卡例子:
1 @Service 2 public class GoodsTradeService extends AbstractPayAdaptor { 3 4 @Autowired 5 private GoodsTradeManager goodsTradeManager; 6 7 /** 8 * 建立訂單 9 * @param id 商品id 10 * @return 11 */ 12 public Message createOrder(long id){ 13 14 } 15 16 @Override 17 public void doPaySuccess(String outTradeNo) { 18 //支付成功,這裡一般要更新資料庫的狀態 19 } 20 21 @Override 22 public void doPayFail(String outTradeNo) { 23 //支付失敗,這裡一般要更新資料庫的狀態 24 } 25 26 @Override 27 public void doRefundSuccess(String outTradeNo) { 28 //退款成功,這裡一般要更新資料庫的狀態 29 } 30 31 @Override 32 public void doRefundFail(String outTradeNo) { 33 //退款失敗,這裡一般要更新資料庫的狀態 34 } 35 }
看起來非常簡單,繼承AbstractPayAdaptor
抽象類,然後通過@Service
註解交給Spring管理,為什麼要交給Spring呢?因為這裡你需要注入資料訪問層的例項(Dao),不然怎麼運算元據庫,只不過我這沒有寫而已~
這裡有一個GoodsTradeManager
,是接下來要介紹的支付管理器,先不管它。
仔細觀察會發現,示例中的介面卡名稱叫GoodsTradeService
,為什麼我不管他叫GoodsTradePayAdaptor
呢?從支付框架的角度看,這的確是一個介面卡實現,但從業務的角度看,它是商品訂單的服務中心,不僅要處理訂單狀態,還要承擔建立訂單的職責,它底層(資料庫層)關聯的本來就是一個訂單表,把它稱作訂單服務,更加容易理解。
因此,所謂的介面卡,就是用來適配支付框架和資料訪問層的。
建立支付管理器
有了介面卡,就有了資料訪問的能力,再配上一個管理器作為統一排程中心,那麼支付這事就搞定了,實現一個管理器非常容易:
1 @PayManagerComponent 2 public class GoodsTradeManager extends AbstractPayManager { 3 4 @Autowired 5 private GoodsTradeService goodsTradeService; 6 7 @Override 8 public String getTradeType() { 9 return "0"; 10 } 11 12 @Override 13 public AbstractPayAdaptor getPayAdaptor() { 14 return goodsTradeService; 15 } 16 }
首先繼承AbstractPayManager
,然後使用@PayManagerComponent
註解註冊管理器,這沒什麼神奇的,只不過是告訴框架這裡有一個管理器,並且把這個管理器交給Spring維護。
getTradeType
方法返回長度為1的字串,建議取值範圍[0-9a-z],型別會拼接到訂單號中,所以不建議使用特殊字元。因此,一個專案中最多可建立36個不同的管理器。
getPayAdaptor
方法返回上一步建立的介面卡,管理器中包含了介面卡。
因此,所謂的管理器,管理的目標就是介面卡,同時擔負起統一支付排程的重任,管理器是支付模組的視窗。
最佳實踐是:每對管理器-介面卡對應一種支付業務。
發起支付
接下來就可以發起支付了,非常簡單,先來看一個支付寶掃碼支付示例:
1 Trade trade = AliTrade 2 .qrcodePay() 3 .subject("商品標題") 4 .body("商品描述") 5 .outTradeNo(goodsTradeManager.newTradeNo("你自己的使用者唯一標識")) 6 .totalAmount("0.01") 7 .build(); 8 TradeToken<String> token = goodsTradeManager.qrcodePay(trade); 9 String url = token.value();
先通過AliTrade
構造器的qrcodePay
方法建立一個掃碼支付訂單,然後呼叫管理器的qrcodePay
方法生成訂單憑證,不同的支付產品的訂單憑證可能不同,你可以自由選擇泛型,掃碼支付的憑證就是一個url連結,因此我使用的String型別,呼叫憑證的value
方法,即可獲得憑證內容,憑證內容直接返回給前端(網頁或APP),前端即可調起支付。
goodsTradeManager
上一步已經建立好,直接@Autowired
注入即可。
newTradeNo
方法非常重要,它可以幫你生成一個訂單號,也就是商戶訂單號,在我的設計中,為了省去繁瑣的全域性唯一訂單號生成,將訂單號和使用者關聯起來,規避了訂單號唯一性問題,使用者唯一標識根據你的系統自由選擇,建議長度在[6-10]之間,並且為固定長度,不能使用特殊字元,使用者唯一標識會直接拼接到訂單號中,長度不固定或太長的話,訂單號會非常難看,不規範,如需更多瞭解,直接看程式碼註釋。
AliTrade
構造器所有的屬性均與支付寶官方文件相對應,具體含義參考程式碼註釋或者支付寶官方文件。
訂單的型別AliTrade.qrcodePay
和管理器方法goodsTradeManager.qrcodePay
必須配套使用。
再來看一個微信H5支付的例子:
1 Trade trade = WxTrade 2 .webMobilePay() 3 .body("商品標題") 4 .outTradeNo(goodsTradeManager.newTradeNo("你自己的使用者唯一標識")) 5 .totalFee("1") 6 .spbillCreateIp("127.0.0.1") 7 .sceneInfo("商品測試場景") 8 .build(); 9 TradeToken<String> token = goodsTradeManager.webMobilePay(trade); 10 String url = token.value();
只不過是把AliTrade
換成了WxTrade
,然後呼叫WxTrade.webMobilePay
構造器,加上配套的goodsTradeManager.webMobilePay
即可完成微信H5支付。
微信H5支付的憑證也是一個url,直接交給前端處理即可。
由此可以看出,我們只需要關心訂單構造器,將訂單構造好,直接呼叫管理器對應的方法即可,管理器不關心支付寶還是微信,只需要接收一個配套的訂單,最後拿到訂單憑證,就算是完工了。
依此類推,即可完成其它型別的支付業務。
非同步回撥
涉及錢的事沒有小事,別忘了還有支付結果非同步回撥。
前端的支付結果回撥是同步回撥,僅供參考,必須以後端的結果為準。
支付寶回撥:
1 @PostMapping(value = "/notify") 2 public void notify(HttpServletRequest request, HttpServletResponse response){ 3 /* 4 解析請求引數 5 */ 6 Map<String, String> params = NoticeManagers.getDefaultManager().receiveAliParams(request); 7 8 /* 9 封裝 10 */ 11 AliPayNoticeInfo info = new AliPayNoticeInfo(); 12 TradeStatus status = NoticeManagers.getDefaultManager().execute(params, info); 13 14 /* 15 持久化回撥資料 16 */ 17 //TODO: 強烈建議將AliPayNoticeInfo持久化到資料庫中,以備不時之需,當然你也可以忽略 18 19 /* 20 業務分發 21 */ 22 AbstractPayManager payManager = (AbstractPayManager) PayManagers.find(status.getTradeNo()); 23 payManager.doTradeStatus(status); 24 25 /* 26 響應 27 */ 28 NoticeManagers.getDefaultManager().sendAliResponse(response); 29 }
微信回撥:
1 @PostMapping(value = "/notify") 2 public void notify(HttpServletRequest request, HttpServletResponse response){ 3 /* 4 解析請求引數 5 */ 6 Map<String, String> params = NoticeManagers.getDefaultManager().receiveWxParams(request); 7 8 /* 9 封裝 10 */ 11 WxPayNoticeInfo info = new WxPayNoticeInfo(); 12 TradeStatus status = NoticeManagers.getDefaultManager().execute(params, info); 13 14 /* 15 持久化回撥資料 16 */ 17 //TODO: 強烈建議將WxPayNoticeInfo持久化到資料庫中,以備不時之需,當然你也可以忽略 18 19 /* 20 業務分發 21 */ 22 AbstractPayManager payManager = (AbstractPayManager) PayManagers.find(status.getTradeNo()); 23 payManager.doTradeStatus(status); 24 25 /* 26 響應 27 */ 28 NoticeManagers.getDefaultManager().sendWxResponse(response); 29 }
最基本的Spring MVC Controller程式碼不用我教了吧。
定義一個控制器,接收HTTP請求、響應物件,通過框架解析出引數和訂單狀態,然後將訂單狀態分發給介面卡,實現訂單狀態更新,最後給支付寶、微信一個響應,告訴他們已經接收到請求。
回撥處理非常規範化,基本不需要做什麼改動(直接Copy),唯一需要做的,也是非常重要的,就是根據你自己專案的實際情況,以恰當的方式持久化回撥資料。
這裡的@PostMapping
請求路徑,就是配置在application.yml
中的notifyURL
,必須保證公網可以無障礙訪問。
主動同步訂單狀態
用來彌補特殊原因造成的非同步回撥丟失,非同步回撥不是100%可靠的。
由於需要請求支付寶、微信伺服器,所以速度較慢。
支付寶訂單:
1 Trade trade = AliTrade.query().outTradeNo("商戶訂單號").build(); 2 TradeStatus status = goodsTradeManager.status(trade); 3 status.isPaySuccess(); //是否支付成功,其它狀態不一一列舉,自行看程式碼
微信訂單:
1 Trade trade = WxTrade.basic().outTradeNo("商戶訂單號").build(); 2 TradeStatus status = goodsTradeManager.status(trade); 3 status.isPaySuccess(); //是否支付成功,其它狀態不一一列舉,自行看程式碼
如何轉賬
公對私轉賬
由公司帳號向個人帳號轉賬。
支付寶轉賬:
1 /* 2 構造轉賬訂單 3 */ 4 AliTransferTrade transferTrade = AliTransferTrade 5 .transfer() 6 .outBizNo("商戶轉賬唯一訂單號") 7 .payeeAccount("收款人支付寶帳號") 8 .amount("0.01") 9 .build(); 10 11 /* 12 轉賬 13 */ 14 try{ 15 AliTransfer.getInstance().transfer(transferTrade); 16 }catch (TransferException e){ 17 // 轉賬失敗處理邏輯... 18 }
轉賬方法無返回值,不發生異常代表轉賬成功,發生異常代表轉賬失敗,自行處理。
訂單引數含義參考支付寶官方文件或程式碼註釋。
微信轉賬:
1 /* 2 構造轉賬訂單 3 */ 4 WxTransferTrade transferTrade = WxTransferTrade 5 .transfer() 6 .partnerTradeNo("商戶轉賬唯一訂單號") 7 .openid("收款人openid") 8 .amount("1") 9 .spbillCreateIp("127.0.0.1") //這裡是呼叫介面的伺服器公網IP,自行獲取 10 .build(); 11 /* 12 轉賬 13 */ 14 try{ 15 WxTransfer.getInstance().transfer(transferTrade); 16 }catch (TransferException e){ 17 // 轉賬失敗處理邏輯... 18 }
轉賬方法無返回值,不發生異常代表轉賬成功,發生異常代表轉賬失敗,自行處理。
訂單引數含義參考微信官方文件或程式碼註釋。
轉賬狀態查詢
支付寶:
1 /* 2 構造轉賬查詢訂單 3 */ 4 AliTransferTrade transferTrade = AliTransferTrade 5 .query() 6 .outBizNo("商戶轉賬唯一訂單號") 7 .build(); 8 /* 9 轉賬查詢 10 */ 11 TransferStatus status = AliTransfer.getInstance().status(transferTrade);; 12 status.isSuccess(); //轉賬成功,其他狀態自行檢視程式碼,不一一列舉
微信:
1 /* 2 構造轉賬查詢訂單 3 */ 4 WxTransferTrade transferTrade = WxTransferTrade 5 .custom() 6 .partnerTradeNo("商戶轉賬唯一訂單號") 7 .build(); 8 /* 9 轉賬查詢 10 */ 11 TransferStatus status = WxTransfer.getInstance().status(transferTrade);; 12 status.isSuccess(); //轉賬成功,其他狀態自行檢視程式碼,不一一列舉
附加工具
獲取客戶端IP地址
微信支付大部分場景需要客戶端IP地址,可以通過本模組PayHttpUtil.getRealClientIp
方法獲取。
如果獲取不到,請檢查代理軟體是否正確設定了X-Forwarded-For
。
其他
如有疑問,歡迎積極反饋,直接提Issues
別客氣。