ruoyi-admin/src/main/java/com/ruoyi/web/controller/api/BankOutController.java
@@ -6,6 +6,7 @@ import com.alibaba.fastjson.TypeReference; import com.ruoyi.common.constant.AmountConstant; import com.ruoyi.common.enums.BillTypeEnum; import com.ruoyi.common.exception.ServiceException; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.system.dto.TBillDto; import com.ruoyi.system.model.TOrderBill; @@ -13,9 +14,7 @@ import com.ruoyi.system.service.TBillService; import com.ruoyi.system.service.TOrderBillService; import com.ruoyi.system.service.TPayOrderService; import com.taxi591.bankapi.dto.CovertPayBackResult; import com.taxi591.bankapi.dto.QueryBillRequest; import com.taxi591.bankapi.dto.QueryBillResponse; import com.taxi591.bankapi.dto.*; import com.taxi591.bankapi.service.BankService; import com.taxi591.bankapi.service.SignatureAndVerification; import lombok.extern.slf4j.Slf4j; @@ -28,9 +27,14 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.math.BigDecimal; import java.math.RoundingMode; import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -54,13 +58,188 @@ @Autowired TPayOrderService payOrderService; public static String getRequestBody(HttpServletRequest request) throws IOException { /** 读取httpbody内容 */ StringBuilder httpBody = new StringBuilder(); BufferedReader br = null; try { br = new BufferedReader(new InputStreamReader( request.getInputStream())); String line = null; while ((line = br.readLine()) != null) { httpBody.append(line); } } catch (IOException ex) { throw ex; } finally { if (br != null) { try { br.close(); } catch (IOException ex) { ex.printStackTrace(); } } } return httpBody.toString(); } @PostMapping(value = "payCallback") public @ResponseBody String payCallback(HttpServletRequest request){ CovertPayBackResult result = bankService.covertPayCallBack(request, (billRequest) -> { tBillService.completePay(billRequest); return true; }); return result.getBack(); public void payCallback(HttpServletRequest servletRequest,HttpServletResponse servletResponse){ String request = null; String responseJson = null; try { log.info("--------进入getRequest4Sale----------------------------------"); // 接收报文 String requestContent = getRequestBody(servletRequest).trim(); String signatureString = requestContent.substring(0, requestContent.indexOf("||")); log.info("-----ChargeBillController------------截取报文的signatureString:{}", signatureString); String requestBody = requestContent.substring(signatureString .length() + 2); log.info("-----ChargeBillController------------截取报文的requestBody:{}", requestBody); //如果有双引号,则截取双引号内requestBody的内容 Pattern p=Pattern.compile("\""); Matcher m=p.matcher(requestBody); while(m.find()){ requestBody=requestBody.replace(m.group(), ""); log.info("-----ChargeBillController------如果有双引号,则截取后的requestBody:{}", requestBody); } //requestBody是base64加密后的数据,需解析出来 request = new String( com.alibaba.fastjson.util.Base64.decodeFast(requestBody)); log.info("-----ChargeBillController------------解析完成后的requestBody-------{}" + request); ChargeBillRequest chargeBillRequest = JSON.parseObject(request, new TypeReference<ChargeBillRequest>() { }); boolean b = signatureAndVerification.read_cer_and_verify_sign(requestBody, signatureString); if (!b){ throw new ServiceException("验签失败"); } /** 销账报文重发次数,通过resendTimes此字段识别销账报文是否为重发的,0表示首次、1表示重发一次,2表示重发2次,最多重发3次*/ if(chargeBillRequest!=null && "0".equals(chargeBillRequest.getMessage().getInfo().getResendTimes())){ ChargeBillResponse chargeBillResponse = new ChargeBillResponse( chargeBillRequest); ChargeBillResponse.Message respMessage = chargeBillResponse .getMessage(); ChargeBillResponse.Message.Head respHead = chargeBillResponse .getMessage().getHead(); ChargeBillResponse.Message.Info respInfo = chargeBillResponse .getMessage().getInfo(); respHead.setTransFlag("02"); respHead.setTimeStamp(DateUtil.format(new Date(),"yyyyMMddHHmmssSSS")); // respHead.setChannel("MBNK"); respHead.setChannel(chargeBillRequest.getMessage().getHead() .getChannel()); // respHead.setTranCode("chargeBill"); respHead.setTransCode(chargeBillRequest.getMessage().getHead() .getTransCode()); respHead.setTransSeqNum(chargeBillRequest.getMessage().getHead() .getTransSeqNum()); //测试销账返回报文中,本来是销账成功的报文,但是不要送0000成功码 (JF190510134746710555这个流水号是在Demo的returnCode设置成null的时候产生的,流水状态为6;) String epayCode = chargeBillRequest.getMessage().getInfo() .getEpayCode(); String traceNo = chargeBillRequest.getMessage().getInfo() .getTraceNo(); String numOpenMerchantOrder = chargeBillRequest.getMessage() .getInfo().getNumOpenMerchantOrder(); respInfo.setNumOpenMerchantOrder(numOpenMerchantOrder); respInfo.setEpayCode(epayCode); respInfo.setTraceNo(traceNo); try{ tBillService.completePay(chargeBillRequest); respHead.setReturnCode("0000"); respHead.setReturnMessage("账单缴费成功"); }catch (Exception e){ respHead.setReturnCode("1111"); respHead.setReturnMessage("账单处理失败"); log.error("支付第一次回调出现异常,{}",request,e); } //第一次处理失败,不退款 respInfo.setRefundFlag("false"); respMessage.setInfo(respInfo); respMessage.setHead(respHead); chargeBillResponse.setMessage(respMessage); responseJson = JSON.toJSONString(chargeBillResponse); //加签名 String signatrue = signatureAndVerification .signWhithsha1withrsa(responseJson); log.info("-----ChargeBillController------------responseJson打印结果是(responseJson加密前):" + responseJson); responseJson = signatrue + "||" + new String(Base64.encodeBase64(responseJson.getBytes("utf-8"))); log.info("-----ChargeBillController------------responseJson打印结果是(responseJson加密后):" + responseJson); servletResponse.setCharacterEncoding("utf-8"); servletResponse.setContentType("text/plain"); servletResponse.getWriter().write(responseJson); }else{ //销账报文重发次数,通过resendTimes此字段识别销账报文是否为重发的,0表示首次、1表示重发一次,2表示重发2次,最多重发3次 //商户端要注意销账重复通知的情况,要进行订单唯一性处理 ChargeBillResponse chargeBillResponse = new ChargeBillResponse( chargeBillRequest); ChargeBillResponse.Message respMessage = chargeBillResponse .getMessage(); ChargeBillResponse.Message.Head respHead = chargeBillResponse .getMessage().getHead(); ChargeBillResponse.Message.Info respInfo = chargeBillResponse .getMessage().getInfo(); respHead.setTransFlag("02"); respHead.setTimeStamp(DateUtil.format(new Date(),"yyyyMMddHHmmssSSS")); // respHead.setChannel("MBNK"); respHead.setChannel(chargeBillRequest.getMessage().getHead() .getChannel()); // respHead.setTranCode("chargeBill"); respHead.setTransCode(chargeBillRequest.getMessage().getHead() .getTransCode()); respHead.setTransSeqNum(chargeBillRequest.getMessage().getHead() .getTransSeqNum()); try{ tBillService.completePay(chargeBillRequest); respHead.setReturnCode("0000"); respHead.setReturnMessage("账单缴费成功"); }catch (Exception e){ respHead.setReturnCode("1111"); respHead.setReturnMessage("账单处理失败"); log.error("支付第一次回调出现异常,{}",request,e); } // 再次推送未处理成功,则返回退款标志 if (!"0000".equals(respHead.getReturnCode())) { respInfo.setRefundFlag("true"); } String epayCode = chargeBillRequest.getMessage().getInfo() .getEpayCode(); String traceNo = chargeBillRequest.getMessage().getInfo() .getTraceNo(); String numOpenMerchantOrder = chargeBillRequest.getMessage() .getInfo().getNumOpenMerchantOrder(); respInfo.setNumOpenMerchantOrder(numOpenMerchantOrder); respInfo.setEpayCode(epayCode); respInfo.setTraceNo(traceNo); respMessage.setInfo(respInfo); respMessage.setHead(respHead); chargeBillResponse.setMessage(respMessage); responseJson = JSON.toJSONString(chargeBillResponse); //加签名 String signatrue = signatureAndVerification .signWhithsha1withrsa(responseJson); log.info("-----ChargeBillController------------responseJson打印结果是(responseJson加密前):" + responseJson); responseJson = signatrue + "||" + new String(Base64.encodeBase64(responseJson.getBytes("utf-8"))); log.info("-----ChargeBillController------------responseJson打印结果是(responseJson加密后):" + responseJson); servletResponse.setCharacterEncoding("utf-8"); servletResponse.setContentType("text/plain"); servletResponse.getWriter().write(responseJson); } }catch (Exception e) { log.error("处理支付回调发生异常:返回内容:{}",request,e); } } @PostMapping(value = "queryBill") @@ -99,8 +278,8 @@ .getInfo(); //缴费账单子账单 ArrayList<QueryBillResponse.Message.Info.Bill> respBills = new ArrayList<QueryBillResponse.Message.Info.Bill>(); ArrayList<QueryBillResponse.Message.Info.Bill.DescDetail> respDescDetail = new ArrayList<QueryBillResponse.Message.Info.Bill.DescDetail>(); // ArrayList<QueryBillResponse.Message.Info.Bill.DescDetail> respDescDetail = // new ArrayList<QueryBillResponse.Message.Info.Bill.DescDetail>(); QueryBillResponse.Message.Info.Bill respBill = respInfo.new Bill(); //缴费子商户账单 // ArrayList<QueryBillResponse.Message.Info.Bill.SplitSubMerInfo> splitSubMerInfos = new ArrayList<QueryBillResponse.Message.Info.Bill.SplitSubMerInfo>(); ruoyi-common/src/main/java/com/ruoyi/common/utils/SecurityUtils.java
@@ -1,6 +1,7 @@ package com.ruoyi.common.utils; import com.ruoyi.common.core.domain.model.LoginUserApplet; import lombok.extern.slf4j.Slf4j; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; @@ -13,6 +14,7 @@ * * @author ruoyi */ @Slf4j public class SecurityUtils { /** @@ -69,8 +71,10 @@ } catch (Exception e) { throw new ServiceException("获取用户账户异常", HttpStatus.UNAUTHORIZED); log.error("获取用户信息发生异常",e); // throw new ServiceException("获取用户账户异常", HttpStatus.UNAUTHORIZED); } return ""; } /** * 获取用户账户小程序 ruoyi-common/src/main/java/com/ruoyi/common/utils/TencentMailUtil.java
@@ -14,13 +14,11 @@ import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.MalformedURLException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.*; import java.util.concurrent.CompletableFuture; @Component @@ -149,9 +147,9 @@ tempFilePath.add(Paths.get(filePath)); FileDataSource source = new FileDataSource(filePath); messageBodyPart.setDataHandler(new DataHandler(source)); String filenameEncode = MimeUtility.encodeText(fileName, "UTF-8", "base64"); // String encodedFileName = Base64.getEncoder().encodeToString(fileName.getBytes(StandardCharsets.UTF_8)); // String filenameEncode = MimeUtility.encodeText(encodedFileName); // String filenameEncode = MimeUtility.encodeText(fileName, "UTF-8", "base64"); String encodedFileName = Base64.getEncoder().encodeToString(fileName.getBytes(StandardCharsets.UTF_8)); String filenameEncode = MimeUtility.encodeText(encodedFileName); messageBodyPart.setFileName(filenameEncode); messageBodyPart.setHeader("Content-Transfer-Encoding", "base64"); messageBodyPart.setHeader("Content-Disposition", "attachment"); ruoyi-system/src/main/java/com/ruoyi/system/service/impl/FlowListenerService.java
@@ -301,7 +301,6 @@ } List<TContractRentType> contractRentTypes = contractRentTypeService.list(); TContractRentType tContractRentType = contractRentTypes.stream().filter(e -> e.getContractId().equals(contract.getId())).findFirst().orElse(null); // 生成第一笔账单 // 第一次应缴费日期 LocalDateTime firstPayTime = contract.getStartTime().plusDays(10).withHour(0).withMinute(0).withSecond(0); @@ -455,7 +454,7 @@ if (dayOfMonth == 1) { money = money.add(contract.getMonthRent()); } else { long allDays = ChronoUnit.DAYS.between(contract.getStartPayTime(), contract.getStartPayTime().with(TemporalAdjusters.lastDayOfMonth())) + 1; long allDays = ChronoUnit.DAYS.between(rentBill.getStartTime(), rentBill.getStartTime().with(TemporalAdjusters.lastDayOfMonth())) ; money =money.add(contract.getMonthRent().divide(new BigDecimal(30), 2, BigDecimal.ROUND_DOWN).multiply(new BigDecimal(allDays))); } // 后续 @@ -463,15 +462,16 @@ rentBill.setPayableFeesMoney(money); rentBill.setOutstandingMoney(rentBill.getPayableFeesMoney()); }else{ LocalDateTime localDateTime = rentBill.getStartTime().plusMonths(1).with(TemporalAdjusters.lastDayOfMonth()); // LocalDateTime localDateTime = rentBill.getStartTime().plusMonths(1).with(TemporalAdjusters.lastDayOfMonth()); LocalDateTime localDateTime = rentBill.getStartTime().with(TemporalAdjusters.lastDayOfMonth()).plusDays(1); while (true){ if (localDateTime.isBefore(rentBill.getEndTime())){ localDateTime = localDateTime.plusMonths(1); money = money.add(contract.getMonthRent()); }else{ money = money.add(contract.getMonthRent().divide(new BigDecimal(30), 2, BigDecimal.ROUND_DOWN).multiply(new BigDecimal(ChronoUnit.DAYS.between(rentBill.getEndTime(),localDateTime.with(TemporalAdjusters.firstDayOfMonth()))+1))); money = money.add(contract.getMonthRent().divide(new BigDecimal(30), 2, BigDecimal.ROUND_DOWN).multiply(new BigDecimal(ChronoUnit.DAYS.between(rentBill.getEndTime(),localDateTime.with(TemporalAdjusters.firstDayOfMonth()))))); break; } localDateTime = localDateTime.plusMonths(1).with(TemporalAdjusters.lastDayOfMonth()); } rentBill.setPayableFeesMoney(money); rentBill.setOutstandingMoney(rentBill.getPayableFeesMoney()); ruoyi-system/src/main/java/com/ruoyi/system/service/impl/TBillServiceImpl.java
@@ -144,6 +144,7 @@ if (isok){ try { TBill save = new TBill(); save.setId(tBill.getId()); TBill presist = getById(tBill.getId()); //如果账单是已缴费状态,本方法不再进行更新账单 if (presist.getPayFeesStatus().equals("3")){ @@ -343,62 +344,66 @@ String uuid = UUID.fastUUID().toString(); boolean lock = redisCache.trylockLoop(CacheConstants.COMPLETE_PAY_LOCK_KEY + orderNo, uuid, 60); if (lock){ TPayOrder order = tPayOrderService.getById(orderNo); if (order==null){ throw new ServiceException("订单不存在"); } if (StringUtils.isNotEmpty(order.getPayNo())){ log.info("订单号已处理:{}",orderNo); return; } /** * 更新订单状态 */ TPayOrder save = new TPayOrder(); save.setId(order.getId()); save.setStatus(1); save.setPayNo(billRequest.getMessage().getInfo().getTraceNo()); save.setPayType(billRequest.getMessage().getHead().getChannel()); try { save.setPayTime(DateUtils.parseDate(billRequest.getMessage().getHead().getTimeStamp(),"yyyyMMddHHmmssSSS")); } catch (ParseException e) { throw new ServiceException("日期格式化错误"); TPayOrder order = tPayOrderService.getById(orderNo); if (order==null){ throw new ServiceException("订单不存在"); } if (StringUtils.isNotEmpty(order.getPayNo())){ log.info("订单号已处理:{}",orderNo); return; } /** * 更新订单状态 */ TPayOrder save = new TPayOrder(); save.setId(order.getId()); save.setStatus(1); save.setPayNo(billRequest.getMessage().getInfo().getTraceNo()); save.setPayType(billRequest.getMessage().getHead().getChannel()); try { save.setPayTime(DateUtils.parseDate(billRequest.getMessage().getHead().getTimeStamp(),"yyyyMMddHHmmssSSS")); } catch (ParseException e) { throw new ServiceException("日期格式化错误"); } save.setCallbackTime(new Date()); BigDecimal payAmount = new BigDecimal(billRequest.getMessage().getInfo().getPayBillAmt()); save.setActPayAmount(payAmount .multiply(AmountConstant.b100).longValue()); save.setStatus(1); save.setPayInfo(billRequest.getMessage().toString()); tPayOrderService.updateById(save); /** * 更新账单状态 */ List<TOrderBill> orderBills = orderBillService.getByOrderNo(order.getId()); List<TBill> bills = orderBills.stream().map(ob -> getById(ob.getBillId())).collect(Collectors.toList()); lockAndUpdateByAmountBatch(bills,payAmount,(bill)->{ TFlowManagement saveFlow = new TFlowManagement(); saveFlow.setPayType(1); saveFlow.setPayer(order.getUserId()); saveFlow.setPayTime(DateUtils.dateToLocalDateTime(save.getPayTime())); saveFlow.setSysSerialNumber(OrderNos.getDid(30)); saveFlow.setBankSerialNumber(save.getPayNo()); saveFlow.setFlowType(2); saveFlow.setPaymentBillId(bill.getId()); saveFlow.setDeductionMoney(bill.getDeductionMoney()); saveFlow.setFlowMoney(payAmount); saveFlow.setRemainingMoney(bill.getOutstandingMoney()); saveFlow.setPreOutstand(bill.getPreOutstand()); tFlowManagementService.save(saveFlow); }); // TBankFlow bankFlow = new TBankFlow(); // bankFlow.setPayType(1); // bankFlow.setPayer(order.getUserId()); // bankFlow.setPayTime(DateUtils.dateToLocalDateTime(save.getPayTime())); // bankFlow.setBankSerialNumber(save.getPayNo()); // bankFlow.setFlowMoney(payAmount); // bankFlow.setFlowStatus(1); // tBankFlowService.save(bankFlow); }finally { redisCache.unlock(CacheConstants.COMPLETE_PAY_LOCK_KEY + orderNo,uuid); } save.setCallbackTime(new Date()); BigDecimal payAmount = new BigDecimal(billRequest.getMessage().getInfo().getPayBillAmt()); save.setActPayAmount(payAmount .multiply(AmountConstant.b100).longValue()); save.setStatus(1); save.setPayInfo(billRequest.getMessage().toString()); tPayOrderService.updateById(save); /** * 更新账单状态 */ List<TOrderBill> orderBills = orderBillService.getByOrderNo(order.getId()); List<TBill> bills = orderBills.stream().map(ob -> getById(ob.getBillId())).collect(Collectors.toList()); lockAndUpdateByAmountBatch(bills,payAmount,(bill)->{ TFlowManagement saveFlow = new TFlowManagement(); saveFlow.setPayType(1); saveFlow.setPayer(order.getUserId()); saveFlow.setPayTime(DateUtils.dateToLocalDateTime(save.getPayTime())); saveFlow.setSysSerialNumber(OrderNos.getDid(30)); saveFlow.setBankSerialNumber(save.getPayNo()); saveFlow.setFlowType(2); saveFlow.setPaymentBillId(bill.getId()); saveFlow.setDeductionMoney(bill.getDeductionMoney()); saveFlow.setFlowMoney(payAmount); saveFlow.setRemainingMoney(bill.getOutstandingMoney()); saveFlow.setPreOutstand(bill.getPreOutstand()); tFlowManagementService.save(saveFlow); }); TBankFlow bankFlow = new TBankFlow(); bankFlow.setPayType(1); bankFlow.setPayer(order.getUserId()); bankFlow.setPayTime(DateUtils.dateToLocalDateTime(save.getPayTime())); bankFlow.setBankSerialNumber(save.getPayNo()); bankFlow.setFlowMoney(payAmount); bankFlow.setFlowStatus(1); tBankFlowService.save(bankFlow); }