龙果支付系统的代码下载地址码云/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 聚合支付,也挺有意思的,有时间能够研究一下。