关于支付这一块,在没有接触之前,感觉还是很神奇的,以为会很麻烦,在同事写了一半的代码上,参考微信API支付文档,一天就搞定了。
在这之前,第一次接触支付的时候,以为只能用服务器测支付的,但是发现调试很麻烦,还要看服务器上的tomcat的打印信息,这次的测试服务器还有点问题,tomcat一直打印一些刷新xml文件的错误信息,但是不影响项目的正常使用。这就很尴尬了,写好了,没法测了都。。。。
在向同事请教之后,同事让我弄个花生壳试试。
之前听说过花生壳,但是具体没有用过,在安装之后,注册账号,开通了一个体验版的,6块钱永久的,用来测试支付还是不错的。发现这就是一个内网渗透的工具,我做毕业设计的时候找了一个免费的,但是不稳定,网速还特别慢,这个花生壳还挺好用的。
直接百度花生壳
,进去官网,注册账号,然后会弹出来购买域名啥的,6块钱弄个体验版的玩玩就可以。
在下图的控制台里面,配置内网渗透的ip,也就是你的电脑的ip和映射的端口。
由于体验版只有一个映射到80端口的地址,部署到测试服务器上之后,记得将内网主机改为测试服务器的ip和端口。
这个外网访问的ip地址,就是后面,项目中配置的回调地址的ip,这个很重要,每次支付成功之后,如果没有进入回调方法,首先要确定,这个ip能访问到你的tomcat,这个必须保证。
下面才是支付开始。
首先要理清楚的就是APP支付的流程,这里贴上微信支付文档里的图:
可能第一次看,看不出来个所以然来,比如我,之前看了半天,还是一脸懵逼的。
如果你也看不出来什么,那么也不用着急,希望你看完这篇博客之后,再回来看这个图,能有所收获。
首先就是微信的配置文件:Configure.java
package com.thinkgem.jeesite.modules.weixin.util;
public class Configure {
//商户支付密钥
public static final String WEIXIN_KEY = "NUQKAR2IB2UOFP1JGDYBIMPXRDKSHD";
//应用ID
public static final String WEIXIN_APPID = "wx95e64fc7ddc53f46";
//应用AppSecret
public static final String WEIXIN_APPSECRET = "b1c864f6041ff345345e165643a1790543";
//商户号
public static final String WEIXIN_MCHID = "1522275497";
// 交易类型
public static final String TRADE_TYPE = "APP";
//支付回调notify_url
public static final String NOTIFY_URL = "http://上面注册的花生壳的域名/ejtapp/PayCtrl/WeiXinNotifyUrl.do";
//订单描述
public static final String BODY = "e境通-商城消费";
public static final String RECHARGE = "e境通-会员充值";
//扩展字段
public static final String PACKAGE_INFO="Sign=WXPay";
//获取tokenurl
public static final String GET_ACCESSTOKEN_URL="https://api.weixin.qq.com/sns/oauth2/access_token";
//获取用户信息url
public static final String GET_USERINFO_URL="https://api.weixin.qq.com/sns/userinfo";
}
配置文件里的这些字段的内容,都是可以在微信商户平台上可以找到的,这些字段信息在后面用到的地方还是比较多的,所以抽离出来作为一个单独的文件。
然后是第一步:先生成预支付交易单
具体文档地址:https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=9_1
具体的参数说明可以在上面的文档说明里面找到,关于请求参数里面的必填字段,都是必须要传的。
既然要生成预支付订单,肯定是先从APP端发起请求到服务端,将订单数据传过来,由服务器跟微信支付的服务器进行交流,所以需要先写一个接口,来告诉服务器,有新订单要用微信付款了。
/**
* 微信支付订单
*
* @param request
* @param response
* @param data
* @return
*/
@RequestMapping("/weiXinPay")
@ResponseBody
public JSONObject WeiXinPay(HttpServletRequest request, HttpServletResponse response, String data) {
JSONObject obj = JSON.parseObject(data);
String orderId = obj.getString("orderId"); // APP端传来订单编号
System.out.println("========APP调用微信统一下单接口========");
ShopOrder findByOrderId = shopOrderService.findByOrderId(orderId); // 利用订单编号,获取订单信息
Map<String, String> wxAppPay = null;
try {
wxAppPay = WechatPayService.WxAppPay(findByOrderId); // 将订单实体传入微信统一下单接口
System.out.println("返回的Map数据wxAppPay:" + wxAppPay);
} catch (Exception e) {
e.printStackTrace();
return JsonUtil.getJson(40, null, "下单失败");
}
System.out.println("result_code:" + wxAppPay.get("result_code"));
System.out.println("return_code:" + wxAppPay.get("return_code"));
if (wxAppPay.get("result_code").equals("SUCCESS") && wxAppPay.get("return_code").equals("SUCCESS")) {
boolean tenpaySign = WeiXinPayUtil.isTenpaySign(wxAppPay);
System.out.println("验证签名结果:" + tenpaySign);
if (tenpaySign) {
// 封装返回信息
SortedMap<String, String> map = new TreeMap<String, String>();
map.put("appid", Configure.WEIXIN_APPID);
map.put("partnerid", Configure.WEIXIN_MCHID);
map.put("prepayid", wxAppPay.get("prepay_id")); // 预支付交易会话标识
map.put("package", Configure.PACKAGE_INFO);
map.put("noncestr", wxAppPay.get("nonce_str")); //随机字符串
map.put("timestamp", String.valueOf(System.currentTimeMillis()).substring(0, 10));
String Sign = WeiXinPayUtil.createSign("UTF-8", map);
map.put("sign", Sign);
System.out.println("==========微信统一下单成功=======");
return JsonUtil.getJson(10, map, "微信统一下单成功");
} else {
return JsonUtil.getJson(30, null, "微信返回签名验证失败");
}
} else {
return JsonUtil.getJson(20, null, "微信统一下单失败");
}
//return JsonUtil.getJson(40, null, "下单失败");
}
下面会将所用到的工具和方法,全部贴出来,只要一步步来就可以了:
WechatPayService.java
/**
* 商品订单
* @param order
* @return
* @throws Exception
*/
public static Map<String, String> WxAppPay(ShopOrder order) throws Exception {
SortedMap<String, String> map = new TreeMap<String, String>();
map.put("appid", Configure.WEIXIN_APPID); // 应用ID
map.put("mch_id", Configure.WEIXIN_MCHID); // 商户号
map.put("nonce_str", WeiXinPayUtil.getRandomStringByLength(32)); // 随机串
map.put("body", Configure.BODY); // 商品内容
map.put("attach", "order"); // 附加数据 表示此次支付的是商品订单
map.put("out_trade_no",String.valueOf( order.getOrderSn())); // 订单号
//计算出总金额以分为单位
String changeY2F = WeiXinPayUtil.changeY2F(order.getOrderAmount());
//System.out.println(mul+":"+changeY2F);
map.put("total_fee",changeY2F);// 支付金额 以分为单位
map.put("spbill_create_ip",InetAddress.getLocalHost().getHostAddress()); // 机器IP
//map.put("time_expire", ""); // 过期时间 可根据需要是否添加
map.put("notify_url", Configure.NOTIFY_URL); // 回调 通知地址
map.put("trade_type", Configure.TRADE_TYPE);// 交易类型
//String xml = WXPayUtil.ArrayToXml(map, "你的商户KEY");
//System.out.println(map);
String Sign = WeiXinPayUtil.createSign("UTF-8",map);
map.put("sign", Sign);
String xml = WeiXinPayUtil.ArrayToXml(map);
//System.out.println(xml);
String sendPost = WeiXinPayUtil.httpsRequest("https://api.mch.weixin.qq.com/pay/unifiedorder", "POST",xml);
Map<String, String> xmlToMap = WeiXinPayUtil.xmlToMap(sendPost);
return xmlToMap;
}
WeiXinPayUtil.java
package com.thinkgem.jeesite.modules.weixin.util;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.ConnectException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Random;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import javax.servlet.http.HttpServletRequest;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.apache.log4j.Logger;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xmlpull.v1.XmlPullParserException;
import com.thinkgem.jeesite.modules.ejt.util.MD5Util;
public class WeiXinPayUtil {
/**
* 日志记录
*/
private static Logger logger = Logger.getLogger(WeiXinPayUtil.class);
public static void main(String[] args) throws Exception {
Map<String, String> xmlToMap = xmlToMap(" ");
System.err.println(xmlToMap);
}
/**
* 将map转换为xml
* @param arr
* @return
*/
public static String ArrayToXml(Map<String, String> arr) {
String 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();
xml += "<" + key + ">" + val + "" + key + ">";
}
xml += "";
return xml;
}
/**
* 请求方法
* @param requestUrl
* @param requestMethod
* @param outputStr
* @return
*/
public static String httpsRequest(String requestUrl, String requestMethod, String outputStr) {
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();
return buffer.toString();
} catch (ConnectException ce) {
logger.info("连接超时:{}"+ ce);
} catch (Exception e) {
logger.info("https请求异常:{}"+ e);
}
return null;
}
/**
* 微信回调参数解析
* @param request
* @return
*/
public static String getPostStr(HttpServletRequest request){
StringBuffer sb = new StringBuffer();
try {
InputStream is = request.getInputStream();
InputStreamReader isr = new InputStreamReader(is, "UTF-8");
BufferedReader br = new BufferedReader(isr);
String s = "";
while ((s = br.readLine()) != null) {
sb.append(s);
}
} catch (IOException e) {
e.printStackTrace();
}
String xml = sb.toString(); //次即为接收到微信端发送过来的xml数据
logger.info(xml+"========================");
return xml;
}
//定义签名,微信根据参数字段的ASCII码值进行排序 加密签名,故使用SortMap进行参数排序
public static String createSign(String characterEncoding,Map<String, String> parameters){
StringBuffer sb = new StringBuffer();
Set es = parameters.entrySet();
Iterator it = es.iterator();
while(it.hasNext()) {
Map.Entry entry = (Map.Entry)it.next();
String k = (String)entry.getKey();
Object v = entry.getValue();
if(null != v && !"".equals(v)
&& !"sign".equals(k) && !"key".equals(k)) {
sb.append(k + "=" + v + "&");
}
}
sb.append("key=" + Configure.WEIXIN_KEY);//最后加密时添加商户密钥,由于key值放在最后,所以不用添加到SortMap里面去,单独处理,编码方式采用UTF-8
String sign = MD5Util.MD5Encode(sb.toString(), characterEncoding).toUpperCase();
return sign;
}
/**
* 获取随机字符串
* @param length
* @return
*/
public static String getRandomStringByLength(int length) {
String base = "abcdefghijklmnopqrstuvwxyz0123456789";
Random random = new Random();
StringBuffer sb = new StringBuffer();
for (int i = 0; i < length; i++) {
int number = random.nextInt(base.length());
sb.append(base.charAt(number));
}
return sb.toString();
}
//元转分
public static String changeY2F(String amount) {
String currency = amount.replaceAll("\\$|\\¥|\\,", ""); // 处理包含, ¥
// 或者$的金额
int index = currency.indexOf(".");
int length = currency.length();
Long amLong = 0l;
if (index == -1) {
amLong = Long.valueOf(currency + "00");
} else if (length - index >= 3) {
amLong = Long.valueOf((currency.substring(0, index + 3)).replace(".", ""));
} else if (length - index == 2) {
amLong = Long.valueOf((currency.substring(0, index + 2)).replace(".", "") + 0);
} else {
amLong = Long.valueOf((currency.substring(0, index + 1)).replace(".", "") + "00");
}
return amLong.toString();
}
/**
* 分转元
* 这个是真的被坑到了,在后面进行金额校验时,微信返回的支付金额仍然是分
* @param amount
* @return
*/
public static String fenToYuan(String amount){
/**金额为分的格式 */
if(!amount.matches("\\-?[0-9]+")) {
logger.error(" FuYouFile 金额格式有误");
return "0";
}
return BigDecimal.valueOf(Long.valueOf(amount)).divide(new BigDecimal(100)).toString();
}
/**
* 验证回调签名
* @return
*/
public static boolean isTenpaySign(Map<String, String> map) {
String characterEncoding="utf-8";
String charset = "utf-8";
String signFromAPIResponse = map.get("sign");
if (signFromAPIResponse == null || signFromAPIResponse.equals("")) {
System.out.println("API返回的数据签名数据不存在,有可能被第三方篡改!!!");
return false;
}
System.out.println("服务器回包里面的签名是:" + signFromAPIResponse);
//过滤空 设置 TreeMap
SortedMap<String,String> packageParams = new TreeMap();
for (String parameter : map.keySet()) {
String parameterValue = map.get(parameter);
String v = "";
if (null != parameterValue) {
v = parameterValue.trim();
}
packageParams.put(parameter, v);
}
StringBuffer sb = new StringBuffer();
Set es = packageParams.entrySet();
Iterator it = es.iterator();
while(it.hasNext()) {
Map.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=" + Configure.WEIXIN_KEY);
//将API返回的数据根据用签名算法进行计算新的签名,用来跟API返回的签名进行比较
//算出签名
String resultSign = "";
String tobesign = sb.toString();
if (null == charset || "".equals(charset)) {
resultSign = MD5Util.MD5Encode(tobesign, characterEncoding).toUpperCase();
}else{
try{
resultSign = MD5Util.MD5Encode(tobesign, characterEncoding).toUpperCase();
}catch (Exception e) {
resultSign = MD5Util.MD5Encode(tobesign, characterEncoding).toUpperCase();
}
}
String tenpaySign = ((String)packageParams.get("sign")).toUpperCase();
return tenpaySign.equals(resultSign);
}
/**
* 解析xml为map
* @param xml
* @return
* @throws Exception
* @throws XmlPullParserException
* @throws IOException
*/
public static Map<String, String> xmlToMap(String strXML) throws Exception {
try{
Map<String, String> data = new HashMap<String, String>();
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
InputStream stream = new ByteArrayInputStream(strXML.getBytes("UTF-8"));
org.w3c.dom.Document doc = documentBuilder.parse(stream);
doc.getDocumentElement().normalize();
NodeList nodeList = doc.getDocumentElement().getChildNodes();
for (int idx = 0; idx < nodeList.getLength(); ++idx) {
Node node = nodeList.item(idx);
if (node.getNodeType() == Node.ELEMENT_NODE) {
org.w3c.dom.Element element = (org.w3c.dom.Element) node;
data.put(element.getNodeName(), element.getTextContent());
}
}
try {
stream.close();
} catch (Exception ex){
// do nothing
}
return data;
}catch (Exception e) {
}
return null;
}
}
MD5Util.java
package com.thinkgem.jeesite.modules.ejt.util;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;
import javax.servlet.http.HttpServletRequest;
public class MD5Util {
private static final String hexDigits[] = { "0", "1", "2", "3", "4", "5",
"6", "7", "8", "9", "a", "b", "c", "d", "e", "f" };
// 字符串拼接
public static String getDigest(TreeMap<String, String> map, String key,
String value) {
StringBuilder sb = new StringBuilder();
for (Map.Entry entry : map.entrySet()) {
sb = sb.append(entry.getKey()).append("=").append(entry.getValue())
.append("&");
}
sb.append(key).append("=").append(value);
System.out.println("拼接后的字符:" + sb.toString());
return sb.toString();
}
/*
* 加密算法
*/
public static String encode(String text) throws UnsupportedEncodingException{
try {
MessageDigest digest = MessageDigest.getInstance("md5");
//digest.update(str.getBytes("UTF-8"));
byte[] result = digest.digest(text.getBytes("UTF-8"));
StringBuilder sb =new StringBuilder();
for(byte b:result){
int number = b&0xff;
String hex = Integer.toHexString(number);
if(hex.length() == 1){
sb.append("0"+hex);
}else{
sb.append(hex);
}
}
return sb.toString().toUpperCase();
} catch (NoSuchAlgorithmException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return "" ;
}
//通过io流获取请求参数
public static TreeMap<String, String> getParamString(HttpServletRequest request) {
Map<String, String[]> map = request.getParameterMap();
TreeMap<String, String> treeMap = new TreeMap<String, String>();
for (Entry<String, String[]> e : map.entrySet()) {
String key = e.getKey();
String[] value = e.getValue();
if(!"sign".equals(key)&&value != null && value.length == 1&&!"".equals(value[0])){
treeMap.put(key, value[0]);
}
}
return treeMap;
}
public static String MD5Encode(String origin, String charsetname) {
String resultString = null;
try {
resultString = new String(origin);
MessageDigest md = MessageDigest.getInstance("MD5");
if (charsetname == null || "".equals(charsetname))
resultString = byteArrayToHexString(md.digest(resultString
.getBytes()));
else
resultString = byteArrayToHexString(md.digest(resultString
.getBytes(charsetname)));
} catch (Exception exception) {
}
return resultString;
}
private static String byteArrayToHexString(byte b[]) {
StringBuffer resultSb = new StringBuffer();
for (int i = 0; i < b.length; i++)
resultSb.append(byteToHexString(b[i]));
return resultSb.toString();
}
private static String byteToHexString(byte b) {
int n = b;
if (n < 0)
n += 256;
int d1 = n / 16;
int d2 = n % 16;
return hexDigits[d1] + hexDigits[d2];
}
}
到这里,我们总结一下,目前到底做了哪些事情,支付走到哪一步了。
预支付交易会话标识:prepay_id
然后服务器就等微信服务器和APP端他俩交流了。
如果用户支付成功,微信服务器便开始请求回调函数,也就是我们之前在Configure.java里面定义的回调地址的接口,这个接口在微信文档里面有说明
https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=9_7&index=3
这里再次强调一下,回调函数的接口链接,必须要外网能访问到才可以成功回调,否则微信收不到回调结果,会在一定时间间隔内,多次发起回调请求。微信总共会发起10次通知,每次通知时间距离最近一次的间隔为15/15/30/180/1800/1800/1800/1800/3600,单位:秒),但微信不保证通知最终一定能成功。也就是说,如果回调接口挂了,用户明明付款成功了,但是数据库里的订单状态还未来得及更新,会仍然显示订单未支付。
/**
* 微信订单支付回调通知地址
*
* @param request
* @param response
* @param data
* @throws Exception
*/
@RequestMapping("/WeiXinNotifyUrl")
@ResponseBody
public String WeiXinNotifyUrl(HttpServletRequest request, HttpServletResponse response)
throws Exception {
System.out.println("=========微信支付统一回调=========");
InputStream inStream;
Map<String, String> params = null;
inStream = request.getInputStream();
ByteArrayOutputStream outSteam = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len = 0;
while ((len = inStream.read(buffer)) != -1) {
outSteam.write(buffer, 0, len);
}
String resultxml = new String(outSteam.toByteArray(), "utf-8");
params = WeiXinPayUtil.xmlToMap(resultxml);
outSteam.close();
inStream.close();
if (!WeiXinPayUtil.isTenpaySign(params)) {
// 支付失败,签名验证失败
return setXml("FAIL", "return_code不正确");
} else {
System.out.println("===============付款成功==============");
// ------------------------------
// 处理业务开始
// ------------------------------
// 此处处理订单状态,结合自己的订单数据完成订单状态的更新
// ------------------------------
String orderNum = params.get("out_trade_no");
System.out.println("微信支付回调返回的订单号:" + orderNum);
String transactionId = params.get("transaction_id"); //微信支付单号
String totaFee = params.get("total_fee");
System.out.println("微信返回的订单金额:" + totaFee); // 用来对比订单中的金额,防止数据被恶意篡改
String attach = params.get("attach"); // 订单类别 这个字段是前面自定义的字段内容
if (attach.equals("order")) { // 商品订单支付回调更新订单数据
ShopOrder findByOrderNo = shopOrderService.findByOrderNo(orderNum);
if (totaFee.equals(findByOrderNo.getOrderAmount())) {
findByOrderNo.setPaySn(transactionId);
findByOrderNo.setOrderState("20");
// 更新订单状态
int selective = shopOrderService.updateByPrimaryKeySelective(findByOrderNo);
if (selective > 0) {
System.out.println("===============更新商品订单成功==============");
return setXml("SUCCESS", "OK");
}
}
}else if (attach.equals("renew")) { // 会员续费订单支付回调更新续费记录
boolean updateRecordLog = updateRecordLog(orderNum, "wechat", transactionId);
if (updateRecordLog) {
return setXml("SUCCESS", "OK");
}
}else { // 会员充值订单支付回调更新充值记录
ShopPdRecharge recharge = shopPdRechargeService.selectByPdrSn(orderNum);
recharge.setPdrPaymentCode("wechat");
recharge.setPdrPaymentName("微信");
recharge.setPdrTradeSn(transactionId);
recharge.setPdrPaymentTime(ZoscDateUtil.getTime());
int updateRecharge = shopPdRechargeService.updateByPrimaryKeySelective(recharge);
ShopMember member = shopMemberService.findByMemberId(recharge.getPdrMemberId());
String oldBalance = member.getAvailablePredeposit();
String newBalance = BigDecimalUtil.strAdd(oldBalance, recharge.getPdrAmount());
member.setAvailablePredeposit(newBalance);
int updateMember = shopMemberService.updateByPrimaryKeySelective(member);
if (updateRecharge + updateMember > 1) {
System.out.println("===============更新充值订单成功==============");
return setXml("SUCCESS", "OK");
}
}
return null;
}
}
// 通过xml 发给微信消息
public static String setXml(String return_code, String return_msg) {
return " + return_code + "]]>" + " + return_msg
+ "]]> ";
}
可以看到,回调是微信服务器请求项目服务器,返回的是"SUCCESS", "OK"
两个参数,告诉微信,我知道支付成功了,数据库中的订单状态也改好了,你不用请求了。这点可以在下图中可以看到
并且在这一页的最下面可以看到返回的数据格式:
到这里,可以说微信支付已经初步完成了,订单状态也更新完了,但是微信API文档里面,还有一个查询订单的玩意,这个是用来干嘛的呢?看下图
应用场景:
该接口提供所有微信支付订单的查询,商户可以通过该接口主动查询订单状态,完成下一步的业务逻辑。
需要调用查询接口的情况:
◆ 当商户后台、网络、服务器等出现异常,商户系统最终未接收到支付通知;
◆ 调用支付接口后,返回系统错误或未知交易状态情况;
◆ 调用被扫支付API,返回USERPAYING的状态;
◆ 调用关单或撤销接口API之前,需确认支付状态;
用咱们自己的话说就是,用户输完交易密码支付成功了,回到APP里面,可以主动查询一下这个订单是否支付成功了,如果查询微信服务器里显示订单支付成功了,可以再次判断数据库里订单信息是否更新成功了,如果没有更新成功,可能是网络延迟或者回调函数接口除了问题。
可以看这页带图的文档:https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=8_1 ,中的第五步第五步:回跳到商户APP中,商户APP根据支付结果个性化展示订单处理结果。见图8.5。
/**
* 查询微信支付结果接口
*
* @param httpServletRequest
* @return
*/
@ResponseBody
@RequestMapping("orderQuery")
public JSONObject orderQuery(HttpServletRequest request, HttpServletResponse response, String data) {
JSONObject obj = JSON.parseObject(data);
String out_trade_no = obj.getString("out_trade_no");
String transactionId = obj.getString("transaction_id"); // 微信预支付id即是prepayid
// 根据订单号查询订单
ShopOrder findByOrderNo = shopOrderService.findByOrderNo(out_trade_no);
if (findByOrderNo.getOrderState().equals("20")) {
return JsonUtil.getJson(10, null, "支付成功");
} else {
String nonce_str = WeiXinPayUtil.getRandomStringByLength(32);
SortedMap<String, String> map = new TreeMap<String, String>();
map.put("appid", Configure.WEIXIN_APPID);
map.put("mch_id", Configure.WEIXIN_MCHID);
map.put("transaction_id", transactionId );// 优先使用微信支付id进行查询
map.put("nonce_str", nonce_str);
String sign = WeiXinPayUtil.createSign("UTF-8", map);
map.put("sign", sign);
String xml = WeiXinPayUtil.ArrayToXml(map);
System.out.println(xml);
String sendPost = WeiXinPayUtil.httpsRequest("https://api.mch.weixin.qq.com/pay/orderquery", "POST", xml);
Map<String, String> xmlToMap = null;
try {
xmlToMap = WeiXinPayUtil.xmlToMap(sendPost);
} catch (Exception e) {
e.printStackTrace();
}
if (xmlToMap.get("return_code ").equals("SUCCESS") && xmlToMap.get("result_code ").equals("SUCCESS")) {
// 这里可以判断订单状态是否更新
return JsonUtil.getJson(10, null, "支付成功");
} else {
return JsonUtil.getJson(20, null, "支付失败");
}
}
}
可以看到,这个查询的接口的流程就是:
return_code
和result_code
都是SUCCESS
证明该订单支付成功了。"SUCCESS", "OK"
,需要重新更新一下订单状态,然后告诉APP,支付成功了,修改成功了。到这里,APP微信支付就算彻底完成了,看到这,当你再回过头来看博客前面提到的微信支付流程图,是否有那么一点点的理解了呢?
如果有什么意见或问题,欢迎评论交流!