Java接入微信小程序支付、退款、发货通知

一,准备工作

申请应用号:appid

申请商户号:mchid

设置APIV3密钥:apiV3Key

下载并配置商户证书,商户证书序列号:mchSerialNo ;商户API私钥路径:privateKey

详情流程可见微信支付官网

二,小程序支付流程

 需要后端完成的一共有三个接口

1,拿到订单参数后,向微信发送请求支付消息,把拿到的参数返回给前端,用于用户付款

2,前端输入密码后,微信会异步回调一个接口,成功扣款后做对应业务处理

3,查询支付结果返回给前端

 三,设置常量和基本配置信息

1,导入依赖
<!--微信小程序支付-->
<dependency>
    <groupId>com.github.wechatpay-apiv3</groupId>
    <artifactId>wechatpay-apache-httpclient</artifactId>
    <version>0.4.9</version>
</dependency>
2,加载配置信息到yml文件中
#  微信
wx:
  #  小程序支付
  pay:
    #  应用号
    appId: ********
    #  商户号
    mchId: ********
    #  商户APIV3密钥(32位)
    apiV3Key: ********************************
    #  商户证书序列号
    mchSerialNo: ****************
    #  支付成功回调 url 需要公网可访问
    notifyUrl: http://改成自己的回调地址/system/order/wechatPayCallback
    #  退款成功回调 url 需要公网可访问
    refundNotifyUrl: http://改成自己的回调地址/system/order/wechatRefundBack
    #  商户API私钥路径 【证书路径】
    privateKey: "**********"
3,用工具类来保存配置信息WeChatToolConfig 
@Data
@Component
public class WeChatToolConfig {
    /**
     * 应用id
     */
    @Value("${wx.pay.appId}")
    private String appId;

    /**
     * 商户id
     */
    @Value("${wx.pay.mchId}")
    private String mchId;

    /**
     * 商户APIV3密钥
     */
    @Value("${wx.pay.apiV3Key}")
    private String apiV3Key;

    /**
     * 商户证书序列号
     */
    @Value("${wx.pay.mchSerialNo}")
    private String mchSerialNo;

    /**
     * 商户私钥
     */
    @Value("${wx.pay.privateKey}")
    private String privateKey;

    /**
     * 支付成功回调url
     */
    @Value("${wx.pay.notifyUrl}")
    private String notifyUrl;

     /**
     * 退款成功回调url
     */
    @Value("${wx.pay.refundNotifyUrl}")
    private String refundNotifyUrl;
}
4,定义一个枚举来保存微信支付API WxApiTypeEnum 
public enum WxApiTypeEnum {
   /**
     * 小程序 下单API
     */
    CREATE_ORDER("https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi"),

    /**
     * 申请退款API 通过 outTradeNo 商户订单号 退单
     */
    REFUND_ORDER("https://api.mch.weixin.qq.com/v3/refund/domestic/refunds"),

    /**
     * 查询支付订单API ONE{}=>outTradeNo – 商户订单号 系统生 TOW{}=>mchId商户的商户号,由微信支付生成并下发。
     */
    QUERY_CREATE_ORDER("https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/{}?mchid={}"),

    /**
     * 查询退款订单API {}=>outRefundNo 商户退款单号
     */
    QUERY_REFUND_ORDER("https://api.mch.weixin.qq.com/v3/refund/domestic/refunds/{}"),

    /**
     * 关闭订单API {}=>outTradeNo 商户订单号
     */
    CLOSE_ORDER("https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/{}/close"),

    /**
     * 货物录入
     */
    GOOD_ENTERING("https://api.weixin.qq.com/wxa/sec/order/upload_shipping_info?access_token={}"),

    /**
     * 接口调用凭证
     */
    API_VOUCHER("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={}&secret={}");

    private final String value;

    WxApiTypeEnum(String value) {
        this.value = value;
    }

    public String getValue() {
        return value;
    }
}
5,定义一个类保存HTTP请求头常量 WechatPayHttpHeaders 
public class WechatPayHttpHeaders {
    // 请求头配置
    public static final String ACCEPT = "Accept";

    public static final String CONTENT_TYPE = "Content-type";

    public static final String APPLICATION_JSON = "application/json";

    public static final String APPLICATION_JSON_UTF = "application/json; charset=utf-8";
    /**
     * 微信回调参数==>微信序列号
     */
    public static final String WECHATPAY_SERIAL = "Wechatpay-Serial";
    /**
     * 微信回调参数==>应答随机串
     */
    public static final String WECHATPAY_NONCE = "Wechatpay-Nonce";
    /**
     * 微信回调参数==>应答时间戳
     */
    public static final String WECHATPAY_TIMESTAMP = "Wechatpay-Timestamp";
    /**
     * 微信回调参数==>应答签名
     */
    public static final String WECHATPAY_SIGNATURE = "Wechatpay-Signature";

    private WechatPayHttpHeaders() {
        // Don't allow instantiation
    }
}
6,微信小程序支付工具类
public class WeChatUtil {
    private static final Logger log = LoggerFactory.getLogger(WeChatUtil.class);
    /**
     * 签名生成 计算签名
     *
     * @param message    签名体
     * @param privateKey 商户私钥
     * @return String
     */
    public static String sign(byte[] message, PrivateKey privateKey) throws NoSuchAlgorithmException, SignatureException, InvalidKeyException {
        Signature sign = Signature.getInstance("SHA256withRSA");
        sign.initSign(privateKey);
        sign.update(message);
        return Base64.getEncoder().encodeToString(sign.sign());
    }

    /**
     * 构造HttpClient 实现 微信申请接口 调用功能
     *
     * @param verifier 微信验签器
     * @return CloseableHttpClient
     */
    public static CloseableHttpClient getHttpClient(WeChatToolConfig config, Verifier verifier) {
        PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(new ByteArrayInputStream(config.getPrivateKey().getBytes(StandardCharsets.UTF_8)));
        // 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签
        WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
                .withMerchant(config.getMchId(), config.getMchSerialNo(), merchantPrivateKey)
                .withValidator(new WechatPay2Validator(verifier));
        return builder.build();
    }

    /**
     * 构造HttpClient 实现 微信申请接口 调用功能  不用微信签名认证 账单申请中可用到
     *
     * @param verifier 微信验签器
     * @return CloseableHttpClient
     */
    public static CloseableHttpClient getHttpClientNoSign(WeChatToolConfig config, Verifier verifier) {
        PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(new ByteArrayInputStream(config.getPrivateKey().getBytes(StandardCharsets.UTF_8)));
        // 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签
        WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
                .withMerchant(config.getMchId(), config.getMchSerialNo(), merchantPrivateKey)
                .withValidator(new WechatPay2Validator(verifier))
                .withValidator(response -> true);
        return builder.build();
    }

    /**
     * 获取验证功能
     *
     * @return Verifier 微信验签器
     */
    public static Verifier getVerifier(WeChatToolConfig config) {
        try {
            PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(new ByteArrayInputStream(config.getPrivateKey().getBytes(StandardCharsets.UTF_8)));
            // 定时更新平台证书功能
            // 获取证书管理器实例
            CertificatesManager certificatesManager = CertificatesManager.getInstance();
            // 向证书管理器增加需要自动更新平台证书的商户信息
            certificatesManager.putMerchant(config.getMchId(), new WechatPay2Credentials(config.getMchId(),
                    new PrivateKeySigner(config.getMchSerialNo(), merchantPrivateKey)), config.getApiV3Key().getBytes(StandardCharsets.UTF_8));
            // 从证书管理器中获取verifier=>验签器
            return certificatesManager.getVerifier(config.getMchId());
        } catch (Exception e) {
            log.warn("验证出错");
            throw new CustomException("微信证书出错 联系客服");
        }
    }


    /**
     * 回调验证
     *
     * @param request 微信回调请求
     * @return String
     */
    public static Notification verifyBack(HttpServletRequest request, WeChatToolConfig config) throws IOException, ValidationException, ParseException {
        // 应答报文主体
        BufferedReader br = request.getReader();
        String str;
        StringBuilder builder = new StringBuilder();
        while ((str = br.readLine()) != null) {
            builder.append(str);
        }
        // 构建request,传入必要参数
        // 参数 1.微信序列号 2.应答随机串 3.应答时间戳 4.应答签名 5.应答报文主体
        NotificationRequest notificationRequest = new NotificationRequest.Builder()
                .withSerialNumber(request.getHeader(WechatPayHttpHeaders.WECHATPAY_SERIAL))
                .withNonce(request.getHeader(WechatPayHttpHeaders.WECHATPAY_NONCE))
                .withTimestamp(request.getHeader(WechatPayHttpHeaders.WECHATPAY_TIMESTAMP))
                .withSignature(request.getHeader(WechatPayHttpHeaders.WECHATPAY_SIGNATURE))
                .withBody(builder.toString())
                .build();
        NotificationHandler handler = new NotificationHandler(WeChatUtil.getVerifier(config), config.getApiV3Key().getBytes(StandardCharsets.UTF_8));
        // 验签和解析请求体
        log.info("验签和解析请求体==============================开始验证==============================");
        return handler.parse(notificationRequest);
    }

    /**
     * 验证用户支付金额 场景 微信回调验证
     *
     * @param jsonObject 微信回调json 返回参数
     * @param total      数据库记录金额
     * @return Boolean
     */
    public static Boolean verifyMoney(JSONObject jsonObject, Integer total) {
        // 总金额计数值 用户支付计算
        int userPayTotal = WeChatPayParam.NUM_ZERO;
        // 1.验证订单金额
        // 用户支付金额
        int payerTotal = (int) jsonObject.getJSONObject(WeChatPayParam.AMOUNT).get(WeChatPayParam.AMOUNT_PAYER_TOTAL);
        userPayTotal += payerTotal;
        // CASH充值型代金券 要加上优惠金额 银行优惠 获取
        // 排空 如果没有优惠则跳过
        if (!ObjectUtil.isEmpty(jsonObject.get(WeChatPayParam.PROMOTION_DETAIL))) {
            JSONArray jsonArray = jsonObject.getJSONArray(WeChatPayParam.PROMOTION_DETAIL);
            for (Object object : jsonArray) {
                JSONObject json = (JSONObject) object;
                // 如果优惠类型为CASH 则要和 用户支付金额 累加
                if (WeChatPayParam.WX_DISCOUNT_TYPE.equals(json.get(WeChatPayParam.PROMOTION_DETAIL_TYPE).toString())) {
                    userPayTotal += (int) json.get(WeChatPayParam.AMOUNT);
                }
            }
        }
        // 2.总金额 预支付时的 金额 与 total 用户支付金额
        // 微信端返回的支付总金额
        int wxTotal = (int) jsonObject.getJSONObject(WeChatPayParam.AMOUNT).get(WeChatPayParam.AMOUNT_TOTAL);
        // 校验通知的信息是否与商户侧的信息一致,防止数据泄露导致出现“假通知”,造成资金损失。
        log.info("微信回调金额===比较=== " + "微信端返回的支付总金额" + jsonObject.getJSONObject(WeChatPayParam.AMOUNT).get(WeChatPayParam.AMOUNT_TOTAL) + "================用户支付总金额计算" + userPayTotal);
        return wxTotal == userPayTotal && total == wxTotal;
    }


    /**
     * 获取接口调用凭证
     */
    public static JSONObject getAccessToken(WeChatToolConfig config) throws Exception {
        log.info("initAccessToken:开始运行...");
        // 获取secret
        String secret = WxMaConfiguration.getMaService(config.getAppId()).getWxMaConfig().getSecret();
        // 获取url
        String url = StrFormatter.format(WxApiTypeEnum.API_VOUCHER.getValue(), config.getAppId(), secret);
        return getBodyAsJsonByGet(url, config);
    }

    /**
     * 调用微信支付 get请求 统一配置
     * 要微信签名认证
     *
     * @param url 请求url
     * @return JSONObject
     */
    public static JSONObject getBodyAsJsonByGet(String url, WeChatToolConfig config) throws IOException {
        // 1.构造httpGet请求
        HttpGet httpGet = new HttpGet(url);
        httpGet.addHeader(WechatPayHttpHeaders.ACCEPT, WechatPayHttpHeaders.APPLICATION_JSON);
        // 2.调起微信查询订单接口
        CloseableHttpResponse response = WeChatUtil.getHttpClient(config, WeChatUtil.getVerifier(config)).execute(httpGet);
        // 3.返回结果信息
        String bodyAsString = EntityUtils.toString(response.getEntity());
        // 转为JSON格式返回
        return JSON.parseObject(bodyAsString);
    }

    /**
     * 调用微信支付 post请求 统一配置
     * 要微信签名认证
     *
     * @param url          请求url
     * @param requestParam 请求参数
     * @param config       基本配置
     * @return JSONObject
     */
    public static JSONObject getBodyAsJsonByPost(String url, JSONObject requestParam, WeChatToolConfig config) throws Exception {
        // 1.设置请求url和请求头
        HttpPost httpPost = new HttpPost(url);
        httpPost.addHeader(WechatPayHttpHeaders.ACCEPT, WechatPayHttpHeaders.APPLICATION_JSON);
        httpPost.addHeader(WechatPayHttpHeaders.CONTENT_TYPE, WechatPayHttpHeaders.APPLICATION_JSON_UTF);

        // 2.传入参数
        String jsonString = JSON.toJSONString(requestParam);
        httpPost.setEntity(new StringEntity(jsonString, StandardCharsets.UTF_8));

        // 3.调用接口 获取预支付交易会话标识 prepay_id
        CloseableHttpResponse response = WeChatUtil.getHttpClient(config, WeChatUtil.getVerifier(config)).execute(httpPost);

        // 4.获取返回值 转为JSON
        String bodyAsString = EntityUtils.toString(response.getEntity());
        return JSON.parseObject(bodyAsString);
    }


    /**
     * 获取签名
     *
     * @param appId      appId
     * @param timestamp  时间戳
     * @param nonce      随机字符串
     * @param wxPackage  订单详情扩展字符串
     * @param privateKey 商户私钥
     */
    public static String getSign(String appId, String timestamp, String nonce, String wxPackage, String privateKey) throws Exception {
        // APP调起支付API 构造签名串
        // 应用id
        String builder = appId + "\n" +
                // 时间戳
                timestamp + "\n" +
                // 随机字符串
                nonce + "\n" +
                // 预支付交易会话标识 prepay_id=wx201410272009395522657a690389285100  必须以此格式
                wxPackage + "\n";

        // 读取privateKey商户私钥
        PrivateKey merchantPrivateKey = PemUtils.getPrivateKey(privateKey);
        // 计算签名
        return WeChatUtil.sign(builder.getBytes(StandardCharsets.UTF_8), merchantPrivateKey);
    }

}
7,微信支付参数 WeChatPayParam
public class WeChatPayParam {

    /**
     * 应用ID
     */
    public final static String APPID = "appid";

    /**
     * 直连商户号
     */
    public final static String MCHID = "mchid";

    /**
     * 用户支付状态
     */
    public final static String TRADE_STATE = "trade_state";

    /**
     * 用户退款状态
     */
    public final static String STATUS = "status";


    /**
     * 商品描述
     */
    public final static String DESCRIPTION = "description";

    /**
     * 通知地址
     */
    public final static String NOTIFY_URL = "notify_url";

    /**
     * 预支付交易会话标识
     */
    public final static String PREPAY_ID = "prepay_id";

    /**
     * 商户订单号
     */
    public final static String OUT_TRADE_NO = "out_trade_no";

    /**
     * 商户退款单号
     */
    public final static String OUT_REFUND_NO = "out_refund_no";

    /**
     * 微信支付系统生成的订单号
     */
    public final static String TRANSACTION_ID = "transaction_id";

    /**
     * 微信支付系统生成的订单号
     */
    public final static String REFUND_ID = "refund_id";
    /**
     * 订单金额
     */
    public final static String AMOUNT = "amount";
    /**
     * 总金额	下单时系统计算金额 单位为分
     */
    public final static String AMOUNT_TOTAL = "total";

    /**
     * 支付者
     */
    public final static String PAYER = "payer";

    /**
     * 用户在直连商户appid下的唯一标识。 下单前需获取到用户的Openid
     */
    public final static String OPENID = "openid";

    /**
     * 总金额	退款时 退款金额
     */
    public final static String AMOUNT_REFUND = "refund";

    /**
     * 总金额	退款时 币种
     */
    public final static String AMOUNT_CURRENCY = "currency";

    /**
     * 用户支付金额
     */
    public final static String AMOUNT_PAYER_TOTAL = "payer_total";
    /**
     * 优惠功能
     */
    public final static String PROMOTION_DETAIL = "promotion_detail";
    /**
     * 优惠类型
     */
    public final static String PROMOTION_DETAIL_TYPE = "type";
    /**
     * 微信优惠 CASH:充值型代金券
     */
    public final static String WX_DISCOUNT_TYPE = "CASH";

    /**
     * 下单回调给微信支付成功信息
     */
    public final static String WX_BACK_OK = "SUCCESS";

    /**
     * 退款回调 退款状态
     */
    public final static String REFUND_STATUS = "refund_status";


    /**
     * 微信回调 通知数据
     */
    public final static String RESOURCE = "resource";

    /**
     * 微信账单类型 交易账单
     */
    public final static String TRADE_BILL = "tradebill";
    /**
     * 微信账单类型 资金账单
     */
    public final static String FUND_FLOW_BILL = "fundflowbill";
    /**
     * 微信账单下载地址
     */
    public final static String DOWNLOAD_URL = "download_url";

    /**
     * 微信回调 通知数据=>数据密文
     */
    public final static String RESOURCE_CIPHERTEXT = "ciphertext";

    /**
     * 微信回调 通知数据=>附加数据
     */
    public final static String RESOURCE_ASSOCIATED_DATA = "associated_data";

    /**
     * 微信回调 通知数据=>随机串
     */
    public final static String RESOURCE_NONCE = "nonce";

    /**
     * 发货信息 状态码
     */
    public final static String ERR_CODE = "errcode";

    /**
     * 发货信息 状态信息
     */
    public final static String ERR_MSG = "errmsg";


    /**
     * 请求支付 错误状态码
     */
    public final static String CODE = "code";

    /**
     * 请求支付 状态信息
     */
    public final static String MESSAGE = "message";

    /**
     * 固定支付参数 package
     */
    public final static String WX_PACKAGE = "prepay_id=";

    /**
     * 币种 RMB
     */
    public final static String CURRENCY_RMB = "CNY";

    /**
     * 支付成功
     */
    public final static String PAY_SUCCESS = "支付成功";
    /**
     * 退款成功
     */
    public final static String REFUND_SUCCESS = "退款成功";

    /**
     * 订单单号类型
     */
    public final static String ORDER_NUMBER_TYPE = "order_number_type";

    /**
     * 商品信息
     */
    public final static String ITEM_DESC = "item_desc";

    /**
     * 订单
     */
    public final static String ORDER_KEY = "order_key";

    /**
     * 物流模式
     */
    public final static String LOGISTICS_TYPE = "logistics_type";

    /**
     * 发货模式
     */
    public final static String DELIVERY_MODE = "delivery_mode";

    /**
     * 物流信息列表
     */
    public final static String SHIPPING_LIST = "shipping_list";

    /**
     * 上传时间
     */
    public final static String UPLOAD_TIME = "upload_time";

    /**
     * 获取到的凭证
     */
    public final static String ACCESS_TOKEN = "access_token";


    /**
     * 数字 0~11
     */
    public final static int NUM_NEGATIVE_ONE = -1;
    public final static int NUM_ZERO = 0;
    public final static int NUM_ONE = 1;
    public final static int NUM_TWO = 2;
    public final static int NUM_THREE = 3;
    public final static int NUM_FOUR = 4;
    public final static int NUM_FIVE = 5;
    public final static int NUM_SIX = 6;
    public final static int NUM_SEVEN = 7;
    public final static int NUM_EIGHT = 8;
    public final static int NUM_NINE = 9;
    public final static int NUM_TEN = 10;
    public final static int NUM_ELEVEN = 11;
    public final static int NUM_15 = 15;
    public final static int NUM_16 = 16;
    public final static int NUM_19 = 19;
    public final static int NUM_TWENTY = 20;
    public final static int NUM_30 = 30;
    public final static int NUM_32 = 32;
    public final static int NUM_50 = 50;
    public final static int NUM_60 = 60;
    public final static int NUM_64 = 64;
    public final static int NUM_90 = 90;
    public final static int NUM_100 = 100;
    public final static int NUM_200 = 200;
    public final static int NUM_1024 = 1024;
    public final static int NUM_5000 = 5000;
    public final static int NUM_24_time = 86400; // 60*60*60*24 一天
}

四,微信支付请求下单接口

1,控制层 拿到需要支付订单id进行一系列操作
    @Autowired
    private WaybillService waybillService;
    
    /**
     * 微信请求下单支付
     *
     * @param waybillId 运单id
     * @return 付款所需参数
     */
    @RepeatSubmit
    @GetMapping("/weChatCreateOrder/{waybillId}")
    @Operation(summary = "微信请求下单支付")
    public AjaxResult<CreateOrderVo> weChatCreateOrder(@PathVariable("waybillId") String waybillId) {
        return waybillService.weChatCreateOrder(waybillId);
    }
2,返回对象 CreateOrderVo 用户付款需要的参数
@Data
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode(callSuper = false)
public class CreateOrderVo implements Serializable {

    //     微信小程序需要参数
    //    "appId": "wx499********7c70e",  // 微信开放平台 - 应用 - AppId,注意和微信小程序、公众号 AppId 可能不一致
    //    "timeStamp": 1597935292,        // 时间戳(单位:秒)
    //    "nonceStr": "c5sEwbaNPiXAF3iv", // 随机字符串
    //    "package": "wx202254********************fbe90000", // 小程序下单接口返回的prepay_id参数值,提交格式如:prepay_id=***;示例值:prepay_id=wx201410272009395522657a690389285100
    //    "signType": "RSA"    签名类型,默认为RSA,仅支持RSA。
    //    "paySign": "A842B45937F6EFF60DEC7A2EAA52D5A0" // 签名,这里用的 MD5/RSA 签名

    @Serial
    private static final long serialVersionUID = 1L;

    /**
     * 时间戳
     */
    @Schema(description = "时间戳")
    private String timeStamp;

    /**
     * 随机字符串
     */
    @Schema(description = "随机字符串")
    private String nonceStr;

    /**
     * 订单详情扩展字符串 package
     */
    @Schema(description = "订单详情扩展字符串 package")
    private String wxPackage;

    /**
     * 签名方式
     */
    @Schema(description = "签名方式")
    private String signType;

    /**
     * 签名
     */
    @Schema(description = "签名")
    private String paySign;

    /**
     * 运单号
     */
    @Schema(description = "运单号")
    private String outTradeNo;


}
3,业务层 请求下单获取prepay_id构造签名
    // 第三节中保存的微信配置信息 注入进来
    @Autowired
    private WeChatToolConfig config;

    @Autowired
    private PayService payService;

    /**
     * 微信请求下单支付
     *
     * @param waybillId 运单id
     * @return 付款所需参数
     */
    @Override
    public AjaxResult<CreateOrderVo> weChatCreateOrder(String waybillId) {
        // 查询运单
        Waybill waybill = getById(waybillId, "运单id不存在");
        // 调用微信请求下单接口
        int total = waybill.getGoodTotalPrice().multiply(new BigDecimal("100.00")).intValue();
        // 获取付款所需参数
        CreateOrderVo createOrder = payService.createPayOrder(waybill.getGoodName(), waybillId, waybill.getOpenId(), total, config);
        log.info("支付返回" + createOrder);
        return AjaxResult.success(createOrder);
    }
    /**
     * 小程序请求下单
     *
     * @param description 商品描述
     * @param outTradeNo  商户订单号
     * @param openId      appid下的唯一标识
     * @param total       总金额
     * @param config      基本配置
     * @return 小程序下单前端Vo
     */
    @Override
    public CreateOrderVo createPayOrder(String description, String outTradeNo, String openId, int total, WeChatToolConfig config) {
        // 配置下单参数
        JSONObject requestParam = new JSONObject();
        // 商户id
        requestParam.put(WeChatPayParam.MCHID, config.getMchId());
        // 应用id
        requestParam.put(WeChatPayParam.APPID, config.getAppId());
        // 商品描述
        requestParam.put(WeChatPayParam.DESCRIPTION, description);
        // 回调地址
        requestParam.put(WeChatPayParam.NOTIFY_URL, config.getNotifyUrl());
        // 订单号
        requestParam.put(WeChatPayParam.OUT_TRADE_NO, outTradeNo);
        // 金额
        JSONObject amount = new JSONObject();
        // 金额 单位 分
        amount.put(WeChatPayParam.AMOUNT_TOTAL, "dev".equals(SpringUtils.getActiveProfile()) ? 1 : total);
        // 货币类型 人民币
        amount.put(WeChatPayParam.AMOUNT_CURRENCY, WeChatPayParam.CURRENCY_RMB);
        // 金额
        requestParam.put(WeChatPayParam.AMOUNT, amount);

        // 支付者
        JSONObject payer = new JSONObject();
        // 微信登录时生成的openid,创建订单时存入订单中
        payer.put(WeChatPayParam.OPENID, openId);
        // 支付者
        requestParam.put(WeChatPayParam.PAYER, payer);

        try {
            // 调用post请求
            JSONObject jsonObject = WeChatUtil.getBodyAsJsonByPost(WxApiTypeEnum.CREATE_ORDER.getValue(), requestParam, config);
            // 异常情况
            if (Objects.nonNull(jsonObject.get(WeChatPayParam.CODE))) {
                throw new CustomException(jsonObject.get(WeChatPayParam.MESSAGE).toString());
            }
            // 时间戳
            String timestamp = String.valueOf(DateUtil.currentSeconds());
            // 随机字符串
            String nonce = RandomUtil.randomString(WeChatPayParam.NUM_32);
            // 订单详情扩展字符串 prepay_id=wx201410272009395522657a690389285100  必须以此格式
            String wxPackage = WeChatPayParam.WX_PACKAGE + jsonObject.get(WeChatPayParam.PREPAY_ID);
            // 获取签名
            String sign = WeChatUtil.getSign(config.getAppId(), timestamp, nonce, wxPackage, config.getPrivateKey());

            return new CreateOrderVo(timestamp, nonce, wxPackage, "RSA", sign, outTradeNo);
        } catch (Exception e) {
            log.error("请求下单失败:" + outTradeNo);
            log.error(e.getMessage());
            throw new CustomException("请求下单失败:" + e.getMessage());
        }
    }

五,微信回调接口 注:需使用工具解决内网穿透问题 例:路由侠

1,控制层
    /**
     * 支付回调给微信确认
     */
    @Operation(summary = "支付回调给微信确认")
    @IgnoreAuth
    @PostMapping("/wechatPayCallback")
    public Map<String, String> wechatCallback(HttpServletRequest request) {
        Map<String, String> result = new HashMap<>(WeChatPayParam.NUM_TWO);
        // 判断支付是否成功
        if (waybillService.verifyCreateOrder(request)) {
            result.put(WeChatPayParam.CODE, WeChatPayParam.WX_BACK_OK);
        } else {
            result.put(WeChatPayParam.CODE, "FAIL");
            result.put(WeChatPayParam.MESSAGE, "失败");
        }
        return result;
    }
2,业务层 判断验证是否成功
    /**
     * 微信支付回调验证判定 核对成功 数据异步入库
     */
    @Override
    @Transactional
    public Boolean verifyCreateOrder(HttpServletRequest request) {
        log.info("===================================进入微信支付回调核对订单中========================================");
        // 获取微信通知详情
        JSONObject decryptData = payService.getDecryptData(request, config);

        // 获取运单号
        String waybillId = decryptData.get(WeChatPayParam.OUT_TRADE_NO).toString();

        // 获取微信支付生成的订单号
        String transactionId = decryptData.get(WeChatPayParam.TRANSACTION_ID).toString();

        log.info(decryptData.get(WeChatPayParam.OUT_TRADE_NO).toString() + "订单回调信息记录:订单状态:" + waybillId);

        // 判断支付是否成功
        if (StrUtil.equals(WeChatPayParam.WX_BACK_OK, decryptData.get(WeChatPayParam.TRADE_STATE).toString())) {
            // 查询运单
            Waybill waybill = getById(waybillId, "运单id不存在");
            // 判断金额是否与数据库一致
            if (WeChatUtil.verifyMoney(decryptData, getAmountByOrderNo(waybillId))) {
                // 修改订单状态,保存微信支付订单号
                updateStatus(waybill, transactionId);
                // 判断发货是否成功
                if (payService.verifyDeliverGood(waybill.getGoodName(), waybill.getOpenId(), transactionId, config)) {
                    // 修改发货状态 确认
                    updateDeliverGood(waybill, ShipmentEnum.OK);
                } else {
                    // 修改发货状态 未确认
                    updateDeliverGood(waybill, ShipmentEnum.NOT);
                }
                return true;
            }
        }
        return false;
    }
    /**
     * 获取通知
     *
     * @param request 请求
     * @param config  微信配置
     * @return 通知详情
     */
    @Override
    public JSONObject getDecryptData(HttpServletRequest request, WeChatToolConfig config) {
        try {
            // 解析微信通知
            Notification notification = WeChatUtil.verifyBack(request, config);
            // 获取通知简要说明
            String summary = notification.getSummary();
            // 判断支付状态
            if (WeChatPayParam.PAY_SUCCESS.equals(summary) || WeChatPayParam.REFUND_SUCCESS.equals(summary)) {
                // 返回详情信息
                return JSON.parseObject(notification.getDecryptData());
            } else {
                // 支付失败
                throw new CustomException(summary);
            }
        } catch (Exception e) {
            log.error("回调失败验证" + e);
            e.printStackTrace();
            throw new CustomException("支付回调失败:" + e.getMessage());
        }
    }
    /**
     * 判断发货是否成功
     *
     * @param description   商品描述
     * @param openId        appid下的唯一标识
     * @param transactionId 微信支付生成订单号
     * @param config        基本配置
     * @return 是否成功
     */
    @Override
    public Boolean verifyDeliverGood(String description, String openId, String transactionId, WeChatToolConfig config) {
        try {
            // 获取接口调用凭证
            JSONObject jsonObject = WeChatUtil.getAccessToken(config);
            // 异常情况
            if (Objects.nonNull(jsonObject.get(WeChatPayParam.CODE))) {
                throw new CustomException(jsonObject.get(WeChatPayParam.MESSAGE).toString());
            }
            String accessToken = jsonObject.get(WeChatPayParam.ACCESS_TOKEN).toString();

            // 构建请求参数
            JSONObject requestParam = new JSONObject();

            // 订单
            JSONObject orderKey = new JSONObject();
            orderKey.put(WeChatPayParam.ORDER_NUMBER_TYPE, OrderNumberTypeEnum.WECHAT.getCode());
            orderKey.put(WeChatPayParam.TRANSACTION_ID, transactionId);
            requestParam.put(WeChatPayParam.ORDER_KEY, orderKey);
            // 物流模式 用户自提
            requestParam.put(WeChatPayParam.LOGISTICS_TYPE, LogisticsTypeEnum.VIRTUAL.getCode());
            // 发货模式 统一发货
            requestParam.put(WeChatPayParam.DELIVERY_MODE, DeliveryModeEnum.UNIFIED_DELIVERY.getCode());
            // 物流信息列表
            List<JSONObject> shippingLists = new ArrayList<>();
            Map<String, Object> map = new HashMap<>();
            map.put(WeChatPayParam.ITEM_DESC, description);
            shippingLists.add(new JSONObject(map));
            requestParam.put(WeChatPayParam.SHIPPING_LIST, shippingLists);
            // 上传时间
            requestParam.put(WeChatPayParam.UPLOAD_TIME, new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ").format(new Date()));
            // 支付者
            JSONObject payer = new JSONObject();
            // 微信登录时生成的openid,创建订单时存入订单中
            payer.put(WeChatPayParam.OPENID, openId);
            requestParam.put(WeChatPayParam.PAYER, payer);
            // 设置请求url
            String url = StrFormatter.format(WxApiTypeEnum.GOOD_ENTERING.getValue(), accessToken);
            // 发送请求
            JSONObject object = WeChatUtil.getBodyAsJsonByPost(url, requestParam, config);
            // 发货失败
            if (!object.get(WeChatPayParam.ERR_CODE).equals(WeChatPayParam.NUM_ZERO)) {
                log.error("发货失败:" + object.get(WeChatPayParam.ERR_CODE) + " " + object.get(WeChatPayParam.ERR_MSG));
                return false;
            }
        } catch (Exception e) {
            e.printStackTrace();
            throw new CustomException(e.getMessage());
        }
        return true;
    }
    /**
     * 更新订单,运单状态 已完成
     *
     * @param waybill       运单
     * @param transactionId 微信支付订单号
     */
    public void updateStatus(Waybill waybill, String transactionId) {
        // 修改运单状态
        waybill.setWaybillStatus(WaybillStatusEnum.COMPLETED);
        waybill.setPayId(transactionId);
        updateById(waybill);

        // 新增散户流水
        individualAddFlow(waybill);

        // 查询订单
        final OrderService orderService = SpringUtils.getBean(OrderService.class);
        LambdaQueryWrapper<Order> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Order::getId, waybill.getOrderId());
        Order order = orderService.getOne(queryWrapper);

        // 修改订单状态
        LambdaUpdateWrapper<Order> orderWrapper = new LambdaUpdateWrapper<>();
        orderWrapper.eq(Order::getId, waybill.getOrderId());
        orderWrapper.set(Order::getOrderStatus, OrderStatusEnum.COMPLETED);
        orderWrapper.set(Order::getGoodWeight, waybill.getNetWeight());
        orderWrapper.set(Order::getGoodTotalPrice, waybill.getNetWeight().multiply(order.getGoodPrice()));
        orderService.update(orderWrapper);
    }

    /**
     * 修改运单确认发货
     *
     * @param waybill  运单
     * @param shipment 确认发货枚举
     */
    private void updateDeliverGood(JkwlWaybill waybill, ShipmentEnum shipment) {
        waybill.setShipment(shipment);
        updateById(waybill);
    }

六,查询支付结果

1,控制层
    /**
     * 查询微信支付订单状态
     *
     * @param waybillId 运单id
     */
    @GetMapping("/getPayOrder/{waybillId}")
    @Operation(summary = "查询微信支付订单状态")
    public AjaxResult<Void> getPayOrder(@PathVariable("waybillId") String waybillId) {
        return waybillService.getPayOrder(waybillId);
    }
2,业务层
/**
 * 查询微信支付是否成功
 *
 * @param waybillId 运单id
 */
@Override
public AjaxResult<Void> getPayOrder(String waybillId) {
    try {
        // 查询微信支付信息
        JSONObject jsonObject = payService.getPayState(waybillId, config);
        // 返回查单结果信息
        final String res = jsonObject.get(WeChatPayParam.TRADE_STATE).toString();
        // 支付成功
        if (WeChatPayParam.WX_BACK_OK.equals(res)) {
        // 根据业务 该干嘛干嘛
        // 例: 修改订单
        // updateStatus(orderId);
        return AjaxResult.success("SUCCESS");
    } catch (Exception e) {
        log.error("支付查单失败" + orderId);
        e.printStackTrace();
    }
    return AjaxResult.error();
}
    /**
     * 获取支付状态
     *
     * @param waybillId 运单id
     * @param config    配置
     * @return 状态详情
     */
    @Override
    public JSONObject getPayState(String waybillId, WeChatToolConfig config) throws IOException {
        // 查单 微信接口 编辑 微信订单号 + 商户号
        String url = StrFormatter.format(WxApiTypeEnum.QUERY_CREATE_ORDER.getValue(), waybillId, config.getMchId());
        // 调用微信接口
        JSONObject jsonObject = WeChatUtil.getBodyAsJsonByGet(url, config);
        // 异常情况
        if (Objects.nonNull(jsonObject.get(WeChatPayParam.CODE))) {
            throw new CustomException(jsonObject.get(WeChatPayParam.MESSAGE).toString());
        }
        return jsonObject;
    }

七,微信退款

    /**
     * 微信退款
     *
     * @param waybillId 运单id
     */
    @RepeatSubmit
    @GetMapping("/weChatRefundOrder/{waybillId}/{amount}")
    @Operation(summary = "微信退款")
    @IgnoreAuth
    public AjaxResult<Void> weChatRefundOrder(@PathVariable("waybillId") String waybillId, @PathVariable("amount") int amount) {
        return waybillService.weChatRefundOrder(waybillId, amount);
    }
    /**
     * 微信退款
     *
     * @param waybillId 运单id
     */
    @Override
    public AjaxResult<Void> weChatRefundOrder(String waybillId, int amount) {
        // 非开发环境 输入多少退多少
        if (!"dev".equals(SpringUtils.getActiveProfile())) {
            // 从数据库获取运单退款金额
            amount = getAmountByOrderNo(waybillId);
        }
        // 调用微信退款接口
        JSONObject jsonObject = payService.refundOrder(waybillId, amount, config);
        // 获取微信退款状态
        String status = jsonObject.get(WeChatPayParam.STATUS).toString();

        return switch (status) {
            // 退款关闭
            case "CLOSED" -> AjaxResult.success("退款关闭");
            // 退款处理中
            case "PROCESSING" -> AjaxResult.success("退款处理中 请稍后");
            // 退款成功
            case "SUCCESS" -> {
                // 可拓展 退款成功后进行的操作 例:修改订单退款订单,运单状态 微信退款入数据库
                // updateStatus(orderId);
                yield AjaxResult.success("退款申请成功 注意退款查收");
            }
            // 退款异常
            default -> AjaxResult.error("退款账户异常 请联系管理人员");
        };
    }
    /**
     * 微信退款
     *
     * @param waybillId    运单id
     * @param refundAmount 退款金额
     * @param config       配置
     * @return 状态详情
     */
    @Override
    public JSONObject refundOrder(String waybillId, Integer refundAmount, WeChatToolConfig config) {
        // 配置参数
        JSONObject requestParam = new JSONObject();
        // 商户订单号 orderId
        requestParam.put(WeChatPayParam.OUT_TRADE_NO, waybillId);
        // 商户退款单号
        requestParam.put(WeChatPayParam.OUT_REFUND_NO, waybillId);
        // 退款回调地址
        requestParam.put(WeChatPayParam.NOTIFY_URL, config.getRefundNotifyUrl());
        // 金额信息 amount (单位都是分)
        JSONObject amount = new JSONObject();
        // 原订单金额 total
        amount.put(WeChatPayParam.AMOUNT_TOTAL, refundAmount);
        // 退款金额 refund
        amount.put(WeChatPayParam.AMOUNT_REFUND, refundAmount);
        // 退款币种 CNY 人民币
        amount.put(WeChatPayParam.AMOUNT_CURRENCY, WeChatPayParam.CURRENCY_RMB);
        // 金额信息 amount: 原订单金额 total 退款金额 refund (单位都是分)
        requestParam.put(WeChatPayParam.AMOUNT, amount);

        try {
            // 调用请求接口
            JSONObject jsonObject = WeChatUtil.getBodyAsJsonByPost(WxApiTypeEnum.REFUND_ORDER.getValue(), requestParam, config);
            // 异常情况
            if (Objects.nonNull(jsonObject.get(WeChatPayParam.CODE))) {
                throw new CustomException(jsonObject.get(WeChatPayParam.MESSAGE).toString());
            }
            log.info("微信申请退款返回结果" + "response:" + jsonObject);
            return jsonObject;
        } catch (Exception e) {
            log.info("微信退款" + waybillId + "失败");
            e.printStackTrace();
            throw new CustomException("退款失败:" + e.getMessage());
        }
    }

八、退款回调

    /**
     * 微信退款回调接口
     */
    @IgnoreAuth
    @Operation(summary = "微信退款回调接口")
    @PostMapping("/wechatRefundBack")
    public Map<String, String> wechatRefundBack(HttpServletRequest request) {
        Map<String, String> result = new HashMap<>(WeChatPayParam.NUM_TWO);
        // 根据退款返回结果 更新订单状态 返回结果
        if (waybillService.verifyRefundOrder(request)) {
            log.info("==============================微信退款成功订单=====================================");
            result.put(WeChatPayParam.CODE, WeChatPayParam.WX_BACK_OK);
        } else {
            result.put(WeChatPayParam.CODE, "FAIL");
            result.put(WeChatPayParam.MESSAGE, "失败");
        }
        return result;
    }
    @Override
    public Boolean verifyRefundOrder(HttpServletRequest request) {
        // 获取微信通知详情
        JSONObject decryptData = jkwlPayService.getDecryptData(request, config);
        // 商户退款单号
        String outRefundNo = decryptData.get(WeChatPayParam.OUT_REFUND_NO).toString();

        log.info("微信退款成功订单:" + outRefundNo);

        // 退款状态
        String refundStatus = decryptData.get(WeChatPayParam.REFUND_STATUS).toString();
        // 数据库核对 如果退款状态已退款说明已处理 直接返回即可
        if (WaybillStatusEnum.CANCELED.equals(getStatus(outRefundNo))) {
            return true;
        }
        return switch (refundStatus) {
            // 退款关闭
            case "CLOSED" -> Boolean.FALSE;
            // 退款成功
            case "SUCCESS" -> {
                // 修改订单退款订单,运单状态 微信退款入数据库
                // updateStatus(outRefundNo);
                yield Boolean.TRUE;
            }
            // 退款异常
            default -> Boolean.FALSE;
        };
    }

九,查询退款订单

    /**
     * 查询退款订单
     *
     * @param waybillId 运单id
     */
    @Operation(summary = "查询退款订单")
    @GetMapping("/getRefundOrder/{waybillId}")
    public AjaxResult<Void> getRefundOrder(@PathVariable("waybillId") String waybillId) {
        return waybillService.getRefundOrder(waybillId);
    }
    /**
     * 查询退款订单
     *
     * @param waybillId 运单id
     */
    @Override
    public AjaxResult<Void> getRefundOrder(String waybillId) {
        try {
            // 调用微信查询退款订单接口
            JSONObject jsonObject = payService.getRefundState(waybillId, config);
            // 获取退款状态
            String string = jsonObject.get(WeChatPayParam.STATUS).toString();

            return switch (string) {
                // 退款关闭
                case "CLOSED" -> AjaxResult.success("退款关闭");
                // 退款处理中
                case "PROCESSING" -> AjaxResult.success("退款处理中 请稍后");
                // 退款成功
                case "SUCCESS" -> AjaxResult.success("退款申请成功 注意退款查收");
                // 退款异常
                default -> AjaxResult.error("退款账户异常 请联系管理人员");
            };
        } catch (Exception e) {
            log.error("支付查单失败" + waybillId);
            e.printStackTrace();
        }
        return AjaxResult.error();
    }
    /**
     * 获取退款状态
     *
     * @param waybillId 运单id
     * @param config    配置
     * @return 状态详情
     */
    @Override
    public JSONObject getRefundState(String waybillId, WeChatToolConfig config) throws IOException {
        // 查单 微信接口 拼接url
        String url = StrFormatter.format(WxApiTypeEnum.QUERY_REFUND_ORDER.getValue(), waybillId);
        // 调用微信接口
        JSONObject jsonObject = WeChatUtil.getBodyAsJsonByGet(url, config);
        // 异常情况
        if (Objects.nonNull(jsonObject.get(WeChatPayParam.CODE))) {
            throw new CustomException(jsonObject.get(WeChatPayParam.MESSAGE).toString());
        }
        return jsonObject;
    }

如果您下载了本程序,但是该程序存在问题无法运行,那么您可以选择退款或者寻求我们的帮助(如果找我们帮助的话,是需要追加额外费用的)。另外,您不会使用资源的话(这种情况不支持退款),也可以找我们帮助(需要追加额外费用) 爬虫(Web Crawler)是一种自动化程序,用于从互联网上收集信息。其主要功能是访问网页、提取数据并存储,以便后续分析或展示。爬虫通常由搜索引擎、数据挖掘工具、监测系统等应用于网络数据抓取的场景。 爬虫的工作流程包括以下几个关键步骤: URL收集: 爬虫从一个或多个初始URL开始,递归或迭代地发现新的URL,构建一个URL队列。这些URL可以通过链接分析、站点地图、搜索引擎等方式获取。 请求网页: 爬虫使用HTTP或其他协议向目标URL发起请求,获取网页的HTML内容。这通常通过HTTP请求库实现,如Python中的Requests库。 解析内容: 爬虫对获取的HTML进行解析,提取有用的信息。常用的解析工具有正则表达式、XPath、Beautiful Soup等。这些工具帮助爬虫定位和提取目标数据,如文本、图片、链接等。 数据存储: 爬虫将提取的数据存储到数据库、文件或其他存储介质中,以备后续分析或展示。常用的存储形式包括关系型数据库、NoSQL数据库、JSON文件等。 遵守规则: 为避免对网站造成过大负担或触发反爬虫机制,爬虫需要遵守网站的robots.txt协议,限制访问频率和深度,并模拟人类访问行为,如设置User-Agent。 反爬虫应对: 由于爬虫的存在,一些网站采取了反爬虫措施,如验证码、IP封锁等。爬虫工程师需要设计相应的策略来应对这些挑战。 爬虫在各个领域都有广泛的应用,包括搜索引擎索引、数据挖掘、价格监测、新闻聚合等。然而,使用爬虫需要遵守法律和伦理规范,尊重网站的使用政策,并确保对被访问网站的服务器负责。
智能网联汽车的安全员高级考试涉及多个方面的专业知识,包括但不限于自动驾驶技术原理、车辆传感器融合、网络安全防护以及法律法规等内容。以下是针对该主题的一些核心知识点解析: ### 关于智能网联车安全员高级考试的核心内容 #### 1. 自动驾驶分级标准 国际自动机工程师学会(SAE International)定义了六个级别的自动驾驶等级,从L0到L5[^1]。其中,L3及以上级别需要安全员具备更高的应急处理能力。 #### 2. 车辆感知系统的组成与功能 智能网联车通常配备多种传感器,如激光雷达、毫米波雷达、摄像头和超声波传感器等。这些设备协同工作以实现环境感知、障碍物检测等功能[^2]。 #### 3. 数据通信与网络安全 智能网联车依赖V2X(Vehicle-to-Everything)技术进行数据交换,在此过程中需防范潜在的网络攻击风险,例如中间人攻击或恶意软件入侵[^3]。 #### 4. 法律法规要求 不同国家和地区对于无人驾驶测试及运营有着严格的规定,考生应熟悉当地交通法典中有关自动化驾驶部分的具体条款[^4]。 ```python # 示例代码:模拟简单决策逻辑 def decide_action(sensor_data): if sensor_data['obstacle'] and not sensor_data['emergency']: return 'slow_down' elif sensor_data['pedestrian_crossing']: return 'stop_and_yield' else: return 'continue_driving' example_input = {'obstacle': True, 'emergency': False, 'pedestrian_crossing': False} action = decide_action(example_input) print(f"Action to take: {action}") ``` 需要注意的是,“橙点同学”作为特定平台上的学习资源名称,并不提供官方认证的标准答案集;建议通过正规渠道获取教材并参加培训课程来准备此类资格认证考试。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

手可摘鑫晨

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值