微信小程序接入微信支付流程笔记

准备流程

使用“普通商户号”(登陆商户平台后,上方有:首页、交易中心、账户中心、营销中心、产品中心、数据中心),在产品中心中开通JSAPI支付,记录商户号。

  • 进入:账户中心 - API安全 - 申请API证书 - 管理证书,按提示添加证书,添加完毕后记录证书序列号
  • 进入:账户中心 - API安全 - 设置APIv3密钥,修改,按提示操作,密钥自行生成,为长度上限32位的字符串;记录该密钥。
  • 进入:账户中心 - API安全 - 设置API密钥,修改,同上(本步骤可能用不到)
  • 进入:产品中心 - AppID账号管理 - 关联AppID,填写需要关联的AppID(小程序、公众号等)。登陆对应的AppID平台,通过关联申请;小程序平台的操作路径为:功能 - 微信支付

除此以外还需要准备

  • 小程序的appId
  • 小程序的appSecret

提前阅读:

正式流程

登陆

  1. 前端:调用该接口获取授权:
  2. 前端:成功后调用该接口: 向后端提供的微信登陆接口传递参数:code,也可以同时额外提供前一接口获取到的其他信息
  3. 后端:在微信登陆接收到 code 后,调用该接口,使用小程序的 appId 和 appSecret + 获取到的 code 请求获得用户信息(openid unionid session_key)并保存,获取成功后检查openid是否已进行过注册,如果没有进行注册,再执行免密登陆。

支付

  1. 前端:向后端接口发送下单请求,提交订单UUID
  2. 后端:
    1. 调用该接口,设置商户订单号为前端提交的UUID,执行下单请求,获取prepay_id;
    2. 使用该prepay_id生成步骤3需要的请求参数并返回给前端,此步骤需要进行手动 签名 ,可以从这里 下载签名工具 对比自己的签名结果是否正确
  3. 前端:
    1. 调用该接口 ,调用wx.requestPayment(OBJECT)发起微信支付,其中请求参数由后端生成,应在前一步请求时返回给前端
    2. 用户根据提示进行支付(非本系统流程)
  4. 后端:微信服务器会将支付结果发送到步骤2中的 notify_url 字段提供的接口,该接口 需要完成的步骤为
    1. 进行 验签 ,如果使用官方SDK的平台证书自动更新功能,则 ScheduledUpdateCertificatesVerifier 类的 getLatestCertificate() 方法可以直接拿到最新的平台证书(X509Certificate类)
    2. 对正文对象进行 解密
    3. 根据解密结果进行本系统内的业务操作(修改订单支付状态等)

部分Java代码

AppId等配置信息(在运行配置文件中设置)

@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;

}

签名、验签工具类

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);
    }
}

报文解密器

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);
        }
    }
}

一些工具Bean

@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());
    }
}

猜你喜欢

转载自blog.csdn.net/hjg719/article/details/121856463