KKSU
2024-01-19 eed76fde47443e42bfbd3c7ceb1500016b1abc88
fapiao
10 files added
10 files modified
805 ■■■■■ changed files
src/main/java/cc/mrbird/febs/mall/controller/AdminMallOrderController.java 15 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/mall/controller/ApiMallInvoiceController.java 33 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/mall/controller/ViewMallOrderController.java 11 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/mall/dto/AdminMallInvoiceDto.java 16 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/mall/dto/ApiMallInvoiceDto.java 23 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/mall/entity/MallInvoice.java 65 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/mall/entity/MallOrderInfo.java 4 ●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/mall/mapper/MallInvoiceMapper.java 21 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/mall/mapper/MallOrderInfoMapper.java 4 ●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/mall/quartz/WxxcxJob.java 13 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/mall/service/MallInvoiceService.java 22 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/mall/service/impl/MallInvoiceServiceImpl.java 97 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/mall/vo/ApiMallInvoiceVo.java 30 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/pay/controller/XcxPayController.java 1 ●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/pay/service/WxFaPiaoService.java 4 ●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/pay/service/impl/WxFaPiaoServiceImpl.java 105 ●●●●● patch | view | raw | blame | history
src/main/resources/mapper/modules/MallInvoiceMapper.xml 51 ●●●●● patch | view | raw | blame | history
src/main/resources/mapper/modules/MallOrderInfoMapper.xml 8 ●●●●● patch | view | raw | blame | history
src/main/resources/templates/febs/views/modules/order/invoiceList.html 276 ●●●●● patch | view | raw | blame | history
src/test/java/cc/mrbird/febs/ProfitTest.java 6 ●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/mall/controller/AdminMallOrderController.java
@@ -22,6 +22,7 @@
import cc.mrbird.febs.mall.service.IAdminMallGoodsService;
import cc.mrbird.febs.mall.service.IAdminMallOrderService;
import cc.mrbird.febs.mall.service.IApiMallTeamLeaderService;
import cc.mrbird.febs.mall.service.MallInvoiceService;
import cc.mrbird.febs.mall.vo.AdminAddAddressTreeVo;
import cc.mrbird.febs.mall.vo.AdminMallOrderRefundAddressVo;
import cc.mrbird.febs.pay.model.OrderStateDto;
@@ -661,4 +662,18 @@
        return new FebsResponse().success().data(data);
    }
    private final MallInvoiceService mallInvoiceService;
    /**
     * 发票列表
     * @param mallInvoiceDto
     * @param request
     * @return
     */
    @GetMapping("invoiceList")
    public FebsResponse invoiceList(AdminMallInvoiceDto mallInvoiceDto, QueryRequest request) {
        Map<String, Object> data = getDataTable(mallInvoiceService.getInvoiceList(mallInvoiceDto, request));
        return new FebsResponse().success().data(data);
    }
}
src/main/java/cc/mrbird/febs/mall/controller/ApiMallInvoiceController.java
New file
@@ -0,0 +1,33 @@
package cc.mrbird.febs.mall.controller;
import cc.mrbird.febs.common.entity.FebsResponse;
import cc.mrbird.febs.mall.dto.ApiMallInvoiceDto;
import cc.mrbird.febs.mall.service.MallInvoiceService;
import cc.mrbird.febs.mall.vo.ApiMallInvoiceVo;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping(value = "/api/invoice")
@Api(value = "ApiMallInvoiceController", tags = "发票管理")
public class ApiMallInvoiceController {
    private final MallInvoiceService mallInvoiceService;
    @ApiOperation(value = "获取发票列表", notes = "获取发票列表")
    @ApiResponses({
            @ApiResponse(code = 200, message = "success", response = ApiMallInvoiceVo.class)
    })
    @GetMapping(value = "/getInvoices")
    public FebsResponse getInvoices(@RequestBody ApiMallInvoiceDto apiMallInvoiceDto) {
        return mallInvoiceService.getInvoices(apiMallInvoiceDto);
    }
}
src/main/java/cc/mrbird/febs/mall/controller/ViewMallOrderController.java
@@ -255,4 +255,15 @@
        return FebsUtil.view("modules/order/goodsStatistics");
    }
    /**
     * 发票列表
     * @return
     */
    @GetMapping("invoiceList")
    @RequiresPermissions("invoiceList:view")
    public String invoiceList() {
        return FebsUtil.view("modules/order/invoiceList");
    }
}
src/main/java/cc/mrbird/febs/mall/dto/AdminMallInvoiceDto.java
New file
@@ -0,0 +1,16 @@
package cc.mrbird.febs.mall.dto;
import io.swagger.annotations.ApiModel;
import lombok.Data;
@Data
@ApiModel(value = "AdminMallInvoiceDto", description = "参数接收类")
public class AdminMallInvoiceDto {
    private Integer state;//开票状态:0:未开票 1:开票中 2:已开票
    private String orderNo;
    private String payOrderNo;
}
src/main/java/cc/mrbird/febs/mall/dto/ApiMallInvoiceDto.java
New file
@@ -0,0 +1,23 @@
package cc.mrbird.febs.mall.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
@ApiModel(value = "ApiMallInvoiceDto", description = "参数接收类")
public class ApiMallInvoiceDto {
    @ApiModelProperty(value = "一页数量", example = "10")
    private Integer pageSize;
    @ApiModelProperty(value = "第几页", example = "1")
    private Integer pageNum;
    @ApiModelProperty(value = "开票状态:0:未开票 1:开票中 2:已开票")
    private Integer state;
    @ApiModelProperty(hidden = true)
    private Long memberId;
}
src/main/java/cc/mrbird/febs/mall/entity/MallInvoice.java
New file
@@ -0,0 +1,65 @@
package cc.mrbird.febs.mall.entity;
import cc.mrbird.febs.common.entity.BaseEntity;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.math.BigDecimal;
@Data
@TableName("mall_invoice")
public class MallInvoice extends BaseEntity {
    private Long memberId;
    /**
     * fapiao_apply_id
     * 发票申请单号,唯一标识一次开票行为。微信支付场景下,为微信支付订单号;非微信支付场景下,为调用【获取抬头填写链接】接口时指定的发票申请单号
     * 对应订单信息的pay_order_no支付订单号
     */
    private String fapiaoApplyId;
    /**
     *【购买方类型】 购买方类型
     * 可选取值:
     * INDIVIDUAL: 个人
     * ORGANIZATION: 单位
     */
    private String type;
    /**
     *【名称】 购买方名称
     */
    private String name;
    /**
     *【纳税人识别号】 购买方纳税人识别号,购买方类型为ORGANIZATION时必须存在
     */
    private String taxpayerId;
    /**
     *【地址】 购买方地址
     */
    private String address;
    /**
     *【电话】 购买方电话
     */
    private String telephone;
    /**
     *【开户银行】 购买方开户银行
     */
    private String bankName;
    /**
     *【银行账号】 购买方银行账号
     */
    private String bankAccount;
    /**
     * 匹配的订单ID
     */
    private Long orderId;
    private String orderNo;//订单编号
    private Integer status;//订单状态
    private BigDecimal amount;//订单金额
    private String goodsImg;//商品图片
    private Integer state;//开票状态:0:未开票 1:开票中 2:已开票
    private String invoiceUrl;//发票的URL路径
}
src/main/java/cc/mrbird/febs/mall/entity/MallOrderInfo.java
@@ -132,6 +132,10 @@
     * 是否送货上门  1:是 2:否
     */
    private Integer isHome;
    /**
     * 是否开过发票 0:未开 1:已开
     */
    private Integer isInvoice;
    @TableField(exist = false)
    private String orderIds;
src/main/java/cc/mrbird/febs/mall/mapper/MallInvoiceMapper.java
New file
@@ -0,0 +1,21 @@
package cc.mrbird.febs.mall.mapper;
import cc.mrbird.febs.mall.dto.AdminMallInvoiceDto;
import cc.mrbird.febs.mall.dto.ApiMallInvoiceDto;
import cc.mrbird.febs.mall.entity.MallInvoice;
import cc.mrbird.febs.mall.vo.ApiMallInvoiceVo;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.apache.ibatis.annotations.Param;
import java.util.List;
public interface MallInvoiceMapper extends BaseMapper<MallInvoice> {
    IPage<ApiMallInvoiceVo> selectApiMallInvoiceVoInPage(IPage<ApiMallInvoiceVo> page, @Param("record")ApiMallInvoiceDto apiMallInvoiceDto);
    List<MallInvoice> selectByFapiaoApplyIdAndMemberId(@Param("fapiaoApplyId")String fapiaoApplyId, @Param("memberId")Long memberId);
    IPage<MallInvoice> getInvoiceListInPage(Page<MallInvoice> page, @Param("record")AdminMallInvoiceDto mallInvoiceDto);
}
src/main/java/cc/mrbird/febs/mall/mapper/MallOrderInfoMapper.java
@@ -47,6 +47,8 @@
    List<MallOrderInfo> selectOrderInfoByStatus(@Param("status") Integer status);
    List<MallOrderInfo> selectOrderInfoByStatusAndIsInvoice(@Param("status") Integer status,@Param("isInvoice") Integer isInvoice);
    List<MallOrderInfo> selectOrderInfoUpTime(@Param("status") Integer status);
    Integer selectCntDirectOrTeam(@Param("type") Integer type, @Param("inviteId") String inviteId);
@@ -83,4 +85,6 @@
    IPage<AdminGoodsStatisticsVo> getGoodsStatisticsInPage(Page<AdminGoodsStatisticsVo> page, @Param("record") MallOrderItem mallOrderItem);
    BigDecimal selectAmountOrTeamAmount(@Param("inviteId") String inviteId, @Param("type") Integer type);
    MallOrderInfo selectBypayOrderNo(@Param("payOrderNo")String payOrderNo);
}
src/main/java/cc/mrbird/febs/mall/quartz/WxxcxJob.java
@@ -6,6 +6,7 @@
import cc.mrbird.febs.common.utils.SpringContextHolder;
import cc.mrbird.febs.mall.entity.MallMemberCoupon;
import cc.mrbird.febs.mall.mapper.MallMemberCouponMapper;
import cc.mrbird.febs.mall.service.MallInvoiceService;
import cc.mrbird.febs.pay.util.WechatConfigure;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
@@ -37,6 +38,8 @@
    private RedisUtils redisUtils;
    @Autowired
    private MallMemberCouponMapper mallMemberCouponMapper;
    @Autowired
    private MallInvoiceService mallInvoiceService;
    @Resource
    RestTemplate restTemplate;
//    @Autowired
@@ -87,4 +90,14 @@
    }
    /**
     * 更新发票记录表数据
     * 定时,每天凌晨一点
     * 已完成的订单,更新到发票记录中
     */
    @Scheduled(cron = "0 0 1 * * ?")
    public void mallInvoiceJob() {
        mallInvoiceService.mallInvoiceJob();
    }
}
src/main/java/cc/mrbird/febs/mall/service/MallInvoiceService.java
New file
@@ -0,0 +1,22 @@
package cc.mrbird.febs.mall.service;
import cc.mrbird.febs.common.entity.FebsResponse;
import cc.mrbird.febs.common.entity.QueryRequest;
import cc.mrbird.febs.mall.dto.AdminMallInvoiceDto;
import cc.mrbird.febs.mall.dto.ApiMallInvoiceDto;
import cc.mrbird.febs.mall.entity.MallInvoice;
import com.baomidou.mybatisplus.core.metadata.IPage;
public interface MallInvoiceService {
    FebsResponse getInvoices(ApiMallInvoiceDto apiMallInvoiceDto);
     /**
     * 更新发票记录表数据
     * 定时,每天凌晨一点
     * 已完成的订单,更新到发票记录中
     */
    void mallInvoiceJob();
    IPage<MallInvoice> getInvoiceList(AdminMallInvoiceDto mallInvoiceDto, QueryRequest request);
}
src/main/java/cc/mrbird/febs/mall/service/impl/MallInvoiceServiceImpl.java
New file
@@ -0,0 +1,97 @@
package cc.mrbird.febs.mall.service.impl;
import cc.mrbird.febs.common.entity.FebsResponse;
import cc.mrbird.febs.common.entity.QueryRequest;
import cc.mrbird.febs.common.enumerates.OrderStatusEnum;
import cc.mrbird.febs.common.utils.LoginUserUtil;
import cc.mrbird.febs.mall.dto.AdminMallInvoiceDto;
import cc.mrbird.febs.mall.dto.ApiMallInvoiceDto;
import cc.mrbird.febs.mall.entity.MallInvoice;
import cc.mrbird.febs.mall.entity.MallMember;
import cc.mrbird.febs.mall.entity.MallOrderInfo;
import cc.mrbird.febs.mall.entity.MallOrderItem;
import cc.mrbird.febs.mall.mapper.MallInvoiceMapper;
import cc.mrbird.febs.mall.mapper.MallOrderInfoMapper;
import cc.mrbird.febs.mall.mapper.MallOrderItemMapper;
import cc.mrbird.febs.mall.service.MallInvoiceService;
import cc.mrbird.febs.mall.vo.AdminMallOrderInfoVo;
import cc.mrbird.febs.mall.vo.ApiMallInvoiceVo;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.List;
@Slf4j
@Service
@RequiredArgsConstructor
public class MallInvoiceServiceImpl extends ServiceImpl<MallInvoiceMapper, MallInvoice> implements MallInvoiceService {
    private final MallOrderInfoMapper mallOrderInfoMapper;
    private final MallOrderItemMapper mallOrderItemMapper;
    @Override
    public FebsResponse getInvoices(ApiMallInvoiceDto apiMallInvoiceDto) {
        MallMember member = LoginUserUtil.getLoginUser();
        apiMallInvoiceDto.setMemberId(member.getId());
        IPage<ApiMallInvoiceVo> page = new Page<>(apiMallInvoiceDto.getPageNum(), apiMallInvoiceDto.getPageSize());
        IPage<ApiMallInvoiceVo> apiMallInvoiceVoIPage = this.baseMapper.selectApiMallInvoiceVoInPage(page, apiMallInvoiceDto);
        return new FebsResponse().success().data(apiMallInvoiceVoIPage);
    }
    @Override
    public void mallInvoiceJob() {
        /**
         * 获取当前已经是已完成且未开过发票的订单
         */
        List<MallOrderInfo> mallOrderInfos = mallOrderInfoMapper.selectOrderInfoByStatusAndIsInvoice(OrderStatusEnum.FINISH.getValue(), 0);
        if(CollUtil.isEmpty(mallOrderInfos)){
            return;
        }
        for(MallOrderInfo mallOrderInfo : mallOrderInfos){
            /**
             * 生成开票记录
             */
            Long memberId = mallOrderInfo.getMemberId();
            /**
             * fapiao_apply_id
             * 发票申请单号,唯一标识一次开票行为。微信支付场景下,为微信支付订单号;非微信支付场景下,为调用【获取抬头填写链接】接口时指定的发票申请单号
             * 对应订单信息的pay_order_no支付订单号
             */
            String payOrderNo = mallOrderInfo.getPayOrderNo();
            List<MallInvoice> mallInvoices = this.baseMapper.selectByFapiaoApplyIdAndMemberId(payOrderNo, memberId);
            if(CollUtil.isNotEmpty(mallInvoices)){
                continue;
            }
            MallInvoice mallInvoice = new MallInvoice();
            mallInvoice.setMemberId(memberId);
            mallInvoice.setFapiaoApplyId(payOrderNo);
            mallInvoice.setOrderNo(mallOrderInfo.getOrderNo());
            mallInvoice.setOrderId(mallOrderInfo.getId());
            mallInvoice.setStatus(mallOrderInfo.getStatus());
            mallInvoice.setAmount(mallOrderInfo.getAmount());
            List<MallOrderItem> mallOrderItemList = mallOrderItemMapper.selectListByOrderId(mallOrderInfo.getId());
            mallInvoice.setGoodsImg(mallOrderItemList.get(0).getSkuImage());
            mallInvoice.setState(0);
            this.baseMapper.insert(mallInvoice);
            /**
             * 更新订单为已开票
             */
            mallOrderInfo.setIsInvoice(1);
            mallOrderInfoMapper.updateById(mallOrderInfo);
        }
    }
    @Override
    public IPage<MallInvoice> getInvoiceList(AdminMallInvoiceDto mallInvoiceDto, QueryRequest request) {
        Page<MallInvoice> page = new Page<>(request.getPageNum(), request.getPageSize());
        return  this.baseMapper.getInvoiceListInPage(page, mallInvoiceDto);
    }
}
src/main/java/cc/mrbird/febs/mall/vo/ApiMallInvoiceVo.java
New file
@@ -0,0 +1,30 @@
package cc.mrbird.febs.mall.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.math.BigDecimal;
@Data
@ApiModel(value = "ApiMallInvoiceVo", description = "发票")
public class ApiMallInvoiceVo {
    @ApiModelProperty(value = "ID")
    private Long id;
    @ApiModelProperty(value = "订单编号")
    private String orderNo;
    @ApiModelProperty(value = "订单金额")
    private BigDecimal amount;
    @ApiModelProperty(value = "商品图片")
    private String goodsImg;
    @ApiModelProperty(value = "开票状态:0:未开票 1:开票中 2:已开票")
    private Integer state;
    @ApiModelProperty(value = "发票的URL路径")
    private String invoiceUrl;
}
src/main/java/cc/mrbird/febs/pay/controller/XcxPayController.java
@@ -250,6 +250,7 @@
//    }
    /**
     * 微信电子发票回调接口
     * POST方式回调
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
src/main/java/cc/mrbird/febs/pay/service/WxFaPiaoService.java
@@ -3,14 +3,10 @@
import org.springframework.web.bind.annotation.RequestBody;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.SignatureException;
import java.text.ParseException;
import java.util.Map;
public interface WxFaPiaoService {
src/main/java/cc/mrbird/febs/pay/service/impl/WxFaPiaoServiceImpl.java
@@ -3,14 +3,19 @@
import cc.mrbird.febs.common.properties.XcxProperties;
import cc.mrbird.febs.common.utils.AppContants;
import cc.mrbird.febs.common.utils.SpringContextHolder;
import cc.mrbird.febs.mall.entity.MallInvoice;
import cc.mrbird.febs.mall.entity.MallOrderInfo;
import cc.mrbird.febs.mall.entity.MallOrderItem;
import cc.mrbird.febs.mall.mapper.MallInvoiceMapper;
import cc.mrbird.febs.mall.mapper.MallOrderInfoMapper;
import cc.mrbird.febs.mall.mapper.MallOrderItemMapper;
import cc.mrbird.febs.pay.model.FPCertificateVo;
import cc.mrbird.febs.pay.model.FPCertificates;
import cc.mrbird.febs.pay.model.FPEncryptCertificate;
import cc.mrbird.febs.pay.service.WxFaPiaoService;
import cc.mrbird.febs.pay.util.JCEUtil;
import cc.mrbird.febs.pay.util.RandomStringGenerator;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
@@ -18,10 +23,7 @@
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import okhttp3.HttpUrl;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPatch;
@@ -56,6 +58,8 @@
public class WxFaPiaoServiceImpl implements WxFaPiaoService {
    private final MallOrderInfoMapper mallOrderInfoMapper;
    private final MallOrderItemMapper mallOrderItemMapper;
    private final MallInvoiceMapper mallInvoiceMapper;
    private final XcxProperties xcxProperties = SpringContextHolder.getBean(XcxProperties.class);
@@ -291,7 +295,7 @@
        log.info("获取到的body信息:" + body);
        //应对签名探测流量
        if(signature.contains("WECHATPAY/SIGNTEST")){
            map.put("code","500");
            map.put("code",500);
            map.put("message", "失败");
            return map;
        }
@@ -348,8 +352,57 @@
                    log.info("微信电子发票回调接口....resource解密-JSONObject:"+parseObj);
                    String fapiao_apply_id = parseObj.getStr("fapiao_apply_id");
                    log.info("微信电子发票回调接口....resource解密-fapiao_apply_id:"+fapiao_apply_id);
                    MallOrderInfo mallOrderInfo = mallOrderInfoMapper.selectByOrderNo(fapiao_apply_id);
                    MallOrderInfo mallOrderInfo = mallOrderInfoMapper.selectBypayOrderNo(fapiao_apply_id);
                    if(ObjectUtil.isEmpty(mallOrderInfo)){
                        map.put("code",404);
                        map.put("message", "订单不存在");
                        return map;
                    }
                    if(ObjectUtil.isNotEmpty(mallOrderInfo)){
                        //更新订单状态
                        mallOrderInfo.setIsInvoice(1);
                        mallOrderInfoMapper.updateById(mallOrderInfo);
                        //获取用户的抬头信息
                        String userInvoiceInfo = getUserInvoiceInfo(fapiao_apply_id);
                        JSONObject userInvoiceInfoJson = JSONUtil.parseObj(userInvoiceInfo);
                        //查看当前订单是否已经开具发票
                        Long memberId = mallOrderInfo.getMemberId();
                        String payOrderNo = mallOrderInfo.getPayOrderNo();
                        List<MallInvoice> mallInvoices = mallInvoiceMapper.selectByFapiaoApplyIdAndMemberId(fapiao_apply_id, mallOrderInfo.getMemberId());
                        if(CollUtil.isEmpty(mallInvoices)){
                            MallInvoice mallInvoice = new MallInvoice();
                            mallInvoice.setMemberId(memberId);
                            mallInvoice.setFapiaoApplyId(payOrderNo);
                            mallInvoice.setOrderNo(mallOrderInfo.getOrderNo());
                            mallInvoice.setOrderId(mallOrderInfo.getId());
                            mallInvoice.setStatus(mallOrderInfo.getStatus());
                            mallInvoice.setAmount(mallOrderInfo.getAmount());
                            List<MallOrderItem> mallOrderItemList = mallOrderItemMapper.selectListByOrderId(mallOrderInfo.getId());
                            mallInvoice.setGoodsImg(mallOrderItemList.get(0).getSkuImage());
                            mallInvoice.setState(1);
                            mallInvoice.setType(userInvoiceInfoJson.getStr("type"));
                            mallInvoice.setName(userInvoiceInfoJson.getStr("name"));
                            mallInvoice.setTaxpayerId(userInvoiceInfoJson.getStr("taxpayer_id"));
                            mallInvoice.setAddress(userInvoiceInfoJson.getStr("address"));
                            mallInvoice.setTelephone(userInvoiceInfoJson.getStr("telephone"));
                            mallInvoice.setBankName(userInvoiceInfoJson.getStr("bank_name"));
                            mallInvoice.setBankAccount(userInvoiceInfoJson.getStr("bank_account"));
                            mallInvoiceMapper.insert(mallInvoice);
                        }else{
                            MallInvoice mallInvoice = mallInvoices.get(0);
                            if(2 != mallInvoice.getState()){
                                mallInvoice.setState(1);
                                mallInvoice.setType(userInvoiceInfoJson.getStr("type"));
                                mallInvoice.setName(userInvoiceInfoJson.getStr("name"));
                                mallInvoice.setTaxpayerId(userInvoiceInfoJson.getStr("taxpayer_id"));
                                mallInvoice.setAddress(userInvoiceInfoJson.getStr("address"));
                                mallInvoice.setTelephone(userInvoiceInfoJson.getStr("telephone"));
                                mallInvoice.setBankName(userInvoiceInfoJson.getStr("bank_name"));
                                mallInvoice.setBankAccount(userInvoiceInfoJson.getStr("bank_account"));
                                mallInvoiceMapper.updateById(mallInvoice);
                            }
                        }
                        //省略查询订单
                        //此处处理业务
                        map.put("code","SUCCESS");
@@ -358,17 +411,55 @@
                        return map;
                    }
                }
                map.put("code","RESOURCE_NOT_EXISTS");
                map.put("code",404);
                map.put("message", "订单不存在");
                return map;
            }catch (Exception e) {
                e.printStackTrace();
            }
        }
        map.put("code","500");
        map.put("code",500);
        map.put("message", "失败");
        return map;
    }
    /**
     * 获取用户的抬头信息
     */
    public String getUserInvoiceInfo(String fapiaoApplyId) throws IOException {
        //初始化请求参数
        Map<String, Object> params = new HashMap<>();
        params.put("scene","WITH_WECHATPAY");
        params.put("fapiao_apply_id","4200002070202401175245187105");
        String baseUrl = "https://api.mch.weixin.qq.com";
        String canonicalUrl = "/v3/new-tax-control-fapiao/user-title";
        String urlparm = baseUrl+canonicalUrl;//有参数的get请求,加密是需要把参数加载进去URL
        List<BasicNameValuePair> parameters = new ArrayList<>();
        for (Map.Entry<String, Object> entry : params.entrySet()) {
            parameters.add(new BasicNameValuePair(entry.getKey(), entry.getValue().toString()));
        }
        StringBuilder dataparm = new StringBuilder();
        params.forEach((k, v) -> dataparm.append("&" + k + "=" + v));
        String string = dataparm.toString();
        if (!"".equals(string)) {
            urlparm = baseUrl+canonicalUrl + "?" + dataparm.substring(1);
        }
        PrivateKey privateKey = this.getPrivateKeyV3();
        String postStr = null;
        try {
            postStr = this.createAuthorization(
                    "GET",
                    urlparm,
                    "",
                    privateKey
            );
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return this.sendGet(baseUrl+canonicalUrl, params, "WECHATPAY2-SHA256-RSA2048 "+postStr);
    }
    /**
     * 验证签名
src/main/resources/mapper/modules/MallInvoiceMapper.xml
New file
@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cc.mrbird.febs.mall.mapper.MallInvoiceMapper">
    <select id="selectApiMallInvoiceVoInPage" resultType="cc.mrbird.febs.mall.vo.ApiMallInvoiceVo">
        select
            a.id,
            a.order_no,
            a.amount,
            a.goods_img,
            a.state,
            a.invoice_url
        from mall_invoice a
        <where>
            <if test="record.memberId != null">
                and a.member_id=#{record.memberId}
            </if>
            <if test="record.state != null">
                and a.state=#{record.state}
            </if>
        </where>
        order by a.created_time desc
    </select>
    <select id="selectByFapiaoApplyIdAndMemberId" resultType="cc.mrbird.febs.mall.entity.MallInvoice">
        select
            a.*
        from mall_invoice a
        where fapiao_apply_id = #{fapiaoApplyId}
            and member_id = #{memberId}
    </select>
    <select id="getInvoiceListInPage" resultType="cc.mrbird.febs.mall.entity.MallInvoice">
        select
        a.*
        from mall_invoice a
        <where>
            <if test="record.state != null">
                and a.state = #{record.state}
            </if>
            <if test="record.orderNo != null">
                and a.order_no = #{record.orderNo}
            </if>
            <if test="record.payOrderNo != null">
                and a.fapiao_apply_id = #{record.payOrderNo}
            </if>
        </where>
        order by a.created_time desc
    </select>
</mapper>
src/main/resources/mapper/modules/MallOrderInfoMapper.xml
@@ -233,6 +233,10 @@
        select * from mall_order_info where status=#{status}
    </select>
    <select id="selectOrderInfoByStatusAndIsInvoice" resultType="cc.mrbird.febs.mall.entity.MallOrderInfo">
        select * from mall_order_info where status = #{status} and is_invoice = #{isInvoice}
    </select>
    <select id="selectOrderInfoUpTime" resultType="cc.mrbird.febs.mall.entity.MallOrderInfo">
        select * from mall_order_info where status=#{status} and now() > date_add(order_time,interval 1440 minute)
    </select>
@@ -458,4 +462,8 @@
            </if>
        </where>
    </select>
    <select id="selectBypayOrderNo" resultType="cc.mrbird.febs.mall.entity.MallOrderInfo">
        select * from mall_order_info where pay_order_no = #{payOrderNo}
    </select>
</mapper>
src/main/resources/templates/febs/views/modules/order/invoiceList.html
New file
@@ -0,0 +1,276 @@
<div class="layui-fluid layui-anim febs-anim" id="febs-invoice" lay-title="发票管理">
    <div class="layui-row febs-container">
        <div class="layui-col-md12">
            <div class="layui-card">
                <div class="layui-card-body febs-table-full">
                    <form class="layui-form layui-table-form" lay-filter="user-table-form">
                        <div class="layui-form-item">
                            <div class="layui-col-md10">
                                <div class="layui-inline">
                                    <label class="layui-form-label">编号:</label>
                                    <div class="layui-input-inline">
                                        <input type="text" placeholder="发票申请单号/微信支付订单号" name="payOrderNo" autocomplete="off" class="layui-input">
                                    </div>
                                </div>
                                <div class="layui-inline">
                                    <label class="layui-form-label">订单编号:</label>
                                    <div class="layui-input-inline">
                                        <input type="text" placeholder="订单编号" name="orderNo" autocomplete="off" class="layui-input">
                                    </div>
                                </div>
                                <div class="layui-inline">
                                    <label class="layui-form-label layui-form-label-sm">状态:</label>
                                    <div class="layui-input-inline">
                                        <select name="state">
                                            <option value="">请选择</option>
                                            <option value="0">未开票</option>
                                            <option value="1">开票中</option>
                                            <option value="2">已开票</option>
                                        </select>
                                    </div>
                                </div>
                            </div>
                            <div class="layui-col-md2 layui-col-sm12 layui-col-xs12 table-action-area">
                                <div class="layui-btn layui-btn-sm layui-btn-primary febs-button-blue-plain table-action" id="query">
                                    <i class="layui-icon">&#xe848;</i>
                                </div>
                                <div class="layui-btn layui-btn-sm layui-btn-primary febs-button-green-plain table-action" id="reset">
                                    <i class="layui-icon">&#xe79b;</i>
                                </div>
                            </div>
                        </div>
                    </form>
                    <table lay-filter="invoiceTable" lay-data="{id: 'invoiceTable'}"></table>
                    <style type="text/css">
                        .layui-table cell{
                            text-align:center;
                            height: auto;
                            white-space: nowrap; /*文本不会换行,在同一行显示*/
                            overflow: hidden; /*超出隐藏*/
                            text-overflow: ellipsis; /*省略号显示*/
                        }
                        .layui-table img{
                            max-width:100px
                        }
                        ::-webkit-scrollbar {
                            height: 20px !important;
                            background-color: #f4f4f4;
                        }
                    </style>
                </div>
            </div>
        </div>
    </div>
