小程序JSAPI微信支付SKD使用

基础配置

maven依赖

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

配置信息

# 微信pay相关
wxpay:
  # appId
  appId: 
  # 商户id
  mchId:  #商户Id
  # V2秘钥
  key: 
  # V3秘钥
  apiV3Key: 
  mchSerialNo: 
  # p12证书文件的绝对路径或者以classpath:开头的类路径. 临时处理
  keyPath: .../apiclient_cert.p12 #证书路径,我放在项目resources目录下
  privateKeyPath: .../apiclient_key.pem #这个也是和上面一样
  privateCertPath: .../apiclient_cert.pem #这个也是一样
  privateKey: 
  publicKeyId: 
  publicKeyPath: .../pub_key.pem #这个也是一样
  # 微信支付的异步通知接口
  # 我这里使用的ngrok内网穿透
  notifyUrl: .../wechat/pay/notify/order #这个是回调函数就是前端要来访问支付的路由,可以自己写,域名写自己的
  # 退款回调地址
  refundNotifyUrl: .../wechat/pay/wl/refund_notify #退款的也一样
@Data
@Configuration
@ConfigurationProperties(prefix = "wxpay")
public class WechatPayConfig {
    private String appId;
    private String mchId;
    private String apiV3Key;
    private String mchSerialNo;
    private String keyPath;
    private String privateKeyPath;
    private String privateCertPath;
    private String notifyUrl;
    private String refundNotifyUrl;
    private String privateKey;
    private String publicKeyId;
    private String publicKeyPath;

}

支付工具类

public class WXPayUtility {
    private static final Gson gson = new GsonBuilder()
            .disableHtmlEscaping()
            .addSerializationExclusionStrategy(new ExclusionStrategy() {
                @Override
                public boolean shouldSkipField(FieldAttributes fieldAttributes) {
                    final Expose expose = fieldAttributes.getAnnotation(Expose.class);
                    return expose != null && !expose.serialize();
                }

                @Override
                public boolean shouldSkipClass(Class<?> aClass) {
                    return false;
                }
            })
            .addDeserializationExclusionStrategy(new ExclusionStrategy() {
                @Override
                public boolean shouldSkipField(FieldAttributes fieldAttributes) {
                    final Expose expose = fieldAttributes.getAnnotation(Expose.class);
                    return expose != null && !expose.deserialize();
                }

                @Override
                public boolean shouldSkipClass(Class<?> aClass) {
                    return false;
                }
            })
            .create();
    private static final char[] SYMBOLS =
            "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray();
    private static final SecureRandom random = new SecureRandom();

    /**
     * 将 Object 转换为 JSON 字符串
     */
    public static String toJson(Object object) {
        return gson.toJson(object);
    }

    /**
     * 将 JSON 字符串解析为特定类型的实例
     */
    public static <T> T fromJson(String json, Class<T> classOfT) throws JsonSyntaxException {
        return gson.fromJson(json, classOfT);
    }

    /**
     * 从公私钥文件路径中读取文件内容
     *
     * @param keyPath 文件路径
     * @return 文件内容
     */
    private static String readKeyStringFromPath(String keyPath) {
        try {
            return new String(Files.readAllBytes(Paths.get(keyPath)), StandardCharsets.UTF_8);
        } catch (IOException e) {
            throw new UncheckedIOException(e);
        }
    }

    /**
     * 读取 PKCS#8 格式的私钥字符串并加载为私钥对象
     *
     * @param keyString 私钥文件内容,以 -----BEGIN PRIVATE KEY----- 开头
     * @return PrivateKey 对象
     */
    public static PrivateKey loadPrivateKeyFromString(String keyString) {
        try {
            keyString = keyString.replace("-----BEGIN PRIVATE KEY-----", "")
                    .replace("-----END PRIVATE KEY-----", "")
                    .replaceAll("\\s+", "");
            return KeyFactory.getInstance("RSA").generatePrivate(
                    new PKCS8EncodedKeySpec(Base64.getDecoder().decode(keyString)));
        } catch (NoSuchAlgorithmException e) {
            throw new UnsupportedOperationException(e);
        } catch (InvalidKeySpecException e) {
            throw new IllegalArgumentException(e);
        }
    }

    /**
     * 从 PKCS#8 格式的私钥文件中加载私钥
     *
     * @param keyPath 私钥文件路径
     * @return PrivateKey 对象
     */
    public static PrivateKey loadPrivateKeyFromPath(String keyPath) {
        return loadPrivateKeyFromString(readKeyStringFromPath(keyPath));
    }

    /**
     * 读取 PKCS#8 格式的公钥字符串并加载为公钥对象
     *
     * @param keyString 公钥文件内容,以 -----BEGIN PUBLIC KEY----- 开头
     * @return PublicKey 对象
     */
    public static PublicKey loadPublicKeyFromString(String keyString) {
        try {
            keyString = keyString.replace("-----BEGIN PUBLIC KEY-----", "")
                    .replace("-----END PUBLIC KEY-----", "")
                    .replaceAll("\\s+", "");
            return KeyFactory.getInstance("RSA").generatePublic(
                    new X509EncodedKeySpec(Base64.getDecoder().decode(keyString)));
        } catch (NoSuchAlgorithmException e) {
            throw new UnsupportedOperationException(e);
        } catch (InvalidKeySpecException e) {
            throw new IllegalArgumentException(e);
        }
    }

    /**
     * 从 PKCS#8 格式的公钥文件中加载公钥
     *
     * @param keyPath 公钥文件路径
     * @return PublicKey 对象
     */
    public static PublicKey loadPublicKeyFromPath(String keyPath) {
        return loadPublicKeyFromString(readKeyStringFromPath(keyPath));
    }

    /**
     * 创建指定长度的随机字符串,字符集为[0-9a-zA-Z],可用于安全相关用途
     */
    public static String createNonce(int length) {
        char[] buf = new char[length];
        for (int i = 0; i < length; ++i) {
            buf[i] = SYMBOLS[random.nextInt(SYMBOLS.length)];
        }
        return new String(buf);
    }

