一.准备微信扫码支付要用到的相关参数,这里将其全部写入一个配置类,代码如下:
public class ZbWxPayConfig {
public static String APP_ID = "***********************";
public static String APP_SECRET = "***********************";
public static String MCH_ID = "***********************"; //商户号
public static String MCH_KEY = "***********************"; //商户平台设置的密钥
public static String UFDODER_URL = "https://api.mch.weixin.qq.com/pay/unifiedorder";//统一下单接口
public static String ORDER_QUERY_URL = "https://api.mch.weixin.qq.com/pay/orderquery";//统一查询订单接口
public static String NOTIFY_URL = "***********************";//支付成功微信回调地址
}
二.相关工具类
public class WxPayUtil {
/**
* @author : YY
* @date : 2018-7-3-下午04:20:37
* @param : @return
* @Description: 生成随机字符串
*/
public static String getNonceStr(){
String allChar = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
int maxPos = allChar.length();
String nonceStr = "";
for ( int i = 0; i < 16; i++) {
nonceStr += allChar.charAt((int) Math.floor(Math.random() * maxPos));
}
return nonceStr;
}
/**
* @author : YY
* @date : 2018-7-3-下午04:26:04
* @param : @return
* @Description: 获取当前时间字符串
*/
public static String getCurrTime() {
Date now = new Date();
SimpleDateFormat outFormat = new SimpleDateFormat("yyyyMMddHHmmss");
String s = outFormat.format(now);
return s;
}
/**
* @author : YY
* @date : 2018-7-3-下午04:30:48
* @param : @return
* @Description: 获取10分钟后的时间字符串
*/
public static String getAddCueeTime(){
long currentTime = System.currentTimeMillis() ;
currentTime +=10*60*1000;
Date date=new Date(currentTime);
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmss");
String s = dateFormat.format(date);
return s;
}
/**
* @author : YY
* @date : 2018-7-3-下午04:26:58
* @param : @param length
* @param : @return
* @Description: 生成指定长度随机数
*/
public static int buildRandom(int length) {
int num = 1;
double random = Math.random();
if (random < 0.1) {
random = random + 0.1;
}
for (int i = 0; i < length; i++) {
num = num * 10;
}
return (int) ((random * num));
}
/**
* @author : YY
* @date : 2018-7-3-下午04:43:23
* @param : @param params
* @param : @param mchKey
* @param : @return
* @Description: 生成微信支付签名
*/
@SuppressWarnings("unchecked")
public static String createSign(SortedMap<String, String> params, String mchKey) {
StringBuffer sb = new StringBuffer();
Set set = params.entrySet();
Iterator it = set.iterator();
while(it.hasNext()){
Entry entry = (Entry) it.next();
String k = (String) entry.getKey();
String v = (String) entry.getValue();
if(null != v && !"".equals(v) && !"sign".equals(k) && !"key".equals(k)){
sb.append(k + "=" + v + "&");
}
}
sb.append("key=" + mchKey);
String sign = ZbMD5Util.EncryptionStr(sb.toString()).toUpperCase();
return sign;
}
/**
* @author : YY
* @date : 2018-7-4-下午03:21:07
* @param : @param packageParams
* @param : @param mchKey
* @param : @return
* @Description: 验证签名 是否签名正确,规则是:按参数名称a-z排序,遇到空值的参数不参加签名。
*/
@SuppressWarnings("unchecked")
public static boolean isTenpaySign(SortedMap<String, String> packageParams,String mchKey) {
if(packageParams != null){
StringBuffer sb = new StringBuffer();
Set es = packageParams.entrySet();
Iterator it = es.iterator();
while (it.hasNext()) {
Entry entry = (Map.Entry) it.next();
String k = (String) entry.getKey();
String v = (String) entry.getValue();
if (!"sign".equals(k) && null != v && !"".equals(v)) {
sb.append(k + "=" + v + "&");
}
}
sb.append("key=" + mchKey);
// 算出摘要
String mysign = MD5Util.EncryptionStr(sb.toString()).toLowerCase();
String tenpaySign = ((String) packageParams.get("sign")).toLowerCase();
return tenpaySign.equals(mysign);
}else{
return false;
}
}
@SuppressWarnings("unchecked")
public static SortedMap<String, String> xmlConvertToMap(String rxml) {
// 解析xml成map
Map<String, String> resultMap = new HashMap<String, String>();
try {
Document dom = DocumentHelper.parseText(rxml);
Element root = dom.getRootElement();
List<Element> elementList = root.elements();
for (Element e : elementList) {
resultMap.put(e.getName(), e.getText());
}
} catch (Exception e) {
e.printStackTrace();
}
// 过滤空 设置 SortedMap
SortedMap<String, String> packageParams = new TreeMap<String, String>();
Iterator it = resultMap.keySet().iterator();
while (it.hasNext()) {
String parameter = (String) it.next();
String parameterValue = resultMap.get(parameter);
String v = "";
if (null != parameterValue) {
v = parameterValue.trim();
}
packageParams.put(parameter, v);
}
return packageParams;
}
/**
* @author : YY
* @date : 2018-7-3-下午04:48:23
* @param : @param arr
* @param : @return
* @Description: 将map数据转化为xml格式字符串
*/
public static String MapToXml(Map<String, String> arr) {
String xml = "<xml>";
Iterator<Entry<String, String>> iter = arr.entrySet().iterator();
while (iter.hasNext()) {
Entry<String, String> entry = iter.next();
String key = entry.getKey();
String val = entry.getValue();
if (IsNumeric(val)) {
xml += "<" + key + ">" + val + "</" + key + ">";
} else
xml += "<" + key + "><![CDATA[" + val + "]]></" + key + ">";
}
xml += "</xml>";
return xml;
}
private static boolean IsNumeric(String str) {
if (str.matches("\\d *")) {
return true;
} else {
return false;
}
}
}
public class MD5Util {
/**
* @author : YY
* @time : 2018-7-3-下午04:44:24
* @package : package com.tcmce.zibo.wxpay.utils;
* @description : MD5加密字符串
*/
public static String EncryptionStr(String str){
StringBuffer sb = new StringBuffer();
try {
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(str.getBytes());
byte[] bytes = md.digest();
for (byte aByte : bytes) {
String s=Integer.toHexString(0xff & aByte);
if(s.length()==1){
sb.append("0"+s);
}else{
sb.append(s);
}
}
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return sb.toString();
}
}
public class HttpUtil {
/**
* @author : YY
* @date : 2018-7-6-上午11:25:01
* @param : @param urlStr
* @param : @param data
* @param : @return
* @param : @throws DocumentException
* @Description: HTTP请求微信接口,返回map格式数据
*/
@SuppressWarnings("unchecked")
public static Map<String, String> postWx(String requestUrl, String outputStr) throws DocumentException{
try {
URL url = new URL(requestUrl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setDoOutput(true);
conn.setDoInput(true);
conn.setUseCaches(false);
// 设置请求方式(GET/POST)
//conn.setRequestMethod(requestMethod);
conn.setRequestProperty("content-type", "application/x-www-form-urlencoded");
// 当outputStr不为null时向输出流写数据
if (null != outputStr) {
OutputStream outputStream = conn.getOutputStream();
// 注意编码格式
outputStream.write(outputStr.getBytes("UTF-8"));
outputStream.close();
}
// 从输入流读取返回内容
InputStream inputStream = conn.getInputStream();
InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "utf-8");
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
String str = null;
StringBuffer buffer = new StringBuffer();
while ((str = bufferedReader.readLine()) != null) {
buffer.append(str);
}
// 释放资源
bufferedReader.close();
inputStreamReader.close();
inputStream.close();
inputStream = null;
conn.disconnect();
if(buffer != null && buffer.toString().length() != 0){
Map<String, String> preMap = new HashMap<String, String>();
Document dom = DocumentHelper.parseText(buffer.toString());
Element root = dom.getRootElement();
List<Element> elementList = root.elements();
for (Element e : elementList) {
preMap.put(e.getName(), e.getText());
}
return preMap;
}
} catch (Exception e) {
logger.error("HTTP请求:["+requestUrl+"]异常: " + e.getMessage());
}
return null;
}
}
三.编写调用微信下单接口(使用AJAX请求该地址,返回JSON格式数据,返回支付二维码地址,用于下一步生成二维码图片)
public class UnifiedOrderServlet extends HttpServlet{
private static Logger logger = Logger.getLogger(UnifiedOrderServlet.class);
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doPost(request, response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setCharacterEncoding("utf-8");
response.setCharacterEncoding("utf-8");
Map<String, String> msgMap = new HashMap<String, String>();
try {
String prodcutId = request.getParameter("prodcutId").trim();//商品ID
String orderType = request.getParameter("orderType").trim();//订单类型
//生成商户订单号
String outTradeNo = WxPayUtil.getCurrTime() + WxPayUtil.buildRandom(4);
//获取商品价格
String totalFee = "999";
//回调参数 长度有限制
Map<String, String> attachMap = new HashMap<String, String>();
attachMap.put("memberId", decryMemberId);
attachMap.put("prodcutId", prodcutId);
JSONObject attachJson = JSONObject.fromObject(attachMap);
//完善微信下单所需参数
SortedMap<String, String> orderParams = new TreeMap<String, String>();
orderParams.put("appid", WxPayConfig.APP_ID);
orderParams.put("mch_id", WxPayConfig.MCH_ID);
orderParams.put("nonce_str", WxPayUtil.getNonceStr());
orderParams.put("body", "商品描述");//商品描述
orderParams.put("attach", attachJson.toString());
orderParams.put("out_trade_no", outTradeNo);
orderParams.put("total_fee", new BigDecimal(totalFee).multiply(new BigDecimal(100)).intValue() + "");//订单总金额,单位为分
orderParams.put("spbill_create_ip", request.getRemoteAddr());
orderParams.put("time_start", WxPayUtil.getCurrTime());交易起始时间
orderParams.put("time_expire", WxPayUtil.getAddCueeTime());//交易结束时间
orderParams.put("notify_url", WxPayConfig.NOTIFY_URL);//异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数。
orderParams.put("trade_type", "NATIVE");//交易类型 JSAPI 公众号支付 NATIVE 扫码支付 APP APP支付
orderParams.put("device_info", "WEB");//终端设备号(门店号或收银设备ID),默认请传"WEB"
orderParams.put("product_id", prodcutId);//商品ID
String sign = WxPayUtil.createSign(orderParams, WxPayConfig.MCH_KEY);//将所有参数进行签名
orderParams.put("sign", sign);//签名
String xmlContent = WxPayUtil.MapToXml(orderParams);
//调用微信下单接口
Map<String, String> resultMap = HttpUtil.postWx(WxPayConfig.UFDODER_URL, xmlContent);
logger.info("下单结果:" + resultMap.toString());
//下单成功
if("SUCCESS".equals(resultMap.get("result_code"))){
//二维码链接
orderParams.put("code_url", resultMap.get("code_url"));
//预支付ID
orderParams.put("prepay_id", resultMap.get("prepay_id"));
orderParams.put("total_fee", totalFee.toString());
orderParams.put("payType", payType);
orderParams.put("memberId", decryMemberId);
orderParams.put("trade_state", "NOTPAY");
orderParams.put("trade_state_desc", "未支付");
orderParams.put("wx_pc", "PC");
orderParams.put("pay_source", "ZB");
//商户系统记录订单信息
此处代码省略
//以下参数为了页面显示所用,
msgMap.put("body", body);
msgMap.put("outTradeNo", outTradeNo);
msgMap.put("totalFee", totalFee.toString());
msgMap.put("codeUrl", resultMap.get("code_url"));//二维码支付地址
msgMap.put("success", "true");
}else{
msgMap.put("success", "false");
msgMap.put("message", "下单失败,您可以尝试稍后再进行下单!");
}
} catch (Exception e) {
msgMap.put("success", "false");
msgMap.put("message", "系统异常,您可以稍后再进行尝试下单!");
logger.error("调用微信下单接口异常:" + e.getMessage());
}
Gson gson = new Gson();
String json = gson.toJson(msgMap);
PrintWriter out = response.getWriter();
out.write(json);
out.close();
}
}
三.利用上一步获得的二维码路径生成二维码,用于页面展示:
public class CreateCodeServlet extends HttpServlet{
//二维码默认尺寸
private static int defaultWidthAndHeight = 250;
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doPost(request, response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setCharacterEncoding("utf-8");
response.setCharacterEncoding("utf-8");
try {
String codeUrl = request.getParameter("code_url").trim();
//以下开始生成二维码
Map<EncodeHintType, Object> hints = new HashMap<EncodeHintType, Object>();
//指定纠错等级
hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.L);
//指定编码格式
hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
//hints.put(EncodeHintType.MARGIN, 1);
try {
BitMatrix bitMatrix = new MultiFormatWriter().encode(codeUrl, BarcodeFormat.QR_CODE, defaultWidthAndHeight, defaultWidthAndHeight,hints);
OutputStream outputStream = response.getOutputStream();
MatrixToImageWriter.writeToStream(bitMatrix, "png", outputStream);//输出二维码
outputStream.flush();
outputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
四.实现支付成功后微信后台回调的函数,第一个配置文件里最后一个参数就是指向这里,必须可以外网访问的地址:
public class WxPayNotifyServlet extends HttpServlet{
private static Logger logger = Logger.getLogger(WxPayNotifyServlet.class);
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doPost(request, response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setCharacterEncoding("utf-8");
response.setCharacterEncoding("utf-8");
String resXml = "";
try {
InputStream inputStream = request.getInputStream();
StringBuffer sb = new StringBuffer();
String s;
BufferedReader in = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
while((s = in.readLine()) != null){
sb.append(s);
}
in.close();
inputStream.close();
logger.error("支付结果:" + sb.toString());
if(sb != null && sb.toString().length() != 0){
SortedMap<String, String> resultMap = ZbWxPayUtil.xmlConvertToMap(sb.toString());
//验证签名
if(ZbWxPayUtil.isTenpaySign(resultMap, ZbWxPayConfig.MCH_KEY)){
if("SUCCESS".equals(resultMap.get("return_code")) && "SUCCESS".equals(resultMap.get("result_code"))){
//调用支付成功后执行业务逻辑代码
Integer rows = this.executePayNotify(resultMap);
if(rows != 0){
resXml = "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>";
}else{
logger.error("----业务结果执行失败 返回FALL 通知微信继续重试----");
resXml = "<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[商户系统出错]]></return_msg></xml> ";
}
}else{
logger.error("----支付失败----" + resultMap.get("return_msg"));
resXml = "<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[支付失败]]></return_msg></xml>";
}
}else{
logger.error("----签名验证失败----");
resXml = "<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[签名验证失败]]></return_msg></xml> ";
}
}else{
logger.error("----未接收到微信通知信息----");
resXml = "<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[未接收到回调信息]]></return_msg></xml> ";
}
} catch (Exception e) {
logger.error("----商户系统异常----");
resXml = "<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[商户系统异常]]></return_msg></xml> ";
e.printStackTrace();
}
BufferedOutputStream out = new BufferedOutputStream(response.getOutputStream());
out.write(resXml.getBytes());
out.flush();
out.close();
}
/**
* @author : YY
* @date : 2018-7-6-下午12:59:58
* @param : @param resultMap
* @param : @return
* @param : @throws Exception
* @Description: 微信扫码支付成功后执行业务代码
*/
private Integer executePayNotify(Map<String, String> resultMap) throws Exception{
Double cashFee = Double.parseDouble(resultMap.get("cash_fee")) / 100,//现金支付金额
totalFee = Double.parseDouble(resultMap.get("total_fee")) / 100;//总金额
String bankType = resultMap.get("bank_type"),//付款银行
feeType = resultMap.get("fee_type"),//货币种类
openid = resultMap.get("openid"),//用户标识
timeEnd = resultMap.get("time_end"),//支付完成时间
transactionId = resultMap.get("transaction_id"),//微信支付订单号
out_trade_no = resultMap.get("out_trade_no"),//商户订单号
attach = resultMap.get("attach");//商家数据包 传入的是JSON格式字符串
//解析订单携带参数
JSONObject attachJsonObj = JSONObject.fromObject(attach);
Map<String, String> argsMap = new HashMap<String, String>();
argsMap.put("bankType", bankType);
argsMap.put("cashFee", cashFee.toString());
argsMap.put("feeType", feeType);
argsMap.put("openid", openid);
argsMap.put("timeEnd", timeEnd);
argsMap.put("memberId", memberId);
argsMap.put("transactionId", transactionId);
argsMap.put("out_trade_no", out_trade_no);
argsMap.put("trade_state", "SUCCESS");
argsMap.put("trade_state_desc", "支付成功");
此处开始执行商户系统逻辑业务代码,自主实现
return 0;
}
}
五.支付功能到此结束,此处添加一个支付状态查询功能,支付成功后微信后台不会主动通知支付页面,需要自主添加查询按钮或定时查询功能:
public class QueryOrderStatusServlet extends HttpServlet{
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doPost(request, response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setCharacterEncoding("utf-8");
response.setCharacterEncoding("utf-8");
Map<String, String> result = new HashMap<String, String>();
try {
String outTradeNo = request.getParameter("outTradeNo");
if(StringUtils.isNotBlank(outTradeNo)){
String nonceStr = WxPayUtil.getNonceStr();
SortedMap<String, String> orderParams = new TreeMap<String, String>();
orderParams.put("appid", WxPayConfig.APP_ID);
orderParams.put("mch_id", WxPayConfig.MCH_ID);
orderParams.put("out_trade_no", outTradeNo);
orderParams.put("nonce_str", nonceStr);
//参数签名
String sign = WxPayUtil.createSign(orderParams, WxPayConfig.MCH_KEY);
orderParams.put("sign", sign);
String xmlContent = WxPayUtil.MapToXml(orderParams);
Map<String, String> resultMap = HttpUtil.postWx(WxPayConfig.ORDER_QUERY_URL, xmlContent);
if("SUCCESS".equals(resultMap.get("return_code"))){
if("SUCCESS".equals(resultMap.get("result_code"))){
String tradeState = resultMap.get("trade_state");
if("SUCCESS".equals(tradeState)){
result.put("code", "success");
result.put("msg", "支付成功 [ 即将跳转至学习页面......请稍后!]。");
}else{
result.put("code", "fail");
result.put("msg", resultMap.get("trade_state_desc") + " [ 请您在微信上确认支付成功后,再执行该操作。]");
}
}else{
result.put("code", "fail");
result.put("msg", resultMap.get("err_code_des"));
}
}else{
result.put("code", "fail");
result.put("msg", "网络异常,您可以稍后再试!");
}
}else{
result.put("code", "fail");
result.put("msg", "订单号有误!");
}
} catch (Exception e) {
result.put("code", "fail");
result.put("msg", "系统异常,您可以稍后再试!");
e.printStackTrace();
}
Gson gson = new Gson();
String json = gson.toJson(result);
PrintWriter out = response.getWriter();
out.write(json);
out.close();
}
}