诺诺开票开发

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;

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值