最近给学校做了一个项目(springboot)里面用到了微信支付,我完成了pc端的支付,使用的是模式二,在这里记录一下,方便以后在工作中回顾
首先,加入相关的pml依赖
说明:-------------------------------------------------------------------------------------------------------------------------------------------
引入lombok是简化javabean中get,set方法的书写,使得属性和这些方法完成解耦
google zxing是第三方生成二维码的工具。它和hutool(第三方工具类)一起使用,生成二维码
还要加入微信sdk(不解释这个)和httpclient(用于和微信支付系统通信)
注意看以上说明------------------------------------------------------------------------------------------------------------------------------
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--google zxing 生成二维码-->
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>core</artifactId>
<version>3.3.3</version>
</dependency>
<!--hutool工具类-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>4.5.16</version>
</dependency>
<!-- 微信支付sdk -->
<dependency>
<groupId>com.github.wxpay</groupId>
<artifactId>wxpay-sdk</artifactId>
<version>0.0.3</version>
</dependency>
<!--http客户端-->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.2</version>
</dependency>
配置文件
说明:我将微信相关的配置抽离在一个properties文件中,方便管理,使得配置信息不与代码耦合
weixinpay.properties
说明:注意配置的前缀(weixinpay)下面会用到
#公众账号ID
weixinpay.appid=*******************
#商户号
weixinpay.mch_id=********************
#用于生成sign key设置路径:微信商户平台(pay.weixin.qq.com)-->账户设置-->API安全-->密钥设置
weixinpay.sign_key=******************
#微信统一下单url
weixinpay.Unified_Order_URL=https://api.mch.weixin.qq.com/pay/unifiedorder
#微信回调通知地址(我们服务器的ip地址和端口号)
weixinpay.notify_url=*************************
WeiXinPayConfig
说明:有了上面的配置信息当然需要读取配置信息为我们所用,使用以下配置类,springboot就可以为我们注入到容器里,在后面的代码中会用到
注意:这里@Data注解就是lombok的注解,使用它就不用写get,set方法了,很方便
这里的@PropertySource就是读取配置文件
这里的prefix 对应上面配置文件中信息
@PropertySource(value = {"classpath:weixinpay.properties"})
@ConfigurationProperties(prefix = "weixinpay")
@Component
@Data
public class WeiXinPayConfig {
private String appid;
private String mch_id;
private String sign_key;
private String Unified_Order_URL;
private String notify_url;
}
基本环境搭好了,现在开始开发
首先在页面填写好金额,然后点击立即捐赠按钮
到了后台我的controller是这样的
说明:我这个项目前台会传要捐赠的项目id,要捐赠的金额,以及支付方式(微信?支付宝?银行卡?)
我们拿到数据后要先在数据库中生成订单,也就是说用户填完金额,只要一点“立即捐赠”按钮,我后端就会生成一个捐赠订单,然后将一些基本数据放在session中,方便后面代码的回显。
这里的代码只是,供大家参考,没要照搬照抄!!!,只要将这个说明中的意思用你自己的代码完成就行了!!!
/**
* @Author 张满
* @Description 去支付页面
* @Date 2019/8/6 12:41
* @Param [money, projectId, payType, session]
* @return java.lang.String
**/
@RequestMapping("/toWeiXinPay")
public String toWeiXinPay(@RequestParam(value = "money",required = true) Double money,
@RequestParam(value = "projectId",required = true)Integer projectId,
@RequestParam(value = "payType",required = true)Integer payType,
HttpSession session
){
User user = (User) session.getAttribute("user");
MoneyDonation moneyDonation=new MoneyDonation();
if(user!=null){
//如果用户已经登录(实名捐赠)
moneyDonation.setUserid(user.getId());
}
moneyDonation.setProjectid(projectId);
moneyDonation.setSum(new BigDecimal(money).setScale(2,BigDecimal.ROUND_HALF_UP));
if(payType == PayTypeConstants.WEI_XIN){
moneyDonation.setType(PayTypeConstants.WEI_XIN);
session.setAttribute("payType","微信支付");
}
moneyDonation.setCreatetime(new Date());
//设置订单状态
moneyDonation.setOrderStatus(OrderStatusConstants.PAYING);
//生成订单id-----类似 b17f24ff026d40949c85a24f4f375d42
String orderId = IdUtil.simpleUUID();
moneyDonation.setOrderId(orderId);
//生成订单
int count = moneyDonationService.generateMoneyDonation(moneyDonation);
if(count>0){
//将订单号和金额传递到微信支付页面
session.setAttribute("orderId",orderId);
session.setAttribute("money",money);
//商品描述 规则类似: 腾讯充值中心-QQ会员充值
Project project = projectService.queryProjectById(projectId);
session.setAttribute("body","湖科捐赠-"+project.getName());
return "redirect:returnProjectPay";
}else {
//返回失败页面
return "redirect:returnPayFail";
}
}
然后重定向到下面的controller,使用这个controller返回捐赠页面
说明:这里session中取的属性就是上面controller中session放的数据,方便回显到页面
/**
* @Author 张满
* @Description 返回支付页面
* @Date 2019/8/6 13:30
* @Param []
* @return java.lang.String
**/
@RequestMapping("/returnProjectPay")
public String returnProjectPay( HttpSession session,
Model model
){
//返回订单号,捐赠金额,支付方式,显示在页面上
model.addAttribute("payType",session.getAttribute("payType"));
model.addAttribute("orderId",session.getAttribute("orderId"));
model.addAttribute("money",session.getAttribute("money"));
model.addAttribute("body",session.getAttribute("body"));
return prefix+"/pay/projectPay";
}
返回的捐赠页面
说明:这里回显的东西就是上面model放的东西
先不要急着问二维码哪来的!!!!!!!!!!!!!!!!!!!!!!!!
先不要急着问二维码哪来的!!!!!!!!!!!!!!!!!!!!!!!!
先不要急着问二维码哪来的!!!!!!!!!!!!!!!!!!!!!!!!马上说!
二维码怎么来的???
说明:在返回的页面中二维码是个图片,但是src不填任何东西,当页面一加载就用js去后台动态的获取我们需要的二维码
注意:这里的Math.random()是为了防止浏览器有缓存,支付一次后,以后就把这个二维码图片缓存下来,以后不去请求二维码图片了。!
<script>
//请求二维码图片
window.onload=function(){
var QRcode = document.getElementById("QRcode");
QRcode.src="/WeiXinPay/generateQRcode?_="+Math.random();
}
</script>
接下来看看生成二维码的controller
说明:这里代码较多,不要害怕,我慢慢说。
在这个controller我们用@Autowired注入了之前配置的WeiXinPayConfig 对象,所有我们就可以在接下来的代码中,用weiXinPayConfig.get("…")拿到相关的配置信息了。
特别注意:这里的 notify_url是回调url ,是用户支付成功后,微信通知我们用户已经支付了时,它就是掉这里我们设置的接口,对它的相关解释,这里不再多说,请参考微信扫码支付文档之统一下单API。
特别注意:微信要求的订单金额单位是分,在前台我们传的是元,所有这里要做转换成分
里面的HttpClientUtils 类,我接下来会给出,使用它来与微信支付系统通信。
里面你不认识的工具类,如:WXPayUtil,这都是微信sdk中提供的。
特别注意:当我们请求微信成功后,微信会给我们一个code_url,我们就是用这个字符串生成二维码的, 这里的QrCodeUtil.generate(code_url,184,184,“jpg”,response.getOutputStream());就是生成二维码,这个工具类是hutool中的,我们已经在开始加入了zxing和hutool,然后调用这个方法,就自动生成了二维码,然后用流的方式,写回页面,页面上就出现了二维码。
@Autowired
private WeiXinPayConfig weiXinPayConfig;
/**
* @Author 张满
* @Description 生成二维码图片
* @Date 2019/8/6 21:51
* @Param []
* @return void
**/
@RequestMapping("/generateQRcode")
public void generateQRcode(HttpSession session, HttpServletResponse response) {
try {
Map map = new HashMap();
map.put("appid",weiXinPayConfig.getAppid()); //微信支付分配的公众账号ID(企业号corpid即为此appId)
map.put("mch_id",weiXinPayConfig.getMch_id()); //微信支付分配的商户号
String nonceStr = WXPayUtil.generateNonceStr();
map.put("nonce_str",nonceStr); //随机字符串,长度要求在32位以内。
map.put("body",session.getAttribute("body")); //商品简单描述
map.put("out_trade_no",session.getAttribute("orderId")); //商户系统内部订单号,要求32个字符内,只能是数字、大小写字母_-|* 且在同一个商户号下唯一
double money= (double)session.getAttribute("money");
money=money*100; //转成单位为分
map.put("total_fee",String.valueOf((int)money)); //订单总金额,单位为分
InetAddress localHost = InetAddress.getLocalHost();
String address = localHost.getHostAddress();
map.put("spbill_create_ip",address); //终端IP
map.put("notify_url",weiXinPayConfig.getNotify_url()+"/WeiXinPay/acceptWeiXinNotice"); //异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数。
map.put("trade_type","NATIVE"); //交易类型
map.put("product_id",session.getAttribute("orderId")); //trade_type=NATIVE时,此参数必传。此参数为二维码中包含的商品ID,商户自行定义。
String signature = WXPayUtil.generateSignature(map, weiXinPayConfig.getSign_key()); //通过签名算法计算得出的签名值 key设置路径见文档,默认MD5加密
map.put("sign",signature);
String requestDataXml = WXPayUtil.mapToXml(map);
//调用微信统一下单api todo 查询时应该坚持订单状态,只有支付成功的才可以被查询出来
String returnXml = HttpClientUtils.doPostByXml(weiXinPayConfig.getUnified_Order_URL(), requestDataXml);
//System.out.println(returnXml);
//将微信支付系统返回的xml转换成map集合
Map<String, String> returnMap = WXPayUtil.xmlToMap(returnXml);
//通讯正常
if(returnMap.get("return_code").equals(WeiXinPayConstants.SUCCESS)){
//返回消息正常
if(returnMap.get("result_code").equals(WeiXinPayConstants.SUCCESS)){
//获得code_url生成二维码
String code_url = returnMap.get("code_url");
QrCodeUtil.generate(code_url,184,184,"jpg",response.getOutputStream());
}else {
//微信支付系统生成订单失败
//输出错误代码
System.out.println(map.get("err_code"));
//输出错误描述
System.out.println(map.get("err_code_des"));
}
}else{
//和微信支付系统同通讯失败
//输出错误消息
System.out.println(map.get("return_msg"));
}
} catch (Exception e) {
e.printStackTrace();
}
}
上面的HttpClientUtils工具类
说明:这里面的方法有好几个,但实际上我们只用了doPostByXml的方法。使用了post方式给微信发送了xml格式的请求参数
package com.iot.donation.util;
import org.apache.http.HttpEntity;
import org.apache.http.NameValuePair;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class HttpClientUtils {
public static String doGet(String url, Map<String, String> param) {
// 创建Httpclient对象
CloseableHttpClient httpclient = HttpClients.createDefault();
String resultString = "";
CloseableHttpResponse response = null;
try {
// 创建uri
URIBuilder builder = new URIBuilder(url);
if (param != null) {
for (String key : param.keySet()) {
builder.addParameter(key, param.get(key));
}
}
URI uri = builder.build();
// 创建http GET请求
HttpGet httpGet = new HttpGet(uri);
// 执行请求
response = httpclient.execute(httpGet);
// 判断返回状态是否为200
if (response.getStatusLine().getStatusCode() == 200) {
resultString = EntityUtils.toString(response.getEntity(), "UTF-8");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (response != null) {
response.close();
}
httpclient.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return resultString;
}
public static String doGet(String url) {
return doGet(url, null);
}
public static String doPost(String url, Map<String, String> param) {
// 创建Httpclient对象
CloseableHttpClient httpClient = HttpClients.createDefault();
CloseableHttpResponse response = null;
String resultString = "";
try {
// 创建Http Post请求
HttpPost httpPost = new HttpPost(url);
// 创建参数列表
if (param != null) {
List<NameValuePair> paramList = new ArrayList<>();
for (String key : param.keySet()) {
paramList.add(new BasicNameValuePair(key, param.get(key)));
}
// 模拟表单
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(paramList);
httpPost.setEntity(entity);
}
// 执行http请求
response = httpClient.execute(httpPost);
resultString = EntityUtils.toString(response.getEntity(), "utf-8");
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
response.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return resultString;
}
public static String doPost(String url) {
return doPost(url, null);
}
public static String doPostJson(String url, String json) {
// 创建Httpclient对象
CloseableHttpClient httpClient = HttpClients.createDefault();
CloseableHttpResponse response = null;
String resultString = "";
try {
// 创建Http Post请求
HttpPost httpPost = new HttpPost(url);
// 创建请求内容
StringEntity entity = new StringEntity(json, ContentType.APPLICATION_JSON);
httpPost.setEntity(entity);
// 执行http请求
response = httpClient.execute(httpPost);
resultString = EntityUtils.toString(response.getEntity(), "utf-8");
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
response.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return resultString;
}
/**
* @Author 张满
* @Description post请求发送xml
* @Date 2019/8/7 12:19
* @Param [url, requestDataXml]
* @return java.lang.String
**/
public static String doPostByXml(String url,String requestDataXml){
CloseableHttpClient httpClient=null;
CloseableHttpResponse httpResponse=null;
//创建httpClient连接对象
httpClient = HttpClients.createDefault();
//创建post请求连接对象
HttpPost httpPost = new HttpPost(url);
//创建连接请求参数对象,并设置连接参数
RequestConfig requestConfig = RequestConfig.custom()
.setConnectTimeout(15000) //连接服务器主机超时时间
.setConnectionRequestTimeout(60000) //连接请求超时时间
.setSocketTimeout(6000) //设置读取响应数据超时时间
.build();
//为httpPost请求设置参数
httpPost.setConfig(requestConfig);
//将上传参数存放到entity属性中
httpPost.setEntity(new StringEntity(requestDataXml,"UTF-8"));
//添加头信息
httpPost.setHeader("Content-Type","text/xml");
String result="";
try {
//发送请求
httpResponse = httpClient.execute(httpPost);
//获取返回内容
HttpEntity httpEntity = httpResponse.getEntity();
result = EntityUtils.toString(httpEntity, "UTF-8");
} catch (IOException e) {
e.printStackTrace();
}
return result;
}
}
通过以上步骤,捐赠页面就生成了一个 可以供用户支付的二维码。你就可以支付了。当用户支付以后,微信那边就会通知你(告诉你用户a捐钱成功了),这时候他就需要调用上面提到的notify_url,就是你和微信通讯时,给他的接口,这个接口要在公网上可以直接访问,具体的要求请看微信文档。
回调接口怎么写?
注意:大家可以参考我这个接口的写法,看懂之后,就可以灵活使用了。
思路:将微信发来的xml格式,利用WXPayUtil(微信sdk提供)转化成map,获取里面的数据,判断微信返回的状态码,如果成功就做一下签名校验(也是sdk提供方法),当校验完成后,开始写我们的业务:主要就是判断这个订单是不是我已经给它的状态改变为已支付(微信会发很多次通知给你),如果改变订单状态,就返回成功给微信,如果没改变就改变了以后发成功。按照自己的业务需求写自己的业务就行了!
/**
* @Author 张满
* @Description 接收微信通知(订单支付情况)
* @Date 2019/8/8 15:44
* @Param []
* @return void
**/
@RequestMapping("/acceptWeiXinNotice")
public void acceptWeiXinNotice(HttpServletRequest request,HttpServletResponse response,HttpSession session){
System.out.println("---------------------回调成功---------------------------");
PrintWriter writer = null;
InputStream inStream = null;
ByteArrayOutputStream outSteam = null;
try {
writer = response.getWriter();
inStream = request.getInputStream();
outSteam = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len = 0;
while ((len = inStream.read(buffer)) != -1) {
outSteam.write(buffer, 0, len);
}
String result = new String(outSteam.toByteArray(), "utf-8");
System.out.println("-------------------result Xml------------------------------");
System.out.println(result);
Map<String, String> map = null;
// 解析微信通知返回的信息
map = WXPayUtil.xmlToMap(result);
if (map.get("return_code").equals(WeiXinPayConstants.SUCCESS)) {
if (map.get("result_code").equals(WeiXinPayConstants.SUCCESS)) {
//进行签名校验
if(WXPayUtil.isSignatureValid(map,weiXinPayConfig.getSign_key())){
//判断该订单是否已经处理过,(对订单状态的改变要加数据锁,进行并发控制)
MoneyDonation moneyDonation = moneyDonationService.findMoneyDonationByOrderId(map.get("out_trade_no"));
//如果已经改变过了订单状态,返回成功
if(moneyDonation!=null && moneyDonation.getOrderStatus().equals(OrderStatusConstants.PAYED)){
Map returnMap = new HashMap();
returnMap.put("return_code",WeiXinPayConstants.SUCCESS);
returnMap.put("return_msg",WeiXinPayConstants.OK);
String returnXml = WXPayUtil.mapToXml(returnMap);
writer.write(returnXml);
writer.flush();
}
//改变订单状态为已支付,(对订单状态的改变要加数据锁,进行并发控制)
if(moneyDonation!=null && moneyDonation.getOrderStatus().equals(OrderStatusConstants.PAYING)){
//改变订单状态为已支付
int count = moneyDonationService.changeOrderStatus(moneyDonation.getId(),OrderStatusConstants.PAYED);
// 给项目的total加上money
Project project = projectService.queryProjectById(moneyDonation.getProjectid());
BigDecimal total = project.getTotal();
System.out.println("之前的项目收到的金额:"+total);
//微信返回的金额单位是分,而数据库中存的是元,所以需要将分转化成元
total=total.add( new BigDecimal(Integer.parseInt(map.get("total_fee"))).divide(new BigDecimal(100)) );
System.out.println("之前的项目收到的金额:"+total);
project.setTotal(total);
int count2 = projectService.updateProject(project);
//响应成功
if(count==1 && count2==1){
Map returnMap = new HashMap();
returnMap.put("return_code",WeiXinPayConstants.SUCCESS);
returnMap.put("return_msg",WeiXinPayConstants.OK);
String returnXml = WXPayUtil.mapToXml(returnMap);
writer.write(returnXml);
writer.flush();
}
}
}
}
}else{
System.out.println(map.get("err_code"));
System.out.println(map.get("err_code_des"));
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
outSteam.close();
inStream.close();
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
效果:当用户支付后,你的账户就收到了钱,并且数据库的订单状态就更改为已支付
把上面思路仔细看懂,用自己的代码完成逻辑,切忌照搬照抄,那样你就很难完成这么简单地一件事
如果你觉得上面的思路对你有所帮助,你可以评论鼓励一下,最好能关注我一下,毕竟第一场这么用心的写博客,我后续也会好好写博客!谢谢!Thanks♪(・ω・)ノ