我對(duì)這個(gè)系統(tǒng)的了解
現(xiàn)在有如下幾個(gè)角色:
平臺(tái):龍果支付系統(tǒng),
商戶(hù):使用龍果支付系統(tǒng)的用戶(hù)递礼,比如某公司的商城系統(tǒng)使用該系統(tǒng),商戶(hù)就是某公司
用戶(hù):使用商戶(hù)系統(tǒng)的用戶(hù)
當(dāng)前龍果支付系統(tǒng)實(shí)現(xiàn)的功能:
商戶(hù)使用平臺(tái)暮蹂,用戶(hù)瀏覽商戶(hù)商品購(gòu)買(mǎi)粹断,向商戶(hù)的第三方賬戶(hù)(微信、支付寶)付款所刀,
商戶(hù)使用掃描設(shè)備獲取用戶(hù)付款碼衙荐,調(diào)用平臺(tái)支付,商戶(hù)第三方賬號(hào)向用戶(hù)收款浮创,
平臺(tái)的流水記錄與第三方(微信忧吟、支付寶)賬單進(jìn)行對(duì)賬,賬單對(duì)應(yīng)不上的放入差錯(cuò)池
一些不全的功能:
結(jié)算斩披,將商戶(hù)在平臺(tái)的賬戶(hù)余額提到商戶(hù)的銀行卡中溜族,這里沒(méi)有這項(xiàng)功能讹俊,只是將平臺(tái)中賬戶(hù)的操作歷史(加款、減款)的金額匯總煌抒,得出可結(jié)算的余額仍劈。
微信H5支付,沒(méi)有這段代碼寡壮。
微信小程序支付贩疙,平臺(tái)中有小程序支付代碼,沒(méi)有調(diào)用案例况既,從微信開(kāi)發(fā)文檔中看这溅,好像是需要小程序的appid,我沒(méi)有測(cè)試棒仍。
可以對(duì)賬悲靴,雖然 spring 配置有定時(shí)任務(wù),但是不能定時(shí)啟動(dòng)對(duì)賬莫其,因?yàn)槌绦蛉肟谶\(yùn)行一次就結(jié)束了癞尚。
結(jié)算,只能賬戶(hù)金額匯總榜配,同樣不能定時(shí)結(jié)算否纬,需要自己改。
項(xiàng)目分析與部署
可以先參考這兩個(gè)教程:
龍果開(kāi)源支付系統(tǒng)業(yè)務(wù)介紹與部署
分析
根據(jù)第一個(gè)教程中可以了解到系統(tǒng)所使用的技術(shù)蛋褥,我只看了龍果支付系統(tǒng)的支付業(yè)務(wù)临燃,我就說(shuō)一下我在支付業(yè)務(wù)中使用的技術(shù):
maven + eclipse,要了解 maven 的聚合烙心、繼承膜廊、依賴(lài)、插件淫茵,雖然我的 maven 很渣爪瓜,一般應(yīng)用沒(méi)有問(wèn)題
spring + mybatis,系統(tǒng)中 mybatis 的用法跟我學(xué)的不太一樣匙瘪,但是差不多能理解
activemq铆铆,消息中間件,沒(méi)有學(xué)過(guò)丹喻,可以花兩三個(gè)小時(shí)入門(mén)薄货,我做了這個(gè)?ActiveMQ 筆記
ngrok,內(nèi)網(wǎng)穿透碍论,將本地 web 應(yīng)用發(fā)布到外網(wǎng)上谅猾,可以自己搭建外網(wǎng)穿透,但是需要云服務(wù)器,我用騰訊云的學(xué)生優(yōu)惠
mysql税娜、tomcat坐搔、微信和支付寶接入開(kāi)發(fā)文檔
這個(gè)項(xiàng)目使用的 jdk7,雖然 maven 項(xiàng)目敬矩,我之前用 maven 的 tomcat 插件運(yùn)行不起來(lái)概行,這里用的 eclipse 配置的本地 tomcat 容器運(yùn)行,后來(lái)主要研究支付業(yè)務(wù)就沒(méi)看 怎么用 tomcat 插件運(yùn)行支付系統(tǒng)谤绳。
了解幾個(gè)概念:
長(zhǎng)款短款:實(shí)際收到的錢(qián)比應(yīng)該收到的錢(qián)多是長(zhǎng)款占锯,反之短款。
微信里的掃碼支付就是支付寶的即時(shí)到賬缩筛,都是用戶(hù)拿著手機(jī)掃二維碼付款消略;微信里的刷卡支付就是支付寶的條碼支付,都是商家用掃碼條形碼的機(jī)器掃描用戶(hù)手機(jī)上的付款碼
項(xiàng)目結(jié)構(gòu)功能
|-- roncoo-pay? //龍果支付系統(tǒng)瞎抛,父工程艺演,管理jar包依賴(lài)的版本||-- roncoo-pay-common-core? //整個(gè)項(xiàng)目用到的工具類(lèi)枚舉類(lèi)等公共類(lèi)資源和依賴(lài)的公共jar包||-- roncoo-pay-service //支付系統(tǒng)的核心業(yè)務(wù)工程,依賴(lài)common-core工程||-- roncoo-pay-web-gateway //給商戶(hù)提供可以請(qǐng)求的支付接口桐臊,依賴(lài)service和core工程||-- roncoo-pay-web-boss //支付系統(tǒng)管理員用的管理后臺(tái)胎撤,依賴(lài)service和core工程||-- roncoo-pay-web-merchant //商家用的管理后臺(tái),依賴(lài)service和core工程||-- roncoo-pay-app-reconciliation //對(duì)賬應(yīng)用工程断凶,依賴(lài)service和core工程||-- roncoo-pay-app-settlement //結(jié)算應(yīng)用工程伤提,依賴(lài)service和core工程||-- roncoo-pay-app-notify //將交易結(jié)果通知商戶(hù)系統(tǒng)的工程,依賴(lài)service和core工程||-- roncoo-pay-app-order-polling //第三方交易結(jié)果查詢(xún)认烁,更新本地?cái)?shù)據(jù)庫(kù)肿男,依賴(lài)service和core工程|-- roncoo-pay-web-sample-shop //模擬商戶(hù)請(qǐng)求gateway支付接口的示例
支付平臺(tái)接入簡(jiǎn)介
接入步驟:
需要 roncoo-pay-web-boss 啟動(dòng),用戶(hù)名密碼:admin/123456却嗡,管理員給支付平臺(tái)舶沛,添加支付產(chǎn)品,這里添加一個(gè)編碼為 ALLPAY窗价,描述:所有支付如庭,給ALLPAY添加支付方式,我這里添加了微信的掃碼支付撼港、刷卡支付坪它、小程序支付,支付寶的即時(shí)到賬帝牡、條碼支付哟楷。
創(chuàng)建一個(gè)商戶(hù),boss 會(huì)給商戶(hù)創(chuàng)建一系列賬號(hào)什么的否灾,就是資金賬戶(hù),然后給這個(gè)商戶(hù)設(shè)置支付配置鸣奔,選擇剛才的 ALLPAY墨技,這樣商戶(hù)就可以使用微信和支付寶的那幾種支付惩阶,還要設(shè)置商戶(hù)的收款渠道,商戶(hù)收款款就是:商戶(hù)使用平臺(tái)微信支付扣汪,平臺(tái)向微信請(qǐng)求断楷,請(qǐng)求時(shí)需要微信賬戶(hù)的 appid 什么的微信配置,商戶(hù)收款這時(shí)候就是獲取的商戶(hù)自己的微信配置崭别,這樣用戶(hù)付款直接打入商戶(hù)冬筒;平臺(tái)收款就是使用平臺(tái)配置的微信配置,用戶(hù)付款打入平臺(tái)茅主,平臺(tái)中商戶(hù)的賬號(hào)余額增加舞痰,但是不能提現(xiàn)到商戶(hù)的銀行卡,因?yàn)橄到y(tǒng)沒(méi)有實(shí)現(xiàn)诀姚。响牛。。
修改相關(guān)配置赫段,啟動(dòng) activemq呀打,roncoo-pay-app-order-polling、roncoo-pay-app-notify糯笙、roncoo-pay-web-gateway贬丛、roncoo-pay-web-sample-shop,這些都可以在本地運(yùn)行给涕,但是 roncoo-pay-web-gateway 需要內(nèi)網(wǎng)穿透發(fā)布到外網(wǎng)豺憔,又或者都發(fā)布到外網(wǎng)服務(wù)器。
正常情況下點(diǎn)擊打開(kāi) shop稠炬,點(diǎn)擊微信掃碼支付或微信刷卡支付是可以的焕阿,后面詳細(xì)介紹。
項(xiàng)目部署
git 下載源碼首启,導(dǎo)入 eclipse暮屡,和視頻教程是一樣的,這里用到 maven 知識(shí)毅桃,上面提的知識(shí)不懂的褒纲,就當(dāng)作瀏覽小學(xué)作文就行,不要認(rèn)真看钥飞。
本地倉(cāng)庫(kù)安裝支付寶的 jar 包莺掠,這里是我的推測(cè),因?yàn)樗莻€(gè)教程有段時(shí)間了读宙,現(xiàn)在支付寶官方接入文檔中有支付 jar 包的 maven 依賴(lài):所以我覺(jué)得應(yīng)該只要把那個(gè)添加到 pom 文件中就行彻秆,所以很多地方需要看第三方支付的接口文檔進(jìn)行了解,我這里只測(cè)試微信,就不做那個(gè)處理了唇兑。
有紅叉 maven update project酒朵,如果 pom 文件這樣的錯(cuò):“Artifact has not been packaged yet” 就可以不用管他了,可以看這里
項(xiàng)目沒(méi)有問(wèn)題扎附,本地測(cè)試就不用 maven 安裝了蔫耽,eclipse 配置一下本地的 tomcat,將 boss 應(yīng)用放入 tomcat 運(yùn)行留夜,一般頁(yè)面能瀏覽 boss匙铡,那種以獨(dú)立 jar 方式運(yùn)行的,就是打包成jar碍粥,然后用 java -jar 運(yùn)行鳖眼,當(dāng)然也可以在 eclipse 中找到 main 入口,直接在 eclipse 中 run 運(yùn)行即纲。下面時(shí)支付寶接入文檔中提到的 maven:
支付流程代碼分析
這里以微信的掃碼支付為例進(jìn)行分析:
準(zhǔn)備工作
首先在 boss 系統(tǒng)中設(shè)置好支付產(chǎn)品具帮,我這里用之前那個(gè) ALLPAY,里面包含了微信的掃碼支付和刷卡支付方式低斋,產(chǎn)品上線(xiàn)
給商戶(hù)選擇支付產(chǎn)品 ALLPAY蜂厅,選擇收款渠道,如果是商家收款膊畴,需要商戶(hù)添加自己的微信配置掘猿,在微信支付開(kāi)發(fā)平臺(tái)獲取的appid、商戶(hù)號(hào)唇跨、密鑰等稠通;平臺(tái)收款修改 service 配置文件中 weixinpay_config.properties,如果支付產(chǎn)品有支付寶的支付方式需要配置支付寶买猖,這里只測(cè)微信改橘,這些配置在哪獲取玉控?為什么用這些配置飞主,就需要你了解微信支付接入開(kāi)發(fā)文檔
修改相關(guān)配置。
weixinpay_config.properties:①notify_url高诺,微信交易結(jié)束后碌识,微信服務(wù)器通知交易結(jié)果,這個(gè)就是微信請(qǐng)求的 url虱而,默認(rèn)的是請(qǐng)求 gateway 工程中 ScanPayNotifyController中的 notify 方法筏餐,所以需要將 gateway 工程放到外網(wǎng)上,并修改 notify_url牡拇,可以讓微信訪(fǎng)問(wèn)到魁瞪。這里用內(nèi)網(wǎng)穿透使微信可以訪(fǎng)問(wèn)gateway穆律。②order_query_url,向平臺(tái)查詢(xún)交易訂單狀態(tài)的地址佩番,修改服務(wù)器地址就行众旗,我這里是本地 tomcat,就修改那個(gè) localhost:8080 就可以趟畏。③bill_type,微信賬單下載的類(lèi)型滩租,這里 SUCCESS 就行赋秀,下載成功交易的微信賬單,用來(lái)對(duì)賬律想。
reconciliation_config.properties:dir猎莲,微信交易賬單下載保存在本地的位置。
mq_config.properties:ActiveMQ 的 url技即,用戶(hù)名著洼、密碼,我用本地的 activemq而叼,默認(rèn)沒(méi)有用戶(hù)名密碼身笤,可以空著
pay_config.properties:shop 工程,測(cè)試接口的案例葵陵,需要修改剛才申請(qǐng)的商戶(hù)賬號(hào)的 payKey液荸、paySecret,這些可以在 boss 管理后臺(tái)獲取脱篙,或者商戶(hù)登陸商戶(hù)后臺(tái) merchant娇钱,查看自己的信息。修改掃碼支付绊困、條碼支付請(qǐng)求地址:只需要在8080后面添加上 gateway 工程名就行文搂,如:scanPayUrl=http://localhost:8080/roncoo-pay-web-gateway/scanPay/initPay,后臺(tái)通知結(jié)果 url 就不用改了秤朗,因?yàn)闆](méi)有寫(xiě)相應(yīng)的控制類(lèi)煤蹭,前臺(tái)頁(yè)面跳轉(zhuǎn)通知要改:returnUrl=http://localhost:8080/roncoo-pay-web-sample-shop,就是支付成功后跳轉(zhuǎn)的頁(yè)面川梅。
啟動(dòng)
前面準(zhǔn)備工作都做好了疯兼,這里開(kāi)始在本地運(yùn)行測(cè)試
啟動(dòng) ActiveMQ
啟動(dòng) roncoo-pay-app-notify 會(huì)加載數(shù)據(jù)庫(kù)所有沒(méi)有通知完商戶(hù)的數(shù)據(jù),然后放入線(xiàn)程池繼續(xù)通知贫途,通知完線(xiàn)程池就開(kāi)始等待吧彪,監(jiān)聽(tīng) tradeQueueName.notify=tradeNotify 的通知,該通知由 gateway 工程發(fā)出丢早,用來(lái)通知商戶(hù)后臺(tái)系統(tǒng)交易結(jié)果姨裸。
啟動(dòng) roncoo-pay-app-order-polling秧倾,會(huì)啟動(dòng)線(xiàn)程池,監(jiān)聽(tīng) orderQueryQueueName.query=orderQuery 的通知傀缩,該通知由 gateway 工程發(fā)出那先,用來(lái)向第三方查詢(xún)交易結(jié)果,這里就是向微信查詢(xún)交易結(jié)果赡艰,更改平臺(tái)數(shù)據(jù)庫(kù)交易結(jié)果售淡。
啟動(dòng) roncoo-pay-web-gateway,支付網(wǎng)關(guān)慷垮,給商戶(hù)提供支付調(diào)用的接口揖闸,有三個(gè)關(guān)鍵控制類(lèi):
ScanPayController:微信掃碼支付和支付寶即時(shí)到賬的控制類(lèi),發(fā)送支付請(qǐng)求料身,該類(lèi)請(qǐng)求第三方獲取二維碼汤纸,返回二維碼頁(yè)面給客戶(hù)端
F2FPayController:微信刷卡支付和支付寶條碼支付的控制類(lèi),商戶(hù)掃描設(shè)備掃描用戶(hù)付款碼芹血,發(fā)起收款請(qǐng)求贮泞,該類(lèi)向第三方發(fā)起收款請(qǐng)求,返回支付結(jié)果幔烛。
ScanPayNotifyController:后臺(tái)通知類(lèi)啃擦,第三方交易結(jié)果如微信交易結(jié)果發(fā)送通知,請(qǐng)求的就是該類(lèi)说贝,該類(lèi)收到請(qǐng)求议惰,向商戶(hù)發(fā)送后臺(tái)通知,更新平臺(tái)交易結(jié)果乡恕,響應(yīng)微信服務(wù)器言询,微信服務(wù)器停止通知。
啟動(dòng) ngrok 內(nèi)網(wǎng)穿透傲宜,將 gateway 發(fā)布到公網(wǎng)运杭。
啟動(dòng) shop 商城測(cè)試工程,測(cè)試 gateway 支付接口的案例函卒。
微信掃碼支付測(cè)試分析
--> 打開(kāi) shop 頁(yè)面
點(diǎn)擊左邊第一個(gè)微信支付辆憔,shop 工程會(huì)發(fā)送一個(gè)表單請(qǐng)求:
上面代碼的意思就是將參數(shù)簽名,然后拼接出一個(gè) form 表單报嵌,和表單提交 js 代碼虱咧,就是 buildRequest 字符串,然后返回 toPay.jsp 锚国,這個(gè)頁(yè)面會(huì)加載這段代碼腕巡,然后向 gateway 提交支付請(qǐng)求,這些不是重點(diǎn)血筑,重點(diǎn)在于支付請(qǐng)求發(fā)送的參數(shù)和 url 地址绘沉,這里請(qǐng)求要用 utf-8 ,不然亂碼煎楣,驗(yàn)證簽名會(huì)失敗。自己花點(diǎn)時(shí)間整理請(qǐng)求參數(shù)车伞,重點(diǎn)是后面的分析择懂。
--> gateway 工程的請(qǐng)求處理
掃碼請(qǐng)求處理類(lèi) ScanPayController 的 initPay 方法響應(yīng)請(qǐng)求,處理請(qǐng)求參數(shù)另玖,獲取用戶(hù)支付配置困曙,根據(jù)配置判斷是否校驗(yàn)請(qǐng)求 ip,然后校驗(yàn)簽名日矫,根據(jù)商戶(hù)參數(shù)中是否有 payWayCode(支付渠道:微信赂弓、支付寶)判斷,
如果沒(méi)有哪轿,返回給消費(fèi)者頁(yè)面,然消費(fèi)者選擇翔怎,這個(gè)就是 shop 中的網(wǎng)關(guān)支付窃诉,這時(shí)候 先生成交易訂單,消費(fèi)者選擇后赤套,生成交易流水記錄飘痛,向第三方發(fā)起預(yù)支付請(qǐng)求,返回預(yù)付款二維碼
如果有容握,調(diào)用?RpTradePaymentManagerServiceImpl?交易訂單管理服務(wù)類(lèi)的 initDirectScanPay 方法宣脉,這個(gè)類(lèi)是交易處理的核心類(lèi),我們發(fā)起的微信掃碼支付剔氏,這里肯定就是 payWayCode=WEIXIN塑猖,然后根據(jù)值選擇返回相應(yīng)的二維碼頁(yè)面。
--> rpTradePaymentManagerService.initDirectScanPay 方法內(nèi)部處理
獲取用戶(hù)支付配置
RpUserPayConfig rpUserPayConfig = rpUserPayConfigService.getByPayKey(payKey);if(rpUserPayConfig ==null) {thrownewUserBizException(UserBizException.USER_PAY_CONFIG_ERRPR,"用戶(hù)支付配置有誤");}
根據(jù)用戶(hù)支付配置中選擇的支付產(chǎn)品和參數(shù)中的 payWayCode 支付渠道谈跛,獲取支付方式羊苟,這里是微信掃碼方式
if(PayWayEnum.WEIXIN.name().equals(payWayCode)) {? ? //條件查詢(xún)支付方式記錄payWay= rpPayWayService.getByPayWayTypeCode(rpUserPayConfig.getProductCode(), payWayCode, PayTypeEnum.SCANPAY.name());payType= PayTypeEnum.SCANPAY;//掃碼支付}elseif(PayWayEnum.ALIPAY.name().equals(payWayCode)) {payWay= rpPayWayService.getByPayWayTypeCode(rpUserPayConfig.getProductCode(), payWayCode, PayTypeEnum.DIRECT_PAY.name());payType= PayTypeEnum.DIRECT_PAY;}if(payWay==null) {thrownew UserBizException(UserBizException.USER_PAY_CONFIG_ERRPR,"用戶(hù)支付配置有誤");}
根據(jù)商戶(hù)信息和訂單號(hào)獲取交易訂單記錄,如果沒(méi)有就創(chuàng)建交易記錄插入到數(shù)據(jù)庫(kù)感憾,如果有且未支付就更新訂單金額:
StringmerchantNo = rpUserPayConfig.getUserNo();// 商戶(hù)編號(hào)RpUserInfo rpUserInfo = rpUserInfoService.getDataByMerchentNo(merchantNo);if(rpUserInfo ==null) {thrownewUserBizException(UserBizException.USER_IS_NULL,"用戶(hù)不存在");}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())) {thrownewTradeBizException(TradeBizException.TRADE_ORDER_ERROR,"訂單已支付成功,無(wú)需重復(fù)支付");? ? }if(rpTradePaymentOrder.getOrderAmount().compareTo(orderPrice) !=0) {? ? ? ? rpTradePaymentOrder.setOrderAmount(orderPrice);// 如果金額不一致,修改金額為最新的金額}}
執(zhí)行 getScanPayResultVo 方法蜡励,該方法主要生成交易流水記錄,根據(jù)支付渠道發(fā)起預(yù)支付請(qǐng)求阻桅,獲取二維碼凉倚,發(fā)送訂單通知,返回預(yù)支付請(qǐng)求結(jié)果嫂沉。過(guò)程如下:
獲取支付渠道稽寒,更新交易訂單的支付方式,主要是網(wǎng)關(guān)支付的時(shí)候沒(méi)有確定支付方式输瓜,這里可以更新支付方式:
ScanPayResultVoscanPayResultVo =newScanPayResultVo();StringpayWayCode=payWay.getPayWayCode();// 支付方式PayTypeEnumpayType=null;if(PayWayEnum.WEIXIN.name().equals(payWay.getPayWayCode())) {payType=PayTypeEnum.SCANPAY;}elseif(PayWayEnum.ALIPAY.name().equals(payWay.getPayWayCode())) {payType=PayTypeEnum.DIRECT_PAY;}//這邊更新交易訂單支付方式的原因就是網(wǎng)關(guān)支付的時(shí)候是先生成訂單瓦胎,在提供用戶(hù)選擇支付方式芬萍,這時(shí)候要更新支付方式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);
根據(jù) payWayCode 這里是 WEIXIN ,微信支付搔啊,根據(jù)資金流入方向柬祠,這里商家收款,獲取商家自己在微信的配置信息负芋,然后封裝成微信預(yù)支付 xml 請(qǐng)求字符串漫蛔,其中包含從 service 中獲取的 notify_url,就是微信交易結(jié)果通知的 url旧蛾,交易完成后莽龟,微信服務(wù)器會(huì)向這個(gè) url 發(fā)送請(qǐng)求,后面再說(shuō)锨天,這里向微信支付發(fā)送 post 請(qǐng)求毯盈,微信支付服務(wù)器響應(yīng)返回預(yù)支付信息,驗(yàn)證返回簽名病袄,正確就將預(yù)支付信息封裝到返回實(shí)體類(lèi):
Stringappid ="";Stringmch_id ="";StringpartnerKey ="";if(FundInfoTypeEnum.MERCHANT_RECEIVES.name().equals(rpTradePaymentOrder.getFundIntoType())) {// 商戶(hù)收款// 根據(jù)資金流向獲取微信的配置信息RpUserPayInfo rpUserPayInfo = rpUserPayInfoService.getByUserNo(rpTradePaymentOrder.getMerchantNo(), payWayCode);? ? appid = rpUserPayInfo.getAppId();? ? mch_id = rpUserPayInfo.getMerchantId();? ? partnerKey = rpUserPayInfo.getPartnerKey();}elseif(FundInfoTypeEnum.PLAT_RECEIVES.name().equals(rpTradePaymentOrder.getFundIntoType())) {// 平臺(tái)收款//這里是平臺(tái)收款搂赋,獲取平臺(tái) 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());StringprePayXml = WeiXinPayUtils.getPrePayXml(weiXinPrePay, partnerKey);LOG.info("掃碼支付,微信請(qǐng)求報(bào)文:{}", prePayXml);// 調(diào)用微信支付的功能,獲取微信支付code_urlMap prePayRequest = WeiXinPayUtils.httpXmlRequest(WeixinConfigUtil.readConfig("prepay_url"),"POST", prePayXml);LOG.info("掃碼支付益缠,微信返回報(bào)文:{}", prePayRequest.toString());if(WeixinTradeStateEnum.SUCCESS.name().equals(prePayRequest.get("return_code")) && WeixinTradeStateEnum.SUCCESS.name().equals(prePayRequest.get("result_code"))) {StringweiXinPrePaySign = WeiXinPayUtils.geWeiXintPrePaySign(appid, mch_id, weiXinPrePay.getDeviceInfo(), WeiXinTradeTypeEnum.NATIVE.name(), prePayRequest, partnerKey);StringcodeUrl =String.valueOf(prePayRequest.get("code_url"));LOG.info("預(yù)支付生成成功,{}", codeUrl);if(prePayRequest.get("sign").equals(weiXinPrePaySign)) {//驗(yàn)證簽名rpTradePaymentRecord.setBankReturnMsg(prePayRequest.toString());? ? ? ? rpTradePaymentRecordDao.update(rpTradePaymentRecord);? ? ? ? scanPayResultVo.setCodeUrl(codeUrl);// 用于生成二維碼scanPayResultVo.setPayWayCode(PayWayEnum.WEIXIN.name());? ? ? ? scanPayResultVo.setProductName(rpTradePaymentOrder.getProductName());? ? ? ? scanPayResultVo.setOrderAmount(rpTradePaymentOrder.getOrderAmount());? ? }else{? ? ? ? thrownewTradeBizException(TradeBizException.TRADE_WEIXIN_ERROR,"微信返回結(jié)果簽名異常");? ? }}else{? ? thrownewTradeBizException(TradeBizException.TRADE_WEIXIN_ERROR,"請(qǐng)求微信異常");}
發(fā)送通知脑奠,交易生成,由 roncoo-pay-app-order-polling 監(jiān)聽(tīng)幅慌,polling 收到通知宋欺,放入線(xiàn)程池,執(zhí)行胰伍,先查詢(xún)交易流水狀態(tài)齿诞,如果是 WAITING_PAY,流水記錄創(chuàng)建時(shí)的狀態(tài)喇辽,等待支付掌挚,就會(huì)向微信服務(wù)器發(fā)起交易查詢(xún),如果交易成功菩咨,更新本地交易記錄和流水記錄的狀態(tài)為成功吠式,如果微信服務(wù)器查詢(xún)失敗,沒(méi)有超過(guò)最大通知次數(shù)抽米,則放入線(xiàn)程池繼續(xù)通知特占,就是繼續(xù)向微信服務(wù)器查詢(xún)交易狀態(tài),具體細(xì)節(jié)忘了云茸,大概是這樣是目,下面代碼是通知 order-polling
rpNotifyService.orderSend(rpTradePaymentRecord.getBankOrderNo());
返回微信掃碼頁(yè)面,生成二維碼标捺,同時(shí)頁(yè)面不斷向支付系統(tǒng)發(fā)送訂單結(jié)果查詢(xún)懊纳,一旦交易訂單狀態(tài)為成功揉抵,頁(yè)面就會(huì)跳轉(zhuǎn)
--> 微信結(jié)果通知
微信服務(wù)器通知支付系統(tǒng),gateway 工程的 ScanPayNotifyController 控制類(lèi)收到通知并響應(yīng)嗤疯,處理方法是 notify冤今,作用是,解析請(qǐng)求茂缚,驗(yàn)證簽名更新系統(tǒng)內(nèi)訂單狀態(tài)戏罢,發(fā)送商家通知,notify 收到通知脚囊,向商家通知龟糕,通知頻率為 60/120/300/900,商家需要回復(fù) success 字符串悔耘,來(lái)終止通知讲岁。
解析返回通知結(jié)果,將通知參數(shù)放入 map 中
//解析返回通知結(jié)果Map notifyMap =newHashMap();if(PayWayEnum.WEIXIN.name().equals(payWayCode)){? ? InputStream inputStream = httpServletRequest.getInputStream();// 從request中取得輸入流notifyMap = WeiXinPayUtils.parseXml(inputStream);}elseif(PayWayEnum.ALIPAY.name().equals(payWayCode)){Map requestParams = httpServletRequest.getParameterMap();? ? notifyMap = AliPayUtil.parseNotifyMsg(requestParams);}
completeScanPay 用來(lái)驗(yàn)證通知簽名更新數(shù)據(jù)庫(kù)衬以,發(fā)送商家通知催首,加款,獲取響應(yīng)第三方的字符串泄鹏,支付寶是返回 success 或 fail
String completeWeiXinScanPay?= rpTradePaymentManagerService.completeScanPay(payWayCode ,notifyMap);
響應(yīng)第三方服務(wù)器
if(!StringUtil.isEmpty(completeWeiXinScanPay)){if(PayWayEnum.WEIXIN.name().equals(payWayCode)){httpServletResponse.setContentType("text/xml");? ? }httpServletResponse.getWriter().print(completeWeiXinScanPay);}
completeScanPay 里面的內(nèi)容:
根據(jù)渠道驗(yàn)證簽名
簽名正確,根據(jù)交易結(jié)果選擇執(zhí)行成功或失敗的方法秧耗,這兩個(gè)方法內(nèi)部都是
更新數(shù)據(jù)庫(kù)交易狀態(tài)
拼接通知參數(shù)备籽,給 activemq 發(fā)送通知 tradeNotify。
如果是交易成功而且是平臺(tái)收款分井,那就對(duì)商家賬戶(hù)進(jìn)行加款
根據(jù)渠道拼湊第三方服務(wù)器響應(yīng)數(shù)據(jù)车猬。
--> roncoo-pay-app-notify,收到 tradeNotify 通知尺锚,向商家發(fā)起通知珠闰,代碼不分析了。
對(duì)賬分析
roncoo-pay-app-reconciliation 是對(duì)賬工程瘫辩,對(duì)賬就是伏嗜,拿微信來(lái)說(shuō),將微信服務(wù)器上下載的微信支付交易的賬單數(shù)據(jù)與平臺(tái)系統(tǒng)的交易訂單進(jìn)行比對(duì)伐厌,看看有沒(méi)有交易錯(cuò)誤的信息承绸,系統(tǒng)會(huì)將錯(cuò)誤信息放入差錯(cuò)池,就是一個(gè)差錯(cuò)數(shù)據(jù)表挣轨,當(dāng)前系統(tǒng)是不支持定時(shí)自動(dòng)進(jìn)行對(duì)賬军熏,但是他在 spring 配置中設(shè)置了自動(dòng)計(jì)時(shí)任務(wù)。
--> 執(zhí)行對(duì)賬任務(wù)
正常情況下卷扮,spring 執(zhí)行定時(shí)任務(wù)荡澎,運(yùn)行 ReconciliationTask 類(lèi)的 main 方法均践,但是計(jì)時(shí)任務(wù)不能啟動(dòng),所以想體驗(yàn)的就直接運(yùn)行 main 方法可以摩幔,這里定時(shí)任務(wù) 10 點(diǎn) 15 分觸發(fā)的原因是彤委,微信會(huì)在早上 9 點(diǎn),對(duì)昨天的交易進(jìn)行整理热鞍,產(chǎn)生賬單葫慎,微信推薦的是在 10 點(diǎn)以后在下載賬單,這里運(yùn)行 main 方法薇宠。
--> 遍歷對(duì)賬接口偷办,完成對(duì)賬
獲取當(dāng)前對(duì)賬的接口信息和對(duì)賬日期,這里寫(xiě)了微信和支付寶兩個(gè)接口信息澄港,所以只能實(shí)現(xiàn)微信和支付的對(duì)賬椒涯,這里以微信對(duì)賬為例
// 判斷接口是否正確ReconciliationInterface reconciliationInter = (ReconciliationInterface) reconciliationInterList.get(num);if(reconciliationInter ==null) {LOG.info("對(duì)賬接口信息"+ reconciliationInter +"為空");continue;}// 獲取需要對(duì)賬的對(duì)賬單時(shí)間(當(dāng)前時(shí)間減去微信配置中的對(duì)賬周期1,即前天的日期回梧,原因?yàn)槲⑿旁诿咳?點(diǎn)生成前天的賬單废岂,建議商戶(hù)在10點(diǎn)后下載前天賬單)DatebillDate = DateUtil.addDay(newDate(), -reconciliationInter.getBillDay());// 獲取對(duì)賬渠道StringinterfaceCode = reconciliationInter.getInterfaceCode();
查詢(xún)對(duì)賬記錄,根據(jù)當(dāng)前的對(duì)賬日期和對(duì)賬接口渠道查詢(xún)狀態(tài)不為錯(cuò)誤和失敗的對(duì)賬記錄狱意,不存在則沒(méi)有對(duì)過(guò)帳湖苞,創(chuàng)建對(duì)賬記錄:
/** step1:判斷是否對(duì)過(guò)賬 **/RpAccountCheckBatchbatch=newRpAccountCheckBatch();Boolean checked = validateBiz.isChecked(interfaceCode, billDate);if(checked) {? ? LOG.info("賬單日["+ sdf.format(billDate) +"],支付方式["+ interfaceCode +"],已經(jīng)對(duì)過(guò)賬,不能再次發(fā)起自動(dòng)對(duì)賬详囤。");? ? continue;}// 創(chuàng)建對(duì)賬記錄batch.setCreater("reconciliationSystem");batch.setCreateTime(newDate());batch.setBillDate(billDate);batch.setBatchNo(buildNoService.buildReconciliationNo());batch.setBankType(interfaceCode);
根據(jù)對(duì)賬接口渠道執(zhí)行相應(yīng)文件下載類(lèi)的賬單下載方法财骨,下載對(duì)應(yīng)的賬單,比如執(zhí)行 WeiXinFileDown 的 fileDown 方法藏姐,下載
/** step2:對(duì)賬文件下載 **/Filefile=null;try{? ? LOG.info("ReconciliationFileDownBiz,對(duì)賬文件下載開(kāi)始");//在微信配置文件中規(guī)定下載微信支付成功的賬單file= fileDownBiz.downReconciliationFile(interfaceCode, billDate);if(file==null) {continue;? ? }? ? LOG.info("對(duì)賬文件下載結(jié)束");}catch(Exception e) {? ? LOG.error("對(duì)賬文件下載異常:", e);? ? batch.setStatus(BatchStatusEnum.FAIL.name());? ? batch.setRemark("對(duì)賬文件下載異常");? ? batchService.saveData(batch);continue;}
根據(jù)不同的對(duì)賬接口的渠道隆箩,解析不同的對(duì)賬文件,包裝成賬單列表 list:
/** step3:解析對(duì)賬文件 **/List bankList = null;try {LOG.info("=ReconciliationFileParserBiz=>對(duì)賬文件解析開(kāi)始>>>");// 解析文件羔杨,微信在這里解析的是成功支付的賬單bankList = parserBiz.parser(batch,file, billDate, interfaceCode);// 如果下載文件異常捌臊,退出if(BatchStatusEnum.ERROR.name().equals(batch.getStatus())) {continue;? ? }LOG.info("對(duì)賬文件解析結(jié)束");} catch (Exceptione) {LOG.error("對(duì)賬文件解析異常:",e);? ? batch.setStatus(BatchStatusEnum.FAIL.name());? ? batch.setRemark("對(duì)賬文件解析異常");? ? batchService.saveData(batch);continue;}
執(zhí)行 check 對(duì)賬,check 是對(duì)賬的核心代碼
/** step4:對(duì)賬流程 **/try{checkBiz.check(bankList, interfaceCode, batch);}catch(Exception e) {LOG.error("對(duì)賬異常:", e);batch.setStatus(BatchStatusEnum.FAIL.name());batch.setRemark("對(duì)賬異常");batchService.saveData(batch);continue;}
check 內(nèi)部代碼:
先檢查賬單列表是否為空兜材,空的話(huà)就結(jié)束對(duì)賬理澎,不為空查詢(xún)平臺(tái)系統(tǒng)中當(dāng)前對(duì)賬日期的當(dāng)前渠道的所有交易記錄和所有交易成功的記錄,初始化差錯(cuò)列表:
// 判斷bankList是否為空if(bankList ==null) {? ? ? ? ? ? bankList =newArrayList();? ? ? ? }// 查詢(xún)平臺(tái)bill_date,interfaceCode成功的交易List platSucessDateList = reconciliationDataGetBiz.getSuccessPlatformDateByBillDate(batch.getBillDate(), interfaceCode);// 查詢(xún)平臺(tái)bill_date,interfaceCode所有的交易List platAllDateList = reconciliationDataGetBiz.getAllPlatformDateByBillDate(batch.getBillDate(), interfaceCode);// 查詢(xún)平臺(tái)緩沖池中所有的數(shù)據(jù)List platScreatchRecordList = rpAccountCheckMistakeScratchPoolService.listScratchPoolRecord(null);// 差錯(cuò)list/** 第三方成功交易賬單中在平臺(tái)交易記錄中不存在护姆,平臺(tái)漏單矾端,將交易記錄放入差錯(cuò)池 **/List mistakeList =newArrayList();// 需要放入緩沖池中平臺(tái)長(zhǎng)款list/** 平臺(tái)成功交易記錄中沒(méi)有匹配到第三方交易成功的記錄黑界,需要放入差錯(cuò)緩沖池 **/List insertScreatchRecordList =newArrayList();// 需要從緩沖池中移除的數(shù)據(jù)/** 在數(shù)據(jù)庫(kù)差錯(cuò)緩存池中奖年,如果第三方成功支付的賬單匹配到緩沖池的賬單,需要移除緩沖池曾棕,這里好像應(yīng)該還有更新訂單狀態(tài)操作,但是代碼沒(méi)有寫(xiě) **/List removeScreatchRecordList =newArrayList();
以平臺(tái)交易成功的記錄為準(zhǔn)遍歷匹配微信賬單列表殴玛,匹配不到的記錄放入差錯(cuò)緩存池捅膘,匹配到的判斷數(shù)據(jù)是否正確不正確的,將錯(cuò)誤信息放入差錯(cuò)池滚粟,更新對(duì)賬記錄寻仗。
再以微信賬單為準(zhǔn),遍歷平臺(tái)的所有交易流水記錄凡壤,匹配平臺(tái)系統(tǒng)訂單狀態(tài)不為成功的記錄署尤,就是狀態(tài)錯(cuò)誤,將錯(cuò)誤信息放入差錯(cuò)池亚侠,沒(méi)有匹配到的再匹配差錯(cuò)緩存池列表曹体,匹配到了校驗(yàn)信息,將錯(cuò)誤信息放入差錯(cuò)池硝烂,將緩存池匹配到的記錄放入移除列表箕别,仍沒(méi)有匹配到,將錯(cuò)誤放入差錯(cuò)池(漏單)滞谢。
--> 清理對(duì)賬緩存池中三天前的記錄串稀,并將三天前的記錄放入到差錯(cuò)池
結(jié)算分析
結(jié)算工程是 roncoo-pay-app-settlement,和對(duì)賬工程一樣狮杨,是不能定時(shí)運(yùn)行的母截,可以主動(dòng)運(yùn)行 main 測(cè)試,后臺(tái)調(diào)用結(jié)賬功能和主動(dòng)運(yùn)行 main 一樣的橄教,結(jié)果都是實(shí)現(xiàn)了商戶(hù)賬號(hào)的可以提現(xiàn)的余額匯總微酬,銀行卡提現(xiàn)的功能是沒(méi)有實(shí)現(xiàn)的。
總結(jié)
這個(gè)系統(tǒng)內(nèi)容還是挺豐富颤陶,建議花點(diǎn)時(shí)間研究研究,還有一個(gè)支付系統(tǒng) XxPay 聚合支付陷遮,也挺有意思的滓走,有時(shí)間可以研究一下。