整合发票第三方–实现自助开票相关功能
一、背景介绍
码农门,你们好,今天要介绍的是一个第三方的API,主要是用于自助开票或开票系统的相关功能设计。我在项目中用到的是诺诺发票,感觉功能还是比较完善的。具体网址和第三方API文档可以参考诺诺发票官网----->>>>
二、技术准备
首先,需要成为诺诺开放平台的会员,并完成认证审核(需要税号);这些工作做完之后,就可以创建应用,接入sdk进行开发。有一些注意的点需要留意:
1.开发者的APP证书(包含APP Key 和 APP Secret)需要保存好
2.应用token选择
(1)如果是自己用,或者对于token没有相关安全限制的,可以选择固定token。但是相对安全性较低,但是开发测试比较方便。
(2)如果是企业服务使用,建议使用动态token,但是要做好token存储,这个token是有限制的,每个令牌是存在有效期(24小时)的,且令牌30天内的调用上限为50次。我在项目中,使用的是redis存储在缓存中。
三、相关参数配置
1.yaml配置文件
##发票接口参数---测试
invoice:
# 接口地址(测试地址)
baseURL: https://sandbox.nuonuocs.cn/open/v1/services
# 上文所述需要保存的APP证书
APPKey: 123456789
APPSecret: 123456789
# 销方税号(即开票方税号)
taxnum: 123456789
2.代码注入配置
//从缓存中获取token
private String accessTokenKey = "invoice_accessToken";
@Value("${invoice.baseURL}")
private String baseURL;
@Value("${invoice.APPKey}")
private String aPPKey;
@Value("${invoice.APPSecret}")
private String aPPSecret;
@Value("${invoice.taxnum}")
private String taxnum;
四、获取应用token(此处选择的是动态token)
public String getToken() {
try {
Object object = cacheService.getDataWithKey(accessTokenKey);
//Object object = null;如果放开这串代码,表明不走缓存,直接请求获取token
if(object!=null){
return object.toString();
}else {
String token = NNOpenSDK.getIntance().getMerchantToken(aPPKey,aPPSecret);
JSONObject jsonObject = JSONObject.parseObject(token);
String accessToken = jsonObject.getString("access_token");
if (ObjectUtil.isNotEmpty(accessToken)){
cacheService.setDataWithParams(accessTokenKey,accessToken,24, TimeUnit.HOURS);
log.info("invoice_accessToken =========>");
return accessToken;
}else {
throw new HbsxException("获取token失败");
}
}
} catch (Exception e){
log.error("InvoiceSettingServiceImpl.getToken.error==>{}",e.getMessage());
if(e instanceof XXXException){
throw new XXXException(ErrorCode.SYSTEM_ERROR.getCode(),e.getMessage());
}
throw new XXXException(ErrorCode.SYSTEM_ERROR.getCode(),"系统异常,请稍后再试!");
}
}
五、自助开票功能
1.这是请求开具发票的一些相关请求参数
2.电子发票不存在作废,只能冲红,冲红也是一种开票,只不过票面类型为红票,所以接口与开蓝票接口一致,仅参数不同。
3.返回参数为发票流水号,开票结果可以用此参数作为唯一标识查询,需要保存。
4.开票接口返回的异常码需要捕捉:
5.示例代码
/**
* 开具发票
* @param invoiceSettingDTO
* @param request
*/
@Override
@Transactional(rollbackFor = HbsxException.class)
public Map<String,Object> createFP(InvoiceSettingDTO invoiceSettingDTO, HttpServletRequest request) {
try {
Map<String,Object> map = new HashMap<>();
String token = getToken();
NNOpenSDK sdk = NNOpenSDK.getIntance();
String method = "nuonuo.electronInvoice.requestBillingNew";//API方法名
InvoiceSetting detail = this.getDetail();
String url = baseURL; //SDK请求地址
JSONObject order = new JSONObject();
JSONObject jsonObject = new JSONObject();
order.put("buyerName",invoiceSettingDTO.getInvoiceTitle());
//纳税人识别号
if (ObjectUtil.isNotEmpty(invoiceSettingDTO.getTaxPayerId())&&invoiceSettingDTO.getTitleType().equals("2")){
order.put("buyerTaxNum",invoiceSettingDTO.getTaxPayerId());
}
//电话
if (ObjectUtil.isNotEmpty(invoiceSettingDTO.getCtel())&&invoiceSettingDTO.getTitleType().equals("2")){
order.put("buyerTel",invoiceSettingDTO.getCtel());
}
//地址
if (ObjectUtil.isNotEmpty(invoiceSettingDTO.getAddress())&&invoiceSettingDTO.getTitleType().equals("2")){
order.put("buyerAddress",invoiceSettingDTO.getAddress());
}
//开户行和卡号
if (ObjectUtil.isNotEmpty(invoiceSettingDTO.getBankName())&&ObjectUtil.isNotEmpty(invoiceSettingDTO.getBankaccountname())&&invoiceSettingDTO.getTitleType().equals("2")){
order.put("buyerAccount",invoiceSettingDTO.getBankName()+invoiceSettingDTO.getBankaccountname());
}
order.put("salerTaxNum",detail.getTaxPayerId());
order.put("salerTel",detail.getCtel());
order.put("salerAddress",detail.getAddress());
order.put("salerAccount",detail.getBankName()+detail.getBankaccountname());
order.put("orderNo",invoiceSettingDTO.getOrderId());
order.put("invoiceDate",invoiceSettingDTO.getOrderTime());
order.put("checker",detail.getReviewedName());
order.put("payee",detail.getPayeeName());
order.put("clerk",detail.getDrawerName());
order.put("pushMode","-1");
order.put("invoiceType","1");
JSONObject invoiceDetail = new JSONObject();
//单价和数量
invoiceDetail.put("price",invoiceSettingDTO.getPayedFee());
invoiceDetail.put("num","1");
//invoiceDetail.put("goodsName",invoiceSettingDTO.getCname());
invoiceDetail.put("withTaxFlag","1");
invoiceDetail.put("taxExcludedAmount",invoiceSettingDTO.getPayedFee());
invoiceDetail.put("taxRate",detail.getActivityTaxrate());
order.put("invoiceDetail",invoiceDetail);
jsonObject.put("order",order);
String content = jsonObject.toJSONString();
String senid = UUID.randomUUID().toString().replace("-", ""); // 唯一标识,由企业自己生成32位随机码
String json = sdk.sendPostSyncRequest(url, senid, aPPKey, aPPSecret, token, taxnum, method, content);
JSONObject result = JSONObject.parseObject(json);
String code = result.getString("code");
System.out.println(json+"==========>>");
System.out.println(json+"==========>>");
System.out.println(json+"==========>>");
if (!code.equals("E0000")){
String errormsg = result.getString("describe");
throw new XXXException(errormsg+",开票失败,请稍后再试");
}else {
JSONObject result1 = result.getJSONObject("result");
//获取发票流水号
String invoiceSerialNum = result1.getString("invoiceSerialNum");
invoiceSettingDTO.setInvoiceId(invoiceSerialNum);
System.out.println(json+"========><");
//查询发票开具状态
invoiceSettingDTO.setInvoiceRed("1");//蓝票
getInfoCX(invoiceSettingDTO);
map.put("invoiceId",invoiceSerialNum);
return map;
}
} catch (Exception e){
log.error("InvoiceSettingServiceImpl.createFP.error==>{}",e.getMessage());
if(e instanceof XXXException){
throw new XXXException(ErrorCode.SYSTEM_ERROR.getCode(),e.getMessage());
}
throw new XXXException(ErrorCode.SYSTEM_ERROR.getCode(),"系统异常,请稍后再试!");
}
}
六、查询开票结果
1.相关请求参数
2.响应结果示例:
{
"code": "E0000",
"describe": "获取成功",
"result": [
{
"serialNo": "19010211130401000006",
"orderNo": "1001000011161",
"status": "2",
"statusMsg": "开票完成(最终状态)",
"failCause": "",
"pdfUrl": "https://invtest.jss.com.cn/group1/M00/0D/A4/wKjScVwsK6CAFzLgAABsVO-OKaE630.pdf",
"pictureUrl": "nnfpkf.jss.com.cn/ArQ6dFE3-9o5x4B",
"invoiceTime": 1546398919000,
"invoiceCode": "131880930142",
"invoiceNo": "18757776",
"exTaxAmount": "0.38",
"taxAmount": "0.02",
"payerName": "个人2",
"payerTaxNo": "110101TRDX8RQU1",
"invoiceKind": "电子增值税普通发票",
"checkCode": "72969719882523170140",
"invoiceItems": [
{
"itemName": "门票",
"itemUnit": "张",
"itemPrice": "0.300000000000000000",
"itemTaxRate": "0.06",
"itemNum": "2.000000000000000000",
"itemAmount": "0.60",
"itemTaxAmount": "0.03",
"itemSpec": "",
"itemCode": "3070101000000000000",
"isIncludeTax": "true",
"invoiceLineProperty": "2",
"zeroRateFlag": "",
"favouredPolicyName": "",
"favouredPolicyFlag": "0"
},
……
]
},
……
]
}
3.示例代码
public Map<String,Object> getInfo(InvoiceSettingDTO invoiceSettingDTO) {
try {
Map<String,Object> map = new HashMap<>();
String status ="";
String pdfUrl ="";
String pictureUrl ="";
String invoiceCode ="";//发票代码
String invoiceNo ="";//发票号码
LambdaQueryWrapper<InvoiceInfo> lq1 = new LambdaQueryWrapper<>();
lq1.eq(InvoiceInfo::getOrderId,invoiceSettingDTO.getOrderId());
InvoiceInfo invoiceInfo = invoiceInfoMapper.selectOne(lq1);
String senid = UUID.randomUUID().toString().replace("-", ""); // 唯一标识,32位随机码,无需修改,保持默认即可
String method = "nuonuo.ElectronInvoice.queryInvoiceResult"; // API方法名
NNOpenSDK sdk = NNOpenSDK.getIntance();
String token = getToken();
JSONObject jsonObject = new JSONObject();
jsonObject.put("serialNos",invoiceSettingDTO.getInvoiceId());
jsonObject.put("isOfferInvoiceDetail","0");
String content = jsonObject.toJSONString();
log.info("author in ==>"+content);
String json = sdk.sendPostSyncRequest(baseURL, senid, aPPKey, aPPSecret, token, taxnum, method, content);
log.info("author out ==>"+json);
//获取结果json
JSONObject result = JSONObject.parseObject(json);
String code = result.getString("code");
if (code.equals("E0000")){
//获取结果集
List<InvoiceInfoVO> infoVOS = result.getJSONArray("result").toJavaList(InvoiceInfoVO.class);
invoiceSettingDTO.getInvoiceSerialNum();
if (infoVOS.size()>=0){
status = infoVOS.get(0).getStatus();
pdfUrl = infoVOS.get(0).getPdfUrl();
pictureUrl = infoVOS.get(0).getPictureUrl();
invoiceCode = infoVOS.get(0).getInvoiceCode();
invoiceNo = infoVOS.get(0).getInvoiceNo();
String failCause = infoVOS.get(0).getFailCause();
System.out.println(json+"======>>>");
System.out.println(json+"======>>>");
System.out.println(json+"======>>>");
System.out.println(json+"======>>>");
if (ObjectUtil.isNotEmpty(failCause)){
throw new XXXException(failCause+",请稍后再试,或致电010-88545091");
}
}else {
throw new XXXException("调用查询发票接口失败!");
}
map.put("status",status);
map.put("pdfUrl",pdfUrl);
map.put("pictureUrl",pictureUrl);
return map;
}else {
String errormsg = result.getString("describe");
throw new HbsxException(errormsg+",请稍后再试");
}
} catch (Exception e){
log.error("InvoiceSettingServiceImpl.getInfo.error==>{}",e.getMessage());
if(e instanceof XXXException){
throw new XXXException(ErrorCode.SYSTEM_ERROR.getCode(),e.getMessage());
}
throw new XXXException(ErrorCode.SYSTEM_ERROR.getCode(),"系统异常,请稍后再试!");
}
}
4.返回码说明:
以上就是诺诺发票的自助开票和查询接口的使用,其他冲红等接口与这两个接口调用方式一致,如有侵权,请联系我删除文章,学习之路山高路远,还请各位大佬指正!