微信扫码支付实现

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

微信平台信息配置

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

获取商户号

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

获取证书序列号

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

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

获取API私钥

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

获取APIv3密钥

  1. 依旧是在API安全页,直接点击设置来设置APIv3密钥即可。在这里插入图片描述
    在这里插入图片描述
  2. APIv3密钥要求是32个字符,支持数字/大小写字母,可以在随机生成字符串的线上工具中进行生成在这里插入图片描述
  3. 将上一步生成的密钥复制粘贴,完成手机号验证码安全认证即可完成APIv3密钥的设置在这里插入图片描述

获取并绑定APPID

公众号和小程序的AppID获取方式雷同,具体参考如图所示指引在这里插入图片描述

  1. 登录微信公众平台,依次点击设置基本设置,页面最下方账号信息处即可看到AppID(小程序ID)在这里插入图片描述

  2. 在微信公众平台,依次点击设置基本设置,找到主体信息在这里插入图片描述

  3. 回到微信开放平台,依次点击产品中心AppID账号管理+关联AppID,将上一步获取到的AppID主体信息粘贴在此并提交在这里插入图片描述

  4. 回到微信公众平台授权即可在这里插入图片描述

开通支付产品

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

代码中的配置

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

引入依赖

此处引入的是v3支付

<dependency>
    <groupId>com.github.wechatpay-apiv3</groupId>
    <artifactId>wechatpay-apache-httpclient</artifactId>
    <version>0.3.0</version>
</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,具体功能介绍在此不做阐述在这里插入图片描述
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;
	}
  • 3
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
WinForm是一个用于开发Windows桌面应用程序的框架。微信扫码支付微信支付提供的一种支付方式,用户可以通过扫描二维码的方式完成支付。 在WinForm中实现微信扫码支付,首先需要获取用户的订单信息和支付金额。可以通过在界面上添加文本框和按钮,让用户输入订单信息和支付金额,并点击按钮进行支付操作。 接下来,需要将用户的订单信息和支付金额传递给微信支付接口。可以使用HttpClient类来发送请求,并获取返回的支付二维码链接。通过发送HTTP请求,传递订单信息和支付金额给微信支付接口,然后获取返回的支付二维码链接。 将获取到的支付二维码链接显示在WinForm界面上,可以使用PictureBox控件来显示二维码图片。将支付二维码链接转换成图片,然后将图片显示在PictureBox控件中。这样用户就可以通过扫描二维码完成支付操作了。 同时,可以使用Thread或者Task来实现支付二维码的轮询,定期查询用户的支付状态。通过发送HTTP请求,获取用户的支付状态信息。根据支付状态,可以在WinForm界面上显示支付成功或者支付失败的提示信息。 总结起来,通过WinForm框架结合微信支付接口,可以实现微信扫码支付功能。用户在WinForm界面上输入订单信息和支付金额,通过发送HTTP请求获取支付二维码链接,并显示在界面上。用户通过扫描二维码完成支付,同时可以在界面上显示支付状态信息。这样就可以方便地实现WinForm微信扫码支付功能
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值