龍果支付系統的代碼下載地址碼雲/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版本。