最近閒來無事,看到網上有一些免籤支付回調的服務商,當時感受很新奇,因而本身動手看看怎麼玩的,先當作果html
App上監聽通知並向服務器POST支付信息android
服務端的支付訂單表git
下面說原理及流程github
1.App上使用NotificationListenerService監聽通知欄通知,一旦微信支付或者支付寶收款收到消息,讀取消息的內容,而後使用正則匹配金額spring
2.App讀取到金額後,構造支付訂單,支付訂單包含:訂單號(App本身生成,不是真實的支付方訂單號),金額,App端標識,支付方式,簽名(保證數據不被篡改)數據庫
3.App將訂單POST到填寫的URL中json
4.服務端收到訂單信息,先校驗簽名是否相符,再查看訂單是否存在(防止重放攻擊),驗證經過後存入數據庫,並向指定的回調地址發起請求springboot
5.服務端若是向指定的回調地址發起請求失敗,使用定時任務重複發起回調,直到回調成功或達到指定次數服務器
以上就是所有過程,服務端使用springboot,能夠很快速搭建微信
固然爲了保證可靠性須要給App加固,防止退出,還有這種只能讀取到金額,其餘信息一無所知,有些侷限性
2019-03-14補充:
代碼很簡單,上傳github徹底是小題大作,下面貼出關鍵代碼
App部分
繼承NotificationListenerService重寫onNotificationPosted方法
1 //來通知時的調用 2 @Override 3 public void onNotificationPosted(StatusBarNotification sbn) { 4 Notification notification = sbn.getNotification(); 5 if (notification == null) { 6 return; 7 } 8 Bundle extras = notification.extras; 9 if (extras != null) { 10 //包名 11 String pkg = sbn.getPackageName(); 12 // 獲取通知標題 13 String title = extras.getString(Notification.EXTRA_TITLE, ""); 14 // 獲取通知內容 15 String content = extras.getString(Notification.EXTRA_TEXT, ""); 16 Log.i(TAG, String.format("收到通知,包名:%s,標題:%s,內容:%s", pkg, title, content)); 17 //處理 18 processOnReceive(pkg, title, content); 19 } 20 } 21 22 /** 23 * 消息來時處理 24 * 25 * @param pkg 26 * @param title 27 * @param content 28 */ 29 private void processOnReceive(String pkg, String title, String content) { 30 if (!AppConstants.LISTEN_RUNNING) { 31 return; 32 } 33 if ("com.eg.android.AlipayGphone".equals(pkg)) { 34 //支付寶 35 if (checkMsgValid(title,content,"alipay") && StringUtils.isNotBlank(parseMoney(content))) { 36 TreeMap<String, String> paramMap = new TreeMap<>(); 37 paramMap.put("title", title); 38 paramMap.put("content", content); 39 paramMap.put("identifier", AppConstants.CLIENT_IDENTIFIER); 40 paramMap.put("orderid", CommonUtils.randomCharSeq()); 41 paramMap.put("gateway", "alipay"); 42 String sign = CommonUtils.calcSign(paramMap, AppConstants.SIGN_KEY); 43 if (StringUtils.isBlank(sign)) { 44 Log.e(TAG, "簽名錯誤"); 45 return; 46 } 47 HttpTask task = new HttpTask(); 48 task.setOnAsyncResponse(this); 49 String json = new Gson().toJson(paramMap); 50 task.execute(AppConstants.POST_URL, "sign=" + sign, json); 51 } 52 } else if ("com.tencent.mm".equals(pkg)) { 53 //微信 54 if (checkMsgValid(title, content, "wxpay") && StringUtils.isNotBlank(parseMoney(content))) { 55 TreeMap<String, String> paramMap = new TreeMap<>(); 56 paramMap.put("title", title); 57 paramMap.put("content", content); 58 paramMap.put("identifier", AppConstants.CLIENT_IDENTIFIER); 59 paramMap.put("orderid", CommonUtils.randomCharSeq()); 60 paramMap.put("gateway", "wxpay"); 61 String sign = CommonUtils.calcSign(paramMap, AppConstants.SIGN_KEY); 62 if (StringUtils.isBlank(sign)) { 63 Log.e(TAG, "簽名錯誤"); 64 return; 65 } 66 HttpTask task = new HttpTask(); 67 task.setOnAsyncResponse(this); 68 String json = new Gson().toJson(paramMap); 69 task.execute(AppConstants.POST_URL, "sign=" + sign, json); 70 } 71 } 72 } 73 74 /** 75 * 解析內容字符串,提取金額 76 * 77 * @param content 78 * @return 79 */ 80 private static String parseMoney(String content) { 81 Pattern pattern = Pattern.compile("收款(([1-9]\\d*)|0)(\\.(\\d){0,2})?元"); 82 Matcher matcher = pattern.matcher(content); 83 if (matcher.find()) { 84 String tmp = matcher.group(); 85 Pattern patternnum = Pattern.compile("(([1-9]\\d*)|0)(\\.(\\d){0,2})?"); 86 Matcher matchernum = patternnum.matcher(tmp); 87 if (matchernum.find()) 88 return matchernum.group(); 89 } 90 return null; 91 } 92 93 /** 94 * 驗證消息的合法性,防止非官方消息被處理 95 * 96 * @param title 97 * @param content 98 * @param gateway 99 * @return 100 */ 101 private static boolean checkMsgValid(String title, String content, String gateway) { 102 if ("wxpay".equals(gateway)) { 103 //微信支付的消息格式 104 //1條:標題:微信支付,內容:微信支付收款0.01元(朋友到店) 105 //多條:標題:微信支付,內容:[4條]微信支付: 微信支付收款1.01元(朋友到店) 106 Pattern pattern = Pattern.compile("^((\\[\\+?\\d+條])?微信支付:|微信支付收款)"); 107 Matcher matcher = pattern.matcher(content); 108 return "微信支付".equals(title) && matcher.find(); 109 } else if ("alipay".equals(gateway)) { 110 //支付寶的消息格式,標題:支付寶通知,內容:支付寶成功收款1.00元。 111 return "支付寶通知".equals(title); 112 } 113 return false; 114 }
服務端接收代碼
/** * 接受App發送的通知內容 * @param content 通知內容json, {"title": "標題", "content": "內容", "identifier": "app端標識", "orderid": "app生成的惟一訂單號", "gateway": "wxpay或alipay"} * @param sign 簽名,簽名方式按照content對應的key1=vaule1&key2=value2...&SECKEY計算md5,key的順序按字母表的順序 * @return */ @RequestMapping(value = "/c/post/notification", method = { RequestMethod.POST }) @ResponseBody public String receiveAppNotification(@RequestBody Map<String, Object> content, String sign) { logger.debug("請求參數,content=>{}, sign=>{}", JSON.toJSONString(content), sign); if (StringUtils.isBlank(sign) || CollectionUtils.isEmpty(content)) { return APIUtil.getReturn(APIConst.PARAM_ERROR); } //再次驗證字段 String contenttext = (String) content.get("content"); String identifier = (String) content.get("identifier"); String orderid = (String) content.get("orderid"); String gateway = (String) content.get("gateway"); if (StringUtils.isAnyBlank(contenttext, identifier, orderid, gateway) || !ImmutableList.of("alipay", "wxpay").contains(gateway)) { return APIUtil.getReturn(APIConst.PARAM_ERROR); } //讀取金額(單位元) Pattern pattern = Pattern.compile("([1-9]\\d*\\.\\d*|0\\.\\d*[1-9]\\d*)"); Matcher matcher = pattern.matcher(contenttext); if (!matcher.find()) { return APIUtil.getReturn(APIConst.PARAM_ERROR); } String amountStr = matcher.group(1); logger.debug("解析的金額:{}", amountStr); BigDecimal amount = null; try { amount = new BigDecimal(amountStr); } catch (NumberFormatException e) { logger.error("金額格式錯誤: {}", amountStr); return APIUtil.getReturn(APIConst.PARAM_ERROR); } //驗證簽名 TreeMap<String, Object> paramMap = new TreeMap<>(content); Iterator<Map.Entry<String, Object>> it = paramMap.entrySet().iterator(); StringBuilder sb = new StringBuilder(); while (it.hasNext()) { Map.Entry<String, Object> entry = it.next(); sb.append(entry.getKey()); sb.append("="); sb.append(entry.getValue()); sb.append("&"); } sb.append(SIGN_KEY); //計算簽名 String calcSign = MD5Util.MD5Encode(sb.toString(), "UTF-8"); if (!calcSign.equalsIgnoreCase(sign)) { return APIUtil.getReturn(1, "簽名錯誤"); } //查詢訂單號是否已經存在 boolean exist = orderService.checkOrderExist(orderid); if (exist) { logger.error("訂單號:{}已存在", orderid); return APIUtil.getReturn(1, "訂單號已存在"); } //訂單寫入數據庫 String account = ""; if (gateway.equals("wxpay")) { account = "W" + identifier; } else if (gateway.equals("alipay")) { account = "A" + identifier; } MqOrder order = new MqOrder(); order.setAccount(account); order.setAmount(amount); order.setGateway(gateway); order.setOrderId(orderid); order.setStatus(0); order.setNotifyCount(0); order.setCreateTime(new Date()); orderService.save(order); return APIUtil.getReturn(APIConst.OK); }
歡迎學習交流,qq:3168598325