我们这里只看支付,其他跟支付无关的东西不看
主要有三个页面
紫色的三个是跟我们支付有关的
1、首先导入一个JS,二维码生成插件 qrious
1、创建一个weixinpay.properties配置文件,配置微信支付需要的参数,这里的回调地址,我们没有用到。
2、在主XML配置文件加载我们的微信支付配置文件。
5、导入我们的HttpClient工具类,后面调用微信支付接口会用到。
6、加入雪花算法工具类,后面用来生成永远不会重复的订单编号。
1、创建一个WeixinPayService,有两个参数。
package com.movie.service.impl;
import com.github.wxpay.sdk.WXPayUtil;
import com.movie.service.WeixinPayService;
import com.movie.utils.HttpClientUtils;
import com.movie.vo.WeiXin;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import sun.net.www.http.HttpClient;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/**
* @BelongsProject: MovieSystem
* @BelongsPackage: com.movie.service.impl
* @Description: TODO
*/
@Service
public class WeixinPayServiceImpl implements WeixinPayService {
@Resource
WeiXin weiXin;
/**
* 调用微信接口获取支付的地址
*
* @param out_trade_no 订单号
* @param total_fee 金额(分)
* @return
*/
@Override
public Map createNative(String out_trade_no, String total_fee) {
// 1.创建参数
Map<String, String> param = new HashMap();// 创建参数
//调用微信支付统一下单的接口,必须转的参数
param.put("appid", weiXin.getAppid());// 公众号
param.put("mch_id", weiXin.getPartner());// 商户号
param.put("nonce_str", WXPayUtil.generateNonceStr());//调用工具类(微信SDK的)。随机字符串
param.put("body", "北海市影城电影票");// 商品描述
param.put("out_trade_no", out_trade_no);// 商户订单号
param.put("total_fee", total_fee);// 总金额(以分为单位)
param.put("spbill_create_ip", "127.0.0.1");// IP
param.put("notify_url", "http://test.easyshop.cn");// 回调地址(随便写)
param.put("trade_type", "NATIVE");// 交易类型,NATIVE类型表示扫码支持
try {
// 2.生成要发送的xml(将Map集合转为XML格式),因为微信要的是XML格式
String xmlParam = WXPayUtil.generateSignedXml(param, weiXin.getPartnerkey());
//使用HttpClient远程调用工具
//https://api.mch.weixin.qq.com/pay/unifiedorder 这个是微信支付的接口
HttpClientUtils client = new HttpClientUtils("https://api.mch.weixin.qq.com/pay/unifiedorder");
client.setHttps(true);
client.setXmlParam(xmlParam);
client.post();
// 3.获得结果
// result是服务器返回的结果,是XML格式的
String result = client.getContent();
//将服务器返回的结果,XML转为Map
Map<String, String> resultMap = WXPayUtil.xmlToMap(result);
//封装返回值
Map<String, String> map = new HashMap<>();
map.put("code_url", resultMap.get("code_url"));// 支付地址
map.put("total_fee", total_fee);// 总金额
map.put("out_trade_no", out_trade_no);// 订单号
return map;
} catch (Exception e) {
e.printStackTrace();
return new HashMap<>();
}
}
}
注释都写得很清楚了,就不多做解释了,我们调用这个方法需要给它传递两个参数,一个是订单号、一个是总金额(就是我们微信扫码付款的金额)。最后创建一个Map,把支付地址,总金额,订单号返回。
3、编写BuyTicketController
package com.movie.web;
import com.movie.bean.Users;
import com.movie.service.BuyTicketService;
import com.movie.service.TicketService;
import com.movie.service.WeixinPayService;
import com.movie.utils.CommomResult;
import com.movie.utils.IdWorker;
import com.movie.vo.PlayVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpSession;
import java.util.List;
import java.util.Map;
@Controller
public class BuyTicketController {
@Autowired
BuyTicketService buyTicketService;
@Autowired
WeixinPayService weixinPayService;
@Autowired
TicketService ticketService;
/**
* 跳转支付页面,生成二维码
*
* @param play_id
* @param seats
* @return
*/
@RequestMapping(value = "/buy_toPay", method = RequestMethod.POST)
public String toPay(Integer play_id, String seats, Model model, HttpSession session) {
//要购买的电影的座位号
session.setAttribute("seats",seats);
//根据电源id查找出电影的详细信息
PlayVo vo = buyTicketService.findPlayVoById(play_id);
//一张票的价格
Double money = vo.getMoney();
//以,进行分割,看看买了几张片
StringBuffer stringBuffer = new StringBuffer();
String[] arrays = seats.split(",");
//待支付金额,总金额
Double sumMoney = money * arrays.length;
//循环遍历,加上排、座
for (String s : arrays) {
s = s.replace("-", "排") + "座";
stringBuffer.append(s + " ");
}
model.addAttribute("seats", stringBuffer.toString());
model.addAttribute("vo", vo);
model.addAttribute("sumMoney", sumMoney);
//2.生成一个永远不会重复的订单编号
IdWorker worker = new IdWorker();
long id = worker.nextId();
String orderId = id+"";
//调用微信接口,获取二维码支付地址
Map<String, String> map = weixinPayService.createNative(orderId, "1"); //微信接口支付金额默认是以分为单位
//微信要支付的地址,把这个地址生成二维码
String code_url = map.get("code_url");
model.addAttribute("code_url", code_url);
//用户支付的订单号
model.addAttribute("orderId", orderId);
return "user_film_pay";
}
}
注释都写得很清楚了,就不多做解释了,把得到的支付地址跟订单号存放在model,给它返回回去。
5、测试一下:
我们扫码支付可以了,那么问题来了,我们扫码了之后,页面怎么知道它支付了还是没支付呢?
这就要查询订单的支付状态了,根据订单号查询订单的支付状态,看看它执行成功了还是失败。
1、在service下添加一个查询订单状态的方法
2、同上面一样,实现它
/**
* 根据订单号查询订单的支付状态
* @param out_trade_no
* @return
*/
@Override
public Map queryPayStatus(String out_trade_no) {
Map param = new HashMap();
param.put("appid", weiXin.getAppid());// 公众账号ID
param.put("mch_id", weiXin.getPartner());// 商户号
param.put("out_trade_no", out_trade_no);// ----->订单号
param.put("nonce_str", WXPayUtil.generateNonceStr());// 随机字符串
//查询订单支付状态接口
String url = "https://api.mch.weixin.qq.com/pay/orderquery";
try {
String xmlParam = WXPayUtil.generateSignedXml(param, weiXin.getPartnerkey());
HttpClientUtils client = new HttpClientUtils(url);
client.setHttps(true);
client.setXmlParam(xmlParam);
//发送POS请求
client.post();
//调用接口返回的结果
String result = client.getContent();
//把返回的XML文件格式转为Map
Map<String, String> map = WXPayUtil.xmlToMap(result);
System.out.println("---------------服务器端接口返回的支付结果:-----------------------");
if(map!=null){
Set<Map.Entry<String, String>> entries = map.entrySet();
for (Map.Entry<String, String> entry : entries) {
System.out.println(entry.getKey()+"--->"+entry.getValue());
}
}
return map;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
传递一个订单号,返回Map结果
3、编写Controller,跟之前的Controller是同一个类
流程是这样的:
我们传递一个订单号,会调用service去查询订单的状态,会返回一个Map。我们对Map进行判断,如果Map不为空,并且里面的 trade_state 的值为 SUCCESS ,则说明支付成功。
如果Map为空,则说明支付异常,失败。
那么还有一个问题,如果用户打开了页面,但是他就是不扫码支付呢?我们的请求只发送一次查询,是不是不合适啊?我们可以给它来一个线程,让它每3秒钟执行一次,来一个循环,时间为5分钟,如果5分钟内没有扫码,则支付超时。
/**
* 根据订单号查询订单支付的状态--轮询订单支付状态接口
*
* @param out_trade_no
* @return
*/
@ResponseBody
@RequestMapping("/pay_queryPayStatus")
public CommomResult queryPayStatus(String out_trade_no,Integer play_id,HttpSession session) {
CommomResult commonResult = null;
try {
//查询支付的订单号是:1320615983324332000
System.out.println("查询支付的订单号是:"+out_trade_no);
System.out.println("查询支付的订单play_id:"+play_id);
String seats=(String)session.getAttribute("seats");// 3-4,5-6,7-8
Users loginUser=(Users) session.getAttribute("loginUser");
commonResult = null;
int x = 0;
while (true) {
//查询订单支付状态
Map map = weixinPayService.queryPayStatus(out_trade_no);
if (map != null) {
if (map.get("trade_state").equals("SUCCESS")) {// 如果成功
//支付成功,把支付成功的数据,存入数据中,后期可以查看订单
String[] seatNumers = seats.split(",");
for (String seatName : seatNumers) {
int seat_id = ticketService.getSeatIdBySeatName(seatName);
//插入到订单表 ticket表中
ticketService.addTicket(out_trade_no,play_id,loginUser.getUser_id(),seat_id);
}
commonResult = new CommomResult(200, "支付成功");
break;
}
}
if (map == null) {
commonResult = new CommomResult(500, "支付出现异常!失败");
break;
}
try {
Thread.sleep(3000);//睡眠3秒钟,每3秒钟执行一次
} catch (InterruptedException e) {
e.printStackTrace();
}
//为了不让循环无休止地运行,我们定义一个循环变量,如果这个变量超过了这个值则退出循环,设置时间为5分钟
x++;
if (x >= 60) {
commonResult = new CommomResult(502, "支付超时!");
break;
}
}
} catch (Exception e) {
commonResult = new CommomResult(500, "支付异常!");
e.printStackTrace();
} finally {
}
return commonResult;
}
我们什么时候开始调用Controller查询订单状态的方法呢?那当然是在页面一打开,二维码出来就开始调用了
等到5分钟没有支付,超时
点击确认,页面刷新,扫码支付
支付成功,跳到成功页面