文章目录
前言
本节内容详解解释了微信小程序授权登录和微信支付的具体实现
提示:以下是本篇文章正文内容,下面案例可供参考
一、微信小程序授权登录
1. 前端环境搭建
- 微信小程序测试申请
-
前提:确保自己的微信与手机号已经绑定
-
申请小程序测试号:https://mp.weixin.qq.com/wxamp/sandbox?doc=1
-
打开手机微信,扫描二维码,申请小程序测试号
-
注册成功以后,打开以下页面进行扫码登录https://mp.weixin.qq.com/
-
扫码之后,在手机中选择小程序测试号
-
登录成功之后,就可以看到小程序的开发者ID,包含两部分,如下图
-
AppID(小程序ID)
-
AppSecret(小程序秘钥)
-
- 导入项目
- 微信小程序开发工具下载地址:https://developers.weixin.qq.com/miniprogram/dev/devtools/stable.html
- 微信小程序开发工具下载地址:https://developers.weixin.qq.com/miniprogram/dev/devtools/stable.html
2. 微信登录后端接口实现
2.1. 微信小程序业务流程分析
微信官方提供的业务流程图如下
整理流程功能说明:用户登录后,我们从微信服务端拿到用户唯一标识当成我们用户来使用
- 流程分析:
- 前端在小程序中集成微信相关依赖,当用户请求登录的同时,调用wx.login() 获取 临时登录凭证code ,并回传到开发者服务器。
- 开发者服务器可以根据用户标识来生成自定义登录态就是token,用于后续业务逻辑中前后端交互时识别用户身份。
- 自定义登录态:后台服务器验证用户成功后,使用JWT生成一个token返回给前端,前端负责把token存储到小程序端的storage中,以后,从小程序中发起其他请求的时候,携带该token到后台验证。
- 根据项目需求进行修改后的实际流程—模拟
- 说明
- 用户在进行小程序登录时,小程序服务器端将会进行登录信息的处理,即保存了用户信息,并生成了一个openid作为用户的唯一标识(可以理解为为用户建立了个表,openid为唯一标识),前端可以调用小程序服务器wx.login() 来获取用户的临时登录凭证code和一些其他的加密信息(微信服务器保存的)。
- 后端通过前端传过来的信息中的code来向小程序服务器请求用户的唯一标识openid和一些其他信息来进行我们自己的处理,因为openid是唯一标识,所以我们也会将openid作为我们处理用户信息的唯一标识(微信服务器端存储的信息和我们需求的信息肯定是有差异的,所以具体的需求信息要我们自己来完成)
- 对于非openid的其他信息的获取,通常要配合我们和微信小程序服务器的token进行访问查询,通常前端调用微信小程序接口获取到的消息都是进行加密的,前端将这些信息传给我们后端后,我们后端通常要配合访问微信服务器的token来配合请求到具体的用户信息。
- 总结
- 前端获取code发送给后端
- 后端通过code想小程序服务器请求openid
- 整个流程可以看出来,在后台实现的过程中,需要调用两次微信开发者平台的接口来获取数据,一般像这种第三方接口的调用,我们通常都会封装一个单独的业务代码,使其更通用。
2.2. 登录相关接口的调用
对应微信小程序官方文档(微信登录接口文档相比较下面的微信支付接口文当还是好很多的。)
- 获取用户openIdhttps://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/user-login/code2Session.html
- 获取手机号
- 获取token:https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/mp-access-token/getAccessToken.html
- 获取手机号:https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/user-info/phone-number/getPhoneNumber.html
- 注意:(微信接口文档写的真是,算了)
在调用获取openId和token的接口时,如果响应成功,则不会返回错误信息-errcode,而获取手机号的接口errcode值为1则代表成功(这一点官方文档并未进行说明)
微信登录相关接口的调用示例
发送请求使用的是hutool工具包中的HttpUtil工具类
package com.zzyl.service.impl;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.zzyl.exception.BaseException;
import com.zzyl.service.WechatService;
import com.zzyl.utils.ObjectUtil;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
/**
* @author sjqn
*/
@Service
public class WechatServiceImpl implements WechatService {
// 登录
private static final String REQUEST_URL = "https://api.weixin.qq.com/sns/jscode2session?grant_type=authorization_code";
// 获取token
private static final String TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential";
// 获取手机号
private static final String PHONE_REQUEST_URL = "https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token=";
@Value("${zzyl.wechat.appId}")
private String appId; //测试号appid
@Value("${zzyl.wechat.appSecret}")
private String secret; //测试号secret
/**
* 获取openid
*
* @param code 登录凭证
* @return
* @throws IOException
*/
@Override
public String getOpenid(String code) throws IOException {
//封装参数
Map<String,Object> requestUrlParam = getAppConfig();
requestUrlParam.put("js_code",code);
String result = HttpUtil.get(REQUEST_URL, requestUrlParam);
JSONObject jsonObject = JSONUtil.parseObj(result);
// 若code不正确,则获取不到openid,响应失败
if (ObjectUtil.isNotEmpty(jsonObject.getInt("errcode"))) {
throw new RuntimeException(jsonObject.getStr("errmsg"));
}
return jsonObject.getStr("openid");
}
/**
* 封装公共参数
* @return
*/
private Map<String, Object> getAppConfig() {
Map<String, Object> requestUrlParam = new HashMap<>();
requestUrlParam.put("appid",appId);
requestUrlParam.put("secret",secret);
return requestUrlParam;
}
/**
* 获取手机号
*
* @param code 手机号凭证
* @return
* @throws IOException
*/
@Override
public String getPhone(String code) throws IOException {
//获取access_token
String token = getToken();
//拼接请求路径
String url = PHONE_REQUEST_URL + token;
Map<String,Object> param = new HashMap<>();
param.put("code",code);
String result = HttpUtil.post(url, JSONUtil.toJsonStr(param));
JSONObject jsonObject = JSONUtil.parseObj(result);
if (jsonObject.getInt("errcode") != 0) {
//若code不正确,则获取不到phone,响应失败
throw new RuntimeException(jsonObject.getStr("errmsg"));
}
return jsonObject.getJSONObject("phone_info").getStr("purePhoneNumber");
}
//获取token
public String getToken(){
Map<String, Object> requestUrlParam = getAppConfig();
String result = HttpUtil.get(TOKEN_URL, requestUrlParam);
//解析
JSONObject jsonObject = JSONUtil.parseObj(result);
//如果code不正确,则失败
if(ObjectUtil.isNotEmpty(jsonObject.getInt("errcode"))){
throw new RuntimeException(jsonObject.getStr("errmsg"));
}
return jsonObject.getStr("access_token");
}
}
二、微信支付
支付:微信的接口文档写的是真的不尽人意…,下面示例代码对应微信的示例代码,接口文档对应微信API字典,建议使用时两个页面都打开,其中接口文档不要在页面上进行任何转跳,微信官网是无法直接转跳到接口文档的(能直接转跳的那版,你就看吧)
示例代码—https://pay.weixin.qq.com/docs/merchant/products/jsapi-payment/introduction.html
接口文档—https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_4_1.shtml
1. 小程序支付
1.1. 小程序支付介绍
具体参考官方文档
https://pay.weixin.qq.com/wiki/doc/apiv3/open/pay/chapter2_8_0.shtml
通过微信官方文档总结得出,我们在使用小程序支付之前,需要按照官方教程,申请如下关键信息
- 申请appId - 即小程序的id
- 申请mchId - 即商户号
- 实现appId和mchId的绑定
- 配置apiV3 key – 即我们调用微信小程序支付的接口时,需要传递的一个参数
- 配置商户证书和私钥 - 也是我们调用微信小程序支付的接口时,需要携带的参数
PS:个人用户无法申请,此处提供的参数只能用于学习测试,无法集成到项目中。
private String privateKey="-----BEGIN PRIVATE KEY-----\n" +
"MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQChXKhMDNcjxTBO\n" +
"FF2NOHai5NwChO12f3KQbmqCNEXWImqC5OtTEmcy/8Kzx8i243TtkVFv+Eqvac69\n" +
"/gpytl8sK8GFCzzUExgtejrtr3cJS//0eaJURGVG9zDPHrGV5vtXqmUPzAzX5SbM\n" +
"sIEKAZoe2bRJRz5zNNx+RMReSOC7F4xn+5tQd3XmZomwFGKO0oryKEocw5IGdYL6\n" +
"1zRkUK9yRtyDZKq7hvPv11YQgnD8+EeGgo5/0kk62sbWW545Hw1qVqkQjkOfbdtL\n" +
"Qjz4BnbakUwdRn3qoCjga8QQp0EKaGbIu4haHReyP8QQZhq0KlRX6XjBbrk8k4yK\n" +
"URVkjrxVAgMBAAECggEAT7HjrSvqVdBeOzHzwnvQnENMJjJg1dW8T3k5QXVvyILW\n" +
"+C73yt+b+KQ5FXHmv+03It5Sympm+JvZcBy4LE/GUZqKyZrzQAruNgfYcuvmbsEK\n" +
"eURZ2CSvoI0VnjYan16lZHbT0ymEblzO/Olv0fFYnUQItuho/51sCTENi0OTOtNt\n" +
"rWahbOnrBk+WsgmhNen+W/KdFXA9EOFKoFclAPEpCUa/mi1/w7AQecHZUISeRXix\n" +
"SZbhHIF4bf7BDXqkgtnWEkBX2hZP61TibGiRUgHvVC6V5+bi07cv7jr6IjJdNvwK\n" +
"MjAw2GgwZvyb8HvcQ0M7ULmYC6ZXYfVuvZDXJuMCAQKBgQDTAltDJ3oE1tJtk3qg\n" +
"ichuWf8OKlvNmN2fxiDG+rxOgTi2gL9N3BKIYWQRo3f2B9C243Z1iM+Q/4LPii6F\n" +
"iLTSPQYRcNaryB1BtNGjexHMK5NuCfZ2etx3zmYz86YiWY6ZsFuISFttdPq01MKR\n" +
"xN/gIfiXvRlv4g1SIK6vy3lpjQKBgQDDxF/Q2xNFZq8WZ4M1DW3r9EcnYgn6U8qK\n" +
"fDJFvW4VT9bHOJOxxmZ3oIccSuPky7gm9DlxRC632z1FyPu6buC7pp7X8+JJPmkq\n" +
"2c0fKYl5B5boJdBCs8QrRQ89oORFuB3/2on9N6YCMccf1VQ3kspm5GhEkO/mdN7u\n" +
"NYBRq1oX6QKBgAT77UPszskGefn0ndTk7EMPBSNEhN6aWcHIYEXS7le8XaaR6/Le\n" +
"2+E0y7RO9CpvjNKFFOs5zA1Uu7ZDQU4OKJc+scH/tFJia659ouYFpin4bYcP3S53\n" +
"QHXj/i4D90ygaOngbIkPjHlNZg6XZ/EhgVg99S1AXjVyVXY3J2knmTqRAoGAfuht\n" +
"yOJMMuBnPpnqB9ll2eFowxIeL8Zj9bSfjnBHzc8NB+cYb9WTFZfeHqw54ldiUPZf\n" +
"ypqNwqiApx2semZoj51rOcmjdyHbYWkCPWJa+Te/T008HhGVaQuC3svPT5cU84jD\n" +
"jYCmwKNuV6eeycwCJAOLyG9A5d/7qObMAaWYaQkCgYBge3oRaGlDujnn0UDwLppi\n" +
"ytsr4TDuDvkaRLCx4OrT86Yr+gWGjo46pBYIPJ8yy59yRTVYTpDiOLLC51qweh2w\n" +
"Ko6+/qtzNwp41jadphbpEgGRE+mpvUWtlj4yPkojSY83Gh36eWVGISDG95z1DDjj\n" +
"sGT8hwQgcQJHrQhWn/cltQ==\n" +
"-----END PRIVATE KEY-----";
private String mchId="1561414331";
private String mchSerialNo="4B3B3DC35414AD50B1B755BAF8DE9CC7CF407606";
private String apiV3Key="CZBK51236435wxpay435434323FFDuv3";
private String appid = "wxffb3637a228223b8";
1.2. 小程序支付流程
通过阅读官方提供的小程序支付流程图,我们实现该功能,需要做如下关键事情:
阶段1:支付
- 小程序向后台发送支付请求
- 后台系统生成订单,并且调用微信小程序下单接口,得到一个关键参数:预支付标识
- 根据预支付标识生成签名信息
- 将支付的签名信息返回给小程序
- 小程序根据商家后台返回的参数向微信官方发起小程序支付请求
阶段2:查询支付结果
6. 微信异步通知
7. 商户后台调用接口主动查询
1.3. 支付阶段
小程序支付阶段,我们主要研究3个核心点,分别是小程序下单、生成签名信息、小程序发起支付请求。接下来我们先来看小程序下单api
1.3.1. 小程序下单api
- 搭建开发环境
可以再官方文档处查阅资料,如下图所示
https://pay.weixin.qq.com/wiki/doc/apiv3/open/pay/chapter2_8_2.shtml
第一步:创建工程
第二步:添加依赖
<!-- springBoot父工程-->
<parent>
<artifactId>spring-boot-starter-parent</artifactId>
<groupId>org.springframework.boot</groupId>
<version>2.7.3</version>
</parent>
<dependencies>
<!-- 微信支付核心依赖-->
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-apache-httpclient</artifactId>
<version>0.4.9</version>
</dependency>
<!-- springBoot测试依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<!-- lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- json工具类-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.76</version>
</dependency>
</dependencies>
第三步:创建测试类,定义微信支付关键参数
package com.itheima.wxpay.test;
public class WxJsapiPayTest {
private String privateKey="-----BEGIN PRIVATE KEY-----\n" +
"MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQChXKhMDNcjxTBO\n" +
"FF2NOHai5NwChO12f3KQbmqCNEXWImqC5OtTEmcy/8Kzx8i243TtkVFv+Eqvac69\n" +
"/gpytl8sK8GFCzzUExgtejrtr3cJS//0eaJURGVG9zDPHrGV5vtXqmUPzAzX5SbM\n" +
"sIEKAZoe2bRJRz5zNNx+RMReSOC7F4xn+5tQd3XmZomwFGKO0oryKEocw5IGdYL6\n" +
"1zRkUK9yRtyDZKq7hvPv11YQgnD8+EeGgo5/0kk62sbWW545Hw1qVqkQjkOfbdtL\n" +
"Qjz4BnbakUwdRn3qoCjga8QQp0EKaGbIu4haHReyP8QQZhq0KlRX6XjBbrk8k4yK\n" +
"URVkjrxVAgMBAAECggEAT7HjrSvqVdBeOzHzwnvQnENMJjJg1dW8T3k5QXVvyILW\n" +
"+C73yt+b+KQ5FXHmv+03It5Sympm+JvZcBy4LE/GUZqKyZrzQAruNgfYcuvmbsEK\n" +
"eURZ2CSvoI0VnjYan16lZHbT0ymEblzO/Olv0fFYnUQItuho/51sCTENi0OTOtNt\n" +
"rWahbOnrBk+WsgmhNen+W/KdFXA9EOFKoFclAPEpCUa/mi1/w7AQecHZUISeRXix\n" +
"SZbhHIF4bf7BDXqkgtnWEkBX2hZP61TibGiRUgHvVC6V5+bi07cv7jr6IjJdNvwK\n" +
"MjAw2GgwZvyb8HvcQ0M7ULmYC6ZXYfVuvZDXJuMCAQKBgQDTAltDJ3oE1tJtk3qg\n" +
"ichuWf8OKlvNmN2fxiDG+rxOgTi2gL9N3BKIYWQRo3f2B9C243Z1iM+Q/4LPii6F\n" +
"iLTSPQYRcNaryB1BtNGjexHMK5NuCfZ2etx3zmYz86YiWY6ZsFuISFttdPq01MKR\n" +
"xN/gIfiXvRlv4g1SIK6vy3lpjQKBgQDDxF/Q2xNFZq8WZ4M1DW3r9EcnYgn6U8qK\n" +
"fDJFvW4VT9bHOJOxxmZ3oIccSuPky7gm9DlxRC632z1FyPu6buC7pp7X8+JJPmkq\n" +
"2c0fKYl5B5boJdBCs8QrRQ89oORFuB3/2on9N6YCMccf1VQ3kspm5GhEkO/mdN7u\n" +
"NYBRq1oX6QKBgAT77UPszskGefn0ndTk7EMPBSNEhN6aWcHIYEXS7le8XaaR6/Le\n" +
"2+E0y7RO9CpvjNKFFOs5zA1Uu7ZDQU4OKJc+scH/tFJia659ouYFpin4bYcP3S53\n" +
"QHXj/i4D90ygaOngbIkPjHlNZg6XZ/EhgVg99S1AXjVyVXY3J2knmTqRAoGAfuht\n" +
"yOJMMuBnPpnqB9ll2eFowxIeL8Zj9bSfjnBHzc8NB+cYb9WTFZfeHqw54ldiUPZf\n" +
"ypqNwqiApx2semZoj51rOcmjdyHbYWkCPWJa+Te/T008HhGVaQuC3svPT5cU84jD\n" +
"jYCmwKNuV6eeycwCJAOLyG9A5d/7qObMAaWYaQkCgYBge3oRaGlDujnn0UDwLppi\n" +
"ytsr4TDuDvkaRLCx4OrT86Yr+gWGjo46pBYIPJ8yy59yRTVYTpDiOLLC51qweh2w\n" +
"Ko6+/qtzNwp41jadphbpEgGRE+mpvUWtlj4yPkojSY83Gh36eWVGISDG95z1DDjj\n" +
"sGT8hwQgcQJHrQhWn/cltQ==\n" +
"-----END PRIVATE KEY-----";
private String mchId="1561414331";
private String mchSerialNo="4B3B3DC35414AD50B1B755BAF8DE9CC7CF407606";
private String apiV3Key="CZBK51236435wxpay435434323FFDuv3";
private String appid = "wxffb3637a228223b8";
}
第四步:创建微信支付核心对象(参考文档CV过来进行修改)
private CloseableHttpClient httpClient; //客服端,用于发送微信支付相关请求
@Before//方法执行前-初始化环境
public void setup() throws IOException {
// 加载商户私钥(privateKey:私钥字符串)
PrivateKey merchantPrivateKey = PemUtil
.loadPrivateKey(new ByteArrayInputStream(privateKey.getBytes("utf-8")));
// 加载平台证书(mchId:商户号,mchSerialNo:商户证书序列号,apiV3Key:V3密钥)
AutoUpdateCertificatesVerifier verifier = new AutoUpdateCertificatesVerifier(
new WechatPay2Credentials(mchId, new PrivateKeySigner(mchSerialNo, merchantPrivateKey)),apiV3Key.getBytes("utf-8"));
// 初始化httpClient
httpClient = WechatPayHttpClientBuilder.create()
.withMerchant(mchId, mchSerialNo, merchantPrivateKey)
.withValidator(new WechatPay2Validator(verifier)).build();
}
@After//执行后关闭链接
public void after() throws IOException {
httpClient.close();
}
- 小程序下单
接下来我们需要使用上面创建的核心对象来完成微信小程序下单api的开发,参考如下官方文档:
其中参数的详细介绍可以参考如下官方文档:
第一步:根据接口文档创建实体,方便封装小程序支付的参数,如下图所示:
Payer类
package com.itheima.wxpay.dto;
import lombok.Data;
@Data
@Builder
public class Payer {
private String openid; //微信用户的标识
}
Amount类:
package com.itheima.wxpay.dto;
import lombok.Data;
@Data
@Builder
public class Amount {
private Integer total; //订单金额,单位分
private String currency; //货币类型,CNY:人民币
}
JsapiPayDto类:
package com.itheima.wxpay.dto;
import lombok.Data;
@Data
@Builder
public class JsapiPayDto {
private String appid; //应用ID
private String mchid; //商户号
private String description; //商品描述
private String out_trade_no; //订单号
private String notify_url; //支付成功微信通知的回调地址
private Amount amount; //订单金额
private Payer payer; //支付者
}
第二步:修改官方的小程序接口api如下
@Test
public void CreateOrder() throws Exception{
//请求URL
HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi");
// 请求body参数
Payer payer = Payer.builder()
.openid("osvjO5VXi2ghcPu68RvVdwHWskjA")
.build();
Amount amount = Amount.builder()
.total(1)
.currency("CNY")
.build();
JsapiPayDto jsapiPayDto = JsapiPayDto.builder()
.appid(appid)
.mchid(mchId)
.description("传智鸿蒙4.0课程")
.out_trade_no("DAFDSJ34JFDASDFJ32FASFDS") //自定义即可,每次都需要唯一
.notify_url("http://localhost:8080/wxpay/notify") //暂时不管理,给上即可
.amount(amount)
.payer(payer)
.build();
String reqdata = JSON.toJSONString(jsapiPayDto);
StringEntity entity = new StringEntity(reqdata,"utf-8");
entity.setContentType("application/json");
httpPost.setEntity(entity);
httpPost.setHeader("Accept", "application/json");
//完成签名并执行请求
CloseableHttpResponse response = httpClient.execute(httpPost);
try {
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == 200) {
System.out.println("success,return body = " + EntityUtils.toString(response.getEntity()));
} else if (statusCode == 204) {
System.out.println("success");
} else {
System.out.println("failed,resp code = " + statusCode+ ",return body = " + EntityUtils.toString(response.getEntity()));
throw new IOException("request failed");
}
} finally {
response.close();
}
}
运行代码,查看控制台测试结果如下,对比官方结果文档:
1.3.2. 小程序发起支付请求
这部分内容由前端发起,不需要我们编写,但是此处我们需要简单的研究一下,参考如下官方接口
可以发现小程序发送请求时,还需要一些其他的参数,比如说paySign,这部分参数需要后端进行返回
1.3.2.1. 生成签名信息
参考如下签名生成的规则:
算法api参考如下官方文档:
来到如下页面:
第一步:修改官方工具类如下
package com.itheima.wxpay.utils;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import java.io.ByteArrayInputStream;
import java.security.PrivateKey;
import java.security.Signature;
import java.util.Base64;
public class SignUtils {
//私钥
private String privateKey="-----BEGIN PRIVATE KEY-----\n" +
"MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQChXKhMDNcjxTBO\n" +
"FF2NOHai5NwChO12f3KQbmqCNEXWImqC5OtTEmcy/8Kzx8i243TtkVFv+Eqvac69\n" +
"/gpytl8sK8GFCzzUExgtejrtr3cJS//0eaJURGVG9zDPHrGV5vtXqmUPzAzX5SbM\n" +
"sIEKAZoe2bRJRz5zNNx+RMReSOC7F4xn+5tQd3XmZomwFGKO0oryKEocw5IGdYL6\n" +
"1zRkUK9yRtyDZKq7hvPv11YQgnD8+EeGgo5/0kk62sbWW545Hw1qVqkQjkOfbdtL\n" +
"Qjz4BnbakUwdRn3qoCjga8QQp0EKaGbIu4haHReyP8QQZhq0KlRX6XjBbrk8k4yK\n" +
"URVkjrxVAgMBAAECggEAT7HjrSvqVdBeOzHzwnvQnENMJjJg1dW8T3k5QXVvyILW\n" +
"+C73yt+b+KQ5FXHmv+03It5Sympm+JvZcBy4LE/GUZqKyZrzQAruNgfYcuvmbsEK\n" +
"eURZ2CSvoI0VnjYan16lZHbT0ymEblzO/Olv0fFYnUQItuho/51sCTENi0OTOtNt\n" +
"rWahbOnrBk+WsgmhNen+W/KdFXA9EOFKoFclAPEpCUa/mi1/w7AQecHZUISeRXix\n" +
"SZbhHIF4bf7BDXqkgtnWEkBX2hZP61TibGiRUgHvVC6V5+bi07cv7jr6IjJdNvwK\n" +
"MjAw2GgwZvyb8HvcQ0M7ULmYC6ZXYfVuvZDXJuMCAQKBgQDTAltDJ3oE1tJtk3qg\n" +
"ichuWf8OKlvNmN2fxiDG+rxOgTi2gL9N3BKIYWQRo3f2B9C243Z1iM+Q/4LPii6F\n" +
"iLTSPQYRcNaryB1BtNGjexHMK5NuCfZ2etx3zmYz86YiWY6ZsFuISFttdPq01MKR\n" +
"xN/gIfiXvRlv4g1SIK6vy3lpjQKBgQDDxF/Q2xNFZq8WZ4M1DW3r9EcnYgn6U8qK\n" +
"fDJFvW4VT9bHOJOxxmZ3oIccSuPky7gm9DlxRC632z1FyPu6buC7pp7X8+JJPmkq\n" +
"2c0fKYl5B5boJdBCs8QrRQ89oORFuB3/2on9N6YCMccf1VQ3kspm5GhEkO/mdN7u\n" +
"NYBRq1oX6QKBgAT77UPszskGefn0ndTk7EMPBSNEhN6aWcHIYEXS7le8XaaR6/Le\n" +
"2+E0y7RO9CpvjNKFFOs5zA1Uu7ZDQU4OKJc+scH/tFJia659ouYFpin4bYcP3S53\n" +
"QHXj/i4D90ygaOngbIkPjHlNZg6XZ/EhgVg99S1AXjVyVXY3J2knmTqRAoGAfuht\n" +
"yOJMMuBnPpnqB9ll2eFowxIeL8Zj9bSfjnBHzc8NB+cYb9WTFZfeHqw54ldiUPZf\n" +
"ypqNwqiApx2semZoj51rOcmjdyHbYWkCPWJa+Te/T008HhGVaQuC3svPT5cU84jD\n" +
"jYCmwKNuV6eeycwCJAOLyG9A5d/7qObMAaWYaQkCgYBge3oRaGlDujnn0UDwLppi\n" +
"ytsr4TDuDvkaRLCx4OrT86Yr+gWGjo46pBYIPJ8yy59yRTVYTpDiOLLC51qweh2w\n" +
"Ko6+/qtzNwp41jadphbpEgGRE+mpvUWtlj4yPkojSY83Gh36eWVGISDG95z1DDjj\n" +
"sGT8hwQgcQJHrQhWn/cltQ==\n" +
"-----END PRIVATE KEY-----";
public String sign(byte[] message) throws Exception {
Signature sign = Signature.getInstance("SHA256withRSA");
PrivateKey merchantPrivateKey = PemUtil
.loadPrivateKey(new ByteArrayInputStream(privateKey.getBytes("utf-8")));
sign.initSign(merchantPrivateKey);//这里需要一个PrivateKey,我们将httpClient相关代码CV过来即可
sign.update(message);
return Base64.getEncoder().encodeToString(sign.sign());
}
public String buildMessage(String appId, long timestamp, String nonceStr, String packageStr) {
return appId + "\n"
+ timestamp + "\n"
+ nonceStr + "\n"
+ packageStr + "\n";
}
}
第二步:编写测试方法
@Test
public void signTest() throws Exception{
SignUtils signUtils = new SignUtils();
String nonceStr = UUID.randomUUID().toString().replaceAll("-", "");
String packageStr = "prepay_id=wx161553341632234546f39649833f920000";
long timestamp = System.currentTimeMillis();
//构建签名体
String message = signUtils.buildMessage(appid, timestamp, nonceStr, packageStr);
//签名
String sign = signUtils.sign(message.getBytes(StandardCharsets.UTF_8));
System.out.println("签名信息:"+sign);
}
第三步:执行测试方法,查询控制台测试结果,对比官方文档
后台能够计算得到签名,那么我们就能够给到前端小程序调起支付所需要的所有的接口参数了。小程序支付的支付demo到此结束。
1.4. 查询支付结果阶段
因见扫码支付中
1.4.1. 微信主动通知
1.4.2. 商户后台调用api查询
虽然微信提供了异步调用通知商户后台支付的结果,但是考虑到网络不稳定等问题,我们还需要提供商户主动查询的接口来保障一定能够得到用户支付的结果,此处的api参考官方文档:
接口的详细说明可以参考如下官方文档
修改官方测试类如下:
@Test
public void QueryOrder() throws Exception {
//请求URL
String outTradeNo = "DAFDSJ34JFDASDFJ32FASFDS";
URIBuilder uriBuilder = new URIBuilder("https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/"+outTradeNo);
uriBuilder.setParameter("mchid", mchId);
//完成签名并执行请求
HttpGet httpGet = new HttpGet(uriBuilder.build());
httpGet.addHeader("Accept", "application/json");
CloseableHttpResponse response = httpClient.execute(httpGet);
try {
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == 200) {
System.out.println("success,return body = " + EntityUtils.toString(response.getEntity()));
} else if (statusCode == 204) {
System.out.println("success");
} else {
System.out.println("failed,resp code = " + statusCode+ ",return body = " + EntityUtils.toString(response.getEntity()));
throw new IOException("request failed");
}
} finally {
response.close();
}
}
PS:此处是用的根据商户的单号进行查询,请注意接口路径和接口参数
执行测试,运行结果如下:
2. 扫码支付
2.1. 微信扫码支付介绍
参考 https://pay.weixin.qq.com/static/product/product_intro.shtml?name=native
2.2 微信扫码支付流程
总结:
- 支付阶端
a. 调用下单接口,获取code_url
b. 根据code_url生成二维码 - 获取订单的支付状态阶段
2.3. 支付阶段
2.3.1. 扫码下单api
因为支付环境配置我们之前已经写完了,所以此处直接参考扫码支付的官方文档即可,如下图所示:
详细的下单参数参考如下官方如下接口
第一步:参考接口文档,定义参数实体
package com.itheima.pojo;
import lombok.Builder;
import lombok.Data;
@Builder
@Data
public class NativePayDto {
private String appid; // 应用id
private String mchid; // 商户id
private String description; //商品描述
private String out_trade_no; //订单号
private String notify_url; // 支付成功回调通知地址
private Amount amount; //订单金额信息
}
第二步:创建新的测试类,拷贝之前的配置参数即可
package com.itheima.wxpay.test;
import com.alibaba.fastjson.JSON;
import com.itheima.wxpay.dto.Amount;
import com.itheima.wxpay.dto.NativePayDto;
import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
import com.wechat.pay.contrib.apache.httpclient.auth.AutoUpdateCertificatesVerifier;
import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Credentials;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Validator;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.security.PrivateKey;
public class WxNativePayTest {
//私钥
private String privateKey="-----BEGIN PRIVATE KEY-----\n" +
"MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQChXKhMDNcjxTBO\n" +
"FF2NOHai5NwChO12f3KQbmqCNEXWImqC5OtTEmcy/8Kzx8i243TtkVFv+Eqvac69\n" +
"/gpytl8sK8GFCzzUExgtejrtr3cJS//0eaJURGVG9zDPHrGV5vtXqmUPzAzX5SbM\n" +
"sIEKAZoe2bRJRz5zNNx+RMReSOC7F4xn+5tQd3XmZomwFGKO0oryKEocw5IGdYL6\n" +
"1zRkUK9yRtyDZKq7hvPv11YQgnD8+EeGgo5/0kk62sbWW545Hw1qVqkQjkOfbdtL\n" +
"Qjz4BnbakUwdRn3qoCjga8QQp0EKaGbIu4haHReyP8QQZhq0KlRX6XjBbrk8k4yK\n" +
"URVkjrxVAgMBAAECggEAT7HjrSvqVdBeOzHzwnvQnENMJjJg1dW8T3k5QXVvyILW\n" +
"+C73yt+b+KQ5FXHmv+03It5Sympm+JvZcBy4LE/GUZqKyZrzQAruNgfYcuvmbsEK\n" +
"eURZ2CSvoI0VnjYan16lZHbT0ymEblzO/Olv0fFYnUQItuho/51sCTENi0OTOtNt\n" +
"rWahbOnrBk+WsgmhNen+W/KdFXA9EOFKoFclAPEpCUa/mi1/w7AQecHZUISeRXix\n" +
"SZbhHIF4bf7BDXqkgtnWEkBX2hZP61TibGiRUgHvVC6V5+bi07cv7jr6IjJdNvwK\n" +
"MjAw2GgwZvyb8HvcQ0M7ULmYC6ZXYfVuvZDXJuMCAQKBgQDTAltDJ3oE1tJtk3qg\n" +
"ichuWf8OKlvNmN2fxiDG+rxOgTi2gL9N3BKIYWQRo3f2B9C243Z1iM+Q/4LPii6F\n" +
"iLTSPQYRcNaryB1BtNGjexHMK5NuCfZ2etx3zmYz86YiWY6ZsFuISFttdPq01MKR\n" +
"xN/gIfiXvRlv4g1SIK6vy3lpjQKBgQDDxF/Q2xNFZq8WZ4M1DW3r9EcnYgn6U8qK\n" +
"fDJFvW4VT9bHOJOxxmZ3oIccSuPky7gm9DlxRC632z1FyPu6buC7pp7X8+JJPmkq\n" +
"2c0fKYl5B5boJdBCs8QrRQ89oORFuB3/2on9N6YCMccf1VQ3kspm5GhEkO/mdN7u\n" +
"NYBRq1oX6QKBgAT77UPszskGefn0ndTk7EMPBSNEhN6aWcHIYEXS7le8XaaR6/Le\n" +
"2+E0y7RO9CpvjNKFFOs5zA1Uu7ZDQU4OKJc+scH/tFJia659ouYFpin4bYcP3S53\n" +
"QHXj/i4D90ygaOngbIkPjHlNZg6XZ/EhgVg99S1AXjVyVXY3J2knmTqRAoGAfuht\n" +
"yOJMMuBnPpnqB9ll2eFowxIeL8Zj9bSfjnBHzc8NB+cYb9WTFZfeHqw54ldiUPZf\n" +
"ypqNwqiApx2semZoj51rOcmjdyHbYWkCPWJa+Te/T008HhGVaQuC3svPT5cU84jD\n" +
"jYCmwKNuV6eeycwCJAOLyG9A5d/7qObMAaWYaQkCgYBge3oRaGlDujnn0UDwLppi\n" +
"ytsr4TDuDvkaRLCx4OrT86Yr+gWGjo46pBYIPJ8yy59yRTVYTpDiOLLC51qweh2w\n" +
"Ko6+/qtzNwp41jadphbpEgGRE+mpvUWtlj4yPkojSY83Gh36eWVGISDG95z1DDjj\n" +
"sGT8hwQgcQJHrQhWn/cltQ==\n" +
"-----END PRIVATE KEY-----";
//商户id
private String mchId="1561414331";
//商户序列号
private String mchSerialNo="4B3B3DC35414AD50B1B755BAF8DE9CC7CF407606";
//apiV3Key
private String apiV3Key="CZBK51236435wxpay435434323FFDuv3";
//小程序id
private String appid = "wxffb3637a228223b8";
private CloseableHttpClient httpClient;
@Before
public void setup() throws IOException {
// 加载商户私钥(privateKey:私钥字符串)
PrivateKey merchantPrivateKey = PemUtil
.loadPrivateKey(new ByteArrayInputStream(privateKey.getBytes("utf-8")));
// 加载平台证书(mchId:商户号,mchSerialNo:商户证书序列号,apiV3Key:V3密钥)
AutoUpdateCertificatesVerifier verifier = new AutoUpdateCertificatesVerifier(
new WechatPay2Credentials(mchId, new PrivateKeySigner(mchSerialNo, merchantPrivateKey)),apiV3Key.getBytes("utf-8"));
// 初始化httpClient
httpClient = WechatPayHttpClientBuilder.create()
.withMerchant(mchId, mchSerialNo, merchantPrivateKey)
.withValidator(new WechatPay2Validator(verifier)).build();
}
@After
public void after() throws IOException {
httpClient.close();
}
}
第三步:修改官方提供的代码,如下:
@Test
public void testNativePay() throws Exception{
HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/pay/transactions/native");
// 请求body参数
Amount amount = Amount.builder().currency("CNY").total(1).build();
NativePayDto nativePayDto = NativePayDto.builder().appid(appid)
.description("百世可乐")
.mchid(mchId)
.notify_url("http://localhost:8080")
.out_trade_no("AEFA234DAFD2342342FRADFAS1")//每次请求都需要唯一
.amount(amount)
.build();
String reqdata = JSON.toJSONString(nativePayDto);
StringEntity entity = new StringEntity(reqdata,"utf-8");
entity.setContentType("application/json");
httpPost.setEntity(entity);
httpPost.setHeader("Accept", "application/json");
//完成签名并执行请求
CloseableHttpResponse response = httpClient.execute(httpPost);
try {
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == 200) { //处理成功
System.out.println("success,return body = " + EntityUtils.toString(response.getEntity()));
} else if (statusCode == 204) { //处理成功,无返回Body
System.out.println("success");
} else {
System.out.println("failed,resp code = " + statusCode+ ",return body = " + EntityUtils.toString(response.getEntity()));
throw new IOException("request failed");
}
} finally {
response.close();
}
}
执行测试,查看控制台执行结果,并且对照官方接口文档:
2.3.2. 生成二维码
复制上面的url即:weixin://wxpay/bizpayurl?pr=p4lpSukzz,随便找个二维码生成的网址生成即可
https://cli.im/
可以调用之前的查单接口,查询支付结果
2.4 获取订单的支付状态阶段
详细查阅微信 https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_4_5.shtml
展开
核心思路分析
- 编写接口,接受微信的支付成功通知
- 支付通知的内容需要解密
- 本地测试的ip不是公网ip,外部无法访问,需要域名穿透
- 微信主动通知的地址是通过下单接口中的请求参数“notify_url”来设置的 ,要求必须为https地址 。
2.4.1.1. 代码实现
根据微信官方的接口文档定义实体,接收参数
ResourceDto类:
package com.itheima.pojo;
import lombok.Data;
@Data
public class ResourceDto {
private String algorithm;
private String ciphertext;
private String associated_data;
private String original_type;
private String nonce;
}
NotifyDto类
package com.itheima.pojo;
import lombok.Data;
@Data
public class NotifyDto {
private String id;
private String create_time;
private String event_type;
private String resource_type;
private ResourceDto resource;
private String summary;
}
定义controller接口:
package com.itheima.controller;
import com.itheima.pojo.NotifyDto;
import com.itheima.service.NativePayService;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/native")
@Log4j2
public class NativePayController {
@Autowired
private NativePayService nativePayService;
@PostMapping("/notify")
public Map<String,String> nativeNotify(@RequestBody NotifyDto dto){
System.out.println("--微信回调--");
Map<String ,String> map = null;
try {
nativePayService.notify(dto);
} catch (Exception e) {
e.printStackTrace();
map = new HashMap<>();
map.put("code","FAIL");
map.put("message","失败");
}
return map;
}
}
定义service接口:
此处的解密参考 https://pay.weixin.qq.com/docs/merchant/development/interface-rules/certificate-callback-decryption.html
package com.itheima.wxpay.service.impl;
import com.alibaba.fastjson.JSON;
import com.itheima.wxpay.dto.NotifyDto;
import com.itheima.wxpay.service.NotifyService;
import com.wechat.pay.contrib.apache.httpclient.util.AesUtil;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.Map;
@Service
public class NotifyServiceImpl implements NotifyService {
private String apiV3Key ="CZBK51236435wxpay435434323FFDuv3";
@Override
public void notify(NotifyDto dto) {
String jsonStr = null;
try {
jsonStr = new AesUtil(apiV3Key.getBytes())
.decryptToString(dto.getResource().getAssociated_data().getBytes(),
dto.getResource().getNonce().getBytes(),
dto.getResource().getCiphertext());
} catch (GeneralSecurityException e) {
e.printStackTrace();
throw new RuntimeException("解析结果出错");
}
System.out.println("-----------微信主动通知支付结果------"+jsonStr);
Map map = JSON.parseObject(jsonStr, Map.class);
String outTradeNo = map.get("out_trade_no").toString();
System.out.println("订单"+outTradeNo+"支付成功");
}
}
2.4.1.2. 域名穿透(cpolar)
本地电脑的ip是局域网ip,外界无法访问。此处我们只能使用域名穿透的一些软件。此处介绍cpolar的使用
**第一步:**找到资料中的cpolar软件,双击安装即可。
**第二步:**在官方注册账号
https://dashboard.cpolar.com/signup
**第三步:**来到验证标签页,复制Authtoken
**第四步:**启动cpolar
**第五步:**输入命令 cpolar authtoken 你的隧道
**第六步:**输入命令cpolar http 8080,映射本地的8080端口
**第七步:**使用生成的域名,代替原来的http://localhost:8080使用即可
2.4.1.2. 域名穿透(花生壳)
此处个人使用的是花生壳(收费)
然后再下单的代码中修改notify_url的地址为域名穿透地址/资源路径,代码如下:
2.4.1.3 测试
此时只需要再次支付即可,对比解析后的订单号和支付的订单号:
2.4.2. 主动查询支付结果
在小程序支付中已经调用过,此处不做演示
3. 退款
3.1. api介绍
详细参考 https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_4_9.shtml
3.2 代码实现
根据官方接口文档定义实体对象方便参数传递
退款金额对象RefundAmount
package com.itheima.pojo;
import lombok.Builder;
import lombok.Data;
@Data
@Builder
public class RefundAmount {
private int refund;// 退款金额
private int total; //原支付交易的订单总金额
private String currency;
}
退款请求对象RefundDto
package com.itheima.wxpay.dto;
import lombok.Builder;
import lombok.Data;
@Data
@Builder
public class RefundDto {
private String out_trade_no; //支付交易对应的商户订单号
private String out_refund_no; //商户系统内部的退款单号
private RefundAmount amount;
}
参考官方文档代码—CV改(复制粘贴修改)
@Test
public void testRefund() throws Exception{
HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/refund/domestic/refunds");
// 请求body参数
RefundAmount refundAmount = RefundAmount.builder()
.refund(1)
.total(1)
.currency("CNY")
.build();
RefundDto refundDto = RefundDto.builder()
.amount(refundAmount)
.out_refund_no("AEFA234DAFD2342342FRADFAS41") //商家后台自定义退款单号
.out_trade_no("AEFA234DAFD2342342FRADFAS4") //订单号
.build();
String reqdata = JSON.toJSONString(refundDto);
StringEntity entity = new StringEntity(reqdata,"utf-8");
entity.setContentType("application/json");
httpPost.setEntity(entity);
httpPost.setHeader("Accept", "application/json");
//完成签名并执行请求
CloseableHttpResponse response = httpClient.execute(httpPost);
try {
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == 200) { //处理成功
System.out.println("success,return body = " + EntityUtils.toString(response.getEntity()));
} else if (statusCode == 204) { //处理成功,无返回Body
System.out.println("success");
} else {
System.out.println("failed,resp code = " + statusCode+ ",return body = " + EntityUtils.toString(response.getEntity()));
throw new IOException("request failed");
}
} finally {
response.close();
}
}
3.3. 测试
idea控制台结果如下:
微信收到退款通知
将结果使用三方json工具解析:如下图所示
4. 关闭订单
有些业务中会有类似这种15分钟超时未支付则不允许支付的情况,此时我们就需要调用微信的接口来关闭订单。
4.1. api介绍
此处参考官方接口文档 https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_1_3.shtml
4.2. 代码实现(CV改)
@Test
public void testClose() throws Exception{
String our_trade_no = "AEFA234DAFD2342342FRADFAS5";
String url = "https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/"+our_trade_no+"/close";
HttpPost httpPost = new HttpPost(url);
// 请求body参数
CloseDto closeDto = CloseDto.builder()
.mchid(mchId)
.build();
String reqdata = JSON.toJSONString(closeDto);
StringEntity entity = new StringEntity(reqdata,"utf-8");
entity.setContentType("application/json");
httpPost.setEntity(entity);
httpPost.setHeader("Accept", "application/json");
//完成签名并执行请求
CloseableHttpResponse response = httpClient.execute(httpPost);
try {
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == 200) { //处理成功
System.out.println("success,return body = " + EntityUtils.toString(response.getEntity()));
} else if (statusCode == 204) { //处理成功,无返回Body
System.out.println("success");
} else {
System.out.println("failed,resp code = " + statusCode+ ",return body = " + EntityUtils.toString(response.getEntity()));
throw new IOException("request failed");
}
} finally {
response.close();
}
}
4.3 测试
测试思路:
调用下单接口–> 查询订单–>调用关闭订单接口–>查询订单
5. SpringBoot中wx_pay的封装
5.1. springBoot自动化配置原理
基于启动类上的注解@SpringBootApplication中的@EnableAutoConfiguration来开启自动化配置,这个注解通过@Import来导入了配置类,如下图所示:
上述配置类会加载所有jar包以及源码下的META-INF下得spring.factories文件,如下图所示:
然后会加载配置中指定的自动化配置key所对应的所有自动化配置类,示例如下图所示:
但是自动化配置类并不是都需要加载的,springBoot通过自动化配置类上添加的各种条件注解@Conditional来判断这个自动化配置类是否满足条件,如果满足条件,即加载配置类,如下图webmvc自动化配置类示例:
5.2. 版本变化
注意springBoot自动化配置有如下的变化
5.3. 微信扫码支付starter封装
- 流程分析–逆向思维
- 我们最终需要实现的目的 -->引入依赖,yaml中做好配置,直接依赖注入xxxTemplate能够直接使用—>需要创建一个xxxTemplate对象—> 提供一个xxxTemplate类,封装扫码支付的api,一些环境级别的参数做成配置–> 提供xxxProperties配置类,然后加载yaml中的配置 -> 然后通过配置类创建对象 -> springBoot自动化配置的原理去加载配置类即可
- 最终实现步骤整理
a. 创建工程,引入相关依赖
b. 提供WxPayProperties配置类,用于加载application.yaml中的环境配置
b. 提供WxPayTemplate类,封装扫码支付的api,环境配置依赖注入得到
c. 提供配置类,启动WxPayProperties类对象,创建WxPayTemplate对象
d. 提供META-INF/spring.factory配置类,配置自动化配置
e. 创建新测试工程,测试starter使用
代码实现
- 创建工程,引入相关依赖
<dependencies>
<!--用于在用户编写yml文件时,提供配置的提示-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
- 提供WxPayProperties配置类,用于加载application.yaml中的环境配置
package com.hxc.pay.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Data
@ConfigurationProperties(prefix = "pay.wx")
public class WxPayProperties {
private String privateKeyFilePath;
//私钥
private String privateKey;
//商户id
private String mchId;
//商户序列号
private String mchSerialNo;
//apiV3Key
private String apiV3Key;
//小程序id
private String appid;
//支付成功回调地址
private String notifyUrl;
}
- 提供WxPayTemplate类,封装扫码支付的api,环境配置依赖注入得到
2个参数实体类:
package com.hxc.pay.dto;
import lombok.Builder;
import lombok.Data;
@Builder
@Data
public class NativePayDto {
private String appid; // 应用id
private String mchid; // 商户id
private String description; //商品描述
private String out_trade_no; //订单号
private String notify_url; // 支付成功回调通知地址
private Amount amount; //订单金额信息
}
package com.hxc.pay.dto;
import lombok.Builder;
import lombok.Data;
@Data
@Builder
public class Amount {
private Integer total; //订单金额,单位分
private String currency; //货币类型,CNY:人民币
}
- 工具类:
package com.hxc.pay.core;
import com.alibaba.fastjson.JSON;
import com.hxc.pay.config.WxPayProperties;
import com.hxc.pay.dto.Amount;
import com.hxc.pay.dto.NativePayDto;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import java.io.IOException;
import java.util.Map;
public class NativePayTemplate {
private WxPayProperties wxPayProperties;
private CloseableHttpClient httpClient;
public NativePayTemplate(WxPayProperties wxPayProperties, CloseableHttpClient httpClient) {
this.wxPayProperties = wxPayProperties;
this.httpClient = httpClient;
}
//扫码支付下单接口
public String navitePay(Integer total,String description,String outTradeNo){
String code_url = null;
try {
HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/pay/transactions/native");
// 请求body参数
Amount amount = Amount.builder().currency("CNY").total(total).build();
NativePayDto nativePayDto = NativePayDto.builder().appid(wxPayProperties.getAppid())
.description(description)
.mchid(wxPayProperties.getMchId())
.notify_url(wxPayProperties.getNotifyUrl())
.out_trade_no(outTradeNo)
.amount(amount)
.build();
String reqdata = JSON.toJSONString(nativePayDto);
StringEntity entity = new StringEntity(reqdata,"utf-8");
entity.setContentType("application/json");
httpPost.setEntity(entity);
httpPost.setHeader("Accept", "application/json");
//完成签名并执行请求
CloseableHttpResponse response = httpClient.execute(httpPost);
code_url = "";
try {
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == 200) { //处理成功
//System.out.println("success,return body = " + EntityUtils.toString(response.getEntity()));
Map map = JSON.parseObject(EntityUtils.toString(response.getEntity()), Map.class);
code_url = map.get("code_url").toString();
} else if (statusCode == 204) { //处理成功,无返回Body
System.out.println("success");
} else {
System.out.println("failed,resp code = " + statusCode+ ",return body = " + EntityUtils.toString(response.getEntity()));
}
} finally {
response.close();
}
} catch (IOException e) {
e.printStackTrace();
}
return code_url;
}
}
- 提供配置类,启动WxPayProperties类对象,创建WxPayTemplate对象
package com.hxc.pay.config;
import com.hxc.pay.core.NativePayTemplate;
import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
import com.wechat.pay.contrib.apache.httpclient.auth.AutoUpdateCertificatesVerifier;
import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Credentials;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Validator;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import org.apache.http.impl.client.CloseableHttpClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.io.*;
import java.security.PrivateKey;
@Configuration
@EnableConfigurationProperties(WxPayProperties.class)//该注解用于加载指定类
public class WxPayAutoConfiguration {
@Bean//加载CloseableHttpClient
public CloseableHttpClient httpClient(WxPayProperties wxPayProperties){
// 加载商户私钥(privateKey:私钥字符串)
// PrivateKey merchantPrivateKey = PemUtil
// .loadPrivateKey(new ByteArrayInputStream(privateKey.getBytes("utf-8")));
CloseableHttpClient httpClient = null;
try {
PrivateKey merchantPrivateKey=
PemUtil.loadPrivateKey(new FileInputStream(new File(wxPayProperties.getPrivateKeyFilePath())));
// 加载平台证书(mchId:商户号,mchSerialNo:商户证书序列号,apiV3Key:V3密钥)
AutoUpdateCertificatesVerifier verifier = new AutoUpdateCertificatesVerifier(
new WechatPay2Credentials(wxPayProperties.getMchId(), new PrivateKeySigner(wxPayProperties.getMchSerialNo(), merchantPrivateKey)),wxPayProperties.getApiV3Key().getBytes("utf-8"));
// 初始化httpClient
httpClient = WechatPayHttpClientBuilder.create()
.withMerchant(wxPayProperties.getMchId(), wxPayProperties.getMchSerialNo(), merchantPrivateKey)
.withValidator(new WechatPay2Validator(verifier)).build();
} catch (Exception e) {
e.printStackTrace();
}
return httpClient;
}
@Bean//创建工具类bean
public NativePayTemplate nativePayTemplate(WxPayProperties wxPayProperties,CloseableHttpClient httpClient){
return new NativePayTemplate(wxPayProperties,httpClient);
}
}
- 提供META-INF/spring.factory配置类,配置自动化配置
com.hxc.pay.config.WxPayAutoConfiguration
三、微信支付
这里提供了封装好的微信支付相关接口的start,不依赖任何第三方框架,供参考学习
GitCode地址:https://gitcode.com/qq_49474843/wx_pay.git