    /**
     * 使用公钥按照 RSA_PKCS1_OAEP_PADDING 算法进行加密
     *
     * @param publicKey 加密用公钥对象
     * @param plaintext 待加密明文
     * @return 加密后密文
     */
    public static String encrypt(PublicKey publicKey, String plaintext) {
        final String transformation = "RSA/ECB/OAEPWithSHA-1AndMGF1Padding";

        try {
            Cipher cipher = Cipher.getInstance(transformation);
            cipher.init(Cipher.ENCRYPT_MODE, publicKey);
            return Base64.getEncoder().encodeToString(cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8)));
        } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
            throw new IllegalArgumentException("The current Java environment does not support " + transformation, e);
        } catch (InvalidKeyException e) {
            throw new IllegalArgumentException("RSA encryption using an illegal publicKey", e);
        } catch (BadPaddingException | IllegalBlockSizeException e) {
            throw new IllegalArgumentException("Plaintext is too long", e);
        }
    }

    public static String aesAeadDecrypt(byte[] key, byte[] associatedData, byte[] nonce,
                                        byte[] ciphertext) {
        final String transformation = "AES/GCM/NoPadding";
        final String algorithm = "AES";
        final int tagLengthBit = 128;

        try {
            Cipher cipher = Cipher.getInstance(transformation);
            cipher.init(
                    Cipher.DECRYPT_MODE,
                    new SecretKeySpec(key, algorithm),
                    new GCMParameterSpec(tagLengthBit, nonce));
            if (associatedData != null) {
                cipher.updateAAD(associatedData);
            }
            return new String(cipher.doFinal(ciphertext), StandardCharsets.UTF_8);
        } catch (InvalidKeyException
                 | InvalidAlgorithmParameterException
                 | BadPaddingException
                 | IllegalBlockSizeException
                 | NoSuchAlgorithmException
                 | NoSuchPaddingException e) {
            throw new IllegalArgumentException(String.format("AesAeadDecrypt with %s Failed",
                    transformation), e);
        }
    }

    /**
     * 使用私钥按照指定算法进行签名
     *
     * @param message    待签名串
     * @param algorithm  签名算法,如 SHA256withRSA
     * @param privateKey 签名用私钥对象
     * @return 签名结果
     */
    public static String sign(String message, String algorithm, PrivateKey privateKey) {
        byte[] sign;
        try {
            Signature signature = Signature.getInstance(algorithm);
            signature.initSign(privateKey);
            signature.update(message.getBytes(StandardCharsets.UTF_8));
            sign = signature.sign();
        } catch (NoSuchAlgorithmException e) {
            throw new UnsupportedOperationException("The current Java environment does not support " + algorithm, e);
        } catch (InvalidKeyException e) {
            throw new IllegalArgumentException(algorithm + " signature uses an illegal privateKey.", e);
        } catch (SignatureException e) {
            throw new RuntimeException("An error occurred during the sign process.", e);
        }
        return Base64.getEncoder().encodeToString(sign);
    }

    /**
     * 使用公钥按照特定算法验证签名
     *
     * @param message   待签名串
     * @param signature 待验证的签名内容
     * @param algorithm 签名算法,如:SHA256withRSA
     * @param publicKey 验签用公钥对象
     * @return 签名验证是否通过
     */
    public static boolean verify(String message, String signature, String algorithm,
                                 PublicKey publicKey) {
        try {
            Signature sign = Signature.getInstance(algorithm);
            sign.initVerify(publicKey);
            sign.update(message.getBytes(StandardCharsets.UTF_8));
            return sign.verify(Base64.getDecoder().decode(signature));
        } catch (SignatureException e) {
            return false;
        } catch (InvalidKeyException e) {
            throw new IllegalArgumentException("verify uses an illegal publickey.", e);
        } catch (NoSuchAlgorithmException e) {
            throw new UnsupportedOperationException("The current Java environment does not support" + algorithm, e);
        }
    }

    /**
     * 根据微信支付APIv3请求签名规则构造 Authorization 签名
     *
     * @param mchid               商户号
     * @param certificateSerialNo 商户API证书序列号
     * @param privateKey          商户API证书私钥
     * @param method              请求接口的HTTP方法,请使用全大写表述,如 GET、POST、PUT、DELETE
     * @param uri                 请求接口的URL
     * @param body                请求接口的Body
     * @return 构造好的微信支付APIv3 Authorization 头
     */
    public static String buildAuthorization(String mchid, String certificateSerialNo,
                                            PrivateKey privateKey,
                                            String method, String uri, String body) {
        String nonce = createNonce(32);
        long timestamp = Instant.now().getEpochSecond();

        String message = String.format("%s\n%s\n%d\n%s\n%s\n", method, uri, timestamp, nonce,
                body == null ? "" : body);

        String signature = sign(message, "SHA256withRSA", privateKey);

        return String.format(
                "WECHATPAY2-SHA256-RSA2048 mchid=\"%s\",nonce_str=\"%s\",signature=\"%s\"," +
                        "timestamp=\"%d\",serial_no=\"%s\"",
                mchid, nonce, signature, timestamp, certificateSerialNo);
    }

    /**
     * 对参数进行 URL 编码
     *
     * @param content 参数内容
     * @return 编码后的内容
     */
    public static String urlEncode(String content) {
        try {
            return URLEncoder.encode(content, StandardCharsets.UTF_8.name());
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 对参数Map进行 URL 编码,生成 QueryString
     *
     * @param params Query参数Map
     * @return QueryString
     */
    public static String urlEncode(Map<String, Object> params) {
        if (params == null || params.isEmpty()) {
            return "";
        }

        int index = 0;
        StringBuilder result = new StringBuilder();
        for (Map.Entry<String, Object> entry : params.entrySet()) {
            result.append(entry.getKey())
                    .append("=")
                    .append(urlEncode(entry.getValue().toString()));
            index++;
            if (index < params.size()) {
                result.append("&");
            }
        }
        return result.toString();
    }

    /**
     * 从应答中提取 Body
     *
     * @param response HTTP 请求应答对象
     * @return 应答中的Body内容,Body为空时返回空字符串
     */
    public static String extractBody(Response response) {
        if (response.body() == null) {
            return "";
        }

        try {
            BufferedSource source = response.body().source();
            return source.readUtf8();
        } catch (IOException e) {
            throw new RuntimeException(String.format("An error occurred during reading response body. " +
                    "Status: %d", response.code()), e);
        }
    }

    /**
     * 根据微信支付APIv3应答验签规则对应答签名进行验证,验证不通过时抛出异常
     *
     * @param wechatpayPublicKeyId 微信支付公钥ID
     * @param wechatpayPublicKey   微信支付公钥对象
     * @param headers              微信支付应答 Header 列表
     * @param body                 微信支付应答 Body
     */
    public static void validateResponse(String wechatpayPublicKeyId, PublicKey wechatpayPublicKey,
                                        Headers headers,
                                        String body) {
        String timestamp = headers.get("Wechatpay-Timestamp");
        String requestId = headers.get("Request-ID");
        try {
            Instant responseTime = Instant.ofEpochSecond(Long.parseLong(timestamp));
            // 拒绝过期请求
            if (Duration.between(responseTime, Instant.now()).abs().toMinutes() >= 5) {
                throw new IllegalArgumentException(
                        String.format("Validate response failed, timestamp[%s] is expired, request-id[%s]",
                                timestamp, requestId));
            }
        } catch (DateTimeException | NumberFormatException e) {
            throw new IllegalArgumentException(
                    String.format("Validate response failed, timestamp[%s] is invalid, request-id[%s]",
                            timestamp, requestId));
        }
        String serialNumber = headers.get("Wechatpay-Serial");
        if (!Objects.equals(serialNumber, wechatpayPublicKeyId)) {
            throw new IllegalArgumentException(
                    String.format("Validate response failed, Invalid Wechatpay-Serial, Local: %s, Remote: " +
                            "%s", wechatpayPublicKeyId, serialNumber));
        }

        String signature = headers.get("Wechatpay-Signature");
        String message = String.format("%s\n%s\n%s\n", timestamp, headers.get("Wechatpay-Nonce"),
                body == null ? "" : body);

        boolean success = verify(message, signature, "SHA256withRSA", wechatpayPublicKey);
        if (!success) {
            throw new IllegalArgumentException(
                    String.format("Validate response failed,the WechatPay signature is incorrect.%n"
                                    + "Request-ID[%s]\tresponseHeader[%s]\tresponseBody[%.1024s]",
                            headers.get("Request-ID"), headers, body));
        }
    }

    /**
     * 根据微信支付APIv3通知验签规则对通知签名进行验证,验证不通过时抛出异常
     * @param wechatpayPublicKeyId 微信支付公钥ID
     * @param wechatpayPublicKey 微信支付公钥对象
     * @param headers 微信支付通知 Header 列表
     * @param body 微信支付通知 Body
     */
    public static void validateNotification(String wechatpayPublicKeyId,
                                            PublicKey wechatpayPublicKey, Headers headers,
                                            String body) {
        String timestamp = headers.get("Wechatpay-Timestamp");
        try {
            Instant responseTime = Instant.ofEpochSecond(Long.parseLong(timestamp));
            // 拒绝过期请求
            if (Duration.between(responseTime, Instant.now()).abs().toMinutes() >= 5) {
                throw new IllegalArgumentException(
                        String.format("Validate notification failed, timestamp[%s] is expired", timestamp));
            }
        } catch (DateTimeException | NumberFormatException e) {
            throw new IllegalArgumentException(
                    String.format("Validate notification failed, timestamp[%s] is invalid", timestamp));
        }
        String serialNumber = headers.get("Wechatpay-Serial");
        if (!Objects.equals(serialNumber, wechatpayPublicKeyId)) {
            throw new IllegalArgumentException(
                    String.format("Validate notification failed, Invalid Wechatpay-Serial, Local: %s, " +
                                    "Remote: %s",
                            wechatpayPublicKeyId,
                            serialNumber));
        }

        String signature = headers.get("Wechatpay-Signature");
        String message = String.format("%s\n%s\n%s\n", timestamp, headers.get("Wechatpay-Nonce"),
                body == null ? "" : body);

        boolean success = verify(message, signature, "SHA256withRSA", wechatpayPublicKey);
        if (!success) {
            throw new IllegalArgumentException(
                    String.format("Validate notification failed, WechatPay signature is incorrect.\n"
                                    + "responseHeader[%s]\tresponseBody[%.1024s]",
                            headers, body));
        }
    }

    /**
     * 对微信支付通知进行签名验证、解析,同时将业务数据解密。验签名失败、解析失败、解密失败时抛出异常
     * @param apiv3Key 商户的 APIv3 Key
     * @param wechatpayPublicKeyId 微信支付公钥ID
     * @param wechatpayPublicKey   微信支付公钥对象
     * @param headers              微信支付应答 Header 列表
     * @param body                 微信支付应答 Body
     * @return 解析后的通知内容,解密后的业务数据可以使用 Notification.getPlaintext() 访问
     */
    public static Notification parseNotification(String apiv3Key, String wechatpayPublicKeyId,
                                                 PublicKey wechatpayPublicKey, Headers headers,
                                                 String body) {
        validateNotification(wechatpayPublicKeyId, wechatpayPublicKey, headers, body);
        Notification notification = gson.fromJson(body, Notification.class);
        notification.decrypt(apiv3Key);
        return notification;
    }

    /**
     * 微信支付API错误异常,发送HTTP请求成功,但返回状态码不是 2XX 时抛出本异常
     */
    public static class ApiException extends RuntimeException {
        private static final long serialVersionUID = 2261086748874802175L;

        private final int statusCode;
        private final String body;
        private final Headers headers;
        private final String errorCode;
        private final String errorMessage;

        public ApiException(int statusCode, String body, Headers headers) {
            super(String.format("微信支付API访问失败,StatusCode: [%s], Body: [%s], Headers: [%s]", statusCode,
                    body, headers));
            this.statusCode = statusCode;
            this.body = body;
            this.headers = headers;

            if (body != null && !body.isEmpty()) {
                JsonElement code;
                JsonElement message;

                try {
                    JsonObject jsonObject = gson.fromJson(body, JsonObject.class);
                    code = jsonObject.get("code");
                    message = jsonObject.get("message");
                } catch (JsonSyntaxException ignored) {
                    code = null;
                    message = null;
                }
                this.errorCode = code == null ? null : code.getAsString();
                this.errorMessage = message == null ? null : message.getAsString();
            } else {
                this.errorCode = null;
                this.errorMessage = null;
            }
        }

        /**
         * 获取 HTTP 应答状态码
         */
        public int getStatusCode() {
            return statusCode;
        }

        /**
         * 获取 HTTP 应答包体内容
         */
        public String getBody() {
            return body;
        }

        /**
         * 获取 HTTP 应答 Header
         */
        public Headers getHeaders() {
            return headers;
        }

        /**
         * 获取 错误码 (错误应答中的 code 字段)
         */
        public String getErrorCode() {
            return errorCode;
        }

        /**
         * 获取 错误消息 (错误应答中的 message 字段)
         */
        public String getErrorMessage() {
            return errorMessage;
        }
    }

    public static class Notification {
        @SerializedName("id")
        private String id;
        @SerializedName("create_time")
        private String createTime;
        @SerializedName("event_type")
        private String eventType;
        @SerializedName("resource_type")
        private String resourceType;
        @SerializedName("summary")
        private String summary;
        @SerializedName("resource")
        private Resource resource;
        private String plaintext;

        public String getId() {
            return id;
        }

        public String getCreateTime() {
            return createTime;
        }

        public String getEventType() {
            return eventType;
        }

        public String getResourceType() {
            return resourceType;
        }

        public String getSummary() {
            return summary;
        }

        public Resource getResource() {
            return resource;
        }

        /**
         * 获取解密后的业务数据(JSON字符串,需要自行解析)
         */
        public String getPlaintext() {
            return plaintext;
        }

        private void validate() {
            if (resource == null) {
                throw new IllegalArgumentException("Missing required field `resource` in notification");
            }
            resource.validate();
        }

        /**
         * 使用 APIv3Key 对通知中的业务数据解密,解密结果可以通过 getPlainText 访问。
         * 外部拿到的 Notification 一定是解密过的,因此本方法没有设置为 public
         * @param apiv3Key 商户APIv3 Key
         */
        private void decrypt(String apiv3Key) {
            validate();

            plaintext = aesAeadDecrypt(
                    apiv3Key.getBytes(StandardCharsets.UTF_8),
                    resource.associatedData.getBytes(StandardCharsets.UTF_8),
                    resource.nonce.getBytes(StandardCharsets.UTF_8),
                    Base64.getDecoder().decode(resource.ciphertext)
            );
        }

        public static class Resource {
            @SerializedName("algorithm")
            private String algorithm;

            @SerializedName("ciphertext")
            private String ciphertext;

            @SerializedName("associated_data")
            private String associatedData;

            @SerializedName("nonce")
            private String nonce;

            @SerializedName("original_type")
            private String originalType;

            public String getAlgorithm() {
                return algorithm;
            }

            public String getCiphertext() {
                return ciphertext;
            }

            public String getAssociatedData() {
                return associatedData;
            }

            public String getNonce() {
                return nonce;
            }

            public String getOriginalType() {
                return originalType;
            }

            private void validate() {
                if (algorithm == null || algorithm.isEmpty()) {
                    throw new IllegalArgumentException("Missing required field `algorithm` in Notification" +
                            ".Resource");
                }
                if (!Objects.equals(algorithm, "AEAD_AES_256_GCM")) {
                    throw new IllegalArgumentException(String.format("Unsupported `algorithm`[%s] in " +
                            "Notification.Resource", algorithm));
                }

                if (ciphertext == null || ciphertext.isEmpty()) {
                    throw new IllegalArgumentException("Missing required field `ciphertext` in Notification" +
                            ".Resource");
                }

                if (associatedData == null || associatedData.isEmpty()) {
                    throw new IllegalArgumentException("Missing required field `associatedData` in " +
                            "Notification.Resource");
                }

                if (nonce == null || nonce.isEmpty()) {
                    throw new IllegalArgumentException("Missing required field `nonce` in Notification" +
                            ".Resource");
                }

                if (originalType == null || originalType.isEmpty()) {
                    throw new IllegalArgumentException("Missing required field `originalType` in " +
                            "Notification.Resource");
                }
            }
        }
    }
}

支付实现

支付方法实现

@Slf4j
@Component
public class WxPayManage {


    @Autowired
    private WechatPayConfig wechatPayConfig;
    private static Config config;

    @Autowired
    private IEsOrderService orderService;



    /**
     * 初始化
     *
     */
    @PostConstruct
    public void configInit() {
        try {
            config = new RSAPublicKeyConfig.Builder()
                    .merchantId(wechatPayConfig.getMchId())
//                    .privateKeyFromPath("classpath:apiclient_key.pem")
                    .privateKeyFromPath(wechatPayConfig.getPrivateKeyPath())
                    .publicKeyFromPath(wechatPayConfig.getPublicKeyPath())
                    .publicKeyId(wechatPayConfig.getPublicKeyId())
                    .merchantSerialNumber(wechatPayConfig.getMchSerialNo())
                    .apiV3Key(wechatPayConfig.getApiV3Key())
                    .build();
            log.info("微信支付配置初始化完成");
        } catch (Exception e) {
            log.error("微信支付配置初始化失败",e);
        }

    }

    /**
     * 支付回调
     */
    public void payNotifyV3(HttpServletRequest request, HttpServletResponse response) {
        log.info("收到app微信支付回调....");
        response.setContentType("application/json;charset=UTF-8");
        response.setCharacterEncoding("UTF-8");
        String jsonResponse = "{\"code\": \"FAIL\", \"message\": \"失败\"}";
        try {
            // 从请求头中获取信息
            String timestamp = request.getHeader("Wechatpay-Timestamp");
            String nonce = request.getHeader("Wechatpay-Nonce");
            String signature = request.getHeader("Wechatpay-Signature");
            String singType = request.getHeader("Wechatpay-Signature-Type");
            String wechatPayCertificateSerialNumber = request.getHeader("Wechatpay-Serial");
            RequestParam requestParam = new RequestParam.Builder()
                    .serialNumber(wechatPayCertificateSerialNumber)
                    .nonce(nonce)
                    .signature(signature)
                    .timestamp(timestamp)
                    .signType(singType)
                    .body(getRequestBody(request))
                    .build();
            // 初始化解析器 NotificationParser
            NotificationParser parser = new NotificationParser((NotificationConfig) config);
            Transaction decryptObject = parser.parse(requestParam, Transaction.class);
            if (decryptObject != null) {
                // 支付成功
                log.info("=============微信支付回调========trade_status:" + decryptObject.getTradeState());
                if (Transaction.TradeStateEnum.SUCCESS.equals(decryptObject.getTradeState())) {
                    // 处理支付成功逻辑
                    try {
                        // 下面就是做支付成功的逻辑
                        System.out.println("微信返回参数+++++++++++++++++++++++++++:" + decryptObject);
                        // 从数据库查询 获取业务类型
                        EsOrder order = orderService.selectEsOrderByOrderNo(decryptObject.getOutTradeNo());
                        if(order != null){
                            order.setPaymentStatus(PaymentStatus.FULL_PAYMENT.getCode());
                            order.setPaidAmount(order.getOrderPrice());
                            if(order.getOrderStatus().equals(OrderStatus.WAIT_CONSULT.getCode())){
                                order.setOrderStatus(OrderStatus.IN_SERVICE.getCode());
                            }
                            order.setPaymentTime(new Date());
                            orderService.updateEsOrder(order);
                        }
                        //响应微信
                        response.setStatus(200);
                        response.getWriter().flush();
                        response.getWriter().close();
                    } catch (Exception e) {
                        log.error("微信回调处理失败:" + e.getMessage());
                    }
                } else {
                    log.info("更新微信支付数据失败。");
                }
            }
            response.setStatus(500);
            response.getWriter().write(jsonResponse);
        } catch (Exception e) {
            log.error("微信支付回调处理异常: {}", e.getMessage());
            try {
                response.setStatus(500);
                response.getWriter().write(jsonResponse);
                response.getWriter().flush();
                response.getWriter().close();
            } catch (IOException ex) {
                throw new RuntimeException(ex);
            }
        }
    }

    /**
     * 从请求中获取请求体
     *
     * @param request request
     */
    private String getRequestBody(HttpServletRequest request) {
        StringBuilder sb = new StringBuilder();
        try (BufferedReader reader = request.getReader()) {
            String line;
            while ((line = reader.readLine()) != null) {
                sb.append(line);
            }
        } catch (IOException e) {
            log.error("读取请求体时发生异常:" + e.getMessage());
        }
        return sb.toString();
    }

    //支付调用方法
    public Map<String, String> prepay(String orderNumber, BigDecimal orderPrice, String goodsName, String miniOpenid) {
        PrepayRequest request1 = new PrepayRequest();
        Amount amount1 = new Amount();
        amount1.setTotal(orderPrice.multiply(BigDecimal.valueOf(100)).intValue());
        request1.setAmount(amount1);
        request1.setAppid(wechatPayConfig.getAppId());
        request1.setMchid(wechatPayConfig.getMchId());
        request1.setDescription(goodsName);
        request1.setNotifyUrl(wechatPayConfig.getNotifyUrl());
        request1.setOutTradeNo(orderNumber);
        Payer payer1 = new Payer();
        payer1.setOpenid(miniOpenid);
        request1.setPayer(payer1);
        JsapiService service  =  new JsapiService.Builder().config(config).build();
        try {
            System.out.println("请求参数-------"+JSONUtil.toJsonStr(request1));
            PrepayResponse response = service.prepay(request1);
            // 生成签名
            Map<String,String> signMap = new HashMap<>();
            signMap.put("appId",wechatPayConfig.getAppId());
            signMap.put("timeStamp", String.valueOf(System.currentTimeMillis() / 1000));
            signMap.put("nonceStr", WXPayUtility.createNonce(32));
            signMap.put("package","prepay_id=" + response.getPrepayId());
            signMap.put("signType","RSA");
            signMap.put("paySign",sign(signMap));
            return signMap;
        } catch (Exception e) {
            throw new ServiceException("支付错误" + e.getMessage());
        }
    }

    public String sign(Map<String,String> signMap){
        String signStr = signMap.get("appId") +"\n" + signMap.get("timeStamp")  +"\n" + signMap.get("nonceStr")  +"\n" + signMap.get("package")  +"\n";
        PrivateKey privateKey = PemUtil.loadPrivateKeyFromPath( wechatPayConfig.getPrivateKeyPath());
        return WXPayUtility.sign(signStr,"SHA256withRSA",privateKey);
    }


    //退款调用方法
    public Refund refund(String orderNumber , BigDecimal orderPrice , BigDecimal returnPrice) {
        RefundService service = new RefundService.Builder().config(config).build();
        CreateRequest request = new CreateRequest();
        //构建订单金额信息
        AmountReq amountReq = new AmountReq();
        //退款金额
        returnPrice = orderPrice.min(returnPrice);
        long returnPriceFen = returnPrice
                .multiply(new BigDecimal("100"))
                .setScale(0, RoundingMode.DOWN)
                .longValueExact();
        amountReq.setRefund(returnPriceFen);
        //原订单金额
        long orderPriceFen = orderPrice
                .multiply(new BigDecimal("100"))
                .setScale(0, RoundingMode.DOWN)
                .longValueExact();
        amountReq.setTotal(orderPriceFen);
        //货币类型(默认人民币)
        amountReq.setCurrency("CNY");
        request.setAmount(amountReq);
        //商户退款单号
        request.setOutRefundNo(orderNumber);
        //商户订单号
        request.setOutTradeNo(orderNumber);
        //退款通知回调地址
        request.setNotifyUrl(wechatPayConfig.getRefundNotifyUrl());
        try {
            System.out.println("退款请求参数-------"+JSONUtil.toJsonStr(request));
            return service.create(request);
        } catch (ServiceException e) {
            throw new RuntimeException("退款申请失败", e);
        }
    }

    //退款回调
    public void refundNotify(HttpServletRequest request, HttpServletResponse response) {
        log.info("收到微信支付退款回调....");
        response.setContentType("application/json;charset=UTF-8");
        response.setCharacterEncoding("UTF-8");
        String jsonResponse = "{\"code\": \"FAIL\", \"message\": \"失败\"}";
        try {
            // 从请求头中获取信息
            String timestamp = request.getHeader("Wechatpay-Timestamp");
            String nonce = request.getHeader("Wechatpay-Nonce");
            String signature = request.getHeader("Wechatpay-Signature");
            String singType = request.getHeader("Wechatpay-Signature-Type");
            String wechatPayCertificateSerialNumber = request.getHeader("Wechatpay-Serial");
            RequestParam requestParam = new RequestParam.Builder()
                    .serialNumber(wechatPayCertificateSerialNumber)
                    .nonce(nonce)
                    .signature(signature)
                    .timestamp(timestamp)
                    .signType(singType)
                    .body(getRequestBody(request))
                    .build();
            NotificationParser parser = new NotificationParser((NotificationConfig) config);

            RefundNotification refundNotification = parser.parse(requestParam, RefundNotification.class);
            String refundStatus = refundNotification.getRefundStatus().toString();
            String outTradeNo = refundNotification.getOutTradeNo();
            log.info("=============微信支付退款回调========trade_status:" + refundStatus);
            if ("SUCCESS".equals(refundStatus)){
                try {
                    System.out.println("微信退款返回参数+++++++++++++++++++++++++++:" + refundNotification);
                    // 从数据库查询 获取业务类型
                    EsOrder order = orderService.selectEsOrderByOrderNo(outTradeNo);
                    if(order != null){
                        order.setAfterSaleStatus(AfterSaleStatus.REFUNDED.getCode());
                        orderService.updateEsOrder(order);
                    }
                    //响应微信
                    response.setStatus(200);
                    response.getWriter().flush();
                    response.getWriter().close();
                } catch (Exception e) {
                    log.error("微信退款回调处理失败:" + e.getMessage());
                }
            }else{
                EsOrder order = orderService.selectEsOrderByOrderNo(outTradeNo);
                if(order != null){
                    order.setAfterSaleStatus(AfterSaleStatus.AUDIT_PASSED.getCode());
                    orderService.updateEsOrder(order);
                }
                log.info("更新微信支付退款数据失败。");
            }
            response.setStatus(500);
            response.getWriter().write(jsonResponse);
        } catch (Exception e) {
            log.error("微信支付退款回调处理异常: {}", e.getMessage());
            try {
                response.setStatus(500);
                response.getWriter().write(jsonResponse);
                response.getWriter().flush();
                response.getWriter().close();
            } catch (IOException ex) {
                throw new RuntimeException(ex);
            }
        }
    }
}

回调地址

@Slf4j
@CrossOrigin
@RestController
@AllArgsConstructor
@RequestMapping("/wechat/pay")
public class PayNotifyController {

    WxPayManage wxPayManage;

    @ApiOperation("支付回调通知处理")
    @PostMapping("/notify/order")
    public void parseOrderNotifyResult(HttpServletRequest request, HttpServletResponse response) {
         wxPayManage.payNotifyV3(request,response);
    }


    @ApiOperation("支付退款回调通知处理")
    @PostMapping("/wl/refund_notify")
    public void parseRefundResult(HttpServletRequest request, HttpServletResponse response) {
        wxPayManage.refundNotify(request,response);
    }

}

业务调用

调用支付

    @ApiOperation("提交订单-支付")
    @PostMapping("/prepay/{id}")
//    @ActionHistoryLog(id = "#id", type = ActionHistoryType.GOOD_BUY)
    public AjaxResult prepay(@PathVariable("id") Long id) {
				.....
        try {
            ....
            //调用支付,到回调获取结果
            Map<String, String> signMap = wxPayManage.prepay(...);
            ...
            return AjaxResult.success(signMap);
        } catch (Exception e) {
            throw new ServiceException("支付错误" + e.getMessage());
        }
    }

调用退款

    @Log(title = "售后订单退款")
    @PostMapping("/refund")
    public AjaxResult updateAfterSalesRefund(...) {
        ...
        //调用退款,到回调获取结果
        Refund refund = adminWxPayManage.refund(...);
        return AjaxResult.success(refund);
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值