支付對帳系統怎麼設計?

支付對賬系統怎麼設計?

支付對賬系統是整個支付清結算體系中具體基礎性意義的一個環節,是確保支付平臺與各類第三方支付渠道數據一致性的關鍵系統,是商戶資金結算、資金劃撥、資金報表等邏輯準確運行的重要前提。支付對賬涉及賬單下載處理、核心對賬、差錯處理等諸多細節邏輯,同時根據交易量大小的不同,需要處理的數據量規模也不盡相同,需要在數據處理時進行一些比較細緻地思考。在本文中,作者以單渠道日成功交易訂單量300W左右規模為背景,以較少的系統資源佔用為目標,給大家介紹下系統的實現細節。同時,對於數據不斷增長的情況下,支付對賬系統該如何演進,作者也會結合自身的實踐與思考與大家一起交流探討!


賬單下載&處理

對於公司自建支付系統來說,一般會根據業務的複雜程度不同,對接多個支付渠道。對於互聯網公司而言,常見的渠道會對接支付寶、微信、ApplePay等;而金融類的公司則更多會對接銀聯、易寶、快錢這類銀行卡代收付通道,也會有直接對接銀行渠道的;海外則是如Adyen、Stripe等這類國際支付公司。

各個渠道的賬單接口下載形式,賬單數據格式等會存在不同的差異,而如果完全按照第三方的原始賬單格式存儲,對於後續的賬單數據處理邏輯會比較複雜,並且每增加一個新的支付渠道,賬單存儲表都需要根據渠道賬單結構新建,增加開發工作量,所以並不合理。

為了實現賬單數據的統一格式化,我們需要將各渠道原始的賬單文件進行統一標準化轉化,同時也需要設計一張相對通用的渠道賬單數據表來存儲不同渠道賬單格式化後的數據,具體結構如下:


支付對賬系統怎麼設計?


另外,在進行賬單數據存儲時為了提高效率,需要將標準賬單文件格式設計得與表結構一致,這樣在完成數據轉換後可以直接將文件load/copy到數據庫中,這樣速度會快很多;而考慮數據規模會增長得超級大,這張表也可以存儲在Hive上,只是對於大部分公司的交易量來說,這麼做會有一些技術實現上的成本,目前作者採用的是Postgresql數據庫(版本為9.5.4)作為賬單存儲庫,數據規模大概已在10億條左右,表示暫無壓力。

此外,對於賬單的下載邏輯也需要考慮防重邏輯的,即同一個渠道賬號的同一天的賬單數據不能重複下載和入庫,所以除了存儲具體的賬單數據外,也需要設計一張賬單下載記錄表,用於存儲那個渠道賬號哪一天的下載情況,並在賬單下載任務啟動起根據該表進行防重複下載邏輯判斷。這裡還需要說明下,在下載原始賬單和轉化標準賬單時由於賬單文件讀寫都是本地磁盤,為了統一集中管理這些賬單文件、也為了數據安全需要採用統一文件存儲服務,如可以採用騰訊雲的CFS文件存儲,或者自己搭建一個文件夾共享服務。

賬單下載記錄中也需要存儲原始賬單文件及標準賬單文件的下載位置,平時基本上不會用到,只是為方便日後用於數據問題排查,需要檢索原始文件時,方便查找及數據重新加載。

核心對賬邏輯

完成相應渠道的賬單下載任務後,系統就可以根據各個渠道的特點及對賬平臺任務系統的排班邏輯,啟動相應渠道的對賬任務了,例如微信賬單在10點左右開始下載,預計完成時間為10分鐘以內,那麼就可以將微信的對賬任務安排在11點開始執行,以此類推,各個渠道根據自身實際情況確定對賬時點。

從邏輯上看對賬的形式是為了完成A表與B表的集合,如下圖所示:


支付對賬系統怎麼設計?


一般情況下,與第三方支付渠道進行對賬時,會以平臺訂單號作為關聯條件,將賬單表中的數據與支付平臺訂單表的數據進行full join得到一個集合全量,得到的集合會是一個交集、兩個補集。處於交集部分的數據集說明根據訂單號是可以對應上的,但是我們還需要進行訂單金額的比對,如果一致則說明無差錯,對平的數據集按照結算數據要求取賬單數據+平臺訂單數據業務字段全集,直接生成對賬明細表,而不一致的則需要生成差錯類型為“金額不一致”的差錯數據,並記入對賬差錯數據表

處於賬單數據這一側的補集屬於長款差錯,即這部分數據在第三方賬單中存在,而在支付平臺訂單成功數據集中未找到,導致這部分差錯數據的原因,可能有跨天交易的情況、也可能是線上測試數據導致、或者屬於系統層面的訂單掉單,在後面差錯處理中會詳解介紹。

而處於支付平臺訂單這一側的補集則屬於短款差錯,即這部分數據在支付平臺成功訂單中存在,而在渠道對賬時點的賬單中不存在,造成這部分差錯的原因有可能是跨天交易情況導致,也有可能是第三方結算錯誤,具體原因需要在設計差錯處理邏輯是根據不同情況進行處理。

在瞭解到對賬的邏輯後,那麼在具體進行系統編碼時應該如何處理呢?

按照上述邏輯,我們需要將賬單數據表與支付平臺訂單表進行full join,但是由於賬單表我們是存儲在Postgresql上的,而支付系統所採用的數據庫可能是Mysql或Oracle,總之,從系統拆分的角度看,一般是不會將對賬處理與在線支付訂單放在一個庫中的,即便在一個庫直接關聯賬單表與支付訂單表也是不明智的,一方面這樣可能會影響實時支付系統的穩定性,另外這些表的數據都是不斷增長的,隨著數據的積累會也會導致對賬數據查詢變慢。

所以,在進行某個渠道對賬時需要根據條件將賬單數據、支付平臺訂單數據分別清洗到兩張中間表中,分別叫做賬單待對賬中間表(A表)

訂單待對賬中間表(B表),然後通過這兩張表進行full join操作,這樣可以確保對賬邏輯不影響別的業務,同時這部分數據可以在日終完成對賬任務後定期清理掉,確保中間表數據規模處於可控狀態。

在代碼層面通過A表 full join B表後,會得到一個結果集,如果這個結果集數據比較大,系統沒有采用Spark+Hive這種方式話,通過傳統編程方式則需要對查詢進行分頁,考慮到數據逐條對賬處理速度較慢,可以一頁獲取數據條數稍多一些,例如一次取5W條,然後在系統內部採用多線程方式對數據集分割後並行處理,每個線程按照特定的對賬邏輯執行,得到對賬明細結果集或差錯結果集後,批量存入對賬數據庫。

核心參考代碼如下:

public boolean execute(ShardingContext shardingContext) {
boolean exeResult = true;
long startTime = System.currentTimeMillis();
String paramValue = shardingContext.getJobParameter();
CheckRequest checkRequest = (CheckRequest) JsonUtils.json2Object(paramValue, MbkCheckRequest.class);//對賬批次請求信息
Map paramMap = new HashMap();
paramMap.put("batchNo", checkRequest.getBatchNo());//批次號
paramMap.put("channel", checkRequest.getChannel());//渠道
paramMap.put("tradeType", checkRequest.getTradeType());//交易類型
//關閉PG執行計劃解釋(PG9問題)

unionCheckOrderMapper.enableNestloopToOff();
int totalCount = unionCheckOrderMapper.countByMap(paramMap);//總數
int pageNum = 50000;//pageSize,每頁5W條數據
int fistPage = 1;
int offset = 2;
PostgreSQLPageModel pm = PostgreSQLPageModel.newPageModel(pageNum, fistPage, totalCount);
int page = pm.getTotalPage();
BlockPoolExecutor exec = new BlockPoolExecutor();
exec.init(); //初始化線程池
ExecutorService pool = exec.getMbkBlockPoolExecutor();
List> results = Collections.synchronizedList(new ArrayList>());// 並行計算結果數據類型定義
for (int i = 1; i <= page; i++) {//分頁進行數據Fetch
long startTime2 = System.currentTimeMillis();
pm.setCurrentPage(i); //設置當前頁碼
offset = pm.getOffset();
paramMap.put("pageSize", pageNum);
paramMap.put("offset", offset);
List outReconList = unionCheckOrderMapper.selectByMap(paramMap);
Map> entityMap = groupListByAvg(outReconList, 1000);
CountDownLatch latch = new CountDownLatch(entityMap.size());
OutReconProcessTask[] outReconProcessTask = new OutReconProcessTask[entityMap.size()];
Iterator>> it = entityMap.entrySet().iterator();
try {
int j = 0;
while (it.hasNext()) {
List uList = (List) it.next().getValue();
outReconProcessTask[j] = new OutReconProcessTask(latch, uList);
results.add(pool.submit(outReconProcessTask[j]));
j++;
}
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
long endTime2 = System.currentTimeMillis();
}
unionCheckOrderMapper.enableNestloopToOn();//開啟PG執行計劃解釋
exec.destory();
long endTime = System.currentTimeMillis();

return exeResult;
}

考慮完成對賬、以及後面會涉及的處理差錯,其結果都是產生對賬明細數據,而對賬明細數據是渠道vs平臺後最為準確的資金數據,對於後續的商戶清分、資金清算、結算都具有重大意義,所以複雜查詢的頻率會比較高,並且數據的使用時間範圍也比較大,對於交易量比較大的公司一年的支付數據量可能達到數十億規模,在數據存儲方面,可以考慮採用TIDB這類分佈式關係型數據庫來存儲對賬結果相關的數據,這樣後續的數據處理邏輯效率會提高很多。可能有人會問為什麼不直接使用Hive進行查詢,這是因為Hive的單條查詢和批量查詢的效率是一樣的,所以並不太適合實時查詢,而如果需要將對賬、結算等數據通過管理系統進行管理,涉及的查詢場景比較多,所以綜合考慮使用分佈式關係型數據庫會更合適一些。

差錯處理邏輯

對賬邏輯執行完成後,會產生一部分對賬邏輯執行過程中,系統無法匹配的對賬差錯數據,這部分數據會在對賬完成後記錄在對賬差錯信息表中,差錯信息表根據差錯類型記錄該筆差錯的詳細信息,除包括渠道類型、金額、交易時間等關鍵信息外,還會對差錯進行分類、定義特定的差錯類型編碼。

根據根據不同情況差錯大概可以分為三類:長款、短款、金額錯誤。其中長款根據對賬處理方式的不同可以分為“渠道成功,平臺訂單不存在”、“渠道成功、平臺狀態非成功”兩種情況,從生產實踐上看,因為支付系統中會存在比較多的支付失敗訂單,而國內支付渠道的賬單多數情況下只會提供用戶支付成功的賬單數據,所以在實際進行對賬時,在A中間表、B中間表清洗的都是雙方認為成功的訂單數據,在這種情況下產生的長款類型也就只有“渠道成功,平臺訂單不存在”這種類型了。

而對於短款來說,就是在當日的賬單數據中沒有匹配到,差錯類型也就是“平臺成功,賬單數據不存在”。而“金額不一致”的情況相對少見,主要出現在如微信、支付寶進行營銷活動時,造成的支付平臺訂單金額與第三方不一致的情況;另外,存在訂單部分退款時,如果支付系統訂單模型沒有很好地滿足這類情況的話,也會導致對賬金額不一致的情況發生。

針對不同的差錯類型情況,需要根據根據系統實際情況,設計相應地處理流程。例如,對於長款差錯需要識別其產生的原因,如果是因為交易跨天導致的,例如交易在平臺時間為某日的23:59:59秒,那麼發送到第三方渠道的時間可能已經是第二天的00:00:01秒這樣,那麼在第一天對賬是會產生一筆短款差錯,此時只需要消除這筆差錯、同時生成對應的對賬明細即可。而如果是因為支付平臺狀態未處理成功,則是系統掉單問題導致,除了正常消除這筆差錯、產生對應的對賬明細數據外,還需要通知支付系統進行狀態更新操作,其涉及的業務邏輯,還需要根據整個支付平臺的流程設計,觸發商戶回調、或者還需要通知賬務系統進行補記賬操作。

總之,需要根據具體的差錯類型及原因,結合整個支付系統的流程來保證系統間數據的一致性,以下是作者根據通用場景設計、根據不同差錯類型設計的處理流程,供大家參考:

(一)、長款差錯通用處理流程




支付對賬系統怎麼設計?


在以上長款處理流程中,關於跨天交易情況的區分,這裡有一個細節的設計:在判斷完支付訂單狀態為成功後,之所以在判斷是否在T-1天或者T-N天是否存在同一筆匹配的短款差錯之前,判斷是否存在對賬明細的情況,是因為在系統設計時考慮訂單結算的實時性,允許對於短款差錯採取T+1日的處理方式

,具體方式會在短款處理流程中說明。


(二)、短款差錯通用處理流程


支付對賬系統怎麼設計?


在以上短款差錯處理流程中,大部分公司為了解決跨天交易的問題,會要求對短款差錯掛賬一天,也就是說要求在T+2日對短款進行處理,因為在T+2日時賬單和對賬出現的長款差錯可以正常抵消處理。在這裡的設計中,允許在T+1日處理,即在沒有第三方賬單信息的情況下,通過訂單查詢接口進行對賬,並默認將這筆交易的渠道結算時間設置為T+2,對於支付訂單,國內大部分渠道這麼設置是正好可以匹配的,而對於退款可能渠道的結算時間為T+3甚至更長,這種情況會導致對賬明細中的渠道結算時間與實際渠道結算時間存在一定的偏差,而在後面T+3或更長的賬單日時產生的長款差錯處理,可以採取更新策略,即根據實際的渠道結算時間更新對賬明細表的結算時間,這樣就確保了數據渠道結算時間的準確性。

上面的情況如果考慮商戶結算邏輯,可能需要對這種情況做點特殊標記,如設置渠道結算時間、商戶結算時間、或渠道實際結算時間來處理,當然如果為了確保資金結算的穩妥性,也可以採取掛賬T+2/T+N處理的方式,這一點由流程和規則決定,系統只是額外提供了一種邏輯處理方式而已。

(三)、金額不一致差錯處理

正常情況下金額不一致一般以第三方渠道賬單為準,從賬務一致性的角度考慮,可能也需要在流程中加入調賬邏輯,具體的流程可根據具體的產品規則設計。

系統演進化方向

對於對賬系統的演變主要需要從考慮數據的增長、任務資源的合理配置以及系統監控這幾個方向去考慮。如果數據量持續增長到傳統方式已無法處理,可以採用Spark Streming+Hive+Tidb等組合技術方案進行改進。

此外對賬系統是一個以定時任務為主的系統,對於定時任務處理框架的選擇可以採用分佈式任務框架(推薦elasticjob/saturn)+自定義任務邏輯的方式綜合處理(如有些任務存在先後順序,如果框架本身不提供這類處理功能,則需要通過業務規則限制)。

而從系統監控角度,由於任務系統不同於實時交易流程,具有執行時間長、數據操作範圍廣泛的特點,除了進行正常的進程級別的監控外,對於各個任務的執行情況,也需要進行比較細緻的監控,這部分可以通過監控打點等方式綜合解決;而對於業務異常日誌的監控則可以通過Sentry等日誌監控工具進行監控。

由於作者水平有限,不足之處還請多多包涵!謝謝你們的關注~


分享到:


相關文章: