微信APP支付V3(java版本)-程序员宅基地

技术标签: java  微信  微信支付  

1.支付配置

注意:(appId账号)微信商户平台必须开通APP支付功能

文件 application.yml新增以下配置
# app微信支付配置
wx:
  appPay:
    # appId账号
    appId: ******
    # 商户号
    mchId: ********
    # apiV3秘钥
    apiV3Key: *********
    # 商户证书序列号
    mchSerialNo: *********
    # 商户私钥路径
    privateKeyUrl: ************
    # 平台证书路径
    wechatPayCertificateUrl: ************
    # 支付回调地址
    notifyUrl: *************

2.业务代码实现

<!-- 微信 -->
<dependency>
    <groupId>com.github.binarywang</groupId>
    <artifactId>weixin-java-open</artifactId>
    <version>3.1.0</version>
</dependency>

<dependency>
    <groupId>com.github.binarywang</groupId>
    <artifactId>weixin-java-mp</artifactId>
    <version>3.1.0</version>
</dependency>

<dependency>
    <groupId>com.github.binarywang</groupId>
    <artifactId>weixin-java-pay</artifactId>
    <version>3.1.0</version>
</dependency>

<dependency>
    <groupId>com.github.wechatpay-apiv3</groupId>
    <artifactId>wechatpay-java</artifactId>
    <version>0.2.7</version>
</dependency>
package com.controller.busi;

import cn.hutool.core.util.RandomUtil;
import com.alibaba.fastjson.JSON;
import com.busi.config.wxpayv3.IJPayHttpResponse;
import com.busi.config.wxpayv3.PayKit;
import com.busi.config.wxpayv3.RequestMethodEnum;
import com.busi.config.wxpayv3.WxPayApi;
import com.config.WxAppPayConfig;
import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner;
import com.wechat.pay.contrib.apache.httpclient.auth.Verifier;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Credentials;
import com.wechat.pay.contrib.apache.httpclient.cert.CertificatesManager;
import com.wechat.pay.contrib.apache.httpclient.notification.Notification;
import com.wechat.pay.contrib.apache.httpclient.notification.NotificationHandler;
import com.wechat.pay.contrib.apache.httpclient.notification.NotificationRequest;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;

import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.PrivateKey;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

@Api(value = "微信app支付Controller", tags = "app-支付")
@RestController
@RequestMapping("/app/pay")
@Slf4j
public class AppWxPayController {

    @Value("${wx.appPay.appId}")
    private String appId;

    @Value("${wx.appPay.mchId}")
    private String mchId;

    @Value("${wx.appPay.apiV3Key}")
    private String apiV3Key;

    @Value("${wx.appPay.mchSerialNo}")
    private String mchSerialNo;

    @Value("${wx.appPay.privateKeyUrl}")
    private String privateKeyUrl;

    @Value("${wx.appPay.wechatPayCertificateUrl}")
    private String wechatPayCertificateUrl;

    @Value("${wx.appPay.notifyUrl}")
    private String notifyUrl;

    @ApiOperation("创建预支付订单")
    @PostMapping("/doUnifiedOrder")
    public Map<String, Object> WxPayApp() {
        Map<String, Object> payMap = new HashMap<>();
        // 调用app微信支付api入参
        com.alibaba.fastjson.JSONObject json = new com.alibaba.fastjson.JSONObject();
        json.put("appid", appId);
        json.put("mchid", mchId);
        // 商品描述
        json.put("description", "测试商品");
        // 订单号
        String outTradeNo = generateNonceStr();
        json.put("out_trade_no", outTradeNo);
        // 回调url
        json.put("notify_url", notifyUrl);
        com.alibaba.fastjson.JSONObject amountJson = new com.alibaba.fastjson.JSONObject();
        amountJson.put("total", 1);
        amountJson.put("currency", "CNY");
        json.put("amount", amountJson);
        // 价格不能小于一分钱
        com.alibaba.fastjson.JSONObject jsonObject = WxAppPayConfig.doPostWexinV3("https://api.mch.weixin.qq.com/v3/pay/transactions/app", json.toJSONString(),
                privateKeyUrl, wechatPayCertificateUrl, mchId, mchSerialNo);
        log.info("调用app微信支付api结果:" + jsonObject);
        assert jsonObject != null;
        String prepay_id = jsonObject.getString("prepay_id");
        // 时间戳
        long timestamp = System.currentTimeMillis() / 1000;
        // 随机串
        String nonceStr = UUID.randomUUID().toString().replace("-", "");
        String sign = WxAppPayConfig.getSign(appId, timestamp, nonceStr, prepay_id, privateKeyUrl, wechatPayCertificateUrl);
        log.info("签名:" + sign);
        payMap.put("prepayid", prepay_id);
        payMap.put("timestamp", String.valueOf(timestamp));
        payMap.put("noncestr", nonceStr);
        payMap.put("sign", sign);
        payMap.put("appid", appId);
        payMap.put("package", "Sign=WXPay");
        payMap.put("extData", "sign");
        payMap.put("partnerid", mchId);
        // TODO 订单信息入库
        // payMap是拉起app微信支付需要的参数
        return payMap;
    }

    @ApiOperation("app微信支付回调")
    @GetMapping("/notify")
    public void notify(HttpServletRequest request) throws Exception {
        log.info("收到app微信支付回调....");
        // 获取报文
        String body = getRequestBody(request);
        // 随机串
        String nonce = request.getHeader("Wechatpay-Nonce");
        // 微信传递过来的签名
        String signature = request.getHeader("Wechatpay-Signature");
        // 证书序列号(微信平台)
        String wechatPaySerial = request.getHeader("Wechatpay-Serial");
        // 时间戳
        String timestamp = request.getHeader("Wechatpay-Timestamp");
        PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(Files.newInputStream(Paths.get(privateKeyUrl)));
        // 获取证书管理器实例
        CertificatesManager certificatesManager = CertificatesManager.getInstance();
        // 向证书管理器增加需要自动更新平台证书的商户信息
        certificatesManager.putMerchant(mchId, new WechatPay2Credentials(mchId,
                        new PrivateKeySigner(mchSerialNo, merchantPrivateKey)),
                apiV3Key.getBytes(StandardCharsets.UTF_8));
        // 从证书管理器中获取verifier
        Verifier verifier = certificatesManager.getVerifier(mchId);
        // 构建request,传入必要参数
        NotificationRequest notificationRequest = new NotificationRequest.Builder().withSerialNumber(wechatPaySerial)
                .withNonce(nonce)
                .withTimestamp(timestamp)
                .withSignature(signature)
                .withBody(body)
                .build();
        NotificationHandler handler = new NotificationHandler(verifier, apiV3Key.getBytes(StandardCharsets.UTF_8));
        // 验签和解析请求体
        Notification notification = handler.parse(notificationRequest);
        String result = notification.getDecryptData();
        log.info("解密报文:" + result);
        com.alibaba.fastjson.JSONObject resultJson = JSON.parseObject(result);
        String trade_state = resultJson.getString("trade_state").trim();
        String out_trade_no = resultJson.getString("out_trade_no").trim();
        String trade_type = resultJson.getString("trade_type").trim();
        log.info("微信支付交易状态码:" + trade_state);
        log.info("微信支付交易订单号:" + out_trade_no);
        log.info("微信支付交易类型:" + trade_type);
        // TODO 支付成功,更新订单信息
        if ("SUCCESS".equals(trade_state)) {

        }
    }

    /**
     * 获取随机字符串 Nonce Str
     */
    public String generateNonceStr() {
        StringBuilder stringBuffer = new StringBuilder();
        int prefix = RandomUtil.randomInt(10000, 99999);
        int suffix = RandomUtil.randomInt(10000, 99999);
        Long time = System.currentTimeMillis();
        return stringBuffer.append(prefix).append(time).append(suffix).toString();
    }

    private String getRequestBody(HttpServletRequest request) {
        StringBuilder sb = new StringBuilder();
        try (ServletInputStream inputStream = request.getInputStream();
             BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
        ) {
            String line;

            while ((line = reader.readLine()) != null) {
                sb.append(line);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return sb.toString();
    }

    // 退款
    public static void main(String[] args) throws Exception {
        // 查询订单信息
        String orderId = "94024170497607658181870";
        int amount = 100;
        String productName = "测试课程";
        com.alibaba.fastjson2.JSONObject jsonObject = new com.alibaba.fastjson2.JSONObject();
        jsonObject.put("out_trade_no", orderId);
        // 退款单号
        String outRefundNo = PayKit.generateStr();
        jsonObject.put("out_refund_no", outRefundNo);
        jsonObject.put("reason", "取消课程【" + productName + "】");
        com.alibaba.fastjson2.JSONObject amountInfo = new com.alibaba.fastjson2.JSONObject();
        amountInfo.put("refund", amount);
        amountInfo.put("total", amount);
        amountInfo.put("currency", "CNY");
        jsonObject.put("amount", amountInfo);
        // 退款参数
        String requestBody = com.alibaba.fastjson2.JSON.toJSON(jsonObject).toString();
        System.out.println("退款参数 ===" + requestBody);

        IJPayHttpResponse response = WxPayApi.v3(
                RequestMethodEnum.POST,
                "https://api.mch.weixin.qq.com",
                "/v3/refund/domestic/refunds",
                "商户号",
                "序列号",
                null,
                "D://file//project//apiclient_key.pem",
                requestBody
        );
        String body = response.getBody();
        System.out.println("退款结果:" + body);
    }
}
package com.busi.config.wxpayv3;

import cn.hutool.core.util.StrUtil;
import cn.hutool.http.ContentType;

import java.io.File;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;

/**
 * @author dori
 * @date 2024/4/17 15:58
 * @description
 */
public class WxPayApi {

    public static IJPayHttpResponse v3(RequestMethodEnum method, String urlPrefix, String urlSuffix, String mchId,
                                       String serialNo, String platSerialNo, String keyPath, String body) throws Exception {
        long timestamp = System.currentTimeMillis() / 1000;
        String authType = AuthTypeEnum.RSA.getUrl();
        String nonceStr = WxPayKit.generateStr();
        return v3(method, urlPrefix, urlSuffix, mchId, serialNo, platSerialNo, keyPath, body, nonceStr, timestamp, authType, null);
    }

    public static IJPayHttpResponse v3(RequestMethodEnum method, String urlPrefix, String urlSuffix,
                                       String mchId, String serialNo, String platSerialNo, String keyPath,
                                       String body, String nonceStr, long timestamp, String authType,
                                       File file) throws Exception {
        // 构建 Authorization
        String authorization = WxPayKit.buildAuthorization(method, urlSuffix, mchId, serialNo,
                keyPath, body, nonceStr, timestamp, authType);

        if (StrUtil.isEmpty(platSerialNo)) {
            platSerialNo = serialNo;
        }

        if (method == RequestMethodEnum.GET) {
            return get(urlPrefix.concat(urlSuffix), authorization, platSerialNo, null);
        } else if (method == RequestMethodEnum.POST) {
            return post(urlPrefix.concat(urlSuffix), authorization, platSerialNo, body);
        } else if (method == RequestMethodEnum.DELETE) {
            return delete(urlPrefix.concat(urlSuffix), authorization, platSerialNo, body);
        } else if (method == RequestMethodEnum.UPLOAD) {
            return upload(urlPrefix.concat(urlSuffix), authorization, platSerialNo, body, file);
        } else if (method == RequestMethodEnum.PUT) {
            return put(urlPrefix.concat(urlSuffix), authorization, platSerialNo, body);
        }
        return null;
    }

    /**
     * @param url    请求url
     * @param params 请求参数
     * @return {@link String}    请求返回的结果
     */
    public static String doGet(String url, Map<String, Object> params) {
        return HttpKit.getDelegate().get(url, params);
    }

    /**
     * get 请求
     *
     * @param url     请求url
     * @param params  请求参数
     * @param headers 请求头
     * @return {@link IJPayHttpResponse}    请求返回的结果
     */
    public static IJPayHttpResponse get(String url, Map<String, Object> params, Map<String, String> headers) {
        return HttpKit.getDelegate().get(url, params, headers);
    }

    /**
     * get 请求
     *
     * @param url           请求url
     * @param authorization 授权信息
     * @param serialNumber  公钥证书序列号
     * @param params        请求参数
     * @return {@link IJPayHttpResponse}    请求返回的结果
     */
    public static IJPayHttpResponse get(String url, String authorization, String serialNumber, Map<String, Object> params) {
        return get(url, params, getHeaders(authorization, serialNumber));
    }

    /**
     * post 请求
     *
     * @param url     请求url
     * @param data    请求参数
     * @param headers 请求头
     * @return {@link IJPayHttpResponse}    请求返回的结果
     */
    public static IJPayHttpResponse post(String url, String data, Map<String, String> headers) {
        return HttpKit.getDelegate().post(url, data, headers);
    }

    /**
     * post 请求
     *
     * @param url           请求url
     * @param authorization 授权信息
     * @param serialNumber  公钥证书序列号
     * @param data          请求参数
     * @return {@link IJPayHttpResponse}    请求返回的结果
     */
    public static IJPayHttpResponse post(String url, String authorization, String serialNumber, String data) {
        return post(url, data, getHeaders(authorization, serialNumber));
    }

    /**
     * delete 请求
     *
     * @param url     请求url
     * @param data    请求参数
     * @param headers 请求头
     * @return {@link IJPayHttpResponse}    请求返回的结果
     */
    public static IJPayHttpResponse delete(String url, String data, Map<String, String> headers) {
        return HttpKit.getDelegate().delete(url, data, headers);
    }

    /**
     * delete 请求
     *
     * @param url           请求url
     * @param authorization 授权信息
     * @param serialNumber  公钥证书序列号
     * @param data          请求参数
     * @return {@link IJPayHttpResponse}    请求返回的结果
     */
    public static IJPayHttpResponse delete(String url, String authorization, String serialNumber, String data) {
        return delete(url, data, getHeaders(authorization, serialNumber));
    }

    /**
     * upload 请求
     *
     * @param url     请求url
     * @param params  请求参数
     * @param headers 请求头
     * @return {@link IJPayHttpResponse}    请求返回的结果
     */
    public static IJPayHttpResponse upload(String url, Map<String, Object> params, Map<String, String> headers) {
        return HttpKit.getDelegate().post(url, params, headers);
    }

    /**
     * upload 请求
     *
     * @param url           请求url
     * @param authorization 授权信息
     * @param serialNumber  公钥证书序列号
     * @param data          请求参数
     * @param file          上传文件
     * @return {@link IJPayHttpResponse}    请求返回的结果
     */
    public static IJPayHttpResponse upload(String url, String authorization, String serialNumber, String data, File file) {
        Map<String, Object> paramMap = new HashMap<>(2);
        paramMap.put("file", file);
        paramMap.put("meta", data);
        return upload(url, paramMap, getUploadHeaders(authorization, serialNumber));
    }


    /**
     * put 请求
     *
     * @param url     请求url
     * @param data    请求参数
     * @param headers 请求头
     * @return {@link IJPayHttpResponse}    请求返回的结果
     */
    public static IJPayHttpResponse put(String url, String data, Map<String, String> headers) {
        return HttpKit.getDelegate().put(url, data, headers);
    }

    /**
     * put 请求
     *
     * @param url           请求url
     * @param authorization 授权信息
     * @param serialNumber  公钥证书序列号
     * @param data          请求参数
     * @return {@link IJPayHttpResponse}    请求返回的结果
     */
    public static IJPayHttpResponse put(String url, String authorization, String serialNumber, String data) {
        return put(url, data, getHeaders(authorization, serialNumber));
    }

    public static String doPost(String url, Map<String, String> params) {
        return HttpKit.getDelegate().post(url, WxPayKit.toXml(params));
    }

    public static String doPostSsl(String url, Map<String, String> params, String certPath, String certPass) {
        return HttpKit.getDelegate().post(url, WxPayKit.toXml(params), certPath, certPass);
    }

    public static String doPostSslByProtocol(String url, Map<String, String> params, String certPath, String certPass, String protocol) {
        return HttpKit.getDelegate().post(url, WxPayKit.toXml(params), certPath, certPass, protocol);
    }

    public static String doPostSsl(String url, Map<String, String> params, InputStream certFile, String certPass) {
        return HttpKit.getDelegate().post(url, WxPayKit.toXml(params), certFile, certPass);
    }

    public static String doPostSslByProtocol(String url, Map<String, String> params, InputStream certFile, String certPass, String protocol) {
        return HttpKit.getDelegate().post(url, WxPayKit.toXml(params), certFile, certPass, protocol);
    }

    public static String doPostSsl(String url, Map<String, String> params, String certPath) {
        if (params.isEmpty() || !params.containsKey("mch_id")) {
            throw new RuntimeException("请求参数中必须包含 mch_id,如接口参考中不包 mch_id, 请使用其他同名构造方法。");
        }
        String certPass = params.get("mch_id");
        return doPostSsl(url, params, certPath, certPass);
    }

    public static String doPostSslByProtocol(String url, Map<String, String> params, String certPath, String protocol) {
        if (params.isEmpty() || !params.containsKey("mch_id")) {
            throw new RuntimeException("请求参数中必须包含 mch_id,如接口参考中不包 mch_id, 请使用其他同名构造方法。");
        }
        String certPass = params.get("mch_id");
        return doPostSslByProtocol(url, params, certPath, certPass, protocol);
    }

    public static String doPostSsl(String url, Map<String, String> params, InputStream certFile) {
        if (params.isEmpty() || !params.containsKey("mch_id")) {
            throw new RuntimeException("请求参数中必须包含 mch_id,如接口参考中不包 mch_id, 请使用其他同名构造方法。");
        }
        String certPass = params.get("mch_id");
        return doPostSsl(url, params, certFile, certPass);
    }

    public static String doPostSslByProtocol(String url, Map<String, String> params, InputStream certFile, String protocol) {
        if (params.isEmpty() || !params.containsKey("mch_id")) {
            throw new RuntimeException("请求参数中必须包含 mch_id,如接口参考中不包 mch_id, 请使用其他同名构造方法。");
        }
        String certPass = params.get("mch_id");
        return doPostSslByProtocol(url, params, certFile, certPass, protocol);
    }

    public static String doUploadSsl(String url, Map<String, String> params, String certPath, String certPass, String filePath) {
        return HttpKit.getDelegate().upload(url, WxPayKit.toXml(params), certPath, certPass, filePath);
    }

    public static String doUploadSslByProtocol(String url, Map<String, String> params, String certPath, String certPass, String filePath, String protocol) {
        return HttpKit.getDelegate().upload(url, WxPayKit.toXml(params), certPath, certPass, filePath, protocol);
    }

    public static String doUploadSsl(String url, Map<String, String> params, String certPath, String filePath) {
        if (params.isEmpty() || !params.containsKey("mch_id")) {
            throw new RuntimeException("请求参数中必须包含 mch_id,如接口参考中不包 mch_id, 请使用其他同名构造方法。");
        }
        String certPass = params.get("mch_id");
        return doUploadSsl(url, params, certPath, certPass, filePath);
    }

    public static String doUploadSslByProtocol(String url, Map<String, String> params, String certPath, String filePath, String protocol) {
        if (params.isEmpty() || !params.containsKey("mch_id")) {
            throw new RuntimeException("请求参数中必须包含 mch_id,如接口参考中不包 mch_id, 请使用其他同名构造方法。");
        }
        String certPass = params.get("mch_id");
        return doUploadSslByProtocol(url, params, certPath, certPass, filePath, protocol);
    }


    private static final String OS = System.getProperty("os.name") + "/" + System.getProperty("os.version");
    private static final String VERSION = System.getProperty("java.version");

    public static Map<String, String> getBaseHeaders(String authorization) {
        String userAgent = String.format(
                "WeChatPay-IJPay-HttpClient/%s (%s) Java/%s",
                WxPayApi.class.getPackage().getImplementationVersion(),
                OS,
                VERSION == null ? "Unknown" : VERSION);

        Map<String, String> headers = new HashMap<>(5);
        headers.put("Accept", ContentType.JSON.toString());
        headers.put("Authorization", authorization);
        headers.put("User-Agent", userAgent);
        return headers;
    }

    public static Map<String, String> getHeaders(String authorization, String serialNumber) {
        Map<String, String> headers = getBaseHeaders(authorization);
        headers.put("Content-Type", ContentType.JSON.toString());
        if (StrUtil.isNotEmpty(serialNumber)) {
            headers.put("Wechatpay-Serial", serialNumber);
        }
        return headers;
    }

    public static Map<String, String> getUploadHeaders(String authorization, String serialNumber) {
        Map<String, String> headers = getBaseHeaders(authorization);
        headers.put("Content-Type", "multipart/form-data;boundary=\"boundary\"");
        if (StrUtil.isNotEmpty(serialNumber)) {
            headers.put("Wechatpay-Serial", serialNumber);
        }
        return headers;
    }

    /**
     * 构建返回参数
     *
     * @param response {@link IJPayHttpResponse}
     * @return {@link Map}
     */
    public static Map<String, Object> buildResMap(IJPayHttpResponse response) {
        if (response == null) {
            return null;
        }
        Map<String, Object> map = new HashMap<>(6);
        String timestamp = response.getHeader("Wechatpay-Timestamp");
        String nonceStr = response.getHeader("Wechatpay-Nonce");
        String serialNo = response.getHeader("Wechatpay-Serial");
        String signature = response.getHeader("Wechatpay-Signature");
        String body = response.getBody();
        int status = response.getStatus();
        map.put("timestamp", timestamp);
        map.put("nonceStr", nonceStr);
        map.put("serialNumber", serialNo);
        map.put("signature", signature);
        map.put("body", body);
        map.put("status", status);
        return map;
    }
}

package com.busi.config.wxpayv3;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.map.CaseInsensitiveMap;
import cn.hutool.core.util.StrUtil;

import java.io.Serializable;
import java.util.List;
import java.util.Map;


public class IJPayHttpResponse implements Serializable {
    private static final long serialVersionUID = 6089103955998013402L;
    private String body;
    private byte[] bodyByte;
    private int status;
    private Map<String, List<String>> headers;

    public String getBody() {
        return body;
    }

    public void setBody(String body) {
        this.body = body;
    }

    public byte[] getBodyByte() {
        return bodyByte;
    }

    public void setBodyByte(byte[] bodyByte) {
        this.bodyByte = bodyByte;
    }

    public int getStatus() {
        return status;
    }

    public void setStatus(int status) {
        this.status = status;
    }

    public Map<String, List<String>> getHeaders() {
        return headers;
    }

    public void setHeaders(Map<String, List<String>> headers) {
        this.headers = headers;
    }

    public String getHeader(String name) {
        List<String> values = this.headerList(name);
        return CollectionUtil.isEmpty(values) ? null : values.get(0);
    }

    private List<String> headerList(String name) {
        if (StrUtil.isBlank(name)) {
            return null;
        } else {
            CaseInsensitiveMap<String, List<String>> headersIgnoreCase = new CaseInsensitiveMap<>(getHeaders());
            return headersIgnoreCase.get(name.trim());
        }
    }

    @Override
    public String toString() {
        return "IJPayHttpResponse{" +
                "body='" + body + '\'' +
                ", status=" + status +
                ", headers=" + headers +
                '}';
    }
}
package com.busi.config.wxpayv3;

import cn.hutool.core.codec.Base64;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.io.resource.ClassPathResource;
import cn.hutool.core.io.resource.Resource;
import cn.hutool.core.lang.Snowflake;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.digest.HmacAlgorithm;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.Principal;
import java.security.PrivateKey;
import java.security.cert.*;
import java.util.*;

/**
 * IJPay 工具类
 *
 * @author dori
 */
public class PayKit {
    /**
     * 对象路径前缀
     */
    public static final String CLASS_PATH_PREFIX = "classpath:";

    /**
     * 生成16进制的 sha256 字符串
     *
     * @param data 数据
     * @param key  密钥
     * @return sha256 字符串
     */
    public static String hmacSha256(String data, String key) {
        return SecureUtil.hmac(HmacAlgorithm.HmacSHA256, key).digestHex(data);
    }

    /**
     * SHA1加密文件,生成16进制SHA1字符串<br>
     *
     * @param dataFile 被加密文件
     * @return SHA1 字符串
     */
    public static String sha1(File dataFile) {
        return SecureUtil.sha1(dataFile);
    }

    /**
     * SHA1加密,生成16进制SHA1字符串<br>
     *
     * @param data 数据
     * @return SHA1 字符串
     */
    public static String sha1(InputStream data) {
        return SecureUtil.sha1(data);
    }

    /**
     * SHA1加密,生成16进制SHA1字符串<br>
     *
     * @param data 数据
     * @return SHA1 字符串
     */
    public static String sha1(String data) {
        return SecureUtil.sha1(data);
    }

    /**
     * 生成16进制 MD5 字符串
     *
     * @param data 数据
     * @return MD5 字符串
     */
    public static String md5(String data) {
        return SecureUtil.md5(data);
    }

    /**
     * AES 解密
     *
     * @param base64Data 需要解密的数据
     * @param key        密钥
     * @return 解密后的数据
     */
    public static String decryptData(String base64Data, String key) {
        return SecureUtil.aes(md5(key).toLowerCase().getBytes()).decryptStr(base64Data);
    }

    /**
     * AES 加密
     *
     * @param data 需要加密的数据
     * @param key  密钥
     * @return 加密后的数据
     */
    public static String encryptData(String data, String key) {
        return SecureUtil.aes(md5(key).toLowerCase().getBytes()).encryptBase64(data.getBytes());
    }

    /**
     * 简化的UUID,去掉了横线,使用性能更好的 ThreadLocalRandom 生成UUID
     *
     * @return 简化的 UUID,去掉了横线
     */
    public static String generateStr() {
        return IdUtil.fastSimpleUUID();
    }

    /**
     * 雪花算法
     *
     * @param workerId     终端ID
     * @param dataCenterId 数据中心ID
     * @return {@link Snowflake}
     */
    public static Snowflake getSnowflake(long workerId, long dataCenterId) {
        return IdUtil.getSnowflake(workerId, dataCenterId);
    }

    /**
     * 把所有元素排序
     *
     * @param params 需要排序并参与字符拼接的参数组
     * @return 拼接后字符串
     */
    public static String createLinkString(Map<String, String> params) {
        return createLinkString(params, false);
    }

    /**
     * @param params 需要排序并参与字符拼接的参数组
     * @param encode 是否进行URLEncoder
     * @return 拼接后字符串
     */
    public static String createLinkString(Map<String, String> params, boolean encode) {
        return createLinkString(params, "&", encode);
    }

    /**
     * @param params  需要排序并参与字符拼接的参数组
     * @param connStr 连接符号
     * @param encode  是否进行URLEncoder
     * @return 拼接后字符串
     */
    public static String createLinkString(Map<String, String> params, String connStr, boolean encode) {
        return createLinkString(params, connStr, encode, false);
    }

    public static String createLinkString(Map<String, String> params, String connStr, boolean encode, boolean quotes) {
        List<String> keys = new ArrayList<>(params.keySet());
        Collections.sort(keys);
        StringBuilder content = new StringBuilder();
        for (int i = 0; i < keys.size(); i++) {
            String key = keys.get(i);
            String value = params.get(key);
            // 拼接时,不包括最后一个&字符
            if (i == keys.size() - 1) {
                if (quotes) {
                    content.append(key).append("=").append('"').append(encode ? urlEncode(value) : value).append('"');
                } else {
                    content.append(key).append("=").append(encode ? urlEncode(value) : value);
                }
            } else {
                if (quotes) {
                    content.append(key).append("=").append('"').append(encode ? urlEncode(value) : value).append('"').append(connStr);
                } else {
                    content.append(key).append("=").append(encode ? urlEncode(value) : value).append(connStr);
                }
            }
        }
        return content.toString();
    }


    /**
     * URL 编码
     *
     * @param src 需要编码的字符串
     * @return 编码后的字符串
     */
    public static String urlEncode(String src) {
        try {
            return URLEncoder.encode(src, CharsetUtil.UTF_8).replace("+", "%20");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 遍历 Map 并构建 xml 数据
     *
     * @param params 需要遍历的 Map
     * @param prefix xml 前缀
     * @param suffix xml 后缀
     * @return xml 字符串
     */
    public static StringBuffer forEachMap(Map<String, String> params, String prefix, String suffix) {
        StringBuffer xml = new StringBuffer();
        if (StrUtil.isNotEmpty(prefix)) {
            xml.append(prefix);
        }
        for (Map.Entry<String, String> entry : params.entrySet()) {
            String key = entry.getKey();
            String value = entry.getValue();
            // 略过空值
            if (StrUtil.isEmpty(value)) {
                continue;
            }
            xml.append("<").append(key).append(">");
            xml.append(entry.getValue());
            xml.append("</").append(key).append(">");
        }
        if (StrUtil.isNotEmpty(suffix)) {
            xml.append(suffix);
        }
        return xml;
    }

    /**
     * 微信下单 map to xml
     *
     * @param params Map 参数
     * @return xml 字符串
     */
    public static String toXml(Map<String, String> params) {
        StringBuffer xml = forEachMap(params, "<xml>", "</xml>");
        return xml.toString();
    }

    /**
     * 针对支付的 xml,没有嵌套节点的简单处理
     *
     * @param xmlStr xml 字符串
     * @return 转化后的 Map
     */
    public static Map<String, String> xmlToMap(String xmlStr) {
        XmlHelper xmlHelper = XmlHelper.of(xmlStr);
        return xmlHelper.toMap();
    }

    /**
     * 构造签名串
     *
     * @param method    {@link RequestMethodEnum} GET,POST,PUT等
     * @param url       请求接口 /v3/certificates
     * @param timestamp 获取发起请求时的系统当前时间戳
     * @param nonceStr  随机字符串
     * @param body      请求报文主体
     * @return 待签名字符串
     */
    public static String buildSignMessage(RequestMethodEnum method, String url, long timestamp, String nonceStr, String body) {
        ArrayList<String> arrayList = new ArrayList<>();
        arrayList.add(method.toString());
        arrayList.add(url);
        arrayList.add(String.valueOf(timestamp));
        arrayList.add(nonceStr);
        arrayList.add(body);
        return buildSignMessage(arrayList);
    }

    /**
     * 构造签名串
     *
     * @param timestamp 应答时间戳
     * @param nonceStr  应答随机串
     * @param body      应答报文主体
     * @return 应答待签名字符串
     */
    public static String buildSignMessage(String timestamp, String nonceStr, String body) {
        ArrayList<String> arrayList = new ArrayList<>();
        arrayList.add(timestamp);
        arrayList.add(nonceStr);
        arrayList.add(body);
        return buildSignMessage(arrayList);
    }

    /**
     * 构造签名串
     *
     * @param signMessage 待签名的参数
     * @return 构造后带待签名串
     */
    public static String buildSignMessage(ArrayList<String> signMessage) {
        if (signMessage == null || signMessage.size() <= 0) {
            return null;
        }
        StringBuilder sbf = new StringBuilder();
        for (String str : signMessage) {
            sbf.append(str).append("\n");
        }
        return sbf.toString();
    }

    /**
     * v3 接口创建签名
     *
     * @param signMessage 待签名的参数
     * @param keyPath     key.pem 证书路径
     * @return 生成 v3 签名
     * @throws Exception 异常信息
     */
    public static String createSign(ArrayList<String> signMessage, String keyPath) throws Exception {
        return createSign(buildSignMessage(signMessage), keyPath);
    }

    /**
     * v3 接口创建签名
     *
     * @param signMessage 待签名的参数
     * @param privateKey  商户私钥
     * @return 生成 v3 签名
     * @throws Exception 异常信息
     */
    public static String createSign(ArrayList<String> signMessage, PrivateKey privateKey) throws Exception {
        return createSign(buildSignMessage(signMessage), privateKey);
    }


    /**
     * v3 接口创建签名
     *
     * @param signMessage 待签名的参数
     * @param keyPath     key.pem 证书路径
     * @return 生成 v3 签名
     * @throws Exception 异常信息
     */
    public static String createSign(String signMessage, String keyPath) throws Exception {
        if (StrUtil.isEmpty(signMessage)) {
            return null;
        }
        // 获取商户私钥
        PrivateKey privateKey = PayKit.getPrivateKey(keyPath);
        // 生成签名
        return RsaKit.encryptByPrivateKey(signMessage, privateKey);
    }

    /**
     * v3 接口创建签名
     *
     * @param signMessage 待签名的参数
     * @param privateKey  商户私钥
     * @return 生成 v3 签名
     * @throws Exception 异常信息
     */
    public static String createSign(String signMessage, PrivateKey privateKey) throws Exception {
        if (StrUtil.isEmpty(signMessage)) {
            return null;
        }
        // 生成签名
        return RsaKit.encryptByPrivateKey(signMessage, privateKey);
    }

    /**
     * 获取授权认证信息
     *
     * @param mchId     商户号
     * @param serialNo  商户API证书序列号
     * @param nonceStr  请求随机串
     * @param timestamp 时间戳
     * @param signature 签名值
     * @param authType  认证类型
     * @return 请求头 Authorization
     */
    public static String getAuthorization(String mchId, String serialNo, String nonceStr, String timestamp, String signature, String authType) {
        Map<String, String> params = new HashMap<>(5);
        params.put("mchid", mchId);
        params.put("serial_no", serialNo);
        params.put("nonce_str", nonceStr);
        params.put("timestamp", timestamp);
        params.put("signature", signature);
        return authType.concat(" ").concat(createLinkString(params, ",", false, true));
    }

    /**
     * 获取商户私钥
     *
     * @param keyPath 商户私钥证书路径
     * @return {@link String} 商户私钥
     * @throws Exception 异常信息
     */
    public static String getPrivateKeyStr(String keyPath) throws Exception {
        return RsaKit.getPrivateKeyStr(getPrivateKey(keyPath));
    }

    /**
     * 获取商户私钥
     *
     * @param keyPath 商户私钥证书路径
     * @return {@link PrivateKey} 商户私钥
     * @throws Exception 异常信息
     */
    public static PrivateKey getPrivateKey(String keyPath) throws Exception {
        String originalKey = getCertFileContent(keyPath);
        if (StrUtil.isEmpty(originalKey)) {
            throw new RuntimeException("商户私钥证书获取失败");
        }
        return getPrivateKeyByKeyContent(originalKey);
    }

    /**
     * 获取商户私钥
     *
     * @param originalKey 私钥文本内容
     * @return {@link PrivateKey} 商户私钥
     * @throws Exception 异常信息
     */
    public static PrivateKey getPrivateKeyByKeyContent(String originalKey) throws Exception {
        String privateKey = originalKey
                .replace("-----BEGIN PRIVATE KEY-----", "")
                .replace("-----END PRIVATE KEY-----", "")
                .replaceAll("\\s+", "");
        return RsaKit.loadPrivateKey(privateKey);
    }

    /**
     * 获取证书
     *
     * @param inputStream 证书文件
     * @return {@link X509Certificate} 获取证书
     */
    public static X509Certificate getCertificate(InputStream inputStream) {
        try {
            CertificateFactory cf = CertificateFactory.getInstance("X509");
            X509Certificate cert = (X509Certificate) cf.generateCertificate(inputStream);
            cert.checkValidity();
            return cert;
        } catch (CertificateExpiredException e) {
            throw new RuntimeException("证书已过期", e);
        } catch (CertificateNotYetValidException e) {
            throw new RuntimeException("证书尚未生效", e);
        } catch (CertificateException e) {
            throw new RuntimeException("无效的证书", e);
        }
    }

    /**
     * 获取证书
     *
     * @param path 证书路径,支持相对路径以及绝得路径
     * @return {@link X509Certificate} 获取证书
     */
    public static X509Certificate getCertificate(String path) {
        if (StrUtil.isEmpty(path)) {
            return null;
        }
        InputStream inputStream;
        try {
            inputStream = getCertFileInputStream(path);
        } catch (IOException e) {
            throw new RuntimeException("请检查证书路径是否正确", e);
        }
        return getCertificate(inputStream);
    }

    /**
     * 获取证书详细信息
     *
     * @param certificate {@link X509Certificate} 证书
     * @return {@link CertificateModel}  获取证书详细信息
     */
    public static CertificateModel getCertificateInfo(X509Certificate certificate) {
        if (null == certificate) {
            return null;
        }
        CertificateModel model = new CertificateModel();
        model.setItself(certificate);
        model.setIssuerDn(certificate.getIssuerDN());
        model.setSubjectDn(certificate.getSubjectDN());
        model.setVersion(certificate.getVersion());
        model.setNotBefore(certificate.getNotBefore());
        model.setNotAfter(certificate.getNotAfter());
        model.setSerialNumber(certificate.getSerialNumber().toString(16));
        return model;
    }

    /**
     * 获取证书详细信息
     *
     * @param path 证书路径,支持相对路径以及绝得路径
     * @return {@link CertificateModel}  获取证书详细信息
     */
    public static CertificateModel getCertificateInfo(String path) {
        X509Certificate certificate = getCertificate(path);
        return getCertificateInfo(certificate);
    }

    /**
     * 检查证书是否可用
     *
     * @param model     {@link CertificateModel} 证书详细 model
     * @param mchId     商户号
     * @param offsetDay 偏移天数,正数向未来偏移,负数向历史偏移
     * @return true 有效 false 无效
     */
    public static boolean checkCertificateIsValid(CertificateModel model, String mchId, int offsetDay) {
        if (null == model) {
            return false;
        }
        Date notAfter = model.getNotAfter();
        if (null == notAfter) {
            return false;
        }
        // 证书颁发者
        Principal issuerDn = model.getIssuerDn();
        if (null == issuerDn || !issuerDn.getName().contains(IJPayConstants.ISSUER)) {
            return false;
        }
        // 证书CN字段
        if (StrUtil.isNotEmpty(mchId)) {
            Principal subjectDn = model.getSubjectDn();
            if (null == subjectDn || !subjectDn.getName().contains(IJPayConstants.CN.concat(mchId.trim()))) {
                return false;
            }
        }
        // 证书序列号固定40字节的字符串
        String serialNumber = model.getSerialNumber();
        if (StrUtil.isEmpty(serialNumber) || serialNumber.length() != IJPayConstants.SERIAL_NUMBER_LENGTH) {
            return false;
        }
        // 偏移后的时间
        DateTime dateTime = DateUtil.offsetDay(notAfter, offsetDay);
        DateTime now = DateUtil.date();
        int compare = DateUtil.compare(dateTime, now);
        return compare >= 0;
    }

    /**
     * 检查证书是否可用
     *
     * @param certificate {@link X509Certificate} 证书
     * @param mchId       商户号
     * @param offsetDay   偏移天数,正数向未来偏移,负数向历史偏移
     * @return true 有效 false 无效
     */
    public static boolean checkCertificateIsValid(X509Certificate certificate, String mchId, int offsetDay) {
        if (null == certificate) {
            return false;
        }
        CertificateModel model = getCertificateInfo(certificate);
        return checkCertificateIsValid(model, mchId, offsetDay);
    }

    /**
     * 检查证书是否可用
     *
     * @param path      证书路径,支持相对路径以及绝得路径
     * @param mchId     商户号
     * @param offsetDay 偏移天数,正数向未来偏移,负数向历史偏移
     * @return true 有效 false 无效
     */
    public static boolean checkCertificateIsValid(String path, String mchId, int offsetDay) {
        return checkCertificateIsValid(getCertificateInfo(path), mchId, offsetDay);
    }

    /**
     * 公钥加密
     *
     * @param data        待加密数据
     * @param certificate 平台公钥证书
     * @return 加密后的数据
     * @throws Exception 异常信息
     */
    public static String rsaEncryptOAEP(String data, X509Certificate certificate) throws Exception {
        try {
            Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-1AndMGF1Padding");
            cipher.init(Cipher.ENCRYPT_MODE, certificate.getPublicKey());

            byte[] dataByte = data.getBytes(StandardCharsets.UTF_8);
            byte[] cipherData = cipher.doFinal(dataByte);
            return Base64.encode(cipherData);
        } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
            throw new RuntimeException("当前Java环境不支持RSA v1.5/OAEP", e);
        } catch (InvalidKeyException e) {
            throw new IllegalArgumentException("无效的证书", e);
        } catch (IllegalBlockSizeException | BadPaddingException e) {
            throw new IllegalBlockSizeException("加密原串的长度不能超过214字节");
        }
    }

    /**
     * 私钥解密
     *
     * @param cipherText 加密字符
     * @param privateKey 私钥
     * @return 解密后的数据
     * @throws Exception 异常信息
     */
    public static String rsaDecryptOAEP(String cipherText, PrivateKey privateKey) throws Exception {
        try {
            Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-1AndMGF1Padding");
            cipher.init(Cipher.DECRYPT_MODE, privateKey);
            byte[] data = Base64.decode(cipherText);
            return new String(cipher.doFinal(data), StandardCharsets.UTF_8);
        } catch (NoSuchPaddingException | NoSuchAlgorithmException e) {
            throw new RuntimeException("当前Java环境不支持RSA v1.5/OAEP", e);
        } catch (InvalidKeyException e) {
            throw new IllegalArgumentException("无效的私钥", e);
        } catch (BadPaddingException | IllegalBlockSizeException e) {
            throw new BadPaddingException("解密失败");
        }
    }

    /**
     * 传入 classPath 静态资源路径返回文件输入流
     *
     * @param classPath 静态资源路径
     * @return InputStream
     */
    public static InputStream getFileToStream(String classPath) {
        Resource resource = new ClassPathResource(classPath);
        return resource.getStream();
    }

    /**
     * 传入 classPath 静态资源路径返回绝对路径
     *
     * @param classPath 静态资源路径
     * @return 绝对路径
     */
    public static String getAbsolutePath(String classPath) {
        return new ClassPathResource(classPath).getAbsolutePath();
    }

    /**
     * 通过路径获取证书文件的输入流
     *
     * @param path 文件路径
     * @return 文件流
     * @throws IOException 异常信息
     */
    public static InputStream getCertFileInputStream(String path) throws IOException {
        if (StrUtil.isBlank(path)) {
            return null;
        }
        // 绝对地址
        File file = new File(path);
        if (file.exists()) {
            return Files.newInputStream(file.toPath());
        }
        // 相对地址
        return getFileToStream(path);
    }

    /**
     * 通过路径获取证书文件的内容
     *
     * @param path 文件路径
     * @return 文件内容
     */
    public static String getCertFileContent(String path) throws IOException {
        InputStream certFileInputStream = getCertFileInputStream(path);
        return IoUtil.read(certFileInputStream, StandardCharsets.UTF_8);
    }

    /**
     * 获取文件真实路径
     *
     * @param path 文件地址
     * @return 返回文件真实路径
     */
    public static String getFilePath(String path) {
        if (StrUtil.startWith(path, CLASS_PATH_PREFIX)) {
            return getAbsolutePath(path);
        } else {
            return path;
        }
    }
}
package com.busi.config.wxpayv3;

/**
 * HTTP 请求的方法
 *
 * @author dori
public enum RequestMethodEnum {
	/**
	 * 上传实质是 post 请求
	 */
	UPLOAD("POST"),
	/**
	 * post 请求
	 */
	POST("POST"),
	/**
	 * get 请求
	 */
	GET("GET"),
	/**
	 * put 请求
	 */
	PUT("PUT"),
	/**
	 * delete 请求
	 */
	DELETE("DELETE"),
	/**
	 * options 请求
	 */
	OPTIONS("OPTIONS"),
	/**
	 * head 请求
	 */
	HEAD("HEAD"),
	/**
	 * trace 请求
	 */
	TRACE("TRACE"),
	/**
	 * connect 请求
	 */
	CONNECT("CONNECT"),
	/**
	 * PATCH 请求
	 */
	PATCH("PATCH"),
	;

	private final String method;

	RequestMethodEnum(String method) {
		this.method = method;
	}

	@Override
	public String toString() {
		return this.method;
	}
}
package com.config;

import com.alibaba.fastjson.JSONObject;
import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import lombok.Data;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.springframework.stereotype.Component;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.nio.charset.StandardCharsets;
import java.security.PrivateKey;
import java.security.Signature;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Base64;

@Component
@Data
public class WxAppPayConfig {

    private static CloseableHttpClient httpClient;

    public static void setup(String privateKeyUrl, String wechatPayCertificateUrl, String mchId, String mchSerialNo) {
        //   PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(privateKey);
        PrivateKey merchantPrivateKey = null;
        X509Certificate wechatPayCertificate = null;

        try {
            merchantPrivateKey = PemUtil.loadPrivateKey(
                    new FileInputStream(privateKeyUrl));
            wechatPayCertificate = PemUtil.loadCertificate(
                    new FileInputStream(wechatPayCertificateUrl));

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }

        ArrayList<X509Certificate> listCertificates = new ArrayList<>();
        listCertificates.add(wechatPayCertificate);

        WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
                .withMerchant(mchId, mchSerialNo, merchantPrivateKey)
                .withWechatPay(listCertificates);
        httpClient = builder.build();
    }

    public static JSONObject doPostWexinV3(String url, String body, String privateKeyUrl, String wechatPayCertificateUrl, String mchId, String mchSerialNo) {
        if (httpClient == null) {
            setup(privateKeyUrl, wechatPayCertificateUrl, mchId, mchSerialNo);
        }

        HttpPost httpPost = new HttpPost(url);
        httpPost.addHeader("Content-Type", "application/json;chartset=utf-8");
        httpPost.addHeader("Accept", "application/json");
        try {
            if (body == null) {
                throw new IllegalArgumentException("data参数不能为空");
            }
            StringEntity stringEntity = new StringEntity(body, "utf-8");
            httpPost.setEntity(stringEntity);
            // 直接执行execute方法,官方会自动处理签名和验签,并进行证书自动更新
            HttpResponse httpResponse = httpClient.execute(httpPost);
            HttpEntity httpEntity = httpResponse.getEntity();
            if (httpResponse.getStatusLine().getStatusCode() == 200) {
                String jsonResult = EntityUtils.toString(httpEntity);
                return JSONObject.parseObject(jsonResult);
            } else {
                System.err.println("微信支付错误信息" + EntityUtils.toString(httpEntity));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;

    }

    // 获取签名
    public static String getSign(String appId, long timestamp, String nonceStr, String pack, String privateKeyUrl, String wechatPayCertificateUrl) {
        String message = buildMessage(appId, timestamp, nonceStr, pack);
        String paySign = null;
        try {
            paySign = sign(message.getBytes(StandardCharsets.UTF_8), privateKeyUrl, wechatPayCertificateUrl);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return paySign;
    }

    private static String buildMessage(String appId, long timestamp, String nonceStr, String pack) {
        return appId + "\n"
                + timestamp + "\n"
                + nonceStr + "\n"
                + pack + "\n";
    }

    private static String sign(byte[] message, String privateKeyUrl, String wechatPayCertificateUrl) throws Exception {
        PrivateKey merchantPrivateKey = null;
        X509Certificate wechatPayCertificate = null;

        try {
            merchantPrivateKey = PemUtil.loadPrivateKey(
                    new FileInputStream(privateKeyUrl));
            wechatPayCertificate = PemUtil.loadCertificate(
                    new FileInputStream(wechatPayCertificateUrl));

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        Signature sign = Signature.getInstance("SHA256withRSA");
        // 这里需要一个PrivateKey类型的参数,就是商户的私钥。
        sign.initSign(merchantPrivateKey);
        sign.update(message);
        return Base64.getEncoder().encodeToString(sign.sign());
    }

}

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/dori_ya/article/details/137860817

智能推荐

攻防世界_难度8_happy_puzzle_攻防世界困难模式攻略图文-程序员宅基地

文章浏览阅读645次。这个肯定是末尾的IDAT了,因为IDAT必须要满了才会开始一下个IDAT,这个明显就是末尾的IDAT了。,对应下面的create_head()代码。,对应下面的create_tail()代码。不要考虑爆破,我已经试了一下,太多情况了。题目来源:UNCTF。_攻防世界困难模式攻略图文

达梦数据库的导出(备份)、导入_达梦数据库导入导出-程序员宅基地

文章浏览阅读2.9k次,点赞3次,收藏10次。偶尔会用到,记录、分享。1. 数据库导出1.1 切换到dmdba用户su - dmdba1.2 进入达梦数据库安装路径的bin目录,执行导库操作  导出语句:./dexp cwy_init/[email protected]:5236 file=cwy_init.dmp log=cwy_init_exp.log 注释:   cwy_init/init_123..._达梦数据库导入导出

js引入kindeditor富文本编辑器的使用_kindeditor.js-程序员宅基地

文章浏览阅读1.9k次。1. 在官网上下载KindEditor文件,可以删掉不需要要到的jsp,asp,asp.net和php文件夹。接着把文件夹放到项目文件目录下。2. 修改html文件,在页面引入js文件:<script type="text/javascript" src="./kindeditor/kindeditor-all.js"></script><script type="text/javascript" src="./kindeditor/lang/zh-CN.js"_kindeditor.js

STM32学习过程记录11——基于STM32G431CBU6硬件SPI+DMA的高效WS2812B控制方法-程序员宅基地

文章浏览阅读2.3k次,点赞6次,收藏14次。SPI的详情简介不必赘述。假设我们通过SPI发送0xAA,我们的数据线就会变为10101010,通过修改不同的内容,即可修改SPI中0和1的持续时间。比如0xF0即为前半周期为高电平,后半周期为低电平的状态。在SPI的通信模式中,CPHA配置会影响该实验,下图展示了不同采样位置的SPI时序图[1]。CPOL = 0,CPHA = 1:CLK空闲状态 = 低电平,数据在下降沿采样,并在上升沿移出CPOL = 0,CPHA = 0:CLK空闲状态 = 低电平,数据在上升沿采样,并在下降沿移出。_stm32g431cbu6

计算机网络-数据链路层_接收方收到链路层数据后,使用crc检验后,余数为0,说明链路层的传输时可靠传输-程序员宅基地

文章浏览阅读1.2k次,点赞2次,收藏8次。数据链路层习题自测问题1.数据链路(即逻辑链路)与链路(即物理链路)有何区别?“电路接通了”与”数据链路接通了”的区别何在?2.数据链路层中的链路控制包括哪些功能?试讨论数据链路层做成可靠的链路层有哪些优点和缺点。3.网络适配器的作用是什么?网络适配器工作在哪一层?4.数据链路层的三个基本问题(帧定界、透明传输和差错检测)为什么都必须加以解决?5.如果在数据链路层不进行帧定界,会发生什么问题?6.PPP协议的主要特点是什么?为什么PPP不使用帧的编号?PPP适用于什么情况?为什么PPP协议不_接收方收到链路层数据后,使用crc检验后,余数为0,说明链路层的传输时可靠传输

软件测试工程师移民加拿大_无证移民,未受过软件工程师的教育(第1部分)-程序员宅基地

文章浏览阅读587次。软件测试工程师移民加拿大 无证移民,未受过软件工程师的教育(第1部分) (Undocumented Immigrant With No Education to Software Engineer(Part 1))Before I start, I want you to please bear with me on the way I write, I have very little gen...

随便推点

Thinkpad X250 secure boot failed 启动失败问题解决_安装完系统提示secureboot failure-程序员宅基地

文章浏览阅读304次。Thinkpad X250笔记本电脑,装的是FreeBSD,进入BIOS修改虚拟化配置(其后可能是误设置了安全开机),保存退出后系统无法启动,显示:secure boot failed ,把自己惊出一身冷汗,因为这台笔记本刚好还没开始做备份.....根据错误提示,到bios里面去找相关配置,在Security里面找到了Secure Boot选项,发现果然被设置为Enabled,将其修改为Disabled ,再开机,终于正常启动了。_安装完系统提示secureboot failure

C++如何做字符串分割(5种方法)_c++ 字符串分割-程序员宅基地

文章浏览阅读10w+次,点赞93次,收藏352次。1、用strtok函数进行字符串分割原型: char *strtok(char *str, const char *delim);功能:分解字符串为一组字符串。参数说明:str为要分解的字符串,delim为分隔符字符串。返回值:从str开头开始的一个个被分割的串。当没有被分割的串时则返回NULL。其它:strtok函数线程不安全,可以使用strtok_r替代。示例://借助strtok实现split#include <string.h>#include <stdio.h&_c++ 字符串分割

2013第四届蓝桥杯 C/C++本科A组 真题答案解析_2013年第四届c a组蓝桥杯省赛真题解答-程序员宅基地

文章浏览阅读2.3k次。1 .高斯日记 大数学家高斯有个好习惯:无论如何都要记日记。他的日记有个与众不同的地方,他从不注明年月日,而是用一个整数代替,比如:4210后来人们知道,那个整数就是日期,它表示那一天是高斯出生后的第几天。这或许也是个好习惯,它时时刻刻提醒着主人:日子又过去一天,还有多少时光可以用于浪费呢?高斯出生于:1777年4月30日。在高斯发现的一个重要定理的日记_2013年第四届c a组蓝桥杯省赛真题解答

基于供需算法优化的核极限学习机(KELM)分类算法-程序员宅基地

文章浏览阅读851次,点赞17次,收藏22次。摘要:本文利用供需算法对核极限学习机(KELM)进行优化,并用于分类。

metasploitable2渗透测试_metasploitable2怎么进入-程序员宅基地

文章浏览阅读1.1k次。一、系统弱密码登录1、在kali上执行命令行telnet 192.168.26.1292、Login和password都输入msfadmin3、登录成功,进入系统4、测试如下:二、MySQL弱密码登录:1、在kali上执行mysql –h 192.168.26.129 –u root2、登录成功,进入MySQL系统3、测试效果:三、PostgreSQL弱密码登录1、在Kali上执行psql -h 192.168.26.129 –U post..._metasploitable2怎么进入

Python学习之路:从入门到精通的指南_python人工智能开发从入门到精通pdf-程序员宅基地

文章浏览阅读257次。本文将为初学者提供Python学习的详细指南,从Python的历史、基础语法和数据类型到面向对象编程、模块和库的使用。通过本文,您将能够掌握Python编程的核心概念,为今后的编程学习和实践打下坚实基础。_python人工智能开发从入门到精通pdf

推荐文章

热门文章

相关标签