</div>
<!-- 表格操作栏 start -->
<script type="text/html" id="user-option">
    <span shiro:lacksPermission="user:view,user:update,user:delete">
        <span class="layui-badge-dot febs-bg-orange"></span> 无权限
    </span>
    <a lay-event="edit" shiro:hasPermission="user:update"><i
            class="layui-icon febs-edit-area febs-blue">&#xe7a5;</i></a>
</script>
<!-- 表格操作栏 end -->
<script data-th-inline="none" type="text/javascript">
    // 引入组件并初始化
    layui.use([ 'jquery', 'form', 'table', 'febs', 'formSelects', 'upload','laydate'], function () {
        var $ = layui.jquery,
            febs = layui.febs,
            form = layui.form,
            table = layui.table,
            upload = layui.upload,
            $view = $('#febs-invoice'),
            $query = $view.find('#query'),
            $reset = $view.find('#reset'),
            $searchForm = $view.find('form'),
            $add = $view.find('#add'),
            sortObject = {field: 'orderTime', type: 'desc'},
            formSelects = layui.formSelects,
            laydate = layui.laydate,
            tableIns;
        let currPageOrder = 1;//首先默认值为1,防止出错
        //获取当前页
        currPageOrder = $view.find(".layui-laypage-em").next().html();
        form.render();
        formSelects.render();
        // 表格初始化
        initInvoiceTable();
        upload.render({
            elem: '#importDeliver'
            ,url: 'admin/order/importDeliver' //此处配置你自己的上传接口即可
            ,accept: 'file' //普通文件
            ,done: function(res){
                console.log("123");
                febs.alert.success('操作成功');
                $query.click();
            }
        });
        // 初始化表格操作栏各个按钮功能
        table.on('tool(invoiceTable)', function (obj) {
            var data = obj.data,
                layEvent = obj.event;
            if (layEvent === 'deliverGoods') {
                febs.modal.open('发货', 'modules/order/deliverGoods/' + data.id, {
                    btn: ['确认','取消'],
                    yes: function (index, layero) {
                        $('#deliver-update').find('#submit').trigger('click');
                        // $query.click();
                    },
                    btn2: function () {
                        layer.closeAll();
                    }
                });
            }
            if (layEvent === 'updateDeliver') {
                febs.modal.open('修改物流信息', 'modules/order/deliverGoods/' + data.id, {
                    btn: ['确认','取消'],
                    yes: function (index, layero) {
                        $('#deliver-update').find('#deliverInfoSubmit').trigger('click');
                        // $query.click();
                    },
                    btn2: function () {
                        layer.closeAll();
                    }
                });
            }
            if (layEvent === 'seeOrder') {
                febs.modal.open( '订单详情', 'modules/order/orderDetail/' + data.id, {
                    maxmin: true,
                });
            }
            if (layEvent === 'cancelOrder') {
                febs.modal.confirm('取消订单', '确认取消订单?', function () {
                    cancelOrder(data.id);
                });
            }
            if (layEvent === 'seePayImage') {
                var t = $view.find('#seePayImage'+data.id+'');
                //页面层
                layer.open({
                    type: 1,
                    title: "支付凭证",
                    skin: 'layui-layer-rim', //加上边框
                    area: ['80%', '80%'], //宽高
                    shadeClose: true, //开启遮罩关闭
                    end: function (index, layero) {
                        return false;
                    },
                    content: '<div style="text-align:center"><img src="' + $(t).attr('src') + '" /></div>'
                });
            }
        });
        function cancelOrder(id) {
            febs.get(ctx + 'admin/order/cancelOrder/' + id, null, function () {
                febs.alert.success('操作成功');
                $query.click();
            });
        }
        // 查询按钮
        $query.on('click', function () {
            var params = $.extend(getQueryParams(), {field: sortObject.field, order: sortObject.type});
            tableIns.reload({where: params, page: {curr: currPageOrder}});
        });
        // 刷新按钮
        $reset.on('click', function () {
            $searchForm[0].reset();
            sortObject.type = 'null';
            tableIns.reload({where: getQueryParams(), page: {curr: currPageOrder}, initSort: sortObject});
        });
        function initInvoiceTable() {
            tableIns = febs.table.init({
                elem: $view.find('table'),
                id: 'invoiceTable',
                url: ctx + 'admin/order/invoiceList',
                defaultToolbar: [],
                //系统自带打印导出
                totalRow : true,
                cols: [[
                        {field: 'orderNo', title: '订单编号', minWidth: 100,align:'left' ,totalRowText:"合计"},
                        {field: 'fapiaoApplyId', title: '发票申请单号', minWidth: 150,align:'left'},
                        {field: 'type', title: '购买方类型',
                            templet: function (d) {
                                if (d.type === 'INDIVIDUAL') {
                                    return '<span style="color:red;">个人</span>'
                                } else if (d.status === 'ORGANIZATION') {
                                    return '<span style="color:green;">单位</span>'
                                }else{
                                    return ''
                                }
                            }, minWidth: 80,align:'center'},
                        {field: 'name', title: '名称', minWidth: 120,align:'left'},
                        {field: 'taxpayerId', title: '纳税人识别号', minWidth: 100,align:'left'},
                        {field: 'address', title: '地址', minWidth: 200,align:'left'},
                        {field: 'telephone', title: '电话', minWidth: 100,align:'left'},
                        {field: 'bankName', title: '开户银行', minWidth: 100,align:'left', totalRow:true},
                        {field: 'bankAccount', title: '银行账号', minWidth: 100,align:'left', totalRow:true},
                        {field: 'state', title: '状态',
                            templet: function (d) {
                                if (d.state === 0) {
                                    return '<span style="color:red;">待申请</span>'
                                } else if (d.state === 1) {
                                    return '<span style="color:green;">待开票</span>'
                                }else if (d.state === 2) {
                                    return '<span style="color:green;">已开票</span>'
                                }else{
                                    return ''
                                }
                            }, minWidth: 100,align:'center'},
                    {field: 'status', title: '订单状态',
                        templet: function (d) {
                            if (d.status === 1) {
                                return '<span style="color:red;">待支付</span>'
                            } else if (d.status === 2) {
                                return '<span style="color:green;">待发货</span>'
                            }else if (d.status === 3) {
                                return '<span style="color:green;">待收货</span>'
                            }else if (d.status === 4) {
                                return '<span style="color:green;">已完成</span>'
                            }else if (d.status === 5) {
                                return '<span style="color:green;">退款中</span>'
                            }else if (d.status === 6) {
                                return '<span style="color:green;">已退款</span>'
                            }else if (d.status === 7) {
                                return '<span style="color:green;">已取消</span>'
                            }else{
                                return ''
                            }
                        }, minWidth: 100,align:'center'},
                        {title: '操作',
                            templet: function (d) {
                                if(d.status === 2){
                                    return '<button class="layui-btn layui-btn-normal layui-btn-xs" lay-event="seeOrder" shiro:hasPermission="user:update">详情</button>'
                                    +'<button class="layui-btn layui-btn-normal layui-btn-xs" lay-event="deliverGoods" shiro:hasPermission="user:update">发货</button>'
                                }else if(d.status === 3){
                                    return '<button class="layui-btn layui-btn-normal layui-btn-xs" lay-event="seeOrder" shiro:hasPermission="user:update">详情</button>'
                                    +'<button class="layui-btn layui-btn-normal layui-btn-xs" lay-event="updateDeliver" shiro:hasPermission="user:update">修改物流信息</button>'
                                }else{
                                    return '<button class="layui-btn layui-btn-normal layui-btn-xs" lay-event="seeOrder" shiro:hasPermission="user:update">详情</button>'
                                }
                            },minWidth: 200,align:'center', fixed:'right'}
                ]]
            });
        }
        // 获取查询参数
        function getQueryParams() {
            return {
                orderNo: $searchForm.find('input[name="orderNo"]').val().trim(),
                payOrderNo: $searchForm.find('input[name="payOrderNo"]').val().trim(),
                state: $searchForm.find("select[name='state']").val(),
            };
        }
    })
</script>
src/test/java/cc/mrbird/febs/ProfitTest.java
@@ -202,8 +202,6 @@
        Map<String, Object> params = new HashMap<>();
        params.put("scene","WITH_WECHATPAY");
        params.put("fapiao_apply_id","4200002070202401175245187105");
        String parseObj = JSONUtil.parseObj(params).toString();
        System.out.println(parseObj);
        String baseUrl = "https://api.mch.weixin.qq.com";
        String canonicalUrl = "/v3/new-tax-control-fapiao/user-title";
@@ -219,8 +217,6 @@
        if (!"".equals(string)) {
            urlparm = baseUrl+canonicalUrl + "?" + dataparm.substring(1);
        }
        System.out.println(urlparm);
        PrivateKey privateKey = wxFaPiaoService.getPrivateKeyV3();
        String postStr = null;
        try {
@@ -235,7 +231,7 @@
        }
        String s = wxFaPiaoService.sendGet(baseUrl+canonicalUrl, params, "WECHATPAY2-SHA256-RSA2048 "+postStr);
        System.out.println(s);
                                }
    }
    @Test
    public void rankProfit2() throws IOException {//查询配置开发选项