微信小程序支付(java)Springboot
程序员文章站
2022-06-15 09:08:12
...
微信小程序支付
获取微信参数
支付必须参数
字段名 | 变量名 | 描述 |
---|---|---|
小程序ID | appid | 微信分配的小程序id |
商户号 | mch_id | 微信支付分配的商户号 |
商户秘钥 | paterner_key | 微信支付的秘钥 |
随机字符串 | nonce_str | 随机字符串,长度要求在32位以内。 |
签名 | sign | 通过签名算法计算得出的签名值, |
商品描述 | body | 商品简单描述, |
商户订单号 | out_trade_no | 商户系统内部订单号,要求32个字符内,只能是数字、大小写字母_- |
标价金额 | total_fee | 订单总金额,单位为分 |
终端IP | spbill_create_ip | 支持IPV4和IPV6两种格式的IP地址。调用微信支付API的机器IP |
通知地址 | notify_url | 异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数,(微信支付完成后调用接口) |
交易类型 | trade_type | JSAPI–JSAPI支付(或小程序支付)、NATIVE–Native支付、APP–app支付,MWEB–H5支付 |
用户标识 | openid | 微信小程序登录后的唯一标识 openid |
获取方式:
小程序ID:
商户号:
商户秘钥:
随机字符串:
nonce_str:WXPayUtil中的generateNonceStr()即可,就是生成UUID的方法;
签名:
sign:用WXPayUtil中的generateSignature(finalMap<String, String> data, String key)方法,data是将除了sign外,其他10个参数放到map中,key是四大配置参数中的API秘钥(paternerKey)
商品描述:
body:所支付的名称
商户订单号:
out_trade_no: 自己后台生成的订单号,只要保证唯一就好
标价金额:
total_fee:订单总金额,单位为分
终端IP:
spbill_create_ip: IP地址
通知地址:
notify_url: 微信支付成功后,微信那边携带参数,请求这个地址,作为业务处理,地址需要公网可以访问
交易类型:
trade_type:JSAPI–JSAPI支付(或小程序支付)、NATIVE–Native支付、APP–app支付,MWEB–H5支付
用户标识:
openid:微信小程序登录后的唯一标识
编码实现
1.maven导入依赖
<dependency>
<groupId>com.github.wxpay</groupId>
<artifactId>wxpay-sdk</artifactId>
<version>0.0.3</version>
</dependency>
2.导入官方工具类
3.编写Controller
1.预下单:
1.1 controller层
/**
* 预下单
* @param wxPayDto
* @param request
* @return
*/
@PostMapping("/wxPay")
public AjaxResult wxPay(WxPayDto wxPayDto, HttpServletRequest request){
// 传入的值为 前台接收的封装类和IP地址
return AjaxResult.success(wxPayService.wxPay(wxPayDto, IpUtils.getIpAddr(request)));
}
1.2 Service 层(注释都在里面。。写的很详细。一步一步看我操作即可)
@Override
public Map<String,String> wxPay(WxPayDto wxPayDto,String ip) {
//新建一个返回的map
Map<String,String> result = new HashMap<>();
//首先redis 中获取需要用到的参数
Object o = redisUtil.get(RedisConstants.WX_PAY_CONFIG);
//new 一个微信参数map
Map<String,Object> wxPayConfig = new HashMap<>();
if(o == null){
//判断redis中的值是否为空,如果为空的话,就去数据库中查询,然后在放入redis中
QueryWrapper<AppConfigEntity> wrapper = new QueryWrapper<>();
wrapper.select("wx_appid","mch_id","paterner_key");
// 查询所需要的参数
AppConfigEntity appConfigEntity = appConfigMapper.selectOne(wrapper);
if(appConfigEntity == null){
throw new CustomException("参数有误,请联系管理员!",500);
}
//放入map中
wxPayConfig.put("wx_appid",appConfigEntity.getWxAppid());
wxPayConfig.put("mch_id",appConfigEntity.getMchId());
wxPayConfig.put("paternerKey",appConfigEntity.getPaternerKey());
//将map转换为json字符串,并放入到redis中
redisUtil.set(RedisConstants.WX_PAY_CONFIG,GsonUtil.toJson(wxPayConfig));
}else{
// redis中不为空,则将数据转换为map类型的。
wxPayConfig = GsonUtil.toMap(o.toString());
}
// 通过订单ID获取用户ID
OrderEntity orderEntity = orderMapper.selectById(wxPayDto.getOrderId());
if(orderEntity == null){
throw new CustomException("订单异常,请重新下单!",500);
}
// 通过用户ID获取openid(这里需要在小程序登录哪里获取openid),我这里在数据库中,所以通过id来查询出来openid。
UserEntity userEntity = userMapper.selectById(orderEntity.getUserId());
try {
//拼接参数
Map<String,String> param = new HashMap<>();
param.put("appid",wxPayConfig.get("wx_appid").toString());
param.put("body",wxPayDto.getName());
param.put("mch_id",wxPayConfig.get("mch_id").toString());
param.put("nonce_str", WXPayUtil.generateNonceStr());
// 回调接口。此路径为微信服务器调用支付结果通知路径 也就是说,用户支付完成之后,微信服务器会调用此接口,路径必须为公网路径,也就是已经部署到服务器上,我这里调试使用的是花生壳路径
param.put("notify_url","www.*******.com/***/**");
// 用户的opneid
param.put("openid",userEntity.getOpenId());
// 商户订单(商户自己生成的订单号)
param.put("out_trade_no",wxPayDto.getOrderNumber());
// ip地址
param.put("spbill_create_ip", ip);
//把数据库中的金额转换为分,并转换为字符串。
param.put("total_fee",String.valueOf((int)(wxPayDto.getFlowmoney() * 100)));
// 交易的类型 JSAPI--JSAPI支付(或小程序支付)、NATIVE--Native支付、APP--app支付,MWEB--H5支付
param.put("trade_type","JSAPI");
// 调用微信支付官网提供的工具类生成sign签名,第一个参数为param中的上面的参数,第二个为支付的秘钥
String sign = WXPayUtil.generateSignature(param, wxPayConfig.get("paternerKey").toString());
//生成之后放入参数中
param.put("sign",sign);
//调用微信官网提供的工具类将map转换为xml格式的文件
String xml = WXPayUtil.mapToXml(param);
log.debug("XML数据为 [{}]",xml);
//拼接请求路径
String url = "https://" + WXPayConstants.DOMAIN_API + "/" + WXPayConstants.UNIFIEDORDER_URL_SUFFIX;
log.debug("url 路径为:{}",url);
//发送post请求,携带xml数据
String xmlStr = HttpUtils.sendPost(url, xml);
log.debug("返回数据为:[{}]",xmlStr);
// 返回的数据转换为map数据,
Map<String, String> stringStringMap = WXPayUtil.xmlToMap(xmlStr);
//将结果放入result map中,返回前台用
result.put("appId",wxPayConfig.get("wx_appid").toString());
result.put("timeStamp",WXPayUtil.getCurrentTimestamp()+"");
result.put("nonceStr",WXPayUtil.generateNonceStr());
result.put("signType",WXPayConstants.MD5);
result.put("package","prepay_id="+stringStringMap.get("prepay_id"));
result.put("paySign", WXPayUtil.generateSignature(result, wxPayConfig.get("paternerKey").toString()));
} catch (Exception e) {
e.printStackTrace();
}
//返回前台
return result;
}
xml数据为:
<xml>
<nonce_str>ZFd2BtolOr3wsePjtlyaftizGgGgpmYF</nonce_str>
<out_trade_no>12020011413250682063662178618273</out_trade_no>
<openid>openid</openid>
<appid>appid</appid>
<total_fee>2222</total_fee>
<sign>614DC04A69F0B8CC22054C4FD4277EDB</sign>
<trade_type>JSAPI</trade_type>
<body>A6包年套餐(365天)</body>
<mch_id>111</mch_id>
<notify_url>www.*******.com/***/**</notify_url>
<spbill_create_ip>127.0.0.1</spbill_create_ip>
</xml>
1.3 httpUtils
package com.hlyun.utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.net.ssl.*;
import java.io.*;
import java.net.ConnectException;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.net.URLConnection;
import java.security.cert.X509Certificate;
/**
* 通用http发送方法
*
* @author ruoyi
*/
public class HttpUtils {
private static final Logger log = LoggerFactory.getLogger(HttpUtils.class);
/**
* 向指定 URL 发送GET方法的请求
*
* @param url 发送请求的 URL
* @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。
* @return 所代表远程资源的响应结果
*/
public static String sendGet(String url, String param) {
StringBuilder result = new StringBuilder();
BufferedReader in = null;
try {
String urlNameString = url;
if(StringUtils.isNotNull(param)){
urlNameString = url + "?" + param;
}
log.info("sendGet - {}", urlNameString);
URL realUrl = new URL(urlNameString);
URLConnection connection = realUrl.openConnection();
connection.setRequestProperty("accept", "*/*");
connection.setRequestProperty("connection", "Keep-Alive");
connection.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
connection.connect();
in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
String line;
while ((line = in.readLine()) != null) {
result.append(line);
}
log.info("recv - {}", result);
}catch (ConnectException e) {
log.error("调用HttpUtils.sendGet ConnectException, url=" + url + ",param=" + param, e);
}catch (SocketTimeoutException e) {
log.error("调用HttpUtils.sendGet SocketTimeoutException, url=" + url + ",param=" + param, e);
}catch (IOException e) {
log.error("调用HttpUtils.sendGet IOException, url=" + url + ",param=" + param, e);
}catch (Exception e){
log.error("调用HttpsUtil.sendGet Exception, url=" + url + ",param=" + param, e);
} finally {
try {
if (in != null) {
in.close();
}
}catch (Exception ex) {
log.error("调用in.close Exception, url=" + url + ",param=" + param, ex);
}
}
return result.toString();
}
/**
* 向指定 URL 发送POST方法的请求
*
* @param url 发送请求的 URL
* @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。
* @return 所代表远程资源的响应结果
*/
public static String sendPost(String url, String param) {
PrintWriter out = null;
BufferedReader in = null;
StringBuilder result = new StringBuilder();
try {
log.info("sendPost - {}", url);
URL realUrl = new URL(url);
URLConnection conn = realUrl.openConnection();
conn.setRequestProperty("accept", "*/*");
conn.setRequestProperty("connection", "Keep-Alive");
conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
conn.setRequestProperty("Accept-Charset", "utf-8");
conn.setRequestProperty("contentType", "utf-8");
conn.setDoOutput(true);
conn.setDoInput(true);
out = new PrintWriter(conn.getOutputStream());
out.print(param);
out.flush();
in = new BufferedReader(new InputStreamReader(conn.getInputStream(), "utf-8"));
String line;
while ((line = in.readLine()) != null) {
result.append(line);
}
log.info("recv - {}", result);
}catch (ConnectException e) {
log.error("调用HttpUtils.sendPost ConnectException, url=" + url + ",param=" + param, e);
} catch (SocketTimeoutException e) {
log.error("调用HttpUtils.sendPost SocketTimeoutException, url=" + url + ",param=" + param, e);
} catch (IOException e) {
log.error("调用HttpUtils.sendPost IOException, url=" + url + ",param=" + param, e);
} catch (Exception e) {
log.error("调用HttpsUtil.sendPost Exception, url=" + url + ",param=" + param, e);
} finally {
try {
if (out != null) {
out.close();
}
if (in != null) {
in.close();
}
} catch (IOException ex) {
log.error("调用in.close Exception, url=" + url + ",param=" + param, ex);
}
}
return result.toString();
}
public static String sendSSLPost(String url, String param) {
StringBuilder result = new StringBuilder();
String urlNameString = url + "?" + param;
try {
log.info("sendSSLPost - {}", urlNameString);
SSLContext sc = SSLContext.getInstance("SSL");
sc.init(null, new TrustManager[] { new TrustAnyTrustManager() }, new java.security.SecureRandom());
URL console = new URL(urlNameString);
HttpsURLConnection conn = (HttpsURLConnection) console.openConnection();
conn.setRequestProperty("accept", "*/*");
conn.setRequestProperty("connection", "Keep-Alive");
conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
conn.setRequestProperty("Accept-Charset", "utf-8");
conn.setRequestProperty("contentType", "utf-8");
conn.setDoOutput(true);
conn.setDoInput(true);
conn.setSSLSocketFactory(sc.getSocketFactory());
conn.setHostnameVerifier(new TrustAnyHostnameVerifier());
conn.connect();
InputStream is = conn.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String ret = "";
while ((ret = br.readLine()) != null) {
if (ret != null && !ret.trim().equals("")) {
result.append(new String(ret.getBytes("ISO-8859-1"), "utf-8"));
}
}
log.info("recv - {}", result);
conn.disconnect();
br.close();
}catch (ConnectException e) {
log.error("调用HttpUtils.sendSSLPost ConnectException, url=" + url + ",param=" + param, e);
} catch (SocketTimeoutException e) {
log.error("调用HttpUtils.sendSSLPost SocketTimeoutException, url=" + url + ",param=" + param, e);
} catch (IOException e) {
log.error("调用HttpUtils.sendSSLPost IOException, url=" + url + ",param=" + param, e);
} catch (Exception e) {
log.error("调用HttpsUtil.sendSSLPost Exception, url=" + url + ",param=" + param, e);
}
return result.toString();
}
private static class TrustAnyTrustManager implements X509TrustManager {
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) {
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) {
}
@Override
public X509Certificate[] getAcceptedIssuers()
{
return new X509Certificate[] {};
}
}
private static class TrustAnyHostnameVerifier implements HostnameVerifier {
@Override
public boolean verify(String hostname, SSLSession session)
{
return true;
}
}
}
2.回调函数
1.1 controller层
/**
* 微信支付回调函数
* @return
*/
@RequestMapping("/callback")
public AjaxResult wxPayCallBack(HttpServletRequest request, HttpServletResponse response){
log.debug("微信支付成功,微信发送的callback信息");
InputStream is = null;
String xml = "";
try {
// 获取流
is = request.getInputStream();
//把流转换为string类型的xml
xml = WXPayUtil.inputStream2String(is, "UTF-8");
//告诉微信服务器收到信息了,不要在调用回调action了========这里很重要回复微信服务器信息用流发送一个xml即可
response.getWriter().write("<xml><return_code><![CDATA[SUCCESS]]></return_code></xml>");
// 关闭流
is.close();
} catch (IOException e) {
e.printStackTrace();
}
//传到Service
return toAjax(wxPayService.wxPayCallBack(xml));
}
1.2 Service层
@Override
public int wxPayCallBack(String xml) {
int res = 0;
try {
//将微信发的xml转map
Map<String, String> notifyMap = WXPayUtil.xmlToMap(xml);
if(notifyMap.get("return_code").equals("SUCCESS")){
//以下根据具体的业务逻辑编写,仅供参考。请根据实际情况编写
//支付成功,获取商户订单号
String out_trade_no = notifyMap.get("out_trade_no");
//支付订单号(微信官网提供)
String transaction_id = notifyMap.get("transaction_id");
// 交易时间
String time_end = notifyMap.get("time_end");
//获取实际支付金额 单位 分
Double total_fee = Double.valueOf(coinToYuan(notifyMap.get("total_fee")));
QueryWrapper<OrderEntity> wrapper = new QueryWrapper<>();
wrapper.eq("order_number",out_trade_no);
OrderEntity orderEntity = orderMapper.selectOne(wrapper);
if(orderEntity != null){
orderEntity.setPayNumber(transaction_id);
orderEntity.setTotalFee(total_fee);
orderEntity.setPayType("1");
orderEntity.setPayTime(DateUtils.parseDate(time_end));
orderEntity.setIsPay("1");
res = orderMapper.updateById(orderEntity);
}else{
throw new CustomException("交易查询失败,请联系管理员核实!",500);
}
}else{
throw new CustomException("交易查询失败,请联系管理员核实!",500);
}
} catch (Exception e) {
e.printStackTrace();
}
return res;
}
小白一枚,代码奉上,大家参考下,有好的建议希望大家也评论下
上一篇: php简单静态页生成过程_php技巧
下一篇: sql实现调用最新5个列表缩略图。