微信支付-异步回调通知

应用后台调用统一下单接口时需要指定回调的notify_url,微信支付平台执行统一下单后,会调用该url,发送一个异步通知给应用后台,同时后台需要调用查询微信后台这笔订单的支付结果以及金额,这是一个并行操作,需要注意的是微信后台收到的金额和订单金额需要进行比对,为了防止钓鱼,所以这个查询是有必要的,必须匹配:收到的到账金额 >= 订单金额,具体细节参考微信支付开发者文档

好吧,来看一下代码,异步通知地址需要自己配置好,在生成预付单的时候就得传过去,这个地址就是自己的应用后台中的某个rest-controller,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
@RequestMapping("/notice")
public void notice(HttpServletRequest request, HttpServletResponse response)
throws IOException {
InputStream 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);
}
outSteam.close();
inStream.close();
String result = new String(outSteam.toByteArray(), "utf-8");
Map<String, String> map = null;
try {
map = XMLUtil.doXMLParse(result);
} catch (JDOMException e) {
e.printStackTrace();
}

// 此处调用订单查询接口验证是否交易成功
WXOrderQuery wxpayResult = reqOrderQueryResult(map);
boolean isSucc = wxpayResult.isSuccess();

// 支付成功,商户处理后同步返回给微信参数
PrintWriter writer = response.getWriter();
if (!isSucc) {
// 支付失败, 记录流水失败
System.out.println("===============支付失败==============");
} else {
orderService.doWXPayNotice(wxpayResult);
System.out.println("===============付款成功,业务处理完毕==============");

// 通知微信已经收到消息,不要再给我发消息了,否则微信会8连击调用本接口
String noticeStr = setXML("SUCCESS", "");
writer.write(noticeStr);
writer.flush();
}

String noticeStr = setXML("FAIL", "");
writer.write(noticeStr);
writer.flush();
}

public static String setXML(String return_code, String return_msg) {
return "<xml><return_code><![CDATA[" + return_code + "]]></return_code><return_msg><![CDATA[" + return_msg + "]]></return_msg></xml>";
}

XMLUtil.java是用于解析支付结果通知信息的工具类,用到了

compile group: 'jdom', name: 'jdom', version: '1.0'

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
/**
* XMLUtil
* 用于解析微信的异步通知信息
* @author zhaozhengkang@cetiti.com
*/
public class XMLUtil {

/**
* 解析xml,返回第一级元素键值对。如果第一级元素有子节点,则此节点的值是子节点的xml数据。
* @param strxml
* @return
* @throws JDOMException
* @throws IOException
*/
public static Map doXMLParse(String strxml) throws JDOMException, IOException {
strxml = strxml.replaceFirst("encoding=\".*\"", "encoding=\"UTF-8\"");
if(null == strxml || "".equals(strxml)) {
return null;
}
Map m = new HashMap();
InputStream in = new ByteArrayInputStream(strxml.getBytes("UTF-8"));
SAXBuilder builder = new SAXBuilder();
Document doc = builder.build(in);
Element root = doc.getRootElement();
List list = root.getChildren();
Iterator it = list.iterator();
while(it.hasNext()) {
Element e = (Element) it.next();
String k = e.getName();
String v = "";
List children = e.getChildren();
if(children.isEmpty()) {
v = e.getTextNormalize();
} else {
v = XMLUtil.getChildrenText(children);
}
m.put(k, v);
}
//关闭流
in.close();
log.error("doXMLParse m==="+m.toString());
return m;
}

/**
* 获取子结点的xml
* @param children
* @return String
*/
public static String getChildrenText(List children) {
StringBuffer sb = new StringBuffer();
if(!children.isEmpty()) {
Iterator it = children.iterator();
while(it.hasNext()) {
Element e = (Element) it.next();
String name = e.getName();
String value = e.getTextNormalize();
List list = e.getChildren();
sb.append("<" + name + ">");
if(!list.isEmpty()) {
sb.append(XMLUtil.getChildrenText(list));
}
sb.append(value);
sb.append("</" + name + ">");
}
}
log.error("getChildrenText sb===="+sb.toString());
return sb.toString();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public WXOrderQuery reqOrderQueryResult(Map<String, String> map) {
WXOrderQuery orderQuery = new WXOrderQuery();
orderQuery.setAppid(map.get("appid"));
orderQuery.setMch_id(map.get("mch_id"));
orderQuery.setTransaction_id(map.get("transaction_id"));
orderQuery.setOut_trade_no(map.get("out_trade_no"));
orderQuery.setNonce_str(map.get("nonce_str"));
String payFlowId = map.get("attach");
orderQuery.setAttach(payFlowId);

//此处需要密钥PartnerKey,此处直接写死,自己的业务需要从持久化中获取此密钥,否则会报签名错误
orderQuery.setPartnerKey(WXPayContants.partnerKey);

Map<String, String> orderMap = orderQuery.reqOrderquery();
//此处添加支付成功后,支付金额和实际订单金额是否等价,防止钓鱼
if (orderMap.get("return_code") != null &&
orderMap.get("return_code").equalsIgnoreCase("SUCCESS")) {
if (orderMap.get("trade_state") != null &&
orderMap.get("trade_state").equalsIgnoreCase("SUCCESS")) {
// 查询订单(交易流水的实际金额),判断微信收到的钱和订单中的钱是否等额
SpPayFlowCargoSource payFlow = spPayFlowCargoSourceService.getPayFlowById(payFlowId);
String total_fee = map.get("total_fee");
orderQuery.setPayFlow(payFlow);
Integer db_fee = payFlow.getFee().multiply(new BigDecimal(100)).intValue();
if (Integer.parseInt(total_fee) == db_fee) {
orderQuery.setSuccess(true);
return orderQuery;
}
}
}
orderQuery.setSuccess(false);
return orderQuery;
}

到这一步,就能判断金额到底对不对,对了那么久成功支付,订单进行下一步流程~

再次强调,一定要防止钓鱼,另外异步调用的时候需要去查看你的订单或者交易流水是否已经成功了,成功就没有必要继续走,直接return就行

在高并发场景下,收到的支付结果通知应该发布到MQ,后台另起一线程订阅MQ并做相应处理,如下图:

微信支付流程