开票平台调用业务流程

2022-11-01  本文已影响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");
    }
上一篇下一篇

猜你喜欢

热点阅读