文章目录
- 前提
- 整体介绍
- 我的maven依赖
- 1、整体流程
- 2、openid 的获取
- 3、统一下单Controller(预支付订单)
- 4、配置类和配置文件
- 5、工具类
- 6、前端接收到必要的参数,进行调起支付页面
- 7、微信支付通知,notify_url的回调Controller
- 8、前端小程序端,定时器调用查询订单状态
- 9、后端提供给小程序查询订单状态的接口
- 10、用户取消订单
- 11、商户端迟迟未收到异步通知结果
- 12、申请退款
- 13、退款回调通知
- 欢迎+Q群讨论:821596752
前提
在进行JSAPI微信支付之前,需要准备好一下配置
申请小程序的appid:wxaxxxxxxxxxxbxx8a (类似这样的)
申请商户号:1xxxxxxxxx6
小程序开通微信支付,绑定已经申请好的商户号。登录小程序后台(mp.weixin.qq.com)。点击左侧导航栏的微信支付,在页面中进行开通。(
注意:以上信息的申请都需要使用企业账户,个人账户不行
商户号官网地址:pay.weixin.qq.com
小程序官网地址: mp.weixin.qq.com
- 需要在商户端(pay.weixin.qq.com),api安全配置好apiv3的密钥
整体介绍
博主这篇博客,主要是小程序对接微信支付(JSAPI)
后端:spring boot
前端:微信小程序,uinapp
适用人群:已经申请好所有的资料,小程序平台,微信商户平台等等,本文不提供任何资料。并且需要有自己的业务场景,部分代码无法直接运行,需要加入自己的订单结构
我的maven依赖
<dependencies>
<dependency>
<groupId>org.jdom</groupId>
<artifactId>jdom2</artifactId>
<version>2.0.6.1</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</dependency>
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-apache-httpclient</artifactId>
<version>0.4.7</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.25</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.1</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.7.0</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.7.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
1、整体流程
支付的流程图:
1-1、如上图,第2点请求下单,访问我们自己的后端接口所需的参数
字段名 | 变量名 | 类型 | 必填 | 示例值 | 描述 |
---|---|---|---|---|---|
应用ID | appid | string[1,32] | 是 | wxd678efh567hg6787 | 由微信生成的应用ID,全局唯一。请求基础下单接口时请注意APPID的应用属性,例如公众号场景下,需使用应用属性为公众号的服务号APPID |
直连商户号 | mchid | string[1,32] | 是 | 1230000109 | 直连商户的商户号,由微信支付生成并下发。 |
商品描述 | description | string[1,127] | 是 | Image形象店-深圳腾大-QQ公仔 | 商品描述 |
商户订单号 | out_trade_no | string[6,32] | 是 | 1217752501201407033233368018 | 商户系统内部订单号,只能是数字、大小写字母_-*且在同一个商户号下唯一 |
通知地址 | notify_url | string[1,256] | 是 | 可以先随便写一个不存的地址都行,不影响正常支付,但是获取不到支付结果信息,无法进行修改订单状态 | 异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数。 公网域名必须为https,如果是走专线接入,使用专线NAT IP或者私有回调域名可使用http |
订单金额 | amount | object | 是 | HashMap | 订单金额信息,他需要一个map,需要进行一层嵌套,可以去参考官网https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_5_1.shtml |
支付者 | payer | object | 是 | //支付者 HashMap | 支付者信息,用户在直连商户appid下的唯一标识。 下单前需获取到用户的Openid |
以上参数需要注意,openid的获取
2、openid 的获取
需要在小程序端调用wx.login获取临时登陆凭证在获取openid
wx.login({
success (res) {
if (res.code) {
//发起网络请求
wx.request({
url: 'https://example.com/onLogin',
data: {
code: res.code
}
})
} else {
console.log('登录失败!' + res.errMsg)
}
}
})
通过上面这个方法获取到res.code然后我们自己在编写一个后端接口,去获取openid
下面这个是我自己写的controller
@Resource
private WxPayConfig wxPayConfig;//这个是一个wx的配置类
@Resource
private CloseableHttpClient wxPayClient;//配置类中的一个bean
@GetMapping("/onLogin")
public string onLogin(HttpServletRequest request){
String js_code = request.getParameter("code");//前端发起请求携带上面获取到的code,后端接收
//app Secret是小程序密钥(在mp.weixin.qq.com中的开发管理-》开发设置-》AppSecret(小程序密钥)中设置)
String baseUrl="https://api.weixin.qq.com/sns/jscode2session?appid="+wxPayConfig.getAppid()+"&secret="+wxPayConfig.getAppSecret()
+"&js_code="+js_code+"&grant_type=authorization_code";
String res=null;
try {
//这里发起请求获取到session-key,和openid
res = requestByGetMethod(baseUrl).split("/n")[0];
System.out.println(res);
} catch (Exception e) {
e.printStackTrace();
}
log.info("res:"+res);
return res;//返回给前端
}
//这个方法就是用于发起get请求的
/**
* 模拟发送url Get 请求
* @param url
* @return
*/
public String requestByGetMethod(String url) {
log.info("发起get请求");
CloseableHttpClient httpClient = HttpClients.createDefault();
StringBuilder entityStringBuilder = null;
try {
HttpGet get = new HttpGet(url);
CloseableHttpResponse httpResponse = null;
httpResponse = httpClient.execute(get);
try {
HttpEntity entity = httpResponse.getEntity();
entityStringBuilder = new StringBuilder();
if (null != entity) {
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(httpResponse.getEntity().getContent(), "UTF-8"), 8 * 1024);
String line = null;
while ((line = bufferedReader.readLine()) != null) {
entityStringBuilder.append(line + "/n");
}
}
} finally {
httpResponse.close();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (httpClient != null) {
httpClient.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return entityStringBuilder.toString();
}
3、统一下单Controller(预支付订单)
调用下单接口,返回prepay_id等信息提供给前端,供前端调起支付页面,这里也对应官方图的第二点下单请求
注意:在请求中你需要携带一下参数,具体需要的参数可以看1-1的表格
@Resource
private WxPayConfig wxPayConfig;
@Resource
private CloseableHttpClient wxPayClient;
@Resource
private Verifier verifier;
@ResponseBody
@RequestMapping("returnparam")
public HashMap<String, String> doOrder(HttpServletRequest request, HttpServletResponse response) throws Exception{
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
//得到openid(微信用户唯一的openid)
String openid = request.getParameter("openid");
//得到价钱(自定义)
int fee = 0;//单位是分
if (null != request.getParameter("price")) {
fee = Integer.parseInt(request.getParameter("price").toString());
}
//得到商品的ID(自定义)
String goodsid=request.getParameter("goodsid");
//订单标题(自定义)
String title = request.getParameter("title");
//时间戳,
String times = System.currentTimeMillis() + "";
//订单编号(自定义 这里以时间戳+随机数)
Random random = new Random();
String did = times+random.nextInt(1000);
log.info("生成订单");
//调用统一下单API
HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi");
// 请求body参数
Gson gson = new Gson();
HashMap<Object, Object> paramsMap = new HashMap<>();
paramsMap.put("appid",wxPayConfig.getAppid());//appid
paramsMap.put("mchid",wxPayConfig.getMchId());//商户号
paramsMap.put("description",title);//商品描述
paramsMap.put("out_trade_no",did);//商户订单号
paramsMap.put("notify_url","http://d4a93w.natappfree.cc/wxBuy");//通知地址,可随便写,如果不需要通知的话,不影响支付,但是影响后续修改订单状态
//订单金额
HashMap<Object, Object> amountMap = new HashMap<>();
amountMap.put("total",fee);//金额
amountMap.put("currency","CNY");//货币类型
paramsMap.put("amount",amountMap);
//支付者
HashMap<Object, Object> playerMap = new HashMap<>();
playerMap.put("openid",openid);
paramsMap.put("payer",playerMap);
//将参数转化未json字符串
String jsonParamsMap = gson.toJson(paramsMap);
log.info("请求参数:"+jsonParamsMap);
StringEntity entity = new StringEntity(jsonParamsMap,"utf-8");
entity.setContentType("application/json");
httpPost.setEntity(entity);
httpPost.setHeader("Accept", "application/json");
//完成签名并执行请求
CloseableHttpResponse resp = wxPayClient.execute(httpPost);
try {
int statusCode = resp.getStatusLine().getStatusCode();
String bodyAsString = EntityUtils.toString(resp.getEntity());
if (statusCode == 200) { //处理成功
log.info("成功,返回结果 = " + bodyAsString);
} else if (statusCode == 204) { //处理成功,无返回Body
log.info("成功");
} else {
System.out.println("小程序下单失败,响应码 = " + statusCode + ",返回结果 = " + bodyAsString);
throw new IOException("request failed");
}
//相应结果
HashMap<String,String> resultMap = gson.fromJson(bodyAsString, HashMap.class);
//获取prepay—id
String prepayId = resultMap.get("prepay_id");
//获取到perpayid之后需要对数据进行二次封装,前端调起支付必须存在的参数
HashMap<String, String> payMap = new HashMap<>();
payMap.put("appid",wxPayConfig.getAppid());//appid
long currentTimestamp = System.currentTimeMillis();//时间戳,别管那么多,他就是需要
payMap.put("timeStamp",currentTimestamp+"");
String nonceStr = UUID.randomUUID().toString()
.replaceAll("-", "")
.substring(0, 32);;//随机字符串,别管那么多他就是需要,要咱就给
payMap.put("nonceStr",nonceStr);
//apiv3只支持这种加密方式
payMap.put("signType","RSA");
payMap.put("package","prepay_id="+prepayId);
//通过appid,timeStamp,nonceStr,signType,package以及商户密钥进行key=value形式进行拼接加密
//加密方法我会放在这个代码段段下面
String aPackage = buildMessageTwo("传入你的appid", currentTimestamp, nonceStr, payMap.get("package"));
//获取对应的签名
//加密方法我会放在这个代码段段下面
String paySign = sign(wxPayConfig.getPrivateKeyPath(),aPackage.getBytes("utf-8"));
payMap.put("paySign",paySign);
/**
* 在这里你可以加入自己的数据库操作,存储一条订单信息,状态为未支付就行了
* 在这里你可以加入自己的数据库操作,存储一条订单信息,状态为未支付就行了
* 在这里你可以加入自己的数据库操作,存储一条订单信息,状态为未支付就行了
*/
log.info("给前端的玩意:"+payMap);//前端会根据这些参数调起支付页面
//到这里,就已经完成了官网图中的第8步了
return payMap;
}finally {
resp.close();
}
}
4、配置类和配置文件
注意:下面这个配置文件需要读取resources中的wxpay.properties配置文件,等下我也会把我的wxpay.properties贴到下方,还有一个证书文件,需要放置在与src同级的目录中《apiclient_key.pem》,这个文件的获取在https://pay.weixin.qq.com/index.php/core/cert/api_cert#/中申请API证书,也就是申请APIV3的那个页面
一定要记得把证书放入到项目目录中!!!
aoiclient_key.pem就是证书