龍果支付系統的代碼下載地址碼雲/Roncoo,雖然官網上說的功能很炫酷,但實際上其實我覺的仍是挺酷的,只是功能沒有他們說的那麼全。目前我只瀏覽了一下支付業務,系統中看到了支付寶和微信的掃碼支付和刷卡支付、微信的小程序支付,測試了微信的掃碼和刷卡支付。css
我對這個系統的瞭解
如今有以下幾個角色:
平臺:龍果支付系統,
商戶:使用龍果支付系統的用戶,好比某公司的商城系統使用該系統,商戶就是某公司
用戶:使用商戶系統的用戶html
當前龍果支付系統實現的功能:java
- 商戶使用平臺,用戶瀏覽商戶商品購買,向商戶的第三方帳戶(微信、支付寶)付款,
- 商戶使用掃描設備獲取用戶付款碼,調用平臺支付,商戶第三方帳號向用戶收款,
- 平臺的流水記錄與第三方(微信、支付寶)帳單進行對帳,帳單對應不上的放入差錯池
一些不全的功能:mysql
- 結算,將商戶在平臺的帳戶餘額提到商戶的銀行卡中,這裏沒有這項功能,只是將平臺中帳戶的操做歷史(加款、減款)的金額彙總,得出可結算的餘額。
- 微信H5支付,沒有這段代碼。
- 微信小程序支付,平臺中有小程序支付代碼,沒有調用案例,從微信開發文檔中看,好像是須要小程序的appid,我沒有測試。
- 能夠對帳,雖然 spring 配置有定時任務,可是不能定時啓動對帳,由於程序入口運行一次就結束了。
- 結算,只能帳戶金額彙總,一樣不能定時結算,須要本身改。
項目分析與部署
能夠先參考這兩個教程:git
分析
根據第一個教程中能夠了解到系統所使用的技術,我只看了龍果支付系統的支付業務,我就說一下我在支付業務中使用的技術:web
- maven + eclipse,要了解 maven 的聚合、繼承、依賴、插件,雖然個人 maven 很渣,通常應用沒有問題
- spring + mybatis,系統中 mybatis 的用法跟我學的不太同樣,可是差很少能理解
- activemq,消息中間件,沒有學過,能夠花兩三個小時入門,我作了這個 ActiveMQ 筆記
- ngrok,內網穿透,將本地 web 應用發佈到外網上,能夠本身搭建外網穿透,可是須要雲服務器,我用騰訊雲的學生優惠
- mysql、tomcat、微信和支付寶接入開發文檔
這個項目使用的 jdk7,雖然 maven 項目,我以前用 maven 的 tomcat 插件運行不起來,這裏用的 eclipse 配置的本地 tomcat 容器運行,後來主要研究支付業務就沒看 怎麼用 tomcat 插件運行支付系統。spring
瞭解幾個概念:sql
- 長款短款:實際收到的錢比應該收到的錢可能是長款,反之短款。
- 微信裏的掃碼支付就是支付寶的即時到帳,都是用戶拿着手機掃二維碼付款;微信裏的刷卡支付就是支付寶的條碼支付,都是商家用掃碼條形碼的機器掃描用戶手機上的付款碼
項目結構功能
|-- roncoo-pay //龍果支付系統,父工程,管理jar包依賴的版本
|
|-- roncoo-pay-common-core //整個項目用到的工具類枚舉類等公共類資源和依賴的公共jar包
|
|-- roncoo-pay-service //支付系統的核心業務工程,依賴common-core工程
|
|-- roncoo-pay-web-gateway //給商戶提供能夠請求的支付接口,依賴service和core工程
|
|-- roncoo-pay-web-boss //支付系統管理員用的管理後臺,依賴service和core工程
|
|-- roncoo-pay-web-merchant //商家用的管理後臺,依賴service和core工程
|
|-- roncoo-pay-app-reconciliation //對帳應用工程,依賴service和core工程
|
|-- roncoo-pay-app-settlement //結算應用工程,依賴service和core工程
|
|-- roncoo-pay-app-notify //將交易結果通知商戶系統的工程,依賴service和core工程
|
|-- roncoo-pay-app-order-polling //第三方交易結果查詢,更新本地數據庫,依賴service和core工程
|-- roncoo-pay-web-sample-shop //模擬商戶請求gateway支付接口的示例
支付平臺接入簡介
接入步驟:數據庫
- 須要 roncoo-pay-web-boss 啓動,用戶名密碼:admin/123456,管理員給支付平臺,添加支付產品,這裏添加一個編碼爲 ALLPAY,描述:全部支付,給ALLPAY添加支付方式,我這裏添加了微信的掃碼支付、刷卡支付、小程序支付,支付寶的即時到帳、條碼支付。
- 建立一個商戶,boss 會給商戶建立一系列帳號什麼的,就是資金帳戶,而後給這個商戶設置支付配置,選擇剛纔的 ALLPAY,這樣商戶就可使用微信和支付寶的那幾種支付,還要設置商戶的收款渠道,商戶收款款就是:商戶使用平臺微信支付,平臺向微信請求,請求時須要微信帳戶的 appid 什麼的微信配置,商戶收款這時候就是獲取的商戶本身的微信配置,這樣用戶付款直接打入商戶;平臺收款就是使用平臺配置的微信配置,用戶付款打入平臺,平臺中商戶的帳號餘額增長,可是不能提現到商戶的銀行卡,由於系統沒有實現。。。
- 修改相關配置,啓動 activemq,roncoo-pay-app-order-polling、roncoo-pay-app-notify、roncoo-pay-web-gateway、roncoo-pay-web-sample-shop,這些均可以在本地運行,可是 roncoo-pay-web-gateway 須要內網穿透發佈到外網,又或者都發布到外網服務器。
- 正常狀況下點擊打開 shop,點擊微信掃碼支付或微信刷卡支付是能夠的,後面詳細介紹。
項目部署
- git 下載源碼,導入 eclipse,和視頻教程是同樣的,這裏用到 maven 知識,上面提的知識不懂的,就看成瀏覽小學做文就行,不要認真看。
- 本地倉庫安裝支付寶的 jar 包,這裏是個人推測,由於他那個教程有段時間了,如今支付寶官方接入文檔中有支付 jar 包的 maven 依賴:因此我以爲應該只要把那個添加到 pom 文件中就行,因此不少地方須要看第三方支付的接口文檔進行了解,我這裏只測試微信,就不作那個處理了。
- 有紅叉 maven update project,若是 pom 文件這樣的錯:「Artifact has not been packaged yet」 就能夠不用管他了,能夠看這裏
- 項目沒有問題,本地測試就不用 maven 安裝了,eclipse 配置一下本地的 tomcat,將 boss 應用放入 tomcat 運行,通常頁面能瀏覽 boss,那種以獨立 jar 方式運行的,就是打包成jar,而後用 java -jar 運行,固然也能夠在 eclipse 中找到 main 入口,直接在 eclipse 中 run 運行。下面時支付寶接入文檔中提到的 maven:
支付流程代碼分析
這裏以微信的掃碼支付爲例進行分析:小程序
準備工做
- 首先在 boss 系統中設置好支付產品,我這裏用以前那個 ALLPAY,裏面包含了微信的掃碼支付和刷卡支付方式,產品上線
- 給商戶選擇支付產品 ALLPAY,選擇收款渠道,若是是商家收款,須要商戶添加本身的微信配置,在微信支付開發平臺獲取的appid、商戶號、密鑰等;平臺收款修改 service 配置文件中 weixinpay_config.properties,若是支付產品有支付寶的支付方式須要配置支付寶,這裏只測微信,這些配置在哪獲取?爲何用這些配置,就須要你瞭解微信支付接入開發文檔
-
修改相關配置。
- weixinpay_config.properties:①notify_url,微信交易結束後,微信服務器通知交易結果,這個就是微信請求的 url,默認的是請求 gateway 工程中 ScanPayNotifyController中的 notify 方法,因此須要將 gateway 工程放到外網上,並修改 notify_url,可讓微信訪問到。這裏用內網穿透使微信能夠訪問gateway。②order_query_url,向平臺查詢交易訂單狀態的地址,修改服務器地址就行,我這裏是本地 tomcat,就修改那個 localhost:8080 就能夠。③bill_type,微信帳單下載的類型,這裏 SUCCESS 就行,下載成功交易的微信帳單,用來對帳。
- reconciliation_config.properties:dir,微信交易帳單下載保存在本地的位置。
- mq_config.properties:ActiveMQ 的 url,用戶名、密碼,我用本地的 activemq,默認沒有用戶名密碼,能夠空着
- pay_config.properties:shop 工程,測試接口的案例,須要修改剛纔申請的商戶帳號的 payKey、paySecret,這些能夠在 boss 管理後臺獲取,或者商戶登錄商戶後臺 merchant,查看本身的信息。修改掃碼支付、條碼支付請求地址:只須要在8080後面添加上 gateway 工程名就行,如:scanPayUrl=http://localhost:8080/roncoo-pay-web-gateway/scanPay/initPay,後臺通知結果 url 就不用改了,由於沒有寫相應的控制類,前臺頁面跳轉通知要改:returnUrl=http://localhost:8080/roncoo-pay-web-sample-shop,就是支付成功後跳轉的頁面。
啓動
前面準備工做都作好了,這裏開始在本地運行測試
- 啓動 ActiveMQ
- 啓動 roncoo-pay-app-notify 會加載數據庫全部沒有通知完商戶的數據,而後放入線程池繼續通知,通知完線程池就開始等待,監聽 tradeQueueName.notify=tradeNotify 的通知,該通知由 gateway 工程發出,用來通知商戶後臺系統交易結果。
- 啓動 roncoo-pay-app-order-polling,會啓動線程池,監聽 orderQueryQueueName.query=orderQuery 的通知,該通知由 gateway 工程發出,用來向第三方查詢交易結果,這裏就是向微信查詢交易結果,更改平臺數據庫交易結果。
-
啓動 roncoo-pay-web-gateway,支付網關,給商戶提供支付調用的接口,有三個關鍵控制類:
- ScanPayController:微信掃碼支付和支付寶即時到帳的控制類,發送支付請求,該類請求第三方獲取二維碼,返回二維碼頁面給客戶端
- F2FPayController:微信刷卡支付和支付寶條碼支付的控制類,商戶掃描設備掃描用戶付款碼,發起收款請求,該類向第三方發起收款請求,返回支付結果。
- ScanPayNotifyController:後臺通知類,第三方交易結果如微信交易結果發送通知,請求的就是該類,該類收到請求,向商戶發送後臺通知,更新平臺交易結果,響應微信服務器,微信服務器中止通知。
- 啓動 ngrok 內網穿透,將 gateway 發佈到公網。
- 啓動 shop 商城測試工程,測試 gateway 支付接口的案例。
微信掃碼支付測試分析
--> 打開 shop 頁面
點擊左邊第一個微信支付,shop 工程會發送一個表單請求:
上面代碼的意思就是將參數簽名,而後拼接出一個 form 表單,和表單提交 js 代碼,就是 buildRequest 字符串,而後返回 toPay.jsp ,這個頁面會加載這段代碼,而後向 gateway 提交支付請求,這些不是重點,重點在於支付請求發送的參數和 url 地址,這裏請求要用 utf-8 ,否則亂碼,驗證簽名會失敗。本身花點時間整理請求參數,重點是後面的分析。
--> gateway 工程的請求處理
掃碼請求處理類 ScanPayController 的 initPay 方法響應請求,處理請求參數,獲取用戶支付配置,根據配置判斷是否校驗請求 ip,而後校驗簽名,根據商戶參數中是否有 payWayCode(支付渠道:微信、支付寶)判斷,
- 若是沒有,返回給消費者頁面,然消費者選擇,這個就是 shop 中的網關支付,這時候 先生成交易訂單,消費者選擇後,生成交易流水記錄,向第三方發起預支付請求,返回預付款二維碼
- 若是有,調用 RpTradePaymentManagerServiceImpl 交易訂單管理服務類的 initDirectScanPay 方法,這個類是交易處理的核心類,咱們發起的微信掃碼支付,這裏確定就是 payWayCode=WEIXIN,而後根據值選擇返回相應的二維碼頁面。
--> rpTradePaymentManagerService.initDirectScanPay 方法內部處理
- 獲取用戶支付配置
RpUserPayConfig rpUserPayConfig = rpUserPayConfigService.getByPayKey(payKey);
if (rpUserPayConfig == null) {
throw new UserBizException(UserBizException.USER_PAY_CONFIG_ERRPR, "用戶支付配置有誤");
}
- 根據用戶支付配置中選擇的支付產品和參數中的 payWayCode 支付渠道,獲取支付方式,這裏是微信掃碼方式
if (PayWayEnum.WEIXIN.name().equals(payWayCode)) {
//條件查詢支付方式記錄
payWay = rpPayWayService.getByPayWayTypeCode(rpUserPayConfig.getProductCode(), payWayCode, PayTypeEnum.SCANPAY.name());
payType = PayTypeEnum.SCANPAY;//掃碼支付
} else if (PayWayEnum.ALIPAY.name().equals(payWayCode)) {
payWay = rpPayWayService.getByPayWayTypeCode(rpUserPayConfig.getProductCode(), payWayCode, PayTypeEnum.DIRECT_PAY.name());
payType = PayTypeEnum.DIRECT_PAY;
}
if (payWay == null) {
throw new UserBizException(UserBizException.USER_PAY_CONFIG_ERRPR, "用戶支付配置有誤");
}
- 根據商戶信息和訂單號獲取交易訂單記錄,若是沒有就建立交易記錄插入到數據庫,若是有且未支付就更新訂單金額:
String merchantNo = rpUserPayConfig.getUserNo();// 商戶編號
RpUserInfo rpUserInfo = rpUserInfoService.getDataByMerchentNo(merchantNo);
if (rpUserInfo == null) {
throw new UserBizException(UserBizException.USER_IS_NULL, "用戶不存在");
}
RpTradePaymentOrder rpTradePaymentOrder = rpTradePaymentOrderDao.selectByMerchantNoAndMerchantOrderNo(merchantNo, orderNo);
if (rpTradePaymentOrder == null) {
rpTradePaymentOrder = sealRpTradePaymentOrder(merchantNo, rpUserInfo.getUserName(), productName, orderNo, orderDate, orderTime, orderPrice, payWayCode, PayWayEnum.getEnum(payWayCode).getDesc(), payType, rpUserPayConfig.getFundIntoType(), orderIp, orderPeriod, returnUrl, notifyUrl, remark, field1, field2, field3, field4, field5);
rpTradePaymentOrderDao.insert(rpTradePaymentOrder);
} else {
if (TradeStatusEnum.SUCCESS.name().equals(rpTradePaymentOrder.getStatus())) {
throw new TradeBizException(TradeBizException.TRADE_ORDER_ERROR, "訂單已支付成功,無需重複支付");
}
if (rpTradePaymentOrder.getOrderAmount().compareTo(orderPrice) != 0) {
rpTradePaymentOrder.setOrderAmount(orderPrice);// 若是金額不一致,修改金額爲最新的金額
}
}
-
執行 getScanPayResultVo 方法,該方法主要生成交易流水記錄,根據支付渠道發起預支付請求,獲取二維碼,發送訂單通知,返回預支付請求結果。過程以下:
- 獲取支付渠道,更新交易訂單的支付方式,主要是網關支付的時候沒有肯定支付方式,這裏能夠更新支付方式:
ScanPayResultVo scanPayResultVo = new ScanPayResultVo(); String payWayCode = payWay.getPayWayCode();// 支付方式 PayTypeEnum payType = null; if (PayWayEnum.WEIXIN.name().equals(payWay.getPayWayCode())) { payType = PayTypeEnum.SCANPAY; } else if (PayWayEnum.ALIPAY.name().equals(payWay.getPayWayCode())) { payType = PayTypeEnum.DIRECT_PAY; } //這邊更新交易訂單支付方式的緣由就是網關支付的時候是先生成訂單,在提供用戶選擇支付方式,這時候要更新支付方式 rpTradePaymentOrder.setPayTypeCode(payType.name()); rpTradePaymentOrder.setPayTypeName(payType.getDesc()); rpTradePaymentOrder.setPayWayCode(payWay.getPayWayCode()); rpTradePaymentOrder.setPayWayName(payWay.getPayWayName()); rpTradePaymentOrderDao.update(rpTradePaymentOrder);
-
- 生成交易流水記錄:
RpTradePaymentRecord rpTradePaymentRecord = sealRpTradePaymentRecord(rpTradePaymentOrder.getMerchantNo(), rpTradePaymentOrder.getMerchantName(), rpTradePaymentOrder.getProductName(), rpTradePaymentOrder.getMerchantOrderNo(), rpTradePaymentOrder.getOrderAmount(), payWay.getPayWayCode(), payWay.getPayWayName(), payType, rpTradePaymentOrder.getFundIntoType(), BigDecimal.valueOf(payWay.getPayRate()), rpTradePaymentOrder.getOrderIp(), rpTradePaymentOrder.getReturnUrl(), rpTradePaymentOrder.getNotifyUrl(), rpTradePaymentOrder.getRemark(), rpTradePaymentOrder.getField1(), rpTradePaymentOrder.getField2(), rpTradePaymentOrder.getField3(), rpTradePaymentOrder.getField4(), rpTradePaymentOrder.getField5());
rpTradePaymentRecordDao.insert(rpTradePaymentRecord);
-
- 根據 payWayCode 這裏是 WEIXIN ,微信支付,根據資金流入方向,這裏商家收款,獲取商家本身在微信的配置信息,而後封裝成微信預支付 xml 請求字符串,其中包含從 service 中獲取的 notify_url,就是微信交易結果通知的 url,交易完成後,微信服務器會向這個 url 發送請求,後面再說,這裏向微信支付發送 post 請求,微信支付服務器響應返回預支付信息,驗證返回簽名,正確就將預支付信息封裝到返回實體類:
String appid = "";
String mch_id = "";
String partnerKey = "";
if (FundInfoTypeEnum.MERCHANT_RECEIVES.name().equals(rpTradePaymentOrder.getFundIntoType())) {// 商戶收款
// 根據資金流向獲取微信的配置信息
RpUserPayInfo rpUserPayInfo = rpUserPayInfoService.getByUserNo(rpTradePaymentOrder.getMerchantNo(), payWayCode);
appid = rpUserPayInfo.getAppId();
mch_id = rpUserPayInfo.getMerchantId();
partnerKey = rpUserPayInfo.getPartnerKey();
} else if (FundInfoTypeEnum.PLAT_RECEIVES.name().equals(rpTradePaymentOrder.getFundIntoType())) {// 平臺收款
//這裏是平臺收款,獲取平臺 service 工程中 weixinpay_config.properties 中的微信配置
appid = WeixinConfigUtil.readConfig("appId");
mch_id = WeixinConfigUtil.readConfig("mch_id");
partnerKey = WeixinConfigUtil.readConfig("partnerKey");
}
WeiXinPrePay weiXinPrePay = sealWeixinPerPay(appid, mch_id, rpTradePaymentOrder.getProductName(), rpTradePaymentOrder.getRemark(), rpTradePaymentRecord.getBankOrderNo(), rpTradePaymentOrder.getOrderAmount(), rpTradePaymentOrder.getOrderTime(), rpTradePaymentOrder.getOrderPeriod(), WeiXinTradeTypeEnum.NATIVE, rpTradePaymentRecord.getBankOrderNo(), "", rpTradePaymentOrder.getOrderIp());
String prePayXml = WeiXinPayUtils.getPrePayXml(weiXinPrePay, partnerKey);
LOG.info("掃碼支付,微信請求報文:{}", prePayXml);
// 調用微信支付的功能,獲取微信支付code_url
Map<String, Object> prePayRequest = WeiXinPayUtils.httpXmlRequest(WeixinConfigUtil.readConfig("prepay_url"), "POST", prePayXml);
LOG.info("掃碼支付,微信返回報文:{}", prePayRequest.toString());
if (WeixinTradeStateEnum.SUCCESS.name().equals(prePayRequest.get("return_code")) && WeixinTradeStateEnum.SUCCESS.name().equals(prePayRequest.get("result_code"))) {
String weiXinPrePaySign = WeiXinPayUtils.geWeiXintPrePaySign(appid, mch_id, weiXinPrePay.getDeviceInfo(), WeiXinTradeTypeEnum.NATIVE.name(), prePayRequest, partnerKey);
String codeUrl = String.valueOf(prePayRequest.get("code_url"));
LOG.info("預支付生成成功,{}", codeUrl);
if (prePayRequest.get("sign").equals(weiXinPrePaySign)) {//驗證簽名
rpTradePaymentRecord.setBankReturnMsg(prePayRequest.toString());
rpTradePaymentRecordDao.update(rpTradePaymentRecord);
scanPayResultVo.setCodeUrl(codeUrl);// 用於生成二維碼
scanPayResultVo.setPayWayCode(PayWayEnum.WEIXIN.name());
scanPayResultVo.setProductName(rpTradePaymentOrder.getProductName());
scanPayResultVo.setOrderAmount(rpTradePaymentOrder.getOrderAmount());
} else {
throw new TradeBizException(TradeBizException.TRADE_WEIXIN_ERROR, "微信返回結果簽名異常");
}
} else {
throw new TradeBizException(TradeBizException.TRADE_WEIXIN_ERROR, "請求微信異常");
}
-
- 發送通知,交易生成,由 roncoo-pay-app-order-polling 監聽,polling 收到通知,放入線程池,執行,先查詢交易流水狀態,若是是 WAITING_PAY,流水記錄建立時的狀態,等待支付,就會向微信服務器發起交易查詢,若是交易成功,更新本地交易記錄和流水記錄的狀態爲成功,若是微信服務器查詢失敗,沒有超過最大通知次數,則放入線程池繼續通知,就是繼續向微信服務器查詢交易狀態,具體細節忘了,大概是這樣,下面代碼是通知 order-polling
rpNotifyService.orderSend(rpTradePaymentRecord.getBankOrderNo());
- 返回微信掃碼頁面,生成二維碼,同時頁面不斷向支付系統發送訂單結果查詢,一旦交易訂單狀態爲成功,頁面就會跳轉
--> 微信結果通知
微信服務器通知支付系統,gateway 工程的 ScanPayNotifyController 控制類收到通知並響應,處理方法是 notify,做用是,解析請求,驗證簽名更新系統內訂單狀態,發送商家通知,notify 收到通知,向商家通知,通知頻率爲 60/120/300/900,商家須要回覆 success 字符串,來終止通知。
- 解析返回通知結果,將通知參數放入 map 中
//解析返回通知結果
Map<String , String> notifyMap = new HashMap<String , String >();
if (PayWayEnum.WEIXIN.name().equals(payWayCode)){
InputStream inputStream = httpServletRequest.getInputStream();// 從request中取得輸入流
notifyMap = WeiXinPayUtils.parseXml(inputStream);
}else if (PayWayEnum.ALIPAY.name().equals(payWayCode)){
Map<String, String[]> requestParams = httpServletRequest.getParameterMap();
notifyMap = AliPayUtil.parseNotifyMsg(requestParams);
}
- completeScanPay 用來驗證通知簽名更新數據庫,發送商家通知,加款,獲取響應第三方的字符串,支付寶是返回 success 或 fail
String completeWeiXinScanPay = rpTradePaymentManagerService.completeScanPay(payWayCode ,notifyMap);
- 響應第三方服務器
if (!StringUtil.isEmpty(completeWeiXinScanPay)){
if (PayWayEnum.WEIXIN.name().equals(payWayCode)){
httpServletResponse.setContentType("text/xml");
}
httpServletResponse.getWriter().print(completeWeiXinScanPay);
}
-
completeScanPay 裏面的內容:
- 根據渠道驗證簽名
-
簽名正確,根據交易結果選擇執行成功或失敗的方法,這兩個方法內部都是
- 更新數據庫交易狀態
- 拼接通知參數,給 activemq 發送通知 tradeNotify。
- 若是是交易成功並且是平臺收款,那就對商家帳戶進行加款
- 根據渠道拼湊第三方服務器響應數據。
--> roncoo-pay-app-notify,收到 tradeNotify 通知,向商家發起通知,代碼不分析了。
對帳分析
roncoo-pay-app-reconciliation 是對帳工程,對帳就是,拿微信來講,將微信服務器上下載的微信支付交易的帳單數據與平臺系統的交易訂單進行比對,看看有沒有交易錯誤的信息,系統會將錯誤信息放入差錯池,就是一個差錯數據表,當前系統是不支持定時自動進行對帳,可是他在 spring 配置中設置了自動計時任務。
--> 執行對帳任務
正常狀況下,spring 執行定時任務,運行 ReconciliationTask 類的 main 方法,可是計時任務不能啓動,因此想體驗的就直接運行 main 方法能夠,這裏定時任務 10 點 15 分觸發的緣由是,微信會在早上 9 點,對昨天的交易進行整理,產生帳單,微信推薦的是在 10 點之後在下載帳單,這裏運行 main 方法。
--> 遍歷對帳接口,完成對帳
- 獲取當前對帳的接口信息和對帳日期,這裏寫了微信和支付寶兩個接口信息,因此只能實現微信和支付的對帳,這裏以微信對帳爲例
// 判斷接口是否正確
ReconciliationInterface reconciliationInter = (ReconciliationInterface) reconciliationInterList.get(num);
if (reconciliationInter == null) {
LOG.info("對帳接口信息" + reconciliationInter + "爲空");
continue;
}
// 獲取須要對帳的對帳單時間(當前時間減去微信配置中的對帳週期1,即前天的日期,緣由爲微信在每日9點生成前天的帳單,建議商戶在10點後下載前天帳單)
Date billDate = DateUtil.addDay(new Date(), -reconciliationInter.getBillDay());
// 獲取對帳渠道
String interfaceCode = reconciliationInter.getInterfaceCode();
- 查詢對帳記錄,根據當前的對帳日期和對帳接口渠道查詢狀態不爲錯誤和失敗的對帳記錄,不存在則沒有對過賬,建立對帳記錄:
/** step1:判斷是否對過帳 **/
RpAccountCheckBatch batch = new RpAccountCheckBatch();
Boolean checked = validateBiz.isChecked(interfaceCode, billDate);
if (checked) {
LOG.info("帳單日[" + sdf.format(billDate) + "],支付方式[" + interfaceCode + "],已經對過帳,不能再次發起自動對帳。");
continue;
}
// 建立對帳記錄
batch.setCreater("reconciliationSystem");
batch.setCreateTime(new Date());
batch.setBillDate(billDate);
batch.setBatchNo(buildNoService.buildReconciliationNo());
batch.setBankType(interfaceCode);
- 根據對帳接口渠道執行相應文件下載類的帳單下載方法,下載對應的帳單,好比執行 WeiXinFileDown 的 fileDown 方法,下載
/** step2:對帳文件下載 **/
File file = null;
try {
LOG.info("ReconciliationFileDownBiz,對帳文件下載開始");//在微信配置文件中規定下載微信支付成功的帳單
file = fileDownBiz.downReconciliationFile(interfaceCode, billDate);
if (file == null) {
continue;
}
LOG.info("對帳文件下載結束");
} catch (Exception e) {
LOG.error("對帳文件下載異常:", e);
batch.setStatus(BatchStatusEnum.FAIL.name());
batch.setRemark("對帳文件下載異常");
batchService.saveData(batch);
continue;
}
- 根據不一樣的對帳接口的渠道,解析不一樣的對帳文件,包裝成帳單列表 list:
/** step3:解析對帳文件 **/
List<ReconciliationEntityVo> bankList = null;
try {
LOG.info("=ReconciliationFileParserBiz=>對帳文件解析開始>>>");
// 解析文件,微信在這裏解析的是成功支付的帳單
bankList = parserBiz.parser(batch, file, billDate, interfaceCode);
// 若是下載文件異常,退出
if (BatchStatusEnum.ERROR.name().equals(batch.getStatus())) {
continue;
}
LOG.info("對帳文件解析結束");
} catch (Exception e) {
LOG.error("對帳文件解析異常:", e);
batch.setStatus(BatchStatusEnum.FAIL.name());
batch.setRemark("對帳文件解析異常");
batchService.saveData(batch);
continue;
}
- 執行 check 對帳,check 是對帳的核心代碼
/** step4:對帳流程 **/
try {
checkBiz.check(bankList, interfaceCode, batch);
} catch (Exception e) {
LOG.error("對帳異常:", e);
batch.setStatus(BatchStatusEnum.FAIL.name());
batch.setRemark("對帳異常");
batchService.saveData(batch);
continue;
}
-
check 內部代碼:
- 先檢查帳單列表是否爲空,空的話就結束對帳,不爲空查詢平臺系統中當前對帳日期的當前渠道的全部交易記錄和全部交易成功的記錄,初始化差錯列表:
// 判斷bankList是否爲空
if (bankList == null) {
bankList = new ArrayList<ReconciliationEntityVo>();
}
// 查詢平臺bill_date,interfaceCode成功的交易
List<RpTradePaymentRecord> platSucessDateList = reconciliationDataGetBiz.getSuccessPlatformDateByBillDate(batch.getBillDate(), interfaceCode);
// 查詢平臺bill_date,interfaceCode全部的交易
List<RpTradePaymentRecord> platAllDateList = reconciliationDataGetBiz.getAllPlatformDateByBillDate(batch.getBillDate(), interfaceCode);
// 查詢平臺緩衝池中全部的數據
List<RpAccountCheckMistakeScratchPool> platScreatchRecordList = rpAccountCheckMistakeScratchPoolService.listScratchPoolRecord(null);
// 差錯list
/** 第三方成功交易帳單中在平臺交易記錄中不存在,平臺漏單,將交易記錄放入差錯池 **/
List<RpAccountCheckMistake> mistakeList = new ArrayList<RpAccountCheckMistake>();
// 須要放入緩衝池中平臺長款list
/** 平臺成功交易記錄中沒有匹配到第三方交易成功的記錄,須要放入差錯緩衝池 **/
List<RpAccountCheckMistakeScratchPool> insertScreatchRecordList = new ArrayList<RpAccountCheckMistakeScratchPool>();
// 須要從緩衝池中移除的數據
/** 在數據庫差錯緩存池中,若是第三方成功支付的帳單匹配到緩衝池的帳單,須要移除緩衝池,這裏好像應該還有更新訂單狀態操做,可是代碼沒有寫 **/
List<RpAccountCheckMistakeScratchPool> removeScreatchRecordList = new ArrayList<RpAccountCheckMistakeScratchPool>();
-
- 以平臺交易成功的記錄爲準遍歷匹配微信帳單列表,匹配不到的記錄放入差錯緩存池,匹配到的判斷數據是否正確不正確的,將錯誤信息放入差錯池,更新對帳記錄。
-
- 再以微信帳單爲準,遍歷平臺的全部交易流水記錄,匹配平臺系統訂單狀態不爲成功的記錄,就是狀態錯誤,將錯誤信息放入差錯池,沒有匹配到的再匹配差錯緩存池列表,匹配到了校驗信息,將錯誤信息放入差錯池,將緩存池匹配到的記錄放入移除列表,仍沒有匹配到,將錯誤放入差錯池(漏單)。
--> 清理對帳緩存池中三天前的記錄,並將三天前的記錄放入到差錯池
結算分析
結算工程是 roncoo-pay-app-settlement,和對帳工程同樣,是不能定時運行的,能夠主動運行 main 測試,後臺調用結帳功能和主動運行 main 同樣的,結果都是實現了商戶帳號的能夠提現的餘額彙總,銀行卡提現的功能是沒有實現的。
總結
這個系統內容仍是挺豐富,建議花點時間研究研究,還有一個支付系統 XxPay 聚合支付,也挺有意思的,有時間能夠研究一下。