IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> spring boot接入微信小程序支付流程 -> 正文阅读

[移动开发]spring boot接入微信小程序支付流程

前言

正好最近项目中有需要做微信支付,跟着官方文档写下来坑还是踩了不少,于是写了这篇流程给自己长长记性,代码比较粗糙大家图一乐就好。

官方文档

官方接入指引--微信支付开发者文档


所用依赖

<dependency>
        <groupId>com.github.wechatpay-apiv3</groupId>
        <artifactId>wechatpay-apache-httpclient</artifactId>
        <version>0.2.3</version>
</dependency>
//用来生成随机字符串的工具类
<dependency>
        <groupId>cn.hutool</groupId>
        <artifactId>hutool-all</artifactId>
        <version>5.7.15</version>
</dependency>

常量解释

以下常量需要通过注册商户号获得

  • APPID: 小程序ID,在微信开发平台里可以获得,格式为wx************
  • API_V3_KEY:在微信商户中心的账号中心里获得,用来解密微信回调的报文
  • MCH_SERIAL_NO:商户证书序列号,在微信商户中心的账号中心里获得。
  • MCH_ID:商户号,开通商户后那一串数字就是商户号了。
  • PRIVATE_KEY: 商户秘钥,商户在注册证书后从apiClient_key.pem文件中可以获得,商户秘钥是用来给交易签名的重要数据,请不要暴露给外部。

以下常量需要后端自行设置

  • PACKAGE:签名固定字符串,小程序支付固定值为"RSA"
  • NOTIFY_URL:回调地址,支付成功后微信回调报文的地址,请设置为外网可见。

流程说明

创建订单

平台用户购买平台商品,前端将动态生成的商品详情,商品价格以及订单号传入创单接口,后端接收参数,请求微信支付系统接口,获取接口返回的预支付ID。
以下是后端代码
注意,代码中WeXinPayConstant里存放的是常量值,rootNode中应填写自己项目对应的值

		//创建HttpClient
        PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(
                new ByteArrayInputStream(WeXinPayConstant.PRIVATE_KEY.getBytes("utf-8")));

        //使用自动更新的签名验证器,不需要传入证书
        AutoUpdateCertificatesVerifier verifier = new AutoUpdateCertificatesVerifier(
                new WechatPay2Credentials(WeXinPayConstant.MCH_ID, new PrivateKeySigner(WeXinPayConstant.MCH_SERIAL_NO, merchantPrivateKey)),
                WeXinPayConstant.API_V3_KEY.getBytes(StandardCharsets.UTF_8));

        CloseableHttpClient httpClient = WechatPayHttpClientBuilder.create()
                .withMerchant(WeXinPayConstant.MCH_ID, WeXinPayConstant.MCH_SERIAL_NO, merchantPrivateKey)
                .withValidator(new WechatPay2Validator(verifier))
                .build();
        //------------------------创建订单----------------------------
        //请求URL
        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();
        rootNode.put("mchid",WeXinPayConstant.MCH_ID)//商户号
                .put("appid", WeXinPayConstant.APPID)// 公众号id
                .put("description", desc)//商品描述
                .put("notify_url", WeXinPayConstant.NOTIFY_URL)//支付回调地址
                .put("out_trade_no", orderNumber);//自己平台订单号
        rootNode.putObject("amount")
                .put("total", amount);//价格 单位:分
        rootNode.putObject("payer")
                .put("openid", openId);//支付用户的OpenId

        objectMapper.writeValue(bos, rootNode);

        httpPost.setEntity(new StringEntity(bos.toString("UTF-8"), "UTF-8"));
        CloseableHttpResponse response = httpClient.execute(httpPost);
        //-----------------获得预支付ID------------------
        String bodyAsString = EntityUtils.toString(response.getEntity());

数据处理

后台获取预支付id后,应向前端返回数据以供前端调用微信支付系统的接口,在接口文档中我们可以看到,前端需要调用wx.requestPayment(OBJECT)发起微信支付。
其中有5项必填项,分别为:时间戳(timeStamp)、随机字符串(nonceStr)、订单详情扩展字符串(package)、签名方式(paySign)、签名(paySign)
这里要
注意
时间戳属性要求的是10位字符串即需要精确到,我们在获取时间戳时需要System.currentTimeMillis()/1000L来获取对应数据
以下是后端代码

	//-----------------数据加密----------------------
        String timestamp=System.currentTimeMillis()/1000L+"";
        String nonce= RandomUtil.randomString(32);
        StringBuffer buffer=new StringBuffer();
        //应用ID
        buffer.append(WeXinPayConstant.APPID).append("\n");
        //时间戳
        buffer.append(timestamp).append("\n");
        //随机字符串
        buffer.append(nonce).append("\n");
        //通过JsonNode读取返回的json对象
        JsonNode node=objectMapper.readTree(bodyAsString);
        //预支付会话ID
        buffer.append("prepay_id=");
        buffer.append(node.get("prepay_id")).append("\n");
		//获得加密后的数据
        String cipherText =  WeXinPayUtil.sign(buffer.toString().getBytes());
        
		//将数据进行包装后返回前端
        WeXinPayReturn weXinPayReturn=new WeXinPayReturn();
        weXinPayReturn.setNonceStr(nonce);
        weXinPayReturn.setSignType(WeXinPayConstant.PACKAGE);
        weXinPayReturn.setPackageCode("prepay_id="+node.get("prepay_id").toString());
        weXinPayReturn.setPaySign(cipherText);
        weXinPayReturn.setTimestamp(timestamp);
		return weXinPayReturn;

生成签名的方法

        //生成签名
		public static String sign(byte[] message) throws Exception{
        	Signature sign = Signature.getInstance("SHA256withRSA");
        	sign.initSign(getPrivateKey(WeXinPayConstant.PRIVATE_KEY));
        	sign.update(message);
        	return Base64.getEncoder().encodeToString(sign.sign());
   	 	}


		public static PrivateKey getPrivateKey(String key) throws IOException {
            try {
		    //获取签名内容
                String privateKey = key.replace("-----BEGIN PRIVATE KEY-----", "")
                        .replace("-----END PRIVATE KEY-----", "")
                        .replaceAll("\\s+", "");

                KeyFactory kf = KeyFactory.getInstance("RSA");

                return kf.generatePrivate(
                    new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKey)));
            } catch (NoSuchAlgorithmException e) {
                throw new RuntimeException("当前Java环境不支持RSA", e);
            } catch (InvalidKeySpecException e) {
                throw new RuntimeException("无效的密钥格式");
            }
        }

发起支付

前端调用微信支付

wx.requestPayment({
  timeStamp: '',
  nonceStr: '',
  package: '',
  signType: 'MD5',
  paySign: '',
  success (res) { },
  fail (res) { }
})

至此前半段支付功能完成

回调通知

用户付款操作完成后,微信会通过POST请求给回调地址NOTIFY_URL发送通知相关支付结果。
回调地址即下单接口中的"notify_url"参数,地址必须为https地址且外网可见,不能带任何参数。

通知参数
回调通知会在请求头中附带数据:
1.Wechatpay-Timestamp 时间戳
2.Wechatpay-Nonce 随机字符串
3.Wechatpay-Serial 序列号
4.Wechatpay-Signature 通知签名

通知应答
支付通知必须响应应答码为200或204才会被当做正常接收,当回调处理异常时,应答的HTTP状态码应为500,或者4xx。
代码中我们应将回调应答以Map形式返回

示例格式

{   
"code": "SUCCESS",
"message": "成功"
}

以下是后端代码

	public Map payCallBack(HttpServletRequest request){
        Map result = new HashMap();
        result.put("code","FAIL");
        try {
            StringBuffer signStr=new StringBuffer();
            signStr.append(request.getHeader("Wechatpay-Timestamp")).append("\n");
            signStr.append(request.getHeader("Wechatpay-Nonce")).append("\n");
            BufferedReader br=request.getReader();
            String str = null;
            //报文
            StringBuffer buffer = new StringBuffer();
            while ((str=br.readLine())!=null){
                buffer.append(str);
            }
            signStr.append(buffer.toString()).append("\n");
            //验签
            if (!WeXinPayUtil.signVerify(request.getHeader("Wechatpay-Serial"),
                    signStr.toString(),
                    request.getHeader("Wechatpay-Signature"))){
                result.put("message","sign error");
                return result;
            }
            //解密报文 获取返回对象
            String resource = WeXinPayUtil.decryptOrder(buffer.toString());
            ReturnResource returnResource= JSONArray.parseObject(resource,ReturnResource.class);
			------操作ReturnResource类进行订单验证业务------
			业务代码省略
			---------------------------------------------
		}catch (IOException e){
            e.printStackTrace();
        }
        return result;
	}

ReturnResource类这里提供一个JSON字符串转实体类的网站:

在线JSON字符串转Java实体类(JavaBean、Entity)-BeJSON.com

验签方法

    public static boolean signVerify(String serial,String message,String signature){
        PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(
                new ByteArrayInputStream(WeXinPayConstant.PRIVATE_KEY.getBytes(StandardCharsets.UTF_8)));
        AutoUpdateCertificatesVerifier verifier = new AutoUpdateCertificatesVerifier(
                new WechatPay2Credentials(WeXinPayConstant.MCH_ID, new PrivateKeySigner(WeXinPayConstant.MCH_SERIAL_NO, merchantPrivateKey)),
                WeXinPayConstant.API_V3_KEY.getBytes(StandardCharsets.UTF_8));
        return verifier.verify(serial, message.getBytes(StandardCharsets.UTF_8), signature);
    }

解密报文方法

    public static String decryptOrder(String body){
        try {
            AesUtil aesUtil=new AesUtil(WeXinPayConstant.API_V3_KEY.getBytes(StandardCharsets.UTF_8));
            ObjectMapper objectMapper = new ObjectMapper();
            JsonNode node=objectMapper.readTree(body);
            JsonNode resource=node.get("resource");
            String cipherText=resource.get("ciphertext").toString().replace("\"","");
            String associatedData=resource.get("associated_data").toString().replace("\"","");
            String nonce=resource.get("nonce").toString().replace("\"","");
            return aesUtil.decryptToString(associatedData.getBytes(StandardCharsets.UTF_8),nonce.getBytes(StandardCharsets.UTF_8),cipherText);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-11-17 12:52:17  更:2021-11-17 12:52:53 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 3:50:22-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码