微信扫码支付实现

前言:在一次项目开发中我的业务模块涉及微信扫码支付和支付宝扫码支付。在查阅现有文档后发现,大部分文章还停留在V2支付,部分文章介绍了V3支付但是并不能走通,于是我便阅读了官方文档,形成了这篇总结性文章,希望对读者有所帮助。
官方开发文档
官方SDK开发工具包
API字典

文章目录

    • 微信平台信息配置
      • 获取商户号
      • 获取证书序列号
      • 获取API私钥
      • 获取APIv3密钥
      • 获取并绑定APPID
      • 开通支付产品
    • 代码中的配置
      • 引入依赖
      • 配置微信工具类
      • 支付请求
      • 回调接口

微信平台信息配置

相信此刻你一定已经注册并登陆过 微信开放平台 了,在此不多赘述。需在平台中获取的开发所需信息如下:
MERCHANT_ID:商户号
MERCHANT_SERIAL_NUMBER:商户API证书的证书序列号
MERCHANT_PRIVATEKEY:商户API私钥
AIV3KEY:API v3密钥
APP_ID:小程序的APPID
注意:想要获取以上信息,必须是商户主账号才可以(即便是管理员,部分操作也无法完成)。
下面我们来逐个获取以上参数

获取商户号

  1. 登录微信开放平台
  2. 如果是主账号,如图红框位置即为商户号。如果不是商户主帐号,显示的则为个人帐号(如图为子管理员账号)在这里插入图片描述
  3. 如果登陆账号不是主账号,则需要依次点击产品中心AppID账号管理+关联AppID,此时即可看到商户号微信扫码支付实现_第1张图片

获取证书序列号

该步骤操作需要商户主账号才可完成。

  1. 依次点击账户中心API安全,此时就进入了API安全页,此页面可设置一个证书两个密钥,API证书是根据网站提供的工具生成的(具体下载操作可参考网站中给出的指引)微信扫码支付实现_第2张图片
  2. 对微信工具生成的文件进行解压缩得到如下信息,后续操作可根据’证书使用说明.txt进行(提示一下:Windows用户直接双击apiclient_cert.p12文件即可上传证书)。微信扫码支付实现_第3张图片
  3. 在根据网站指引完成了API证书上传后,商户主账号就可直接点击管理证书查看证书序列号。微信扫码支付实现_第4张图片

获取API私钥

在获取证书序列号的第二步操作中的apiclient_key.pem文件即为所需的商户API私钥。微信扫码支付实现_第5张图片
注意:此处有一个小坑,在粘贴复制密钥时,除了保留-----BEGIN PRIVATE KEY----------END PRIVATE KEY-----的换行外,中间内容在粘贴时一定要去除复制时产生的换行符(推荐复制内容后去网上的去换行工具中去除下换行符)微信扫码支付实现_第6张图片

获取APIv3密钥

  1. 依旧是在API安全页,直接点击设置来设置APIv3密钥即可。微信扫码支付实现_第7张图片
    微信扫码支付实现_第8张图片
  2. APIv3密钥要求是32个字符,支持数字/大小写字母,可以在随机生成字符串的线上工具中进行生成微信扫码支付实现_第9张图片
  3. 将上一步生成的密钥复制粘贴,完成手机号验证码安全认证即可完成APIv3密钥的设置微信扫码支付实现_第10张图片

获取并绑定APPID

公众号和小程序的AppID获取方式雷同,具体参考如图所示指引微信扫码支付实现_第11张图片

  1. 登录微信公众平台,依次点击设置基本设置,页面最下方账号信息处即可看到AppID(小程序ID)微信扫码支付实现_第12张图片

  2. 在微信公众平台,依次点击设置基本设置,找到主体信息微信扫码支付实现_第13张图片

  3. 回到微信开放平台,依次点击产品中心AppID账号管理+关联AppID,将上一步获取到的AppID主体信息粘贴在此并提交微信扫码支付实现_第14张图片

  4. 回到微信公众平台授权即可微信扫码支付实现_第15张图片

开通支付产品

  1. 登录微信开放平台,来到产品中心页面。
  2. 开启支付产品中的Native支付(扫码支付)功能。
  3. 开启运营工具中的企业付款到零钱(付款资金直接进入微信零钱,提现方式根据用户需求来开通不同产品即可)。微信扫码支付实现_第16张图片

代码中的配置

对于v3支付来说,此时可谓万事俱备只欠东风,下面来配置代码中的工具类和第三方请求。

引入依赖

此处引入的是v3支付

<dependency>
    <groupId>com.github.wechatpay-apiv3groupId>
    <artifactId>wechatpay-apache-httpclientartifactId>
    <version>0.3.0version>
dependency>

配置微信工具类

此工具类仅供参考,配置信息也可根据个人喜好放在yml文件中(为方便阅读此处放在类中作为常量)。
此处的工具类有以下作用:

  1. 配置商户信息
  2. 微信接口请求所需工具方法
  3. 微信验签
public class WeChartUtil {
    public static final String MERCHANT_ID = "商户id";
    public static final String MERCHANT_SERIAL_NUMBER = "证书序列号";
    public static final String MERCHANT_PRIVATEKEY = "-----BEGIN PRIVATE KEY-----\n" +
"你的密钥内容(记得去换行)" +
  "\n" + "-----END PRIVATE KEY-----";
    public static final String AIV3KEY = "APIv3密钥";
    public static final String NOTIFY_URL = "回调地址";
    public static final String APP_ID = "小程序的AppID";

    /**
     * @Author Spence_Dou
     * @Description 生成订单号
     * @Date 11:35 2021/12/23
     * @return void
    */
    public static String orderNo(){
        return UUID.randomUUID().toString()
                .replaceAll("-", "")
                .substring(0, 32);
    }

    /**
     * @Author Spence_Dou
     * @Description 生成订单过期时间
     * @Date 11:41 2021/12/23
     * @return java.time.LocalDateTime
    */
    public static String timeExpire(){
        //过期时间:5分钟后
        long time = 5*60*1000;
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX");
        Date now = new Date();
        //30分钟后的时间
        Date afterDate = new Date(now.getTime() + time);
        return simpleDateFormat.format(afterDate);
    }

    /**
     * @Author Spence_Dou
     * @Description 分转元 保留小数点后两位
     * @Date 11:38 2021/12/23
     * @Param num 转换金额
     * @return java.lang.String
    */
    public static BigDecimal transition(Integer num) {
        BigDecimal bigDecimal1 = new BigDecimal(num + "");
        BigDecimal bigDecimal2 = new BigDecimal("100.00");
        return bigDecimal1.divide(bigDecimal2).setScale(2);
    }

