美文网首页
开票平台调用业务流程

开票平台调用业务流程

作者: 一只浩子 | 来源:发表于2022-11-01 18:58 被阅读0次

    上一篇文章搭建了开票中间平台B,下一步平台C调用开票平台B业务流程

    一、开票系统调用流程
    1. 首先,调用平台C须提前注册到开票平台B,获取到对应的clientId,clientSecret。注册信息包括回要回调的地址callBackUrl(开票成功或失败回调使用);
    2. 业务流程图如下


      开票系统流程图.png
    3. 调用平台C 获取token,缓存在redis中
        /**
         * 获取开放平台Token
         * @return
         */
        private String getOpenToken(String redisKey) {
            String accessToken = (String) redisUtil.get(redisKey);
            if (StringUtils.isNotBlank(accessToken)) {
                return accessToken;
            }
            //获取token Url
            StringBuilder reqUrl = new StringBuilder();
            reqUrl.append(url + OpenInvoiceConstant.ACCESS_TOKEN_URL);
            reqUrl.append("?client_id=").append(clientId);
            reqUrl.append("&client_secret=").append(clientSecret);
    
            AjaxResponse<Object> response = restTemplate.postForObject(reqUrl.toString(), null, AjaxResponse.class);
            if (null == response || !response.isState()) {
                throw new MsgException("获取开放平台Token出错!");
            }
            Map<String, Object> resMap = (Map<String, Object>) response.getData();
            accessToken = (String) resMap.get("access_token");
    
            // 设置token 过期时间(开放平台30分钟过期)
            long expires_in = (Integer) resMap.get("expires_in") - 60;
            redisUtil.set(redisKey, accessToken, expires_in);
            return accessToken;
        }
    
    1. 调用开票平台B 开票接口
        /**
         * 调用诺诺开票接口
         * @param invoiceGuid 发票Guid
         * @throws Exception
         */
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void openInvoice(String invoiceGuid, String centerGuid) throws Exception {
            TfcInvoicePO invoicePO = dao.findByPrimaryKey(invoiceGuid);
            AssertU.notNull(invoicePO, "查询发票信息出错");
    
            //1、调用开票平台开票
            String redisKey = StaticValue.OPEN_INVOICE_TOKEN_PREFIX + ":" + centerGuid;
            String token = getOpenToken(redisKey);
            //请求头参数
            HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
            headers.add("access_token", token);
            //请求参数
            Map<String, Object> params = buildInvoiceOrder(centerGuid, invoicePO);
    
            HttpEntity<Object> httpEntity = new HttpEntity<>(params, headers);
            //封装发票订单
            String openUrl = url + OpenInvoiceConstant.OPEN_INVOICE_URL;
            ResponseEntity<AjaxResponse> responseEntity = restTemplate.exchange(openUrl, HttpMethod.POST, httpEntity, AjaxResponse.class);
            AssertU.notNull(responseEntity, "提交发票失败");
            AjaxResponse<Object> response = responseEntity.getBody();
            AssertU.isTrue(response.isState(), response.getMsg());
            Map<String, Object> data = (Map<String, Object>) response.getData();
    
            //2、调用第三方开票成功,将本地发票状态改为开票中
            TfcInvoicePO updateInvoicePO = new TfcInvoicePO();
            updateInvoicePO.setGuid(invoiceGuid);
            updateInvoicePO.setStatus(InvoiceStatusEnum.INVOICING.getKey());
            updateInvoicePO.setInvoiceWay(InvoiceWayEnum.ONLINE.getKey());
            updateInvoicePO.setInvoiceSerialNo(String.valueOf(data.get("invoiceSerialNum")));
            dao.update(updateInvoicePO, "guid");
        }
    
    1. 封装订单信息 buildInvoiceOrder
    private Map<String, Object> buildInvoiceOrder(String centerGuid, TfcInvoicePO invoicePO) throws Exception {
            Map<String, Object> params = new HashMap<>();
            params.put("buyerAccount", invoicePO.getAccountBank());
            params.put("buyerAddress", invoicePO.getByrAddress());
            params.put("buyerName", invoicePO.getByrName());
            params.put("buyerTaxNum", invoicePO.getTaxpayerNum());
            params.put("buyerTel", invoicePO.getTel());
            params.put("email", invoicePO.getEmail());
            //p:普通发票(电票),c:普通发票(纸票),s:专用发票(纸票),b:专用发票(电票)
            String invoiceType = invoicePO.getInvoiceType();
            String invoiceLine = "";
            if (InvoiceTypeEnum.GENERAL_ELECTRIC.getKey().equals(invoiceType)) {
                invoiceLine = "p";
            } else if (InvoiceTypeEnum.GENERAL_PAPER.getKey().equals(invoiceType)) {
                invoiceLine = "c";
            } else if (InvoiceTypeEnum.SPECIAL_ELECTRIC.getKey().equals(invoiceType)) {
                invoiceLine = "b";
            } else if (InvoiceTypeEnum.SPECIAL_PAPER.getKey().equals(invoiceType)) {
                invoiceLine = "s";
            }
            params.put("invoiceLine", invoiceLine);
            //蓝票
            params.put("invoiceType", "1");
            params.put("clerk", "xxx");
            params.put("payee", "xxx");
            params.put("checker", "xxx");
            params.put("salerTaxNum", YBCX_TAXNUM);
    
            // 发票明细信息
            //是否有折扣(发票金额不等于合计金额,有折扣)
            boolean isDiscount = !invoicePO.getInvoiceAmount().equals(invoicePO.getAmount());
            //是否合并(有折扣:TRUE,不能打印明细票)
            String isMerge = isDiscount ? BooleanEnum.TRUE.getKey() : (invoicePO.getDetailTicket().equals(BooleanEnum.TRUE.getKey()) ? "FALSE" : "TRUE");
    
            //查询全部明细
            List<InvoiceDetailInfoDto> detailList = detailDAO.findDetailList(invoicePO.getGuid(), "", isMerge);
            AssertU.notEmpty(detailList, "查询发票明细信息出错");
    
            //查询商品税务税收设置信息
            Map<String, TctTaxRelationDto> relationMap = getTaxDtoList(centerGuid, detailList);
    
            //发票明细
            ArrayList<Map<String, Object>> invoiceDetailList = new ArrayList<>(detailList.size());
    
            // 通过商品编号分组; 有折扣,只能合并明细开票
            // 有折扣的情况:size = 2 将 被折扣行 与 折扣行 放一起;size  = 1 说明明细没有折扣行
            // 没有折扣的情况:明细没有折扣行
            Map<String, List<InvoiceDetailInfoDto>> detailListMap = detailList.stream().collect(Collectors.groupingBy(InvoiceDetailInfoDto::getPdtCode));
            //对key排序,报错条数与供应链发票详情条数一致
            detailListMap = detailListMap.entrySet().stream()
                    .sorted(Map.Entry.comparingByKey())
                    .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (oldVal, newVal) -> newVal, LinkedHashMap::new));
    
            for (Map.Entry<String, List<InvoiceDetailInfoDto>> entry : detailListMap.entrySet()) {
                List<InvoiceDetailInfoDto> dtoList = entry.getValue();
                for (InvoiceDetailInfoDto detailDto : dtoList) {
                    //税率
                    String taxRate = String.valueOf(MoneyMath.divEight(Double.valueOf(detailDto.getTaxRate()), 100));
    
                    Map<String, Object> detailMap = new HashMap<>();
                    //是否正常行
                    boolean isNormalRow = InvoiceLineEnum.NORMAL.getKey().equals(detailDto.getInvoiceLine());
                    TctTaxRelationDto taxRelationDto = relationMap.get(detailDto.getBasePdtCode());
                    AssertU.notNull(taxRelationDto, "查询商品税率信息出错");
                    detailMap.put("goodsName", detailDto.getPdtName());
                    detailMap.put("goodsCode", taxRelationDto.getGoodsCode());
                    //单价含税标志:"0":不含税,"1":含税
                    detailMap.put("withTaxFlag", "1");
                    detailMap.put("unit", detailDto.getUnit());
                    detailMap.put("price", String.valueOf(detailDto.getPrice()));
                    //数量
                    detailMap.put("num", String.valueOf(detailDto.getWeight()));
                    //税额
                    // 合并明细避免尾差,重新计算 税额 =(销售金额/(1+税率))*税率 保留两位)
                    double taxAmount = MoneyMath.setScaleTwoDouble((detailDto.getAmount() / (1 + Double.valueOf(taxRate))) * Double.valueOf(taxRate));
                    detailMap.put("tax", String.valueOf(taxAmount));
                    //含税金额
                    double taxIncludedAmount = detailDto.getAmount();
                    detailMap.put("taxIncludedAmount", String.valueOf(taxIncludedAmount));
                    //不含税金额
                    double taxExcludedAmount = MoneyMath.subEight(taxIncludedAmount, taxAmount);
                    detailMap.put("taxExcludedAmount", String.valueOf(taxExcludedAmount));
    
                    //税率
                    detailMap.put("taxRate", taxRate);
                    //优惠政策标识:0,不使用;1,使用
                    boolean usePolicy = taxRelationDto.getUsePolicy().equals(BooleanEnum.TRUE.getKey());
                    detailMap.put("favouredPolicyFlag", usePolicy ? "1" : "0");
                    //增值税特殊管理(优惠政策名称),当favouredPolicyFlag为1时,此项必填
                    String policyType = TaxPolicyTypeEnum.getValueByKey(taxRelationDto.getPolicyType());
                    if (usePolicy) {
                        detailMap.put("favouredPolicyName", policyType);
                    }
                    //零税率标识:空, 非零税率; "1",免税; "2",不征税; "3",普通零税率;
                    //1、当税率为:0%,且增值税特殊管理:为“免税”, 零税率标识:需传“1”
                    //2、当税率为:0%,且增值税特殊管理:为"不征税", 零税率标识:需传“2”
                    //3、当税率为:0%,且增值税特殊管理:为空 ,零税率标识:需传“3”;
                    String zeroRateFlag = "";
                    if (ZERO_TAX_RATE.equals(taxRate) && usePolicy && TaxPolicyTypeEnum.MS.getValue().equals(policyType)) {
                        zeroRateFlag = "1";
                    } else if (ZERO_TAX_RATE.equals(taxRate) && usePolicy && TaxPolicyTypeEnum.BZS.getValue().equals(policyType)) {
                        zeroRateFlag = "2";
                    } else if (ZERO_TAX_RATE.equals(taxRate) && !usePolicy) {
                        zeroRateFlag = "3";
                    }
                    detailMap.put("zeroRateFlag", zeroRateFlag);
                    //发票行性质:0,正常行;1,折扣行;2,被折扣行;红票只有正常行
                    detailMap.put("invoiceLineProperty", "0");
                    //有折扣并且存在被折扣行与折扣行
                    if (isDiscount && dtoList.size() == 2) {
                        if (isNormalRow) {
                            detailMap.put("invoiceLineProperty", "2");
                        } else {
                            detailMap.put("invoiceLineProperty", "1");
                        }
                    }
                    invoiceDetailList.add(detailMap);
                }
            }
            params.put("invoiceDetail", invoiceDetailList);
            return params;
        }
    
    1. 开票平台B接收到C开票请求后,调用诺诺平台A开票接口,A回调B,B回调C,C更新开票单状态;
      开票单状态4个,暂存,开票中,开票失败,开票完成;
      开票前为暂存状态,提交开票请求后,状态会变成开票中,接收回调信息后,改变发票状态为开票失败或开票完成;
        @RequestMapping("/callback")
        @ResponseBody
        @GreenLight
        public AjaxResponse invoiceCallback(HttpServletRequest request) {
            AjaxResponse<Object> response = new AjaxResponse();
            //返回的内容
            String content = request.getParameter("content");
            Map map = JSON.parseObject(content, Map.class);
            //发票流水号
            String serialNo = (String) map.get("c_fpqqlsh");
            //商户税号
            String saleTaxNum = (String) map.get("c_saletaxnum");
    
            try {
                invoiceService.invoiceCallback(saleTaxNum, serialNo);
            } catch (Exception e) {
                this.packErrorResponse("服务器异常:", e);
            }
            return response;
        }
    
    /**
         * 发票开票、红冲回调处理
         * @param saleTaxNum 税号
         * @param serialNo 流水号
         * @throws Exception
         */
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void invoiceCallback(String saleTaxNum, String serialNo) throws Exception {
            //查询发票记录(回调接口,权限开放没有运营中心,用主运营中心获取Token)
            Map<String, Object> data = invoiceInfoQuery(serialNo, DeptEnum.DEPT_00000000000000000000000000000001.getKey());
    
            //发票代码
            String invoiceCode = String.valueOf(data.get("invoiceCode"));
            //发票号码
            String invoiceNo = String.valueOf(data.get("invoiceNo"));
            //状态
            String status = String.valueOf(data.get("status"));
            //失败原因
            String failCause = String.valueOf(data.get("failCause"));
    
            TfcInvoicePO invoicePO = dao.findBySerialNo(serialNo);
            if (ObjectUtils.isEmpty(invoicePO)) {
                return;
            }
    
            //更新发票状态
            TfcInvoicePO updatePO = new TfcInvoicePO();
            updatePO.setGuid(invoicePO.getGuid());
            //开票完成("2")
            if ("2".equals(status)) {
                updatePO.setStatus(InvoiceStatusEnum.INVOICED.getKey());
                updatePO.setInvoiced(BooleanEnum.TRUE.getKey());
                updatePO.setInvoiceCode(invoiceCode);
                updatePO.setInvoiceNum(invoiceNo);
            } else if ("22".equals(status)) {
                //开票失败("22")
                updatePO.setStatus(InvoiceStatusEnum.INVOICE_FAILED.getKey());
                updatePO.setFailCause(failCause);
            }
            dao.update(updatePO, "guid");
        }
    

    相关文章

      网友评论

          本文标题:开票平台调用业务流程

          本文链接:https://www.haomeiwen.com/subject/rpjltdtx.html