使用本模塊,可輕鬆實現支付寶支付、微信支付對接,從而專一於業務,無需關心第三方邏輯。前端
模塊徹底獨立,無支付寶、微信SDK依賴。git
基於Spring Boot。github
依賴Redis。web
支付寶:電腦網站支付、手機網站支付、掃碼支付、APP支付。spring
微信:電腦網站支付(同掃碼支付)、手機網站支付(微信外H5支付)、掃碼支付、APP支付、JSAPI支付(微信內H5支付)。數據庫
統一支付方法。後端
異步回調封裝。api
訂單狀態查詢。服務器
退款。微信
公對私轉帳。
請確保支付寶、微信賬號已經申請了相應業務、權限
只須要簡單的、非侵入式的配置,便可集成到項目中。
添加模塊到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
別客氣。