All Downloads are FREE. Search and download functionalities are using the official Maven repository.

cn.felord.payment.wechat.v3.crypto.WechatPaySigner Maven / Gradle / Ivy

The newest version!
/*
 *  Copyright (c) 2023. felord.cn
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *       https://www.apache.org/licenses/LICENSE-2.0
 *  Website:
 *       https://felord.cn
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package cn.felord.payment.wechat.v3.crypto;

import cn.felord.payment.PayException;
import cn.felord.payment.wechat.v3.retrofit.HttpHeaders;
import cn.felord.utils.AlternativeJdkIdGenerator;
import cn.felord.utils.Base64Utils;
import lombok.SneakyThrows;
import okhttp3.Headers;

import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.Signature;
import java.security.SignatureException;
import java.time.Instant;
import java.util.Arrays;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;

/**
 * 默认签名工具
 *
 * @author dax
 * @since 2024/8/10 16:57
 */
public final class WechatPaySigner {
    private static final String TOKEN_PATTERN = "mchid=\"%s\",nonce_str=\"%s\",timestamp=\"%d\",serial_no=\"%s\",signature=\"%s\"";
    private static final AlternativeJdkIdGenerator ID_GENERATOR = new AlternativeJdkIdGenerator();

    private WechatPaySigner() {
    }

    /**
     * Sign string.
     *
     * @param appMerchant the app merchant
     * @param uri         the uri
     * @param httpMethod  the http method
     * @param body        the body
     * @return the string
     */
    @SneakyThrows
    public static String sign(AppMerchant appMerchant, URI uri, String httpMethod, String body) {
        String canonicalUrl = Optional.ofNullable(uri.getRawQuery())
                .map(query ->
                        uri.getRawPath().concat("?").concat(query))
                .orElse(uri.getRawPath());
        String nonceStr = ID_GENERATOR.generate32();
        long timestamp = Instant.now().getEpochSecond();
        final String signMessage = buildSignMessage(httpMethod, canonicalUrl, String.valueOf(timestamp), nonceStr, body);
        AuthType authType = appMerchant.authType();
        Signature signer = Signature.getInstance(authType.alg());
        signer.initSign(appMerchant.privateKey());
        signer.update(signMessage.getBytes(StandardCharsets.UTF_8));
        String encoded = Base64Utils.encodeToString(signer.sign());
        String token = String.format(TOKEN_PATTERN, appMerchant.merchantId(), nonceStr, timestamp, appMerchant.serialNumber(), encoded);
        return authType.toAuthHeader(token);
    }

    /**
     * Sign string.
     *
     * @param appMerchant       the app merchant
     * @param orderedComponents the ordered components
     * @return the string
     */
    @SneakyThrows
    public static String sign(AppMerchant appMerchant, String... orderedComponents) {
        final String signMessage = buildSignMessage(orderedComponents);
        AuthType authType = appMerchant.authType();
        Signature signer = Signature.getInstance(authType.alg());
        signer.initSign(appMerchant.privateKey());
        signer.update(signMessage.getBytes(StandardCharsets.UTF_8));
        return Base64Utils.encodeToString(signer.sign());
    }

    /**
     * Verify boolean.
     *
     * @param responseHeaders the response headers
     * @param body            the body
     * @param tenpayKey       the tenpay key
     * @return the boolean
     */
    public static boolean verify(Headers responseHeaders, String body, TenpayKey tenpayKey) {
        String wechatpaySignature = Objects.requireNonNull(responseHeaders.get(HttpHeaders.WECHAT_PAY_SIGNATURE.headerName()));
        String wechatpaySignatureType = responseHeaders.get(HttpHeaders.WECHAT_PAY_SIGNATURE_TYPE.headerName());
        String wechatpayTimestamp = responseHeaders.get(HttpHeaders.WECHAT_PAY_TIMESTAMP.headerName());
        String wechatpayNonce = responseHeaders.get(HttpHeaders.WECHAT_PAY_NONCE.headerName());
        final String message = buildSignMessage(wechatpayTimestamp, wechatpayNonce, body);
        AuthType authType = AuthType.fromSignType(wechatpaySignatureType);
        try {
            Signature signer = Signature.getInstance(authType.alg());
            signer.initVerify(tenpayKey.toPublicKey());
            signer.update(message.getBytes(StandardCharsets.UTF_8));
            return signer.verify(Base64Utils.decodeFromString(wechatpaySignature));
        } catch (NoSuchAlgorithmException | InvalidKeyException | SignatureException e) {
            throw new PayException("Signature verification failed", e);
        }
    }

    /**
     * 请求时设置签名   组件
     *
     * @param components the components
     * @return string string
     */
    private static String buildSignMessage(String... components) {
        return Arrays.stream(components)
                .collect(Collectors.joining("\n", "", "\n"));
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy