微信小程序开通并对接微信支付教程及核心源码

硬性条件

小程序必须为个体工商户或者企业账户,个人无法开通支付功能

小程序需要认证(300元认证费)

营业执照

准备工作

  1. 注册微信小程序。
  2.  注册商户号,后期用户支付的钱会自动进入商户号中,并在次日打入注册商户号时所用的银行卡中。

小程序与商户号绑定

小程序界面中申请开通微信支付(如下图,我这边已开通)

小程序绑定商户号

商户号API证书申请及APIv3密钥设置

 证书申请需要下载微信官方的工具,具体操作如下:

 

 

 

证书生成完成后会得到一个压缩文件。解压后如下:

 具体代码

依赖:

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

微信支付相关配置文件如下:(放在resources文件夹下)其中商户私钥文件即为商户号申请的API证书路径

 配置类,用于在springboot启动时加载配置文件中的内容

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

}

小程序拉起支付时调用的接口

 /**
     * 用户微信支付
     */
    @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);
        }
    }

支付结果通知的业务处理方法

/**
     * 用户支付完成后的业务逻辑处理
     */
@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;
    }

小程序端代码

            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
			},

注意:微信支付结果的通知只能使用https协议接收,不可使用http,所以在测试的时候需要使用到内网穿透。推荐使用:ngrok

有问题欢迎评论区交流;转载请注明出处

猜你喜欢

转载自blog.csdn.net/weiqiang915/article/details/125763657