支付公司部分和项目组划分非常明确,拧螺丝难以有全局的思考,看看开源的支付项目都在做些什么也许有益于从另一种角度思考。
不算web3生态,目前看下来主要有三大类:
- demo/SDK。封装集成了各种支付的接口,简化了支付集成的过程,并且提供了案例。例如https://github.com/Javen205/IJPay 和 https://github.com/egzosn/pay-java-parent 和 https://github.com/go-pay/gopay
- 支付网关。提供一个集成多种支付站点的聚合层,根据用户的支付方式选择路由到不同的支付处理商,当然有一些增值服务例如统一的审计、错误控制、处理商选择甚至是合规和分析统计功能。例如https://github.com/juspay/hyperswitch
- 支付系统。完整的支付系统,提供账户体系、用户体系、支付接入体系、支付交易体系、对账清结算体系。https://github.com/roncoo/roncoo-pay
支付网关还是个有意思的存在,目前竞争厂商太多,为了触及尽可能多的客户,一般都会想要支持,避免分辨不同的码或者增加繁琐的选择跳转步骤,一个支付网关可以很好地聚合所有的支付方法,让买卖双方体验都更丝滑。其实回过头来看第三方支付,本质上也是个支付网关,聚合了不同的银行系统,支持各种各样的银行卡乃至于余额。开源的支付网关更像是一个套娃的存在。
支付系统真的蛮少开源的。一方面是太复杂了,开源需要摘出核心代码非常费力。而且不同的金融系统有严格的安全机制,龙果支付系统目测也是删除了银行卡部分内容,资金的解冻冻结扣款替换为了本地数据库金额的变动而且对账部分完全走的是支付宝渠道,明显是阉割版本。更像是介绍支付行业运作的卖课作品。链接: https://blog.roncoo.com/article/124373。
只不过,开源即使如此,也是对社会的贡献。
对账部分很有意思,和大家分享一下,我们先看下对账任务层代码。因为用户可能选择不同的支付渠道,需要分渠道进行对账,比如先支付宝后微信支付。不同渠道的对账文件解析存在一定差异。对账成功与否会对应更新状态,如果超过3天依然对账失败生成差错记录交由人工处理。
@Component
public class ReconciliationTask {
private static final Log LOG = LogFactory.getLog(ReconciliationTask.class);
@Autowired
private ReconciliationFileDownBiz fileDownBiz;
@Autowired
private ReconciliationFileParserBiz parserBiz;
@Autowired
private ReconciliationCheckBiz checkBiz;
@Autowired
private ReconciliationValidateBiz validateBiz;
@Autowired
private RpAccountCheckBatchService batchService;
@Autowired
private BuildNoService buildNoService;
@PostConstruct
public void taskRun() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
@SuppressWarnings("rawtypes")
// 获取全部有效的对账接口(目前是写死了,可以做持久化到数据库,再查出来)
List reconciliationInterList = ReconciliationInterface.getInterface();
// 根据不同的渠道发起对账
for (int num = 0; num < reconciliationInterList.size(); num++) {
// 判断接口是否正确
ReconciliationInterface reconciliationInter = (ReconciliationInterface) reconciliationInterList.get(num);
// 获取需要对账的对账单时间
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);
/** step2:对账文件下载 **/
File file = null;
LOG.info("ReconciliationFileDownBiz,对账文件下载开始");
file = fileDownBiz.downReconciliationFile(interfaceCode, billDate);
LOG.info("对账文件下载结束");
/** step3:解析对账文件 **/
List<ReconciliationEntityVo> bankList = null;
LOG.info("=ReconciliationFileParserBiz=>对账文件解析开始>>>");
// 解析文件
bankList = parserBiz.parser(batch, file, billDate, interfaceCode);
LOG.info("对账文件解析结束");
/** step4:对账流程 **/
checkBiz.check(bankList, interfaceCode, batch);
}
/** step5:清理缓冲池 **/
// 如果缓冲池中有三天前的数据就清理掉并记录差错
validateBiz.validateScratchPool();
}
}
对账核心逻辑包含:
- 基于平台订单的对账。只需要用平台成功的订单对账,因为失败的订单很可能没有打到微信支付宝。检查平台长款(=银行短款)的情况,也就是购买消费成功了,但是银行没有扣款,需要触发扣款。
- 基于支付渠道对账文件(银行、支付宝、微信)的对账,对账文件通常包含失败的交易。这时需要输入平台所有的订单。成功订单如果出现银行长款,意味着平台收到了钱但是没有实际完成订单需要补发商品或者启动退款。失败订单也需要对账(即使有第1步的对账),如果平台没有对应订单或者金额对不上,可以及时发现、预防、跟进排查系统错误故障、安全风险问题或者支付攻击,因为只要支付渠道有订单平台一定也该有订单创建出来而且金额必须一致。
/**
* 对账核心方法
*
* @param bankList
* 对账文件解析出来的数据
* @param interfaceCode
* 支付渠道
* @param batch
* 对账批次记录
*/
public void check(List<ReconciliationEntityVo> bankList, String interfaceCode, RpAccountCheckBatch batch) {
// 判断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>();
LOG.info(" 开始以平台的数据为准对账,平台长款记入缓冲池");
baseOnPaltForm(platSucessDateList, bankList, mistakeList, insertScreatchRecordList, batch);
LOG.info("结束以平台的数据为准对账");
LOG.info(" 开始以银行通道的数据为准对账");
baseOnBank(platAllDateList, bankList, platScreatchRecordList, mistakeList, batch, removeScreatchRecordList);
LOG.info(" 结束以银行通道的数据为准对账");
// 保存数据
rpAccountCheckTransactionService.saveDatasaveDate(batch, mistakeList, insertScreatchRecordList, removeScreatchRecordList);
}
最后,简单提一下结算部分,因为银行部分被阉割了其实代码信息量并不多。买家资金流动:冻结借记卡或三方支付余额(转账到平台金融托管账户) or 预授权信用额度(无转账); 卖家资金流动:实时或者定时结算落款(线下支付场景目测实时支付方式对应实时到账,信用卡定时入账),涉及到线上异步会有所不同,比如网购场景,用户确认收货后商家才能capture落款,中途平台需要托管金额。
评论区