龙果支付系统的代码下载地址码云/Roncoo,虽然官网上说的功能很炫酷,但实际上其实我觉的仍是挺酷的,只是功能没有他们说的那么全。目前我只浏览了一下支付业务,系统中看到了支付宝和微信的扫码支付和刷卡支付、微信的小程序支付,测试了微信的扫码和刷卡支付。html
如今有以下几个角色:
平台:龙果支付系统,
商户:使用龙果支付系统的用户,好比某公司的商城系统使用该系统,商户就是某公司
用户:使用商户系统的用户java
当前龙果支付系统实现的功能:mysql
一些不全的功能:git
能够先参考这两个教程:web
根据第一个教程中能够了解到系统所使用的技术,我只看了龙果支付系统的支付业务,我就说一下我在支付业务中使用的技术:spring
这个项目使用的 jdk7,虽然 maven 项目,我以前用 maven 的 tomcat 插件运行不起来,这里用的 eclipse 配置的本地 tomcat 容器运行,后来主要研究支付业务就没看 怎么用 tomcat 插件运行支付系统。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支付接口的示例
接入步骤:小程序
这里以微信的扫码支付为例进行分析:segmentfault
修改相关配置。
前面准备工做都作好了,这里开始在本地运行测试
启动 roncoo-pay-web-gateway,支付网关,给商户提供支付调用的接口,有三个关键控制类:
--> 打开 shop 页面
点击左边第一个微信支付,shop 工程会发送一个表单请求:
上面代码的意思就是将参数签名,而后拼接出一个 form 表单,和表单提交 js 代码,就是 buildRequest 字符串,而后返回 toPay.jsp ,这个页面会加载这段代码,而后向 gateway 提交支付请求,这些不是重点,重点在于支付请求发送的参数和 url 地址,这里请求要用 utf-8 ,否则乱码,验证签名会失败。本身花点时间整理请求参数,重点是后面的分析。
--> gateway 工程的请求处理
扫码请求处理类 ScanPayController 的 initPay 方法响应请求,处理请求参数,获取用户支付配置,根据配置判断是否校验请求 ip,而后校验签名,根据商户参数中是否有 payWayCode(支付渠道:微信、支付宝)判断,
--> rpTradePaymentManagerService.initDirectScanPay 方法内部处理
RpUserPayConfig rpUserPayConfig = rpUserPayConfigService.getByPayKey(payKey); if (rpUserPayConfig == null) { throw new UserBizException(UserBizException.USER_PAY_CONFIG_ERRPR, "用户支付配置有误"); }
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);
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, "请求微信异常"); }
rpNotifyService.orderSend(rpTradePaymentRecord.getBankOrderNo());
--> 微信结果通知
微信服务器通知支付系统,gateway 工程的 ScanPayNotifyController 控制类收到通知并响应,处理方法是 notify,做用是,解析请求,验证签名更新系统内订单状态,发送商家通知,notify 收到通知,向商家通知,通知频率为 60/120/300/900,商家须要回复 success 字符串,来终止通知。
//解析返回通知结果 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); }
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 里面的内容:
签名正确,根据交易结果选择执行成功或失败的方法,这两个方法内部都是
--> 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);
/** 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; }
/** 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; }
/** 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 聚合支付,也挺有意思的,XxPay Spring boot版本。