WeChat applet opening and docking WeChat payment tutorial and core source code

Hard conditions

The mini program must be an individual business or enterprise account, and individuals cannot activate the payment function

Mini programs require certification (300 yuan certification fee)

business license

Preparation

  1. Sign up for the WeChat Mini Program.
  2.  After registering a merchant account, the money paid by the user in the later period will automatically enter the merchant account and be entered into the bank card used when registering the merchant account the next day.

The Mini Program is bound to the Merchant ID

Apply for opening WeChat payment in the applet interface (as shown in the picture below, I have already opened it here)

Bind the merchant account with the applet

Merchant account API certificate application and APIv3 key setting

 To apply for a certificate, you need to download the official WeChat tool. The specific operations are as follows:

 

 

 

After the certificate is generated, a compressed file will be obtained. After decompression, it is as follows:

 specific code

rely:

        <dependency>
            <groupId>com.github.wechatpay-apiv3</groupId>
            <artifactId>wechatpay-apache-httpclient</artifactId>
            <version>0.3.0</version>
        </dependency>
        <!--发送http请求-->
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
        </dependency>

The configuration files related to WeChat payment are as follows: (placed in the resources folder) where the merchant private key file is the API certificate path applied for by the merchant account

 Configuration class, used to load the content in the configuration file when springboot starts

@Configuration
@PropertySource("classpath:wxpay.properties") //读取配置文件
@ConfigurationProperties(prefix = "wxpay") //读取wxpay节点
@Data //使用set方法将wxpay节点中的值填充到当前类的属性中
@Slf4j
public class WxPayConfig {

    // 商户号
    private String mchId;

    // 商户API证书序列号
    private String mchSerialNo;

    // 商户私钥文件
    private String privateKeyPath;

    // APIv3密钥
    private String apiV3Key;

    // APPID
    private String appid;

    // 微信服务器地址
    private String domain;

    // 接收结果通知地址(支付结果通知地址)
    private String notifyDomain01;

    // APIv2密钥
//    private String partnerKey;

    /**
     * 获取商户的私钥文件
     *
     * @param filename
     * @return
     */
    public PrivateKey getPrivateKey(String filename) {
        try {
            return PemUtil.loadPrivateKey(new FileInputStream(filename));
        } catch (FileNotFoundException e) {
            throw new RuntimeException("私钥文件不存在", e);
        }
    }

    /**
     * 获取签名验证器
     *
     * @return
     */
    @Bean
    public ScheduledUpdateCertificatesVerifier getVerifier() {
        log.info("获取签名验证器");

        //获取商户私钥
        PrivateKey privateKey = this.getPrivateKey(privateKeyPath);

        //私钥签名对象
        PrivateKeySigner privateKeySigner = new PrivateKeySigner(mchSerialNo, privateKey);

        //身份认证对象
        WechatPay2Credentials wechatPay2Credentials = new WechatPay2Credentials(mchId, privateKeySigner);

        // 使用定时更新的签名验证器,不需要传入证书
        ScheduledUpdateCertificatesVerifier verifier = new ScheduledUpdateCertificatesVerifier(
                wechatPay2Credentials,
                apiV3Key.getBytes(StandardCharsets.UTF_8));

        return verifier;
    }


    /**
     * 获取http请求对象
     *
     * @param verifier
     * @return
     */
    @Bean(name = "wxPayClient")
    public CloseableHttpClient getWxPayClient(ScheduledUpdateCertificatesVerifier verifier) {
        log.info("获取httpClient");

        //获取商户私钥
        PrivateKey privateKey = this.getPrivateKey(privateKeyPath);

        WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
                .withMerchant(mchId, mchSerialNo, privateKey)
                .withValidator(new WechatPay2Validator(verifier));
        // ... 接下来,你仍然可以通过builder设置各种参数,来配置你的HttpClient

        // 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签,并进行证书自动更新
        CloseableHttpClient httpClient = builder.build();

        return httpClient;
    }

    /**
     * 获取HttpClient,无需进行应答签名验证,跳过验签的流程
     */
    @Bean(name = "wxPayNoSignClient")
    public CloseableHttpClient getWxPayNoSignClient() {

        //获取商户私钥
        PrivateKey privateKey = getPrivateKey(privateKeyPath);

        //用于构造HttpClient
        WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
                //设置商户信息
                .withMerchant(mchId, mchSerialNo, privateKey)
                //无需进行签名验证、通过withValidator((response) -> true)实现
                .withValidator((response) -> true);

        // 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签,并进行证书自动更新
        CloseableHttpClient httpClient = builder.build();

        log.info("== getWxPayNoSignClient END ==");

        return httpClient;
    }

}

The interface called when the applet pulls up the payment

 /**
     * 用户微信支付
     */
    @PostMapping("/userPay")
    public R userPay(@RequestBody UserPayDto userPayDto) {
        try {
            // JSAPI下单
            HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi");
            httpPost.addHeader("Accept", "application/json");
            httpPost.addHeader("Content-type", "application/json; charset=utf-8");
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectMapper objectMapper = new ObjectMapper();
            ObjectNode rootNode = objectMapper.createObjectNode();
//            totalPrice = totalPrice.multiply(new BigDecimal(100));
            rootNode.put("mchid", wxPayConfig.getMchId())//商户id
                    .put("appid", wxPayConfig.getAppid())//appid
                    .put("description", "微信支付测试")//商品描述
                    .put("notify_url", wxPayConfig.getNotifyDomain01())//回调地址
                    .put("out_trade_no", );//本地系统订单号
            rootNode.putObject("amount")
                    .put("total",);//金额(单位:分)
            rootNode.putObject("payer")//付款者openid
                    .put("openid", userPayDto.getUserOpenId());
            objectMapper.writeValue(bos, rootNode);
            httpPost.setEntity(new StringEntity(bos.toString("UTF-8"), "UTF-8"));
            
            //调Api请求
            CloseableHttpResponse response2 = wxPayClient.execute(httpPost);
            String bodyAsString = EntityUtils.toString(response2.getEntity());
            
            String[] split = bodyAsString.split("\"");
            long startTs = System.currentTimeMillis();
            String s = Long.toString(startTs);
            String prepayId = "prepay_id=" + split[3];
            String key = wxPayConfig.getAppid() + "\n" + s + "\n" +
                    userPayDto.getPwd() + "\n"
                    + prepayId + "\n";
            String sign = sign(key, wxPayConfig.getPrivateKeyPath());
            return Objects.requireNonNull(Objects.requireNonNull(R.ok().put("prepayId", prepayId)).put("sign", sign)).put("timestamp", s).put("orderNo", orderNo);
        } catch (Exception e) {
            e.printStackTrace();
            return R.error();
        } finally {
            
            log.error("保存预订单信息完成。结果:{}", b);
        }

    }

    /**
     * 支付通知
     * 微信支付通过支付通知接口将用户支付成功消息通知给商户
     */
    @PostMapping("/notify")
    public String nativeNotify(HttpServletRequest request, HttpServletResponse response) {
        System.err.println(request);
        System.err.println(request.getRequestURL());
        Gson gson = new Gson();
        Map<String, String> resultMap = new HashMap<>();
        try {
            String body = HttpUtils.readData(request);
            Map<String, Object> bodyMap = gson.fromJson(body, Map.class);
            String requestId = (String) bodyMap.get("id");
            log.error("支付通知的id ===> {}", requestId);
            //签名的验证
            WechatPay2ValidatorForRequest wechatPay2ValidatorForRequest
                    = new WechatPay2ValidatorForRequest(verifier, requestId, body);
            if (!wechatPay2ValidatorForRequest.validate(request)) {
                log.error("通知验签失败");
                //失败应答
                response.setStatus(500);
                resultMap.put("code", "ERROR");
                resultMap.put("message", "通知验签失败");
                return gson.toJson(resultMap);
            }
            log.error("通知验签成功");
            response.setStatus(200);
            //订单的业务逻辑处理
            wxPayService.processOrder(bodyMap);
            resultMap.put("code", "SUCCESS");
            resultMap.put("message", "成功");
            return gson.toJson(resultMap);
        } catch (Exception e) {
            e.printStackTrace();
            log.error("通知验签失败");
            //失败应答
            response.setStatus(500);
            resultMap.put("code", "ERROR");
            resultMap.put("message", "失败");
            return gson.toJson(resultMap);
        }
    }

Business processing method of payment result notification

/**
     * 用户支付完成后的业务逻辑处理
     */
@Override
    @Transactional(rollbackFor = Exception.class)
    public void processOrder(Map<String, Object> bodyMap) throws GeneralSecurityException {
        log.error("处理订单");
        String plainText = this.decryptFromResource(bodyMap);
        //将明文转换成map
        Gson gson = new Gson();
        HashMap plainTextMap = gson.fromJson(plainText, HashMap.class);
        System.err.println("plainTextMap:" + plainTextMap);
        String orderNo = (String) plainTextMap.get("out_trade_no");
        log.error("验证" + orderNo + "支付");

        CtrlDeviceDto ctrlDeviceDto = new CtrlDeviceDto();
        R r = new R();
        QueryWrapper<WxOrderFlowEntity> wrapper = new QueryWrapper<WxOrderFlowEntity>().eq("order_num", orderNo);
        //若 trade_state 参数为 SUCCESS ,则控制设备出酒
        if ("SUCCESS".equals(plainTextMap.get("trade_state"))) {
            log.error("用户支付完成。单号:{}", orderNo);
        } else {
            log.error("trade_state 参数 非SUCCESS。单号:{}", orderNo);
        }
        
    }

/**
     * 对称解密
     */
    private String decryptFromResource(Map<String, Object> bodyMap) throws GeneralSecurityException {
        log.info("密文解密");
        // 通知数据
        Map<String, String> resourceMap = (Map<String, String>) bodyMap.get("resource");
        // 数据密文
        String ciphertext = resourceMap.get("ciphertext");
        // 随机串
        String nonce = resourceMap.get("nonce");
        //附加数据
        String associatedData = resourceMap.get("associated_data");
        log.info("密文 ===> {}", ciphertext);

        AesUtil aesUtil = new AesUtil(wxPayConfig.getApiV3Key().getBytes(StandardCharsets.UTF_8));
        String plainText = aesUtil.decryptToString(
                associatedData.getBytes(StandardCharsets.UTF_8),
                nonce.getBytes(StandardCharsets.UTF_8),
                ciphertext);
        log.info("明文 ===> {}", plainText);

        return plainText;
    }

applet code

            wxPay() {
				var vm = this
				vm.loadModal = true
				var openId = uni.getStorageSync('userOpenId')
				var pwd = this.get32Pwd()
				wxPayRequest('/userPay', {
					userOpenId: openId,
					pwd: pwd,
					}, 'POST').then((res) => {
					vm.loadModal = false
					if (res.code === 0) {
						var that = this
						var prepayId = res.prepayId;
						var sign = res.sign;
						var timestamp = res.timestamp;
						wx.requestPayment({
							timeStamp: timestamp, //时间戳,自1970年以来的秒数
							nonceStr: pwd, //随机串
							package: prepayId,
							signType: "RSA", //微信签名方式:
							paySign: sign,
							success: function(res) {
								vm.loadModalMakeOrder = true
								console.log('用户付款完成');
							},
							fail: function(res) {},
							complete: function(res) {},
						});
					} else {
						uni.showToast({
							title: '创建订单失败',
							icon: 'error',
							duration: 2000
						});
					}
				}, (error) => {
					vm.loadModal = false
					console.log(error);
				})
			},
            //获取32位随机串
			get32Pwd() {
				var $chars = "ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678";
				var maxPos = $chars.length;
				var pwd = "";
				for (var i = 0; i < 32; i++) {
					pwd += $chars.charAt(Math.floor(Math.random() * maxPos));
				}
				return pwd
			},

Note: The notification of the WeChat payment result can only be received using the https protocol, not http, so it is necessary to use intranet penetration during the test. Recommended use: ngrok

If you have any questions, welcome to communicate in the comment area; please indicate the source for reprinting

Guess you like

Origin blog.csdn.net/weiqiang915/article/details/125763657