微信直连支付(V3版本)、(跨境)海关报关

1、开发准备概述:

1.1 微信直连官方文档地址:微信支付-开发者文档

        微信环境准备参考1.1官方文档,支付思维导图如下:

2、开发代码

2.1 代码环境

  • 开发语言:JAVA
  • JDK版本:1.8
  • MAVEN版本:3.8.1
  • SpringBoot: 2.2.1(建议升级,这版本有漏洞,练手无所谓)

本文版本是基于老项目改造的,没有升级,建议可根据自身情况选择最新稳定版本的架构

2.2 支付依赖

<dependency>
	<groupId>com.github.wechatpay-apiv3</groupId>
	<artifactId>wechatpay-java</artifactId>
	<version>0.2.15</version>
</dependency>

 application.yml支付相关配置

#微信直连配置
wechat:
  #商户API私钥
  merchCertFilePath: /root/conf/beta/apiclient_key.pem
  #微信支付的平台公钥,验签使用 生成秘钥方法:java -jar CertificateDownloader.jar -k ${apiV3key} -m ${mchId} -f ${mchPrivateKeyFilePath} -s ${mchSerialNo} -o ${outputFilePath}
  wechatCertFilePath: /root/conf/beta/wechatpay_6FDF4C2B9EE0E9EC46CD85FE72AF2DA111116111.pem
  mchId: 1411111111
  #证书序列号
  merchCertSerialNo: 4A844A858D1E95889D06E4AAD7276E4238111111
  appKeyV3: 1C4FCC49C061111B321111115D38A111
  appKeyV2: kjWEIXINZHILIANqazwxQA1111111111
  callback:
    pay: https://xxxx.cn/cashier/pay/wechatpay/callBack
    refund: https://xxxx.cn/cashier/pay/wechatpay/refundCallBack
  declare:
    pfxPwd: 1411111111
    pfxPath: /root/conf/beta/apiclient_cert.p12

2.3 支付代码

2.3.1 WechatPayController

package com.etone.backend.controller;

import com.alibaba.fastjson.JSONObject;
import com.etone.backend.service.CashierService;
import com.etone.backend.service.PayService;
import com.etone.backend.service.WechatPayService;
import com.etone.backend.vo.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;

@RestController
@RequestMapping("pay/wechatpay/")
public class WechatPayController {

    @Resource
    private WechatPayService wechatPayService;


    /**
     * 1.微信直连--预下单接口
     */
    @RequestMapping("wxPreOrder")
    public Result wxPreOrder(@RequestBody JSONObject reqData){
        return wechatPayService.wxPreOrder(reqData);
    }

    /**
     * 2.微信直连--支付回调
     */
    @RequestMapping("callBack")
    public JSONObject wxCallback(HttpServletRequest request) {
        JSONObject resp = new JSONObject(1);
        try {
            wechatPayService.wxCallback(request);
            resp.put("result", "success");
        }catch (Exception e) {
            resp.put("result", "failed");
        }
        return resp;
    }

    /**
     * 3.支付结果查询接口
     */
    @RequestMapping("/queryWxPayOrder")
    public Result queryWxPayOrder(@RequestBody JSONObject req){
        return wechatPayService.queryWxPayOrder(req);
    }

    /**
     * 4.退款接口
     */
    @RequestMapping("/wxOrderRefund")
    public Result wxOrderRefund(@RequestBody JSONObject req){
        return wechatPayService.wxOrderRefund(req);
    }

    /**
     * 5.微信直连对账文件处理
     * 去微信下载对账文件,每天早上10点之前跑批,跑前一天的数据
     */
    @RequestMapping("/generateTradeBillFile")
    public Result generateTradeBillFile(@RequestBody JSONObject req){
        return wechatPayService.generateTradeBillFile(req);
    }

    /**
     * 6.直连报关
     */
    @RequestMapping("/declareOrder")
    public Result declareOrder(@RequestBody JSONObject req){
        return wechatPayService.declareOrder(req);
    }

    /**
     * 7.直连报关查询
     */
    @RequestMapping("/queryDeclareOrder")
    public Result queryDeclareOrder(@RequestBody JSONObject req){
        return wechatPayService.queryDeclareOrder(req);
    }

    /**
     * 8.直连报关重推
     */
    @RequestMapping("/redeclareOrder")
    public Result redeclareOrder(@RequestBody JSONObject req){
        return wechatPayService.redeclareOrder(req);
    }

    /**
     * 9.微信原始报文查询
     */
    @RequestMapping("/queryWechatOriginalOrder")
    public Result queryWechatOriginalOrder(@RequestBody JSONObject req){
        return wechatPayService.queryWechatOriginalOrder(req);
    }

}

2.3.2 WechatPayService

package com.etone.backend.service;

import com.alibaba.fastjson.JSONObject;
import com.etone.backend.vo.Result;
import javax.servlet.http.HttpServletRequest;


public interface WechatPayService {
    /**
     * 预下单
     * @param reqData
     * @return
     */
    String wxPreOrder(JSONObject reqData) throws Exception;

    /**
     * 直连支付回调
     * @param request
     */
    JSONObject wxCallback(HttpServletRequest request);

    /**
     * 退款
     * @param req
     * @return
     */
    Result wxOrderRefund(JSONObject req);

    Result queryWxPayOrder(JSONObject req);

    Result generateTradeBillFile(JSONObject req);

    Result declareOrder(JSONObject req);

    Result queryDeclareOrder(JSONObject req);

    Result redeclareOrder(JSONObject req);

    Result queryWechatOriginalOrder(JSONObject req);
}

2.3.3 WechatPayServiceImpl

package com.etone.backend.service.impl;

import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson2.support.csv.CSVParser;
import com.etone.backend.constant.Const;
import com.etone.backend.dto.PayCallbackDTO;
import com.etone.backend.exception.InnerPayException;
import com.etone.backend.mapper.WechatPayMapper;
import com.etone.backend.service.ConsumerService;
import com.etone.backend.service.DeclareService;
import com.etone.backend.service.GatewayItfService;
import com.etone.backend.service.WechatPayService;
import com.etone.backend.utils.wechat.*;
import com.etone.backend.vo.Result;
import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import com.wechat.pay.java.core.Config;
import com.wechat.pay.java.core.RSAAutoCertificateConfig;
import com.wechat.pay.java.core.exception.ServiceException;
import com.wechat.pay.java.service.billdownload.BillDownloadService;
import com.wechat.pay.java.service.billdownload.model.GetTradeBillRequest;
import com.wechat.pay.java.service.billdownload.model.QueryBillEntity;
import com.wechat.pay.java.service.payments.jsapi.JsapiService;
import com.wechat.pay.java.service.payments.jsapi.JsapiServiceExtension;
import com.wechat.pay.java.service.payments.jsapi.model.*;
import com.wechat.pay.java.service.payments.model.Transaction;
import com.wechat.pay.java.service.refund.RefundService;
import com.wechat.pay.java.service.refund.model.AmountReq;
import com.wechat.pay.java.service.refund.model.CreateRequest;
import com.wechat.pay.java.service.refund.model.Refund;
import com.wechat.pay.java.service.refund.model.Status;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;

@Service
@Slf4j
public class WechatPayServiceImpl implements WechatPayService {
    @Resource
    private ConsumerService consumerService;
    @Resource
    private IRedisService iRedisService;

    @Value("${wechat.merchCertFilePath}")
    private String privateKeyPath;
    @Value("${wechat.wechatCertFilePath}")
    private String wechatCertFilePath;
    @Value("${wechat.mchId}")
    private String merchantId;
    @Value("${wechat.merchCertSerialNo}")
    private String merchantSerialNumber;
    @Value("${wechat.appKeyV3}")
    private String apiV3key;
    @Resource
    private WechatPayMapper wechatPayMapper;
    @Value("${wechat.callback.pay}")
    private String wxPayCallBackUrl;
    @Value("${wechat.callback.refund}")
    private String wxRefundCallBackUrl;
    @Resource
    private DeclareService declareService;
    @Resource
    private GatewayItfService gatewayItfService;


    @Override
    public String wxPreOrder(JSONObject reqData) {
        String merOrderNum=reqData.getString("mer_order_num");
        log.info("{}|微信直连预下单接口请求 {}", merOrderNum, reqData);
        //1.存订单
        reqData.put("orderDate", DateUtil.format(new Date(),"yyyyMMdd"));
        reqData.put("orderTime", DateUtil.format(new Date(),"HHmmss"));
        reqData.put("backUrl",wxPayCallBackUrl);
        reqData.put("sq_no",UUID.randomUUID().toString().replace("-",""));
        reqData.put("mer_id",merchantId);
        wechatPayMapper.savePayOrder(reqData);
        //2.发送请求调微信直连
        PrepayRequest prepayRequest=new PrepayRequest();
        //应用ID 由微信生成的应用ID,全局唯一。请求基础下单接口时请注意APPID的应用属性,例如公众号场景下,需使用应用属性为公众号的服务号APPID
        //示例值:wxd678efh567hg6787
        prepayRequest.setAppid(reqData.getString("sub_appid"));
        //直连商户号string[1,32] 直连商户的商户号,由微信支付生成并下发 示例值:1230000109
        prepayRequest.setMchid(merchantId);
        //商品描述string[1,127] /示例值:Image形象店-深圳腾大-QQ公仔
        prepayRequest.setDescription("海外购产品");
        //商户订单号string[6,32] 商户系统内部订单号,只能是数字、大小写字母_-*且在同一个商户号下唯一
        prepayRequest.setOutTradeNo(merOrderNum);
        //通知地址 异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数。 公网域名必须为https,如果是走专线接入,使用专线NAT IP或者私有回调域名可使用http
        prepayRequest.setNotifyUrl(wxPayCallBackUrl);

        //订单金额信息
        Amount amount=new Amount();
        //总金额	订单总金额,单位为分。
        amount.setTotal(reqData.getInteger("tran_amt"));
        //货币类型 CNY:人民币,境内商户号仅支持人民币
        amount.setCurrency("CNY");
        prepayRequest.setAmount(amount);
        //支付者信息 用户标识 用户在直连商户appid下的唯一标识。 下单前需获取到用户的Openid
        Payer payer=new Payer();
        payer.setOpenid(reqData.getString("sub_openid"));
        prepayRequest.setPayer(payer);
        //原始请求报文
        String payRequest=JSONObject.toJSONString(prepayRequest);
        log.info("微信直连支付请求原始报文:{}",payRequest);
        //获取证书
        JsapiServiceExtension service=new JsapiServiceExtension.Builder().config(getConfig(merchantId)).build();
        //请求微信预下单
        PrepayWithRequestPaymentResponse prepayResponse=service.prepayWithRequestPayment(prepayRequest);
        String prepayId=prepayResponse.getPackageVal().replace("prepay_id=","");
        //原始响应报文
        String payResponse="";
        if(StringUtils.isNotBlank(prepayId)){
            JSONObject params=new JSONObject();
            params.put("prepay_id",prepayId);
            payResponse=params.toJSONString();
        }
        log.info("微信直连预下单接口返回 {}", payResponse);
        //保存原始报文
        wechatPayMapper.updateWxRequestAndResponse(payRequest,payResponse,merOrderNum,prepayId);
        //拼接返回参数
        JSONObject result=new JSONObject();
        result.put("appId",prepayResponse.getAppId());
        result.put("paySign",prepayResponse.getPaySign());
        result.put("nonceStr",prepayResponse.getNonceStr());
        result.put("package",prepayResponse.getPackageVal());
        result.put("timeStamp",prepayResponse.getTimeStamp());
        result.put("signType",prepayResponse.getSignType());
        reqData.put("pay_info",result.toString());
        reqData.put("resp_code",Const.GATEWAY_STATUS_SUCCESS);
        return reqData.toString();
    }
    private Config getConfig(String merchantId){
        // 使用自动更新平台证书的RSA配置
        // 建议将 config 作为单例或全局静态对象,避免重复的下载浪费系统资源
        return new RSAAutoCertificateConfig.Builder()
                        .merchantId(merchantId)
                        .privateKeyFromPath(privateKeyPath)
                        .merchantSerialNumber(merchantSerialNumber)
                        .apiV3Key(apiV3key)
                        .build();
    }


    /**
     * 微信返回应答不验签
     * @param request
     * @return
     */
    private CloseableHttpResponse sendRequestwithValidator(HttpUriRequest request) {
        log.info(">>>微信通道,开始发送http请求");
        try {
            PrivateKey privateKey = PemUtil.loadPrivateKey(Files.newInputStream(Paths.get(privateKeyPath)));
            X509Certificate x509Certificate = PemUtil.loadCertificate(Files.newInputStream(Paths.get(wechatCertFilePath)));
            List<X509Certificate> certList = new ArrayList<>();
            certList.add(x509Certificate);
            WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
                    .withMerchant(merchantId, merchantSerialNumber, privateKey)
                    .withWechatPay(certList)
                    // 无需进行签名验证、通过withValidator((response) -> true)实现
                    .withValidator((response) -> true);
            // 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签
            CloseableHttpClient httpClient = builder.build();
            return httpClient.execute(request);
        } catch (IOException e) {
            e.printStackTrace();
            log.error("IOException:{}",e.getMessage());
        } catch (Exception e) {
            e.printStackTrace();
            log.error("Exception:{}",e.getMessage());
        }
        return null;
    }

    @Override
    public JSONObject wxCallback(HttpServletRequest request) {
        log.info(">>> 微信支付回调v3开始!");
        JSONObject result=new JSONObject();
        result.put("code","SUCCESS");
        result.put("message","");
        try {
            //1 签名验证 解密回调内容
            WechatResponseBeanV3 wechatResponseBeanV3 = decode(request);
            if (wechatResponseBeanV3 == null) {
                result.put("code","FAIL");
                result.put("message","解密回调内容失败!");
                return result;
            }
            String successTime=LocalDateTime.parse(wechatResponseBeanV3.getSuccess_time(), DateTimeFormatter.ISO_OFFSET_DATE_TIME).format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
            JSONObject payOrder=new JSONObject();
            payOrder.put("out_trade_no",wechatResponseBeanV3.getOut_trade_no());
            payOrder.put("transaction_id",wechatResponseBeanV3.getTransaction_id());
            payOrder.put("openid",wechatResponseBeanV3.getPayer().getOpenid());
            payOrder.put("success_time",successTime);
            payOrder.put("trade_state_desc",wechatResponseBeanV3.getTrade_state_desc());
            payOrder.put("total",Long.valueOf(wechatResponseBeanV3.getAmount().getTotal()));

            PayCallbackDTO payCallback = new PayCallbackDTO();
            // 2. 更新订单状态
            if(wechatResponseBeanV3.isPaySuccess()){
                payOrder.put("orderStatus","11");
                payCallback.setRespCode("0000");
                payOrder.put("trade_state","21");
            }else {
                payOrder.put("orderStatus","12");
                payCallback.setRespCode("9999");
                payOrder.put("trade_state","22");
            }
            wechatPayMapper.updatePayOrder(payOrder);
            //3. 回调业务
            payCallback.setMerOrderNum(wechatResponseBeanV3.getOut_trade_no());
            payCallback.setTranAmt(wechatResponseBeanV3.getAmount().getTotal());
            payCallback.setPaymentTime(successTime);
            payCallback.setPayTypeChannel("WECHAT");
            payCallback.setBussId("WXZL");
            String redisKey = Const.RES_INNER_PAY_INFO + payCallback.getMerOrderNum();
            Map payInfo = iRedisService.hmgetOrDefault(redisKey);
            String redisStatus = (String) payInfo.get("status");
            // 如果redis中记录的支付信息,已经是确定的成功或失败,说明已轮询主查,回调这里就不再处理了
            if (Const.BusinessCode.SUCCESS_COMMON.getCode().equals(redisStatus)
                    || Const.BusinessCode.ERROR_COMMON.getCode().equals(redisStatus)) {
                return result;
            }
            JSONObject resp;
            try {
                // 回调三方
                resp = consumerService.payCallback("bh", payCallback);
            } catch (Exception e) {
                throw new InnerPayException("回调三方业务系统异常");
            }
            String resCode = resp.getString("resCode");
            if (Const.BusinessCode.SUCCESS_COMMON.getCode().equals(resCode)) {
                //更新redis中状态
                String status = Const.GATEWAY_STATUS_SUCCESS.equals(payCallback.getRespCode()) ? Const.BusinessCode.SUCCESS_COMMON.getCode() : Const.BusinessCode.ERROR_COMMON.getCode();
                payInfo.put("status", status);
                iRedisService.hmset(redisKey, payInfo);
            } else {
                throw new InnerPayException("三方回调处理异常");
            }
        } catch (Exception e) {
            log.error("", e);
        }
        return result;

    }

    @Override
    public Result wxOrderRefund(JSONObject reqData) {
        log.info("微信直连退款接口请求参数:{}", reqData);
        //商户号
        String merId=reqData.getString("mer_id");
        //原订单号
        String merOrderNum=reqData.getString("old_mer_order_num");
        //退款订单号
        String refundOrderNum=reqData.getString("mer_order_num");
        //订单状态
        String orderStatus="11";
        //查询下订单流水,入参订单号、商户号
        JSONObject orderInfo= wechatPayMapper.queryPayOrderInfo(merOrderNum,merId,orderStatus);
        if(orderInfo==null||orderInfo.isEmpty()){
            return Result.error("订单不存在!");
        }
        Integer oldRefundAmt=Optional.ofNullable(orderInfo.getInteger("REFUND_AMT")).orElse(0);
        //原订单金额
        Long tranAmt=orderInfo.getLong("TRAN_AMT");
        //退款金额
        Long refundAmt=reqData.getLong("refund_amt");
        //退款金额校验
        if(refundAmt>tranAmt){
            return Result.error("退款金额不可大于交易金额!");
        }
        if(refundAmt<1){
            return Result.error("退款金额不可小于1分!");
        }
        //插入退款流水
        //拼接退款参数
        getRefundParams(orderInfo,refundAmt,refundOrderNum);
        wechatPayMapper.saveRefundOrder(orderInfo);
        JSONObject params = new JSONObject();
        params.put("mer_order_num",refundOrderNum);
        params.put("merch_id",merchantId);
        try {
            RefundService service=new RefundService.Builder().config(getConfig(merchantId)).build();
            //金额
            AmountReq amountReq=new AmountReq();
            amountReq.setCurrency("CNY");
            amountReq.setTotal(tranAmt);
            amountReq.setRefund(refundAmt);
            CreateRequest request = new CreateRequest();
            request.setAmount(amountReq);
            request.setOutTradeNo(merOrderNum);
            request.setNotifyUrl(wxRefundCallBackUrl);
            request.setOutRefundNo(refundOrderNum);
            Refund response=service.create(request);
            //拼接返回参数
            reqData.remove("card_list");
            reqData.put("tran_amt",tranAmt);
            reqData.put("settle_date","");
            reqData.put("req_refund_amt",oldRefundAmt);
            reqData.put("resp_code","0000");
            reqData.put("resp_msg","退款成功!");
            String status=response.getStatus().name();
            //status等于CLOSED或ABNORMAL,则为失败
            if (Status.CLOSED.name().equals(status)||Status.ABNORMAL.name().equals(status)) {
                //退款失败 22
                params.put("channelOrderStatus","22");
                //退款失败 12
                params.put("orderStatus","12");
                params.put("channelOrderMsg",status);
                wechatPayMapper.updateRefundOrder(params);
                reqData.put("resp_code","9999");
                reqData.put("resp_msg","退款失败!");
                return Result.res(Const.BusinessCode.ERROR_COMMON,reqData);
            }
            /**
             * 校验金额一致、更新渠道订单号、渠道状态
             * 退款中为退款成功
             */
            if (Status.PROCESSING.name().equals(status)||Status.SUCCESS.name().equals(status)) {
                if (refundAmt == response.getAmount().getRefund()) {
                    params.put("orderId",response.getOutRefundNo());
                    //退款成功 21
                    params.put("channelOrderStatus","21");
                    //退款成功 11
                    params.put("orderStatus","11");
                    params.put("channelOrderId",response.getRefundId());
                    wechatPayMapper.updateRefundOrder(params);
                    long newRefundAmt=BigDecimal.valueOf(oldRefundAmt).add(new BigDecimal(refundAmt)).longValue();
                    reqData.put("req_refund_amt",newRefundAmt);
                    //更新退款金额
                    wechatPayMapper.updateOrderRefundAmt(merOrderNum,newRefundAmt);
                }
            }
            return Result.ok(reqData);
        } catch (ServiceException e) {
            //状态不等于200为失败
            log.error("退款接口异常,退款订单号:{},异常信息:{}" ,refundOrderNum,e.getResponseBody());
            //退款失败 22
            params.put("channelOrderStatus","22");
            //退款失败 12
            params.put("orderStatus","12");
            params.put("channelOrderMsg",e.getErrorMessage());
            wechatPayMapper.updateRefundOrder(params);
            reqData.put("resp_code","9999");
            reqData.put("resp_msg",e.getErrorMessage());
            return Result.res(Const.BusinessCode.ERROR_COMMON,reqData);
        }
    }


    private void getRefundParams(JSONObject orderInfo,Long refundAmt,String refundOrderNum){
        orderInfo.put("refundAmt",refundAmt);
        orderInfo.put("refundOrderNum",refundOrderNum);
        orderInfo.put("SQ_NO",UUID.randomUUID().toString().replace("-",""));
        orderInfo.put("orderDate", DateUtil.format(new Date(),"yyyyMMdd"));
        orderInfo.put("orderTime", DateUtil.format(new Date(),"HHmmss"));
        orderInfo.put("backUrl",wxRefundCallBackUrl);
    }

    @Override
    public Result queryWxPayOrder(JSONObject req) {
        log.info(">>>微信查询支付订单接口");
        //商户订单号
        String out_trade_no=req.getString("mer_order_num");
        try {
            JsapiService jsapiService=new JsapiService.Builder().config(getConfig(merchantId)).build();
            QueryOrderByOutTradeNoRequest request=new QueryOrderByOutTradeNoRequest();
            request.setOutTradeNo(out_trade_no);
            request.setMchid(merchantId);
            //查询返回结果
            Transaction result =jsapiService.queryOrderByOutTradeNo(request);
            log.info("微信订单查询接口返回 {}", result);
            JSONObject params = new JSONObject();
            params.put("mer_order_num",out_trade_no);
            params.put("merch_id",merchantId);
            //描述
            String message=result.getTradeStateDesc();
            //状态
            String trade_state=result.getTradeState().name();
            //微信订单号
            String transaction_id=result.getTransactionId();
            //未支付
            if (Transaction.TradeStateEnum.NOTPAY.name().equals(trade_state)) {
                return Result.error(message);
            }
            /**
             * 校验金额一致、更新订单状态
             * 异常情况不用更新订单?
             */
            if (Transaction.TradeStateEnum.SUCCESS.name().equals(trade_state)) {
                params.put("txnFlag","00");
                params.put("orderStatus","11");
                params.put("orderId",out_trade_no);
                params.put("channelOrderId",transaction_id);
                params.put("channelOrderMsg",message);
                wechatPayMapper.updateQueryResult(params);
                return Result.ok(result);
            } else {
                return Result.error(message);
            }
        } catch (ServiceException e) {
            log.error("微信直连订单查询异常,订单号:{},异常信息:{}",out_trade_no, e.getResponseBody());
            return Result.error(e.getErrorMessage());
        }
    }

    @Override
    public Result generateTradeBillFile(JSONObject req) {
        String tradeBillDate=req.getString("tradeBillDate");
        if(StringUtils.isBlank(tradeBillDate)){
            tradeBillDate=DateUtil.format(DateUtil.yesterday(), "yyyy-MM-dd");
        }
        try {
            BillDownloadService service=new BillDownloadService.Builder().config(getConfig(merchantId)).build();
            GetTradeBillRequest request = new GetTradeBillRequest();
            request.setBillDate(tradeBillDate);
            QueryBillEntity billEntity=service.getTradeBill(request);
            JSONObject params = new JSONObject();
            String downloadUrl=billEntity.getDownloadUrl();
            params.put("download_url",downloadUrl);
            HttpGet httpGetDown = new HttpGet(downloadUrl);
            httpGetDown.addHeader("Accept", "application/json");
            //完成签名并执行请求
            CloseableHttpResponse responseDown = sendRequestwithValidator(httpGetDown);
            String bodyString=EntityUtils.toString(responseDown.getEntity());
            CSVParser parser=CSVParser.of(bodyString);
            System.out.println("对账详情表头:"+Arrays.toString(parser.readLine()));
            List<JSONObject> listDetail=new ArrayList<>();
            String mer_id="";
            Integer refundSum=0;
            String create_time=DateUtil.format(new Date(), "yyyy-MM-dd HH:mm:ss");
            while(true){
                String [] sf=parser.readLine();
                if(sf!=null){
                    if("总交易单数".equals(sf[0])){
                        JSONObject total=new JSONObject();
                        sf=parser.readLine();
                        log.info("总交易数据{}",Arrays.toString(sf));
                        total.put("mer_id",mer_id);
                        total.put("trade_id",UUID.randomUUID().toString().replace("-",""));
                        total.put("settle_date",tradeBillDate);
                        total.put("trans_volume",sf[0].replace("`",""));
                        String transAmt=new BigDecimal(sf[1].replace("`","")).multiply(new BigDecimal("100")).setScale(0, RoundingMode.DOWN).toString();
                        String refundAmt=new BigDecimal(sf[2].replace("`","")).multiply(new BigDecimal("100")).setScale(0, RoundingMode.DOWN).toString();
                        //交易总金额=正向-逆向交易
                        String transAllAmt=new BigDecimal(transAmt).subtract(new BigDecimal(refundAmt)).toString();
                        total.put("trans_amt",transAllAmt);
                        //手续费
                        String charges=new BigDecimal(sf[4].replace("`","")).multiply(new BigDecimal("100")).setScale(0, RoundingMode.DOWN).toString();
                        total.put("charges",charges);
                        total.put("refund_volume",refundSum);
                        total.put("refund_amt",refundAmt);
                        //结算金额=交易总金额-手续费
                        String settle_amt=new BigDecimal(transAllAmt).subtract(new BigDecimal(charges)).toString();
                        total.put("settle_amt",settle_amt);
                        total.put("create_time",create_time);
                        wechatPayMapper.insertWxTradeBillTotal(total);
                    }else {
                        if(StringUtils.isBlank(mer_id)){
                            mer_id=sf[2].replace("`","");
                        }
                        JSONObject detail=new JSONObject();
                        detail.put("mer_id",sf[2].replace("`",""));
                        detail.put("trade_id",UUID.randomUUID().toString().replace("-",""));
                        String order_no=sf[6].replace("`","");
                        detail.put("tran_time",sf[0].replace("`",""));
                        detail.put("product_type","微信直连");
                        String tran_type="00";
                        //订单金额
                        String tran_amt=new BigDecimal(sf[24].replace("`","")).multiply(new BigDecimal("100")).setScale(0, RoundingMode.DOWN).toString();
                        if("REFUND".equals(sf[9].replace("`",""))){
                            tran_type="02";
                            refundSum=refundSum+1;
                            tran_amt=new BigDecimal(sf[16].replace("`","")).multiply(new BigDecimal("100")).setScale(0, RoundingMode.DOWN).toString();
                            order_no=sf[15].replace("`","");
                        }
                        detail.put("order_no",order_no);
                        detail.put("tran_type",tran_type);
                        detail.put("tran_channel","微信直连");
                        detail.put("tran_amt",tran_amt);
                        //手续费
                        String charges=new BigDecimal(sf[22].replace("`","")).multiply(new BigDecimal("100")).setScale(0, RoundingMode.DOWN).toString();
                        detail.put("charges",charges);
                        detail.put("charges_rates",sf[23].replace("`",""));
                        detail.put("t_charges","");
                        //结算金额=订单金额-手续费
                        detail.put("settle_amt",new BigDecimal(tran_amt).subtract(new BigDecimal(charges)).toString());
                        detail.put("create_time",create_time);
                        listDetail.add(detail);
                    }
                }else {
                    break;
                }
            }
            if(!listDetail.isEmpty()){
                log.info("微信直连对账跑批数据:{}", listDetail);
                wechatPayMapper.insertWxTradeBillDetail(listDetail);
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }



        return Result.ok();
    }

    @Override
    public Result declareOrder(JSONObject req) {
        log.info("报关请求参数:{}",req);
        //根据订单号,查询商户号
        JSONObject orderInfo=wechatPayMapper.queryMerIdByOrderNo(req.getString("out_trade_no"));
        if(orderInfo==null||orderInfo.isEmpty()){
            log.error("报关请求订单不存在!");
            return Result.error("订单不存在!");
        }
        String mer_id=orderInfo.getString("MER_ID");
        req.put("mer_id",mer_id);
        String orderChannel=req.getString("order_channel");
        if("WXZL".equals(orderChannel)){
            //微信直连报关
            return declareService.declareOrder(req);
        }else {
            //调用网关报关接口
            String result=gatewayItfService.declare(req,ConstUtil.WG_DECLARE);
            JSONObject declare=JSONObject.parseObject(result);
            if(declare!=null && !declare.isEmpty()){
                if(declare.containsKey("result_code")){
                    if("SUCCESS".equals(declare.getString("result_code"))){
                        return Result.ok(declare);
                    }
                }
            }
            return Result.res("99","报关失败",declare);
        }
    }

    @Override
    public Result queryDeclareOrder(JSONObject req) {
        String orderChannel=req.getString("order_channel");
        //根据订单号,查询商户号
        JSONObject orderInfo=wechatPayMapper.queryMerIdByOrderNo(req.getString("out_trade_no"));
        if(orderInfo==null||orderInfo.isEmpty()){
            log.error("报关查询订单不存在!");
            return Result.error("订单不存在!");
        }
        if("WXZL".equals(orderChannel)){
            //微信直连报关
            return declareService.queryDeclareOrder(req);
        }else {
            //调用网关报关接口
            String result=gatewayItfService.declare(req,ConstUtil.WG_QUERY_DECLARE);
            JSONObject declare=JSONObject.parseObject(result);
            if(declare!=null && !declare.isEmpty()){
                if(declare.containsKey("result_code")){
                    if("SUCCESS".equals(declare.getString("result_code"))){
                        return Result.ok(declare);
                    }
                }
            }
            return Result.res("99","报关查询失败",declare);
        }
    }

    @Override
    public Result redeclareOrder(JSONObject req) {
        String orderChannel=req.getString("order_channel");
        //根据订单号,查询商户号
        JSONObject orderInfo=wechatPayMapper.queryMerIdByOrderNo(req.getString("out_trade_no"));
        if(orderInfo==null||orderInfo.isEmpty()){
            log.error("报关重推订单不存在!");
            return Result.error("订单不存在!");
        }
        if("WXZL".equals(orderChannel)){
            //微信直连报关
            return declareService.redeclareOrder(req);
        }else {
            //调用网关报关接口
            String result=gatewayItfService.declare(req,ConstUtil.WG_REDECLARE);
            JSONObject declare=JSONObject.parseObject(result);
            if(declare!=null && !declare.isEmpty()){
                if(declare.containsKey("result_code")){
                    if("SUCCESS".equals(declare.getString("result_code"))){
                        return Result.ok(declare);
                    }
                }
            }
            return Result.res("99","报关重推失败",declare);
        }
    }

    @Override
    public Result queryWechatOriginalOrder(JSONObject req) {
        //订单号
        String merOrderNo=req.getString("mer_order_num");
        if(StringUtils.isBlank(merOrderNo)){
            return Result.error("订单编号不可为空!");
        }
        //查询
        JSONObject jsonObject= wechatPayMapper.queryPayOrderInfo(merOrderNo,merchantId,"11");
        if(jsonObject==null|| jsonObject.isEmpty()){
            return Result.error("未查询到订单!");
        }
        String wxPayRequest=jsonObject.getString("WX_PAY_REQUEST");
        JSONObject result=new JSONObject();
        result.put("WX_PAY_REQUEST",wxPayRequest);
        result.put("WX_PAY_RESPONSE",jsonObject.getString("WX_PAY_RESPONSE"));
        return Result.ok(result);
    }

    public WechatResponseBeanV3 decode(HttpServletRequest request) {
        try {
            BufferedReader reader = new BufferedReader(new InputStreamReader(request.getInputStream()));
            StringBuilder sb = new StringBuilder();
            String line;
            while ((line = reader.readLine()) != null) {
                sb.append(line);
            }
            String body = sb.toString();
            if (StringUtils.isBlank(body)) {
                log.error("请求内容为空");
                return null;
            }
            String nonce = request.getHeader(WechatCryptUtil.Nonce);
            String timestamp = request.getHeader(WechatCryptUtil.Timestamp);
            String signature = request.getHeader(WechatCryptUtil.Signature);
            String verifyMessage = timestamp + "\n" + nonce + "\n" + body + "\n";
            log.info(">>>verifyMessage {}", verifyMessage);
            boolean checkSign = WechatCryptUtil.verify(wechatCertFilePath, verifyMessage, signature);
            if (checkSign) {
                log.info(">>>微信回调验签成功");
                WechatResponseBeanV3 wechatResponseBeanV3 = WechatCryptUtil.decodeNotify(body, apiV3key);
                log.info(">>> wechatResponseBeanV3 {}", wechatResponseBeanV3);
                return wechatResponseBeanV3;
            }
            log.info(">>>微信回调验签失败");
            return null;
        } catch (Exception e) {
            log.info(">>>微信回调验签错误 {}", e.getMessage());

            log.error("", e);
        }
        return null;
    }



}

2.3.4 WechatPayMapper

package com.etone.backend.mapper;

import com.alibaba.fastjson.JSONObject;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Component;
import java.util.List;

@Mapper
@Component
public interface WechatPayMapper {

    void savePayOrder(JSONObject params);

    void updatePayOrder(JSONObject params);

    void updateRefundOrder(JSONObject params);

    void updateQueryResult(JSONObject params);

    JSONObject queryPayOrderInfo(String merOrderNum,String merId,String orderStatus);

    void insertWxTradeBillTotal(JSONObject total);

    void insertWxTradeBillDetail(@Param("list") List<JSONObject> list);

    void saveRefundOrder(JSONObject orderInfo);

    void updateWxRequestAndResponse(String payRequest,String payResponse,String merOrderNum,String prepayId);

    void updateOrderRefundAmt(String merOrderNum, long newRefundAmt);

    JSONObject queryDeclareOrder(String merOrderNo,String sub_order_no);

    void saveDeclareOrder(JSONObject params);

    void updateDeclareOrder(JSONObject jsonObject);

    JSONObject queryMerIdByOrderNo(String outTradeNo);
}

2.3.5 WechatPayMapper.xml

<?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="com.etone.backend.mapper.WechatPayMapper">

    <sql id="columns">
        id, customer_id, trans_no, trans_channel, trans_acct, trans_acct_type,
        trans_type, trans_time, trans_amt, trans_target, trans_status, trans_result, refund_voucher
    </sql>

    <insert id="savePayOrder">
        insert into tbl_kq_txn_dtl (sq_no,order_id,channel,order_date,order_time,txn_flag,merch_id,mer_order_num,back_url,tran_amt,user_id,pay_type,app_id)
        values (#{sq_no}, #{mer_order_num},#{channel},#{orderDate},#{orderTime}, '00', #{mer_id},#{mer_order_num}, #{backUrl}, #{tran_amt}, #{user_phone},'WXZL',#{sub_appid})
    </insert>
    <insert id="insertWxTradeBillTotal">
        insert into t_wx_trade_bill_total
        (trade_id, mer_id, settle_date, trans_volume, trans_amt, charges, refund_volume, refund_amt, settle_amt, create_time)
        values
            (#{trade_id}, #{mer_id}, #{settle_date}, #{trans_volume}, #{trans_amt}, #{charges}, #{refund_volume}, #{refund_amt}, #{settle_amt}, #{create_time})

    </insert>
    <insert id="insertWxTradeBillDetail" parameterType="list">
        <foreach collection="list" item="detail" separator=";" open="begin" close=";end;">
            insert into t_wx_trade_bill_detail
                (trade_id, mer_id, order_no, tran_time, product_type, tran_type, tran_channel, tran_amt, charges, t_charges, settle_amt,charges_rates)
            values
                (#{detail.trade_id}, #{detail.mer_id}, #{detail.order_no}, #{detail.tran_time}, #{detail.product_type}, #{detail.tran_type}, #{detail.tran_channel}, #{detail.tran_amt}, #{detail.charges}, #{detail.t_charges}, #{detail.settle_amt},#{detail.charges_rates})
        </foreach>
    </insert>
    <insert id="saveRefundOrder">
        insert into tbl_kq_txn_dtl (sq_no,order_id,channel,order_date,order_time,txn_flag,merch_id,mer_order_num,back_url,tran_amt,user_id,pay_type,orig_mer_order_num,order_status)
        values (#{SQ_NO}, #{refundOrderNum},#{CHANNEL},#{orderDate},#{orderTime}, '02', #{MERCH_ID},#{refundOrderNum}, #{backUrl}, #{refundAmt}, '','WXZL',#{MER_ORDER_NUM},'00')
    </insert>
    <insert id="saveDeclareOrder">
        insert into tbl_declare_record
        (event_id, state, sub_order_no, channel_type, merch_id, out_trade_no, transaction_id, req_date, req_time, customs, mch_customs_no, cert_type, cert_id, cert_name, order_fee, transport_fee, product_fee)
        values
            (#{event_id}, '', #{sub_order_no}, 'WXZL', #{merch_id}, #{out_trade_no}, #{transaction_id}, #{req_date}, #{req_time}, #{customs}, #{mch_customs_no}, #{cert_type}, #{cert_id}, #{cert_name}, #{order_fee}, #{transport_fee}, #{product_fee})

    </insert>

    <update id="updatePayOrder">
        update tbl_kq_txn_dtl set
        order_status = #{orderStatus}
        <if test="transaction_id != null and transaction_id != ''">
            ,channel_Order_Id = #{transaction_id}
        </if>
        <if test="trade_state != null and trade_state != ''">
            ,channel_order_status = #{trade_state}
        </if>
        <if test="trade_state_desc != null and trade_state_desc != ''">
            ,channel_order_msg = #{trade_state_desc}
        </if>
        where mer_order_num=#{out_trade_no}
        <if test="orderStatus =='12'">
            and order_status <![CDATA[ <> '11'  ]]>
        </if>
        and txn_flag='00'
    </update>

    <update id="updateRefundOrder">
        update tbl_kq_txn_dtl set
        order_status = #{orderStatus}
        <if test="channelOrderId != null and channelOrderId != ''">
            ,channel_Order_Id = #{channelOrderId}
        </if>
        <if test="channelOrderStatus != null and channelOrderStatus != ''">
            ,channel_order_status = #{channelOrderStatus}
        </if>
        <if test="channelOrderMsg != null and channelOrderMsg != ''">
            ,channel_Order_msg = #{channelOrderMsg}
        </if>
        where mer_order_num=#{mer_order_num}
        and merch_id=#{merch_id}
        <if test="orderStatus =='12'">
            and order_status <![CDATA[ <> '11'  ]]>
        </if>
        and txn_flag='02'
    </update>

    <update id="updateQueryResult">
        update tbl_kq_txn_dtl set
        order_status = #{orderStatus}
        <if test="qrSeq != null and qrSeq != ''">
            ,qr_seq = #{qrSeq}
        </if>
        <if test="reserve1 != null and reserve1 != ''">
            ,reserve1 = #{reserve1}
        </if>
        <if test="channelOrderId != null and channelOrderId != ''">
            ,channel_Order_Id = #{channelOrderId}
        </if>
        <if test="channelClientId != null and channelClientId != ''">
            ,channel_client_id = #{channelClientId}
        </if>
        <if test="channelOrderFinishTime != null and channelOrderFinishTime != ''">
            ,channel_Order_Finish_Time = #{channelOrderFinishTime}
        </if>
        <if test="channelOrderMsg != null and channelOrderMsg != ''">
            ,channel_Order_msg = #{channelOrderMsg}
        </if>
        where mer_order_num=#{mer_order_num}
        and merch_id=#{merch_id}
        <if test="orderId != null and orderId != ''">
            and order_id=#{orderId}
        </if>
        <if test="txnFlag != null and txnFlag != ''">
            and txn_flag=#{txnFlag}
        </if>
        <if test="orderStatus =='12'">
            and order_status <![CDATA[ <> '11'  ]]>
        </if>

    </update>

    <update id="updateWxRequestAndResponse">
        update tbl_kq_txn_dtl t
        set t.wx_pay_request=#{payRequest},
            t.wx_pay_response=#{payResponse},
            t.prepay_id=#{prepayId}
        where t.MER_ORDER_NUM=#{merOrderNum} and t.txn_flag='00'
    </update>
    <update id="updateOrderRefundAmt">
        update tbl_kq_txn_dtl t set t.refund_amt=#{newRefundAmt} where t.mer_order_num=#{merOrderNum} and t.txn_flag='00' and t.order_status='11'
    </update>
    <update id="updateDeclareOrder">
        update tbl_declare_record t set t.return_code=#{return_code}
        <if test="result_code != null and result_code != ''">
            ,t.result_code=#{result_code}
        </if>
        <if test="err_code != null and err_code != ''">
            ,t.err_code=#{err_code}
        </if>
        <if test="err_code_des != null and err_code_des != ''">
            ,t.err_code_des=#{err_code_des}
        </if>
        <if test="modify_time != null and modify_time != ''">
            ,t.modify_time=#{modify_time}
        </if>
        <if test="cert_check_result != null and cert_check_result != ''">
            ,t.cert_check_result=#{cert_check_result}
        </if>
        <if test="verify_department != null and verify_department != ''">
            ,t.verify_department=#{verify_department}
        </if>
        <if test="verify_department_trade_id != null and verify_department_trade_id != ''">
            ,t.verify_department_trade_id=#{verify_department_trade_id}
        </if>
        <if test="state != null and state != ''">
            ,t.state=#{state}
        </if>
        where t.event_id=#{event_id}

    </update>

    <select id="queryPayOrderInfo" resultType="com.alibaba.fastjson.JSONObject">
        select t.* from  tbl_kq_txn_dtl t
                 where t.mer_order_num=#{merOrderNum}
                   and t.merch_id=#{merId}
                   and t.txn_flag='00'
                   <if test="orderStatus != null and orderStatus != ''">
                       and t.order_status=#{orderStatus}
                   </if>
    </select>
    <select id="queryDeclareOrder" resultType="com.alibaba.fastjson.JSONObject">
        select t.* from tbl_declare_record t where t.out_trade_no=#{merOrderNo} and t.sub_order_no=#{sub_order_no} and t.state in ('SUCCESS','EXCEPT') and  rownum =1
    </select>
    <select id="queryMerIdByOrderNo" resultType="com.alibaba.fastjson.JSONObject">
        select t.mer_id from OMS_PAYMENT_INFO t  where t.payid=#{outTradeNo}  and t.payment_status='00' and rownum=1
    </select>
</mapper>

2.4 报关代码

2.4.1 DeclareService

package com.etone.backend.service;

import com.alibaba.fastjson.JSONObject;
import com.etone.backend.vo.Result;

/**
 * @description: 海关报关接口
 */
public interface DeclareService {

    Result declareOrder(JSONObject req);

    Result queryDeclareOrder(JSONObject req);

    Result redeclareOrder(JSONObject req);
}

2.4.2 DeclareServiceImpl

package com.etone.backend.service.impl;

import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson.JSONObject;
import com.etone.backend.mapper.WechatPayMapper;
import com.etone.backend.service.DeclareService;
import com.etone.backend.utils.wechat.ConstUtil;
import com.etone.backend.utils.wechat.WXPayConstants;
import com.etone.backend.utils.wechat.WXPayUtil;
import com.etone.backend.vo.Result;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.netty.handler.ssl.ClientAuth;
import io.netty.handler.ssl.JdkSslContext;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.asynchttpclient.*;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import javax.net.ssl.*;
import java.io.File;
import java.io.InputStream;
import java.nio.file.Files;
import java.security.KeyStore;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.*;
import java.util.concurrent.TimeUnit;

import static org.asynchttpclient.Dsl.asyncHttpClient;

/**
 * @description: 实现微信支付海关报关接口
 */

@Service
@Slf4j
public class DeclareServiceImpl implements DeclareService {

    @Resource
    private WechatPayMapper wechatPayMapper;
    @Value("${wechat.mchId}")
    private String mchId;
    @Value("${wechat.appKeyV2}")
    private String appKeyV2;
    @Value("${wechat.declare.pfxPwd}")
    private String pfxPwd;
    @Value("${wechat.declare.pfxPath}")
    private String pfxPath;

    @Override
    public Result declareOrder(JSONObject req){
        Map<String, String> paramMap = new HashMap<>();
        //订单号
        String merOrderNo = req.getString("out_trade_no");
        //查询订单
        JSONObject orderInfo=wechatPayMapper.queryPayOrderInfo(merOrderNo,mchId,"11");
        if(orderInfo==null|| orderInfo.isEmpty()){
            return Result.error("订单不存在");
        }
        String appId=orderInfo.getString("APP_ID");
        //微信订单号
        String transaction_id=orderInfo.getString("CHANNEL_ORDER_ID");
        String sub_order_no=req.getString("sub_order_no");
        //1、查询报关流水
        JSONObject declareInfo=wechatPayMapper.queryDeclareOrder(merOrderNo,sub_order_no);
        String event_id=UUID.randomUUID().toString().replace("-","");
        if(declareInfo!=null){
            log.info("订单已报关!订单编号:{},子订单号:{}",merOrderNo,sub_order_no);
            return Result.error("订单已报关成功,请勿重复报关!");
        }else {
            //2、插入报关流水
            req.put("event_id", event_id);
            req.put("transaction_id", transaction_id);
            if(StringUtils.isNotEmpty(req.getString("cert_id")))
            {
                //身份证参数
                paramMap.put("cert_type", "IDCARD");
                paramMap.put("cert_id", req.getString("cert_id"));
                paramMap.put("name", req.getString("cert_name"));
            }else {
                //身份证参数
                paramMap.put("cert_type", "");
                paramMap.put("cert_id", "");
                paramMap.put("cert_name", "");
            }
            req.put("merch_id",mchId);
            req.put("req_date", DateUtil.format(new Date(),"yyyyMMdd"));
            req.put("req_time", DateUtil.format(new Date(),"HHmmss"));
            wechatPayMapper.saveDeclareOrder(req);
        }
        paramMap.put("out_trade_no", merOrderNo);
        //拆单参数
        paramMap.put("sub_order_no",  sub_order_no);
        paramMap.put("transaction_id", transaction_id);
        //海关参数
        paramMap.put("customs", req.getString("customs"));
        paramMap.put("mch_customs_no", req.getString("mch_customs_no"));
        paramMap.put("fee_type", "CNY");
        paramMap.put("order_fee", req.getString("order_fee"));
        paramMap.put("transport_fee", req.getString("transport_fee"));
        paramMap.put("product_fee", req.getString("product_fee"));
        this.generateSign(paramMap,appId);
        Optional<JSONObject> optionalWechatBean =  sendRequest(ConstUtil.DECLARE_URL, paramMap,appId);
        if(optionalWechatBean.isPresent()){
            JSONObject jsonObject = optionalWechatBean.get();
            jsonObject.put("event_id", event_id);
            //更新报关结果
            wechatPayMapper.updateDeclareOrder(jsonObject);
            String state=jsonObject.getString("state");
            //认为成功的状态码:UNDECLARED--未申报 SUBMITTED--申报已提交 PROCESSING--申报中 SUCCESS--申报成功
            //认定失败的状态码:FAIL-- 申报失败 EXCEPT --海关接口异常
            if("UNDECLARED".equals(state)||"SUBMITTED ".equals(state)||"PROCESSING".equals(state)||"SUCCESS".equals(state)){
                return Result.ok(jsonObject);
            }else {
                return Result.res("99","报关失败",jsonObject);
            }
        }
        return Result.error("报关错误!");

    }

    @Override
    public Result queryDeclareOrder(JSONObject req) {
        Map<String, String> paramMap = new HashMap<>();
        paramMap.put("out_trade_no", req.getString("out_trade_no"));
        //海关参数
        paramMap.put("customs", req.getString("customs"));
        //拆单参数
        if(StringUtils.isNotBlank(req.getString("sub_order_no"))){
            paramMap.put("sub_order_no",  req.getString("sub_order_no"));
        }
        //查询订单
        JSONObject orderInfo=wechatPayMapper.queryPayOrderInfo(req.getString("out_trade_no"),mchId,"11");
        if(orderInfo==null|| orderInfo.isEmpty()){
            return Result.error("订单不存在");
        }
        String appId=orderInfo.getString("APP_ID");
        this.generateSign(paramMap,appId);
        Optional<JSONObject> optionalWechatBean =  sendRequest(ConstUtil.QUERY_DECLARE_URL, paramMap,appId);
        if(optionalWechatBean.isPresent()){
            JSONObject result = optionalWechatBean.get();
            return Result.ok(result);
        }
        return Result.error("未查询到数据!");
    }

    @Override
    public Result redeclareOrder(JSONObject req) {
        //订单号
        String merOrderNo = req.getString("out_trade_no");
        //1、查询订单
        JSONObject orderInfo=wechatPayMapper.queryPayOrderInfo(merOrderNo,mchId,"11");
        if(orderInfo==null|| orderInfo.isEmpty()){
            return Result.error("订单不存在");
        }
        String appId=orderInfo.getString("APP_ID");
        String sub_order_no=req.getString("sub_order_no");
        //2、查询报关流水
        JSONObject declareInfo=wechatPayMapper.queryDeclareOrder(merOrderNo,sub_order_no);
        if(declareInfo==null||declareInfo.isEmpty()){
            log.info("没有提交报关或报关状态错误!订单编号:{},子订单号:{}",merOrderNo,sub_order_no);
            return Result.error("没有提交报关或报关状态错误!");
        }
        String event_id=declareInfo.getString("EVENT_ID");
        Map<String, String> paramMap = new HashMap<>();
        paramMap.put("out_trade_no", req.getString("out_trade_no"));
        //海关参数
        paramMap.put("customs", req.getString("customs"));
        paramMap.put("mch_customs_no", req.getString("mch_customs_no"));
        //拆单参数
        if(StringUtils.isNotBlank(req.getString("sub_order_no"))){
            paramMap.put("sub_order_no",  req.getString("sub_order_no"));
        }
        this.generateSign(paramMap,appId);
        Optional<JSONObject> optionalWechatBean =  sendRequest(ConstUtil.REDECLARE_URL, paramMap,appId);
        if(optionalWechatBean.isPresent()){
            JSONObject jsonObject = optionalWechatBean.get();
            jsonObject.put("event_id", event_id);
            //更新报关结果
            wechatPayMapper.updateDeclareOrder(jsonObject);
            return Result.ok(jsonObject);
        }
        return Result.error("报关重推错误!");
    }

    private static String sign_type = "HMAC-SHA256";

    private void generateSign(Map<String, String> map,String appId) {

        /**
         * 设置共用参数
         */
        map.put("appid", appId);
        map.put("mch_id", mchId);
        //设置签名
        String sign = null;
        try {
            sign = WXPayUtil.generateSignature(map, appKeyV2,
                    WXPayConstants.SignType.MD5);
        } catch (Exception e) {
            e.printStackTrace();
        }
        map.put("sign", sign);
    }
    private Optional<JSONObject> sendRequest(String url, Map<String, String> request,String appId) {
        log.info(">>>微信报关通道,开始发送http 请求");
        try {
            generateSign(request,appId);
            String requestXml = WXPayUtil.mapToXml(request);
            log.info(">>>微信报关通道,请求数据 {}", requestXml);
            InputStream keyStoreStream = Files.newInputStream(new File(pfxPath).toPath());
            char[] keyStorePassword = pfxPwd.toCharArray();
            KeyStore ks = KeyStore.getInstance("JKS");
            ks.load(keyStoreStream, keyStorePassword);
            char[] certificatePassword = pfxPwd.toCharArray();
            KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
            kmf.init(ks, certificatePassword);
            KeyManager[] keyManagers = kmf.getKeyManagers();
            TrustManager[] trustManagers = new TrustManager[]{new X509TrustManager() {
                @Override
                public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {}
                @Override
                public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {}
                @Override
                public X509Certificate[] getAcceptedIssuers() {
                    return new X509Certificate[0];
                }
            }};
            SecureRandom secureRandom = new SecureRandom();
            SSLContext sslContext = SSLContext.getInstance("TLS");
            sslContext.init(keyManagers, trustManagers, secureRandom);
            AsyncHttpClientConfig asyncHttpClientConfig = new DefaultAsyncHttpClientConfig.Builder()
                    .setKeepAlive(true)
                    .setSslContext(new JdkSslContext(sslContext, true, ClientAuth.REQUIRE))
                    .setMaxConnections(102400)
                    .setConnectTimeout(20 * 1000)
                    .setReadTimeout(20 * 1000)
                    .build();
            AsyncHttpClient asyncHttpClient = asyncHttpClient(asyncHttpClientConfig);
            ListenableFuture<Response> responseListenableFuture = asyncHttpClient.preparePost(url)
                    .setHeader("Content-Type", "application/xml;;charset=utf-8")
                    .setBody(requestXml)
                    .execute();
            Response response = responseListenableFuture.get(30, TimeUnit.SECONDS);
            String result = response.getResponseBody();
            log.info(">>>微信报关返回数据xml: {}", result);
            if (StringUtils.isNotEmpty(result)) {
                //校验返回值
                Map<String, String> resultMap = WXPayUtil.xmlToMap(result);
                    ObjectMapper objectMapper = new ObjectMapper();
                    objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
                    JSONObject jsonObject = objectMapper.convertValue(resultMap, JSONObject.class);
                    log.info("微信报关返回数据Json:{}",jsonObject);
                    return Optional.of(jsonObject);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return Optional.empty();
    }
}

 3、总结概述

第一次发博客练练手,代码中有参杂少许业务代码,如有问题可留言,问必答

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值