开票平台调用业务流程
2022-11-01 本文已影响0人
一只浩子
上一篇文章搭建了开票中间平台B,下一步平台C调用开票平台B业务流程
一、开票系统调用流程
- 首先,调用平台C须提前注册到开票平台B,获取到对应的clientId,clientSecret。注册信息包括回要回调的地址callBackUrl(开票成功或失败回调使用);
-
业务流程图如下
开票系统流程图.png - 调用平台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;
}
- 调用开票平台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");
}
- 封装订单信息 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;
}
- 开票平台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");
}