WeChat applet access WeChat payment process notes

preparation process

Use the "ordinary merchant account" (after logging in to the merchant platform, there are: home page, transaction center, account center, marketing center, product center, data center), open JSAPI payment in the product center, and record the merchant number.

  • Enter: Account Center - API Security - Apply for API Certificate - Manage Certificate, follow the prompts to add the certificate, record the serial number of the certificate after adding
  • Enter: Account Center - API Security - Set the APIv3 key, modify it, follow the prompts, and the key will be generated by itself, which is a string with a maximum length of 32 characters; record the key.
  • Enter: Account Center - API Security - Set API key, modify, same as above (this step may not be used)
  • Enter: Product Center - AppID Account Management - Associate AppID, fill in the AppID (small program, official account, etc.) that needs to be associated. Log in to the corresponding AppID platform and apply through the association; the operation path of the mini program platform is: function - WeChat payment

In addition, you need to prepare

  • appId of the applet
  • applet appSecret

Read ahead:

formal process

log in

  1. Front end: Call this interface to obtain authorization:
  2. Front-end: Call this interface after success : Pass the parameter: code to the WeChat login interface provided by the back-end, and also provide other information obtained by the previous interface at the same time
  3. Back-end: After WeChat login receives the code, call this interface , use the appId and appSecret of the applet + the obtained code to request and save the user information (openid unionid session_key), and check whether the openid has been registered after successful acquisition. If you have not registered, perform password-free login.

to pay

  1. Front-end: Send an order request to the back-end interface and submit the order UUID
  2. rear end:
    1. Call this interface , set the merchant order number as the UUID submitted by the front end, execute the order request, and obtain the prepay_id;
    2. Use this prepay_id to generate the request parameters required in step 3 and return it to the front end. This step requires manual signature You can download to compare whether your signature result is correct
  3. front end:
    1. Call this interface and call wx.requestPayment(OBJECT) to initiate WeChat payment, where the request parameters are generated by the backend and should be returned to the frontend in the previous request
    2. The user pays according to the prompt (not the process of this system)
  4. Backend: The WeChat server will send the payment result to the interface provided by the notify_url field in step 2. The steps to be completed for this interface are
    1. For signature verification , if you use the platform certificate automatic update function of the official SDK, the getLatestCertificate() method of the ScheduledUpdateCertificatesVerifier class can directly get the latest platform certificate (X509Certificate class)
    2. Decrypt the body object
    3. Perform business operations in the system according to the decryption results (modify order payment status, etc.)

Part of the Java code

Configuration information such as AppId (set in the running configuration file)

@Repository
@ConfigurationProperties(prefix = "wx")
@Data
public class WxConfig {
    
    
    String appId;
    String appSecret;
    /**
     * 商户号Id
     */
    String mchId;
    /**
     * 证书序列号
     */
    String serialNo;
    /**
     * apiV3 私钥
     */
    String apiV3Key;
    /**
     * api私钥
     */
    String apiKey;
    /**
     * 认证类型
     */
    String authType;
    /**
     * 商户私钥文件路径
     */
    String privateKeyPath;

}

Signature and signature verification tools

public class SignUtils {
    
    
    public static String buildMessage(String... message) {
    
    
        return String.join("\n", message) + "\n";
    }

    public static String sign(PrivateKey privateKey, String message)
            throws SignatureException, NoSuchAlgorithmException, InvalidKeyException {
    
    
        return sign(privateKey, message.getBytes(StandardCharsets.UTF_8));
    }

    public static String sign(PrivateKey privateKey, byte[] message)
            throws SignatureException, NoSuchAlgorithmException, InvalidKeyException {
    
    
        Signature sign = Signature.getInstance("SHA256withRSA");
        sign.initSign(privateKey);
        sign.update(message);
        return Base64.getEncoder().encodeToString(sign.sign());
    }

    public static String sign(PrivateKey privateKey, String... message)
            throws SignatureException, NoSuchAlgorithmException, InvalidKeyException {
    
    
        return sign(privateKey, buildMessage(message));
    }

    public static boolean check(X509Certificate certificate, String timestamp, String nonce, Object requestBody, String signature)
            throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
    
    
        //构造验签名串
        String signatureStr = buildMessage(timestamp, nonce, JSONObject.toJSONString(requestBody));
        System.out.println("signatureStr = " + signatureStr);
        // 加载SHA256withRSA签名器
        Signature signer = Signature.getInstance("SHA256withRSA");
        // 用微信平台公钥对签名器进行初始化(调上一节中的获取平台证书方法)
        signer.initVerify(certificate);
        // 把我们构造的验签名串更新到签名器中
        signer.update(signatureStr.getBytes(StandardCharsets.UTF_8));
        return signer.verify(Base64Utils.decodeFromString(signature));
    }

    public static boolean check(X509Certificate certificate, HttpServletRequest httpServletRequest, Object requestBody)
            throws NoSuchAlgorithmException, SignatureException, InvalidKeyException {
    
    
        String signature = httpServletRequest.getHeader("Wechatpay-Signature");
        String timestamp = httpServletRequest.getHeader("Wechatpay-Timestamp");
        String nonce = httpServletRequest.getHeader("Wechatpay-Nonce");

        return check(certificate, timestamp, nonce, requestBody, signature);
    }
}

message decryptor

public class AesDecrypt {
    
    
    static final int KEY_LENGTH_BYTE = 32;
    static final int TAG_LENGTH_BIT = 128;

    private final byte[] apiV3Key;


    public AesDecrypt(byte[] apiV3Key) {
    
    
        if (apiV3Key.length != KEY_LENGTH_BYTE) {
    
    
            throw new IllegalArgumentException("无效的ApiV3Key,长度必须为32个字节");
        }
        this.apiV3Key = apiV3Key;
    }

    public AesDecrypt(String apiV3Key) {
    
    
        this(apiV3Key.getBytes());
    }

    public String decryptToString(String associatedData, String nonce, String ciphertext)
            throws GeneralSecurityException {
    
    
        return decryptToString(
                associatedData.getBytes(StandardCharsets.UTF_8),
                nonce.getBytes(StandardCharsets.UTF_8),
                ciphertext
        );
    }

    public String decryptToString(byte[] associatedData, byte[] nonce, String ciphertext)
            throws GeneralSecurityException {
    
    
        try {
    
    
            Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");

            SecretKeySpec key = new SecretKeySpec(apiV3Key, "AES");
            GCMParameterSpec spec = new GCMParameterSpec(TAG_LENGTH_BIT, nonce);

            cipher.init(Cipher.DECRYPT_MODE, key, spec);
            cipher.updateAAD(associatedData);

            return new String(cipher.doFinal(Base64.getDecoder().decode(ciphertext)), StandardCharsets.UTF_8);
        } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
    
    
            throw new IllegalStateException(e);
        } catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
    
    
            throw new IllegalArgumentException(e);
        }
    }
}

some utility beans

@Configuration
@Slf4j
public class WxConfiguration {
    
    

    @Bean
    public PrivateKey merchantPrivateKey(WxConfig wxConfig) throws FileNotFoundException {
    
    
        log.info("加载私钥");
        File file = new File(wxConfig.getPrivateKeyPath());
        System.out.println("file.exists() = " + file.exists());
        return PemUtil.loadPrivateKey(new FileInputStream(wxConfig.getPrivateKeyPath()));
    }

    @Bean
    public ScheduledUpdateCertificatesVerifier verifier(WxConfig wxConfig, PrivateKey merchantPrivateKey) {
    
    
        log.info("加载验证器");
        return new ScheduledUpdateCertificatesVerifier(
                new WechatPay2Credentials(wxConfig.getMchId(), new PrivateKeySigner(wxConfig.getSerialNo(), merchantPrivateKey)),
                wxConfig.getApiV3Key().getBytes(StandardCharsets.UTF_8));
    }

    @Bean
    public AesDecrypt aesDecrypt(WxConfig wxConfig) {
    
    
        log.info("加载解密器");
        return new AesDecrypt(wxConfig.getApiV3Key());
    }
}

Guess you like

Origin blog.csdn.net/hjg719/article/details/121856463