1.准备工作
诺诺开放平台-电子发票API接口,智能编码接入,业务系统对接发票
仔细看看诺诺的开发说明,然后获取到诺诺的密钥;以及销售方相关的账号信息
2.开发
pom.xml
<dependency>
<groupId>com.nuonuo</groupId>
<artifactId>open-sdk</artifactId>
<version>1.0.5.2</version>
</dependency>
3.业务实现
package org.mtgcms.api.impl;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import nuonuo.open.sdk.NNOpenSDK;
import org.mtgcms.api.InvoiceService;
import org.mtgcms.api.OrderOperLogService;
import org.mtgcms.api.OrderService;
import org.mtgcms.api.common.enums.InvoiceStatusEnum;
import org.mtgcms.api.common.enums.OperTypeEnum;
import org.mtgcms.api.common.enums.OrderStatusEnum;
import org.mtgcms.api.common.exception.BusinessException;
import org.mtgcms.api.common.model.order.*;
import org.mtgcms.api.common.pojo.Order;
import org.mtgcms.api.common.pojo.OrderOperLog;
import org.mtgcms.api.common.util.DateUtil;
import org.mtgcms.api.common.vo.order.InvoiceResponse;
import org.mtgcms.api.common.vo.order.OrderTicketVo;
import org.mtgcms.api.common.vo.order.OrderVo;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
/**
* 诺诺开票接口实现类
*
*/
@Service("nuonuoInvoiceService")
public class NuonuoInvoiceServiceImpl implements InvoiceService {
@Resource
private OrderService orderService;
@Resource
private OrderOperLogService orderOperLogService;
// 诺诺开票相关配置(实际应从配置文件读取)
//诺诺接口地址
private String NUONUO_API_URL = "https://sdk.nuonuo.com/open/v1/services";
//回调方法地址
private String NUONUO_CALL_BACK_URL = "";
//密钥key
private String APP_KEY = "";
//密钥secret
private String APP_SECRET = "";
//销售方税号
private String SALER_TAX_PAY_NUMBER = "";
//销售方银行及账号
private String SALER_ACCOUNT = "";
//销售方电话
private String SALER_TEL = "";
//销售方地址
private String SALER_ADDRESS = "";
//税率(根据商品来定)
private String TAX_RATE = "0.06";
//诺诺平台添加的第三方应用code(线上环境使用,测试使用token)
private String NUONUO_CODE = "";
//诺诺平台添加的第三方应用redirect_uri(线上环境使用,测试使用token)
private String NUONUO_REDIRECT_URI = "";
//测试环境使用的token,找诺诺相关技术人员提供
private String TEST_TOKEN = "";
//开票方法名
private String INVOICE_METHOD = "nuonuo.OpeMplatform.requestBillingNew";
//查询方法名
private String QUERY_METHOD = "nuonuo.OpeMplatform.queryInvoiceResult";
@Override
@Transactional(rollbackFor = Exception.class)
public InvoiceResponse issueInvoice(OrderInvoiceReq orderInvoiceReq) {
// 1. 获取订单信息
Long orderId = orderInvoiceReq.getOrderId();
OrderVo order = orderService.getOrderInfo(orderId);
if (order == null) {
throw new BusinessException("订单不存在");
}
// 2. 检查订单状态,只有已支付的订单才能开票
if (!OrderStatusEnum.COMPLETED.getCode().equals(order.getOrderStatus())) {
throw new BusinessException("只有已支付的订单才能开具发票");
}
// 3. 检查是否已经开过发票
if (order.getInvoiceStatus() != null &&
InvoiceStatusEnum.INVOICED.getCode().equals(order.getInvoiceStatus())) {
throw new BusinessException("该订单已开具发票");
}
// 4. 构造开票请求参数
InvoiceOrderReq invoiceOrderReq = buildInvoiceRequest(orderInvoiceReq, order);
try {
// 5. 调用诺诺开票接口
InvoiceResponse response = callNuonuoInvoiceAPI(invoiceOrderReq);
// 6. 处理开票结果
if (response.isSuccess()) {
// 更新订单发票状态
updateOrderInvoiceInfo(orderId, response);
// 记录开票操作日志
saveInvoiceLog(orderId, "发票开具成功,发票号码:" + response.getInvoiceNumber(),
OperTypeEnum.INVOICE_ORDER.getCode());
} else {
// 记录开票失败日志
saveInvoiceLog(orderId, "发票开具失败:" + response.getErrorMessage(),
OperTypeEnum.INVOICE_ORDER.getCode());
throw new BusinessException("发票开具失败:" + response.getErrorMessage());
}
return response;
} catch (Exception e) {
// 异常处理
saveInvoiceLog(orderId, "发票开具异常:" + e.getMessage(),
OperTypeEnum.INVOICE_ORDER.getCode());
throw new BusinessException("发票开具异常:" + e.getMessage());
}
}
@Override
public void callback(String result) {
// 5. 解析响应结果
JSONObject jsonObject = JSON.parseObject(result);
Order order = new Order();
if ("E0000".equals(jsonObject.getString("code"))) {
JSONObject resultJson = jsonObject.getJSONObject("result");
order.setInvoiceNumber(resultJson.getString("invoiceSerialNum"));
order.setInvoiceStatus(InvoiceStatusEnum.INVOICED.getCode());
} else {
String message = jsonObject.getString("message");
JSONObject resultJson = jsonObject.getJSONObject("result");
order.setInvoiceNumber(resultJson.getString("invoiceSerialNum"));
order.setOrderStatus(InvoiceStatusEnum.INVOICE_FAILED.getCode());
order.setInvoiceMsg(message);
}
orderService.updateOrderByInvoiceNumber(order);
}
@Override
public InvoiceResponse queryInvoice(String invoiceNumber) {
InvoiceResponse response = new InvoiceResponse();
try {
// 1. 生成访问令牌
String accessToken = generateAccessToken();
// 2. 构造请求参数
InvoiceQueryReq invoiceQueryReq = new InvoiceQueryReq();
invoiceQueryReq.setIsOfferInvoiceDetail("0");
invoiceQueryReq.setSerialNos(Arrays.asList(invoiceNumber));
//invoiceQueryReq.setOrderNos(Arrays.asList(invoiceNumber));
String requestBody = JSON.toJSONString(invoiceQueryReq);
// 4. 发送HTTP请求(此处为示意代码)
// 实际实现应使用HttpClient等发送真实请求
String result = sendHttpPost(NUONUO_API_URL, requestBody, accessToken, QUERY_METHOD);
// 5. 解析响应结果
JSONObject jsonObject = JSON.parseObject(result);
if ("E0000".equals(jsonObject.getString("code"))) {
JSONArray resultJson = jsonObject.getJSONArray("result");
if(!CollectionUtils.isEmpty(resultJson)) {
response.setSuccess(true);
JSONObject object = resultJson.getJSONObject(0);
response.setInvoiceNumber(object.getString("serialNo"));
response.setOrderNo(object.getString("orderNo"));
response.setInvoiceStatus(object.getString("statusMsg"));
response.setInvoiceUrl(object.getString("pdfUrl"));
response.setPictureUrl(object.getString("pictureUrl"));
response.setErrorMessage(object.getString("failCause"));
}else {
response.setSuccess(false);
response.setErrorMessage("未查询到相关发票");
}
} else {
response.setSuccess(false);
response.setErrorMessage(jsonObject.getString("message"));
}
} catch (Exception e) {
response.setSuccess(false);
response.setErrorMessage("查询发票信息异常:" + e.getMessage());
}
return response;
}
/**
* 构造开票请求参数
*
* @param orderReq 订单信息
* @return 开票请求对象
*/
private InvoiceOrderReq buildInvoiceRequest(OrderInvoiceReq orderReq, OrderVo orderVo) {
InvoiceOrderReq request = new InvoiceOrderReq();
// 基本信息
request.setOrderNo(orderVo.getOrderNo());
request.setInvoiceDate(DateUtil.format(orderVo.getCreateTime(), DateUtil.PATTERN_DATETIME));
request.setCallBackUrl(NUONUO_CALL_BACK_URL);
if ("bs".equals(orderReq.getInvoiceLine())) {
//增值税专用发票(电子)
request.setInvoiceLine(orderReq.getInvoiceLine());
} else {
//增值税普通发票(电子)
request.setInvoiceLine("pc");
}
if ("2".equals(orderReq.getInvoiceType())) {
request.setInvoiceType(orderReq.getInvoiceType());
} else {
request.setInvoiceType("1");
}
// 购买方信息
request.setBuyerName(orderReq.getBuyerName());
request.setBuyerTaxNum(orderReq.getBuyerTaxNum());
request.setBuyerAddress(orderReq.getBuyerAddress());
request.setBuyerPhone(orderReq.getBuyerPhone());
request.setBuyerAccount(orderReq.getBuyerAccount());
request.setBuyerTel(orderReq.getBuyerTel());
request.setEmail(orderReq.getEmail());
// 销售方信息(从配置获取)
request.setSalerAccount(SALER_ACCOUNT);
request.setSalerTaxNum(SALER_TAX_PAY_NUMBER);
request.setSalerAddress(SALER_ADDRESS);
request.setSalerTel(SALER_TEL);
//商品明细
request.setInvoiceDetail(transInvoiceDetail(orderVo.getOrderTicketList()));
return request;
}
private List<InvoiceDetailReq> transInvoiceDetail(List<OrderTicketVo> orderTicketList) {
List<InvoiceDetailReq> invoiceDetailReqList = new ArrayList<>();
orderTicketList.forEach(item -> {
InvoiceDetailReq invoiceDetailReq = new InvoiceDetailReq();
invoiceDetailReq.setGoodsName(item.getName());
invoiceDetailReq.setGoodsCode(item.getCode());
//实际支付单价
invoiceDetailReq.setPrice();
invoiceDetailReq.setNum();
invoiceDetailReq.setWithTaxFlag("1");
//税率
invoiceDetailReq.setTaxRate(TAX_RATE);
invoiceDetailReqList.add(invoiceDetailReq);
});
return invoiceDetailReqList;
}
public static BigDecimal toScale(BigDecimal value) {
return value.setScale(2, RoundingMode.HALF_UP); // 四舍五入到两位小数
}
public static String toScaleStr(BigDecimal value) {
return String.valueOf(toScale(value));
}
/**
* 调用诺诺开票接口
*
* @param request 开票请求
* @return 开票响应
*/
private InvoiceResponse callNuonuoInvoiceAPI(InvoiceOrderReq request) {
InvoiceResponse response = new InvoiceResponse();
try {
// 1. 生成访问令牌
String accessToken = generateAccessToken();
// 3. 构造请求参数
InvoiceBaseReq invoiceBaseReq = new InvoiceBaseReq();
invoiceBaseReq.setOrder(request);
String requestBody = JSON.toJSONString(invoiceBaseReq);
// 4. 发送HTTP请求(此处为示意代码)
// 实际实现应使用HttpClient等发送真实请求
String result = sendHttpPost(NUONUO_API_URL, requestBody, accessToken, INVOICE_METHOD);
// 5. 解析响应结果
JSONObject jsonObject = JSON.parseObject(result);
if ("E0000".equals(jsonObject.getString("code"))) {
response.setSuccess(true);
JSONObject resultJson = jsonObject.getJSONObject("result");
response.setInvoiceNumber(resultJson.getString("invoiceSerialNum"));
} else {
response.setSuccess(false);
response.setErrorMessage(jsonObject.getString("message"));
}
} catch (Exception e) {
response.setSuccess(false);
response.setErrorMessage("调用诺诺开票接口异常:" + e.getMessage());
}
return response;
}
/**
* 生成访问令牌
*
* @return 访问令牌
*/
private String generateAccessToken() {
//todo 待上线时提供密钥
// String json = NNOpenSDK.getIntance().getISVToken(APP_KEY,APP_SECRET,CODE,TAX_PAY_NUMBER,REDIRECT_URI);
// String accessToken = JSONObject.parseObject(json).getString("access_token");
// if(null == accessToken){
// throw new BusinessException("获取诺诺accessToken异常:"+json);
// }
//return JSONObject.parseObject(json).getString("access_token");
return TEST_TOKEN;
}
/**
* 发送HTTP POST请求
*
* @param url 请求地址
* @param body 请求体
* @return 响应结果
*/
private String sendHttpPost(String url, String body, String accessToken, String method) {
// 实际应使用HttpClient等发送HTTP请求
// 此处返回模拟响应
String senid = UUID.randomUUID().toString().replace("-", ""); // 唯一标识,由企业自己生成32位随机码
NNOpenSDK sdk = NNOpenSDK.getIntance();
String responseStr = sdk.sendPostSyncRequest(url, senid, APP_KEY, APP_SECRET, accessToken, SALER_TAX_PAY_NUMBER, method, body, 30000);
System.out.println(responseStr);
return responseStr;
}
/**
* 更新订单发票信息
*
* @param orderId 订单ID
* @param response 开票响应
*/
private void updateOrderInvoiceInfo(Long orderId, InvoiceResponse response) {
Order order = new Order();
order.setId(orderId);
order.setInvoiceStatus(InvoiceStatusEnum.INVOICING.getCode());
order.setInvoiceMsg(response.getErrorMessage());
order.setInvoiceNumber(response.getInvoiceNumber());
order.setInvoiceTime(DateUtil.now());
order.setUpdateTime(DateUtil.now());
orderService.updateById(order);
}
/**
* 保存发票操作日志
*
* @param orderId 订单ID
* @param remarks 备注
* @param operType 操作类型
*/
private void saveInvoiceLog(Long orderId, String remarks, Integer operType) {
OrderOperLog orderOperLog = new OrderOperLog();
orderOperLog.setOrderId(orderId);
orderOperLog.setRemarks(remarks);
orderOperLog.setOperType(operType);
orderOperLog.setCreateTime(DateUtil.now());
orderOperLogService.save(orderOperLog);
}
}
三个开票的请求实体类
import lombok.Data;
@Data
public class InvoiceBaseReq {
private InvoiceOrderReq order;
}
import lombok.Data;
import java.util.List;
@Data
public class InvoiceOrderReq {
//必填 购方名称
private String buyerName;
//购方税号(企业要填,个人可为空;数电
//专票、二手车销售统一发票时必填)
private String buyerTaxNum;
//购方电话(购方地址+电话总共不超100字
//符;二手车销售统一发票时必填)
private String buyerTel;
//购方地址(购方地址+电话总共不超100字
//符;二手车销售统一发票时必填)
private String buyerAddress;
//购方银行开户行及账号
private String buyerAccount;
//必填 销方税号(使用沙箱环境请求时消息体参
//数salerTaxNum和消息头参数userTax
//填写339902999999789113)
private String salerTaxNum;
//必填 销方电话(在诺税通saas工作台配置过的
//可以不传,以传入的为准)
private String salerTel;
//必填 销方地址(在诺税通saas工作台配置过的
//可以不传,以传入的为准)
private String salerAddress;
//销方银行开户行及账号(二手车销售统一发
//票时必填)
private String salerAccount;
//
private String zrrzjlx;
//
private String zrrzjhm;
//
private String zrrgj;
//
private String escxsfzjlx;
//
private String escxsfzjhm;
//
private String escxsfgj;
//
private String showBankAccountType;
//
private String showAddressTelType;
//必填 订单号(每个企业唯一)
private String orderNo;
//必填 订单时间
private String invoiceDate;
//
private String invoiceCode;
//
private String invoiceNum;
//
private String redReason;
//
private String billInfoNo;
//
private String departmentId;
//
private String clerkId;
//
private String remark;
//
private String taxReductionFlag;
//
private String checker;
//
private String payee;
//
private String showCheckerType;
//
private String clerk;
//
private String listFlag;
//
private String listName;
//推送方式:-1,不推送;0,邮箱;1,手机(默认);2,邮箱、手机
private String pushMode;
//必填 购方手机(pushMode为1或2时,此项为
//必填,同时受企业资质是否必填控制)
private String buyerPhone;
//必填 推送邮箱(pushMode为0或2时,此项为
//必填,同时受企业资质是否必填控制)
private String email;
//
private String ccPhone;
//
private String ccEmail;
//必填 开票类型:1:蓝票;2:红票 (数电票冲红请
//对接数电快捷冲红接口)
private String invoiceType;
/**
* 发票种类:p,普通发票(电票)(默认);
* c,普通发票(纸票);
* s,专用发票(纸质);
* e,收购发票(电票);
* f,收购发票(纸质);
* r,普通发票(卷式);
* b,增值税电子专用发票;
* j,机动车销售统一发票;
* dj,数电机动车发票(电子);
* sj,数电机动车发票(纸质);
* u,二手车销售统一发票;
* du,数电二手车发票(电子);
* su,数电二手车发票(纸质);
* bs:电子发票(增值税专用发票)-即数电专票(电子),
* pc:电子发票(普通发票)-即数电普票(电子),
* es:数电纸质发票(增值税专用发票);
* ec:数电纸质发票(普通发票)
*/
private String invoiceLine;
//
private String paperInvoiceType;
//
private String specificFactor;
//
private String forceFlag;
//
private String proxyInvoiceFlag;
//
private String taxRebateProxy;
//
private String invoiceDifferenceType;
//
private String callBackUrl;
//
private String extensionNumber;
//
private String terminalNumber;
//
private String machineCode;
//
private String vehicleFlag;
//
private String hiddenBmbbbh;
//
private String nextInvoiceCode;
//
private String nextInvoiceNum;
//
private String invoiceNumEnd;
//
private String surveyAnswerType;
//
private String buyerManagerName;
//
private String managerCardType;
//
private String managerCardNo;
//
private String bField1;
//
private String bField2;
//
private String bField3;
//
private String naturalPersonFlag;
//
private String sellerNaturalPersonFlag;
//
private String certificateType;
//
private String taxNumVerifyFlag;
//
private String naturalPersonVerifyFlag;
//
private String isIgnoreType;
//
private String mtrl;
//
private String mtql;
//
private String mthf;
//
private String gmfssqjd;
//
private String gmfjddm;
//
private String gmfdz;
//
private String scrapProductSalesType;
//
private String pdfCustomTag;
/**
* 必填 发票明细
*/
private List<InvoiceDetailReq> invoiceDetail;
}
import lombok.Data;
/**
* 发票明细,支持填写商品明细最大2000行
* (包含折扣行、被折扣行)
*/
@Data
public class InvoiceDetailReq {
//必填 商品名称(如invoiceLineProperty =1,
//则此商品行为折扣行,折扣行不允许多行
//折扣,折扣行必须紧邻被折扣行,商品名
//称必须与被折扣行一致)。数电票长度限
//制为100个字符,增值税票仍然为90个。
private String goodsName;
//
private String goodsCode;
//
private String selfCode;
//必填 单价含税标志:0:不含税,1:含税
private String withTaxFlag;
//
private String price;
//
private String num;
//
private String unit;
//
private String specType;
//必填 税率,注:1、纸票清单红票存在为null的
//情况;2、二手车发票税率为null或者0
private String tax;
//
private String taxRate;
//
private String taxExcludedAmount;
//
private String taxIncludedAmount;
//
private String invoiceLineProperty;
//
private String favouredPolicyFlag;
//
private String immediateTaxReturnType;
//
private String favouredPolicyName;
//
private String deduction;
//
private String zeroRateFlag;
//
private String mtzl;
//
private String dField1;
//
private String dField2;
//
private String dField3;
//
private String dField4;
//
private String dField5;
//
private String medicalClinicInfo;
//
private String medicalClinicCode;
//
private String medicalClinicOther;
//
private String detailRemark;
}

532

被折叠的 条评论
为什么被折叠?