    /**
     * @Author Spence_Dou
     * @Description 微信支付回调签名验证
     * @Date 16:30 2021/12/23
     * @Param serial 请求头序列号
     * @Param message 请求报文
     * @Param signature 签名
     * @return boolean
    */
    public static boolean signVerify(String serial, String message, String signature) {
        try {
            PrivateKey key = PemUtil.loadPrivateKey(new ByteArrayInputStream(MERCHANT_PRIVATEKEY.getBytes(StandardCharsets.UTF_8)));
            ScheduledUpdateCertificatesVerifier verifier = new ScheduledUpdateCertificatesVerifier(
                    new WechatPay2Credentials(MERCHANT_ID, new PrivateKeySigner(MERCHANT_SERIAL_NUMBER, key)),
                    AIV3KEY.getBytes(StandardCharsets.UTF_8));
            return verifier.verify(serial, message.getBytes(StandardCharsets.UTF_8), signature);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

    /**
     * @Author Spence_Dou
     * @Description 解密密文
     * @Date 17:04 2021/12/23
     * @Param body 请求数据
     * @return java.lang.String
    */
    public static String decryptOrder(String body){
        try {
            AesUtil util = new AesUtil(AIV3KEY.getBytes(StandardCharsets.UTF_8));
            ObjectMapper objectMapper = new ObjectMapper();
            JsonNode node = objectMapper.readTree(body);
            JsonNode resource = node.get("resource");
            String ciphertext = resource.get("ciphertext").textValue();
            String associatedData = resource.get("associated_data").textValue();
            String nonce = resource.get("nonce").textValue();
            return util.decryptToString(associatedData.getBytes(StandardCharsets.UTF_8), nonce.getBytes(StandardCharsets.UTF_8), ciphertext);
        } catch (IOException | GeneralSecurityException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * @Author Spence_Dou
     * @Description 关闭订单
     * @Date 17:34 2021/12/23
     * @Param outTradeNo 订单号
     * @return void
    */
    public static void closeOrder(String outTradeNo) {
        PrivateKey key = PemUtil.loadPrivateKey(new ByteArrayInputStream(MERCHANT_PRIVATEKEY.getBytes(StandardCharsets.UTF_8)));

        // 使用定时更新的签名验证器,不需要传入证书
        ScheduledUpdateCertificatesVerifier verifier = new ScheduledUpdateCertificatesVerifier(
                new WechatPay2Credentials(MERCHANT_ID, new PrivateKeySigner(MERCHANT_SERIAL_NUMBER, key)),
                AIV3KEY.getBytes(StandardCharsets.UTF_8));

        WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
                .withMerchant(MERCHANT_ID, MERCHANT_SERIAL_NUMBER, key)
                .withValidator(new WechatPay2Validator(verifier));
        CloseableHttpClient httpClient = builder.build();

        HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/"+outTradeNo+"/close");
        httpPost.addHeader("Accept", "application/json");
        httpPost.addHeader("Content-type","application/json; charset=utf-8");

        try {
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectMapper objectMapper = new ObjectMapper();

            ObjectNode rootNode = objectMapper.createObjectNode();
            rootNode.put("mchid",MERCHANT_ID);
            objectMapper.writeValue(bos, rootNode);

            httpPost.setEntity(new StringEntity(bos.toString("UTF-8"), "UTF-8"));
            CloseableHttpResponse response = httpClient.execute(httpPost);

            String bodyAsString = EntityUtils.toString(response.getEntity());
            System.out.println(bodyAsString);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

支付请求

  • 该请求方式参照文章开头提供的SDK开发工具包中的请求方式
  • 不同的产品应参考 API字典 请求不同的地址,此处以Native支付为例
		PrivateKey key = PemUtil.loadPrivateKey(new ByteArrayInputStream(WeChartUtil.MERCHANT_PRIVATEKEY.getBytes(StandardCharsets.UTF_8)));
		// 使用定时更新的签名验证器,不需要传入证书
		ScheduledUpdateCertificatesVerifier verifier = new ScheduledUpdateCertificatesVerifier(
				new WechatPay2Credentials(WeChartUtil.MERCHANT_ID, new PrivateKeySigner(WeChartUtil.MERCHANT_SERIAL_NUMBER, key)),
				WeChartUtil.AIV3KEY.getBytes(StandardCharsets.UTF_8));

		WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
				.withMerchant(WeChartUtil.MERCHANT_ID, WeChartUtil.MERCHANT_SERIAL_NUMBER, key)
				.withValidator(new WechatPay2Validator(verifier));
		// 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签,并进行证书自动更新
		CloseableHttpClient httpClient = builder.build();

		HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/pay/transactions/native");
		httpPost.addHeader("Accept", "application/json");
		httpPost.addHeader("Content-type","application/json; charset=utf-8");

		ByteArrayOutputStream bos = new ByteArrayOutputStream();
		ObjectMapper objectMapper = new ObjectMapper();
		// 生成订单号
		String outTradeNo = WeChartUtil.orderNo();
		// 请求参数
		ObjectNode rootNode = objectMapper.createObjectNode();
		rootNode.put("mchid",WeChartUtil.MERCHANT_ID)
				.put("appid", WeChartUtil.APP_ID)
				.put("notify_url", WeChartUtil.NOTIFY_URL)
				.put("out_trade_no", outTradeNo)
				.put("time_expire", WeChartUtil.timeExpire())
				.put("description", description); // 订单描述(前端获取)
		rootNode.putObject("amount")
				.put("total", 1); // 此处支付金额单位为分
		try {
			objectMapper.writeValue(bos, rootNode);
			httpPost.setEntity(new StringEntity(bos.toString("UTF-8"), "UTF-8"));
			CloseableHttpResponse response = httpClient.execute(httpPost);
			String bodyAsString = EntityUtils.toString(response.getEntity());
			// 这里就是请求支付后返回的数据了
			JSONObject details = JSONObject.parseObject(bodyAsString);
			// 支付二维码
			String qrCode= details.getString("code_url");
			/**
		 	* 此处可进行订单数据持久化等业务操作
		 	* ...
			*/
		} catch (IOException e) {
			e.printStackTrace();
		}

回调接口

  • 这里就是v3支付和v2支付一个比较明显的区别点,v2支付的回调地址是在官方网站中配置的,而v3支付中,回调地址是作为请求参数传递到微信的
  • 此接口地址对应支付请求时的请求参数notify_url,具体功能介绍在此不做阐述微信扫码支付实现_第17张图片
public Map wxCallback(HttpServletRequest request) {
		Map result = new HashMap();
		result.put("code", "FAIL");
		try {
			StringBuilder signStr = new StringBuilder();
			signStr.append(request.getHeader("Wechatpay-Timestamp")).append("\n");
			signStr.append(request.getHeader("Wechatpay-Nonce")).append("\n");

			BufferedReader br = request.getReader();
			String str = null;
			StringBuilder builder = new StringBuilder();
			while ((str = br.readLine()) != null){
				builder.append(str);
			}
			signStr.append(builder.toString()).append("\n");
			// 验证签名
			if (!WeChartUtil.signVerify(request.getHeader("Wechatpay-Serial"), signStr.toString(), request.getHeader("Wechatpay-Signature"))){
				result.put("message", "sign error");
				return result;
			}
			// 解密密文
			String decryptOrder = WeChartUtil.decryptOrder(builder.toString());
			// 验证订单
			JSONObject details = JSONObject.parseObject(decryptOrder);
			// 获取订单状态
			String ciphertext = details.getString("trade_state");
			if ("SUCCESS".equals(ciphertext)){
				/**
		 		* 此处可进行订单状态数据持久化等业务操作
		 		* ...
				*/
				result.put("code", "SUCCESS");
				result.put("message", "成功");
				// 关闭订单
				WeChartUtil.closeOrder(details.getString("out_trade_no"));
			}
		} catch (IOException e) {
			e.printStackTrace();
			//强制手动事务回滚
			TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
		}
		return result;
	}

你可能感兴趣的:(第三方,java,微信,开发语言)