這兩天 app上面要加上微信支付 挖了兩天的坑終於是有點頭緒了 最後竟然是
- 模型搭好 網上找尋 一些工具 值得一提 微信是比較頭疼的 他傳入的參數必須是xml 所以要做好解析 xml 和 拼接xml 文件的打算
xml 工具方法 還有微信支付是發送post 請求 不像支付寶那樣 是sdk 回調也是在request 裏面
package com.trilink.common.bean;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.ConnectException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.SortedMap;
import javax.servlet.http.HttpServletRequest;
import org.apache.log4j.Logger;
import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import com.aliyun.opensearch.util.URLEncoder;
import com.trilink.common.util.MD5Util;
import com.trilink.common.util.StringUtil;
public class WeiXinPayUtil {
/**
* 日誌記錄
*/
private static Logger logger = Logger.getLogger(WeiXinPayUtil.class);
/**
* 簽名
* @param params
* @param paternerKey
* @return
* @throws UnsupportedEncodingException
*/
/**
* 簽名2
* @param params
* @return
* @throws UnsupportedEncodingException
*/
public static String createSign(String characterEncoding,SortedMap<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=" + ConfigUtils.WXappkey);//最後加密時添加商戶密鑰,由於key值放在最後,所以不用添加到SortMap裏面去,單獨處理,編碼方式採用UTF-8
String aa=sb.toString();
System.err.println("aaaaaaaaaaaaaaaa:"+aa);
String sign = MD5Util.MD5Encode(aa.trim(), characterEncoding).toUpperCase();
return sign;
}
/**
* 排序
* @param params
* @param encode
* @return
* @throws UnsupportedEncodingException
*/
public static String createSign(Map<String, String> params, boolean encode) throws UnsupportedEncodingException {
Set<String> keysSet = params.keySet();
Object[] keys = keysSet.toArray();
Arrays.sort(keys);
StringBuffer temp = new StringBuffer();
boolean first = true;
for (Object key : keys) {
if (first) {
first = false;
} else {
temp.append("&");
}
temp.append(key).append("=");
Object value = params.get(key);
String valueString = "";
if (null != value) {
valueString = value.toString();
}
if (encode) {
temp.append(URLEncoder.encode(valueString, "UTF-8"));
} else {
temp.append(valueString);
}
}
return temp.toString();
}
/**
* 將map轉換爲xml
* @param arr
* @return
*/
public static String ArrayToXml(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();
xml += "<" + key + ">" + val + "</" + key + ">";
}
xml += "</xml>";
return xml;
}
/**
* 解析xml爲map
* @param xml
* @return
* @throws Exception
* @throws XmlPullParserException
* @throws IOException
*/
public static Map<String, String> xmlToMap(String xml) throws Exception{
if(xml!=null){
Map<String, String> map = new HashMap<String, String>();
Document document = DocumentHelper.parseText(xml);
Element root = document.getRootElement();
for (Iterator iterator = root.elementIterator(); iterator.hasNext();) {
Element e = (Element) iterator.next();
map.put(e.getName(), e.getText());
System.out.println(e.getName()+"="+e.getText());
}
return map;
}else{
return null;
}
}
/**
* 請求方法
* @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);
}
System.err.println("返回結果"+buffer.toString());
// 釋放資源
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;
}
}
這上面有一個是生成 簽名 的方法 這個必須要按照文檔裏面傳入參數 多了 無所謂 要按照順序
字典順序 可以用 SortedMap 進行有序化
- 中轉站 將參數封裝
package com.trilink.common.bean;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.UUID;
import org.apache.log4j.Logger;
import com.trilink.common.util.MD5Util;
public class WeiXinPay {
/**
* 日誌記錄
*/
private static Logger logger = Logger.getLogger(WeiXinPay.class);
/**
* 微信統一下單
* @param appid 微信開發者id/微信公衆號id
* @param mch_id 商戶號
* @param key 簽名密匙
* @param openid appid爲微信公衆號id時爲用戶標識,appid微信開發者id爲null
* @param orderCode 訂單號
* @param total_fee 總價 單位爲分
* @param attach 自定義數據
* @param ip 終端ip
* @param notify_url 回調地址
* @param trade_type 支付類型(APP,JSAPI)
* @param body 支付描述
* @return
* @throws Exception
*/
public static Map<String, String> weixinUnifiedOrder(String appid,String mch_id,String orderCode,String total_fee,String ip,String notify_url,String trade_type,String body,String key) throws Exception{
SortedMap<String, String> condition = new TreeMap<String, String>();
//1應用ID
condition.put("appid", appid);
System.out.println(appid);
//2商戶號
condition.put("mch_id", mch_id);
System.out.println(mch_id);
//3隨機字符串
condition.put("nonce_str", UUID.randomUUID().toString().replace("-", ""));
System.out.println(UUID.randomUUID().toString().replace("-", ""));
//4商品描述
condition.put("body", body);
System.out.println(body);
//5商戶訂單號
condition.put("out_trade_no", orderCode);
System.out.println(orderCode);
//6訂單總金額,單位爲分
condition.put("total_fee", total_fee);
System.out.println(total_fee);
//7終端IP
condition.put("spbill_create_ip", ip);
System.out.println(ip);
String encode = URLEncoder.encode(notify_url, "utf-8");
//8接收微信支付異步通知回調地址,通知url必須爲直接可訪問的url,不能攜帶參數。
condition.put("notify_url", encode);
System.out.println(notify_url);
//9支付類型
condition.put("trade_type", trade_type);
System.out.println(trade_type);
//簽名
String sign = WeiXinPayUtil.createSign("UTF-8",condition);
System.out.println(condition);
condition.put("sign", sign);
// String str="";
// String sign1 ="appid=wx66411860150db74c&body=test&mch_id=1528762281&nonce_str=bca79d1bb2e04205b8840131a509d600&key=d6e576ec4a1e11e98646d663bd87jade";
//
// sign=MD5Util.MD5Encode("UTF-8", sign1);
System.out.println("!!!!!!"+sign);
String xml = WeiXinPayUtil.ArrayToXml(condition);
System.out.println(xml);
String result = WeiXinPayUtil.httpsRequest(ConfigUtils.weixinUnifiedOrderUrl, "POST", xml);
Map<String, String> resuleMap = WeiXinPayUtil.xmlToMap(result);
resuleMap.put("ip", ip);
logger.info("1111111111111111111"+result);
if (result.indexOf("SUCCESS") != -1) {
if(resuleMap.get("return_code")!=null && "SUCCESS".equals(resuleMap.get("return_code"))){
//成功
return resuleMap;
}else{
return resuleMap;
}
}else{
return resuleMap;
}
}
}
- MD5 加密工具類 簽名是需要 MD5加密的
public class MD5Util {
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];
}
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 final String hexDigits[] = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f" };
}
- 配置必要參數
public class ConfigUtils {
//請求地址
public static String weixinUnifiedOrderUrl="";
//祕鑰
public static String WXappkey="";
//appID
public static String WXAPPappid="";
//商戶ID
public static String WXmch_id="";
//回調地址
public static String WXnotify_url="";
}
5.開搞
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import com.trilink.common.bean.ConfigUtils;
import com.trilink.common.bean.R;
import com.trilink.common.bean.WeiXinPay;
import com.trilink.common.bean.WeiXinPayUtil;
import com.trilink.common.dao.zhyl.AppDiscountMapper;
import com.trilink.common.dao.zhyl.AppMeetingDiscountMapper;
import com.trilink.common.dao.zhyl.AppPayRecordMapper;
import com.trilink.common.dao.zhyl.AppSequenceMapper;
import com.trilink.common.dao.zhyl.AppSignRecordMapper;
import com.trilink.common.dao.zhyl.AppSignUpMapper;
import com.trilink.common.dao.zhyl.DoctorMapper;
import com.trilink.common.dao.zhyl.PatientMapper;
import com.trilink.common.entity.AppDiscount;
import com.trilink.common.entity.AppDiscountExample;
import com.trilink.common.entity.AppPayRecord;
import com.trilink.common.entity.AppSequence;
import com.trilink.common.entity.AppSequenceExample;
import com.trilink.common.entity.AppSignRecord;
import com.trilink.common.entity.AppSignRecordExample;
import com.trilink.common.entity.AppSignUp;
import com.trilink.common.entity.Doctor;
import com.trilink.common.entity.Patient;
import com.trilink.common.service.AppPayService;
import com.trilink.common.util.StringUtil;
@Controller
@RequestMapping("weixin")
public class AppWeixinController {
@Autowired
private DoctorMapper doctorMapper;
@Autowired
private PatientMapper patientMapper;
@Autowired
private AppSignUpMapper appSignUpMapper;
@Autowired
private AppSignRecordMapper appSignRecordMapper;
@Autowired
private AppPayRecordMapper appPayRecordMapper;
@Autowired
private AppDiscountMapper appDiscountMapper;
@Autowired
private AppMeetingDiscountMapper appMeetingDiscountMapper;
@Autowired
private AppSequenceMapper appSequenceMapper;
@Autowired
private AppPayService appPayService;
/**
* 微信app支付統一下單
* @param request
* @param orderNumber
* @param payMoney
* @param orderType
* @return
* @throws Exception
*/
@RequestMapping(value="weixinPay",method={RequestMethod.POST})
@ResponseBody
/**
* ResultState 是我自己封裝的一個返回結果的對象,有需要的後邊我會附代碼,很簡單的,畢竟我本人水平也不好。
* 在這裏,訂單加簽我讓前端穿了3個參數 orderNumber 訂單編號 需要提供給微信的 payMoney 用戶支付金額 需要提供給微信的 orderType訂單類型 確定body字段的
* 這裏大家可以根據自己的業務邏輯確定參數,沒必要跟我的一致,返回結果也是,大家也可應自己定義的
*/
public R weixinUnifiedOrder(@RequestParam() Map<String, String> allParams,HttpServletRequest request) throws Exception{
String userId = allParams.get("userId");
String userType = allParams.get("roleType");
//用,隔開
String signUpIds = allParams.get("signUpIds");
//用,隔開
String recommand = allParams.get("recommand");
String userName=allParams.get("userName");
String mobile=allParams.get("mobile");
String meetingId=allParams.get("meetingId");
String payType=allParams.get("payType");
String money=allParams.get("money");
String outTradeNo = getOutTradeNo();
AppPayRecord appPay=new AppPayRecord();
appPay.setcTime(new Date());
appPay.setUserType(Integer.parseInt(userType));
appPay.setOutTradeNo(outTradeNo);
appPay.setPayUser(Integer.parseInt(userId));
appPay.setPayType(Integer.parseInt(payType));
appPay.setPayStatus(2);
appPay.setStatus(0);
appPay.setPayFee(payFee);
int insert = appPayRecordMapper.insert(appPay);
if(insert>0) {
AppSignRecord record=new AppSignRecord();
record.setStatus(0);
record.setMeetingId(Integer.parseInt(meetingId));
record.setMobile(mobile);
record.setUserType(Integer.parseInt(userType));
record.setUserId(Integer.parseInt(userId));
record.setUserName(userName);
record.setSignUpIds(signUpIds);
record.setRecommand(recommand);
record.setTandeNo(appPay.getId());
record.setReportTime(new Date());
record.setPayMoney(payFee);
if(appSignRecordMapper.insert(record)>0) {
Map<String, String> result=new HashMap<String, String>();
//微信支付需要傳參使用者的ip,這裏獲取了一下,可以自己網上找,懶得找的下邊我會附代碼
String remoteAddr = getIpAddress(request);
String ip="";
if (remoteAddr.length()>16) {
String[] split = remoteAddr.split(",");
for (int i = 0; i < split.length; i++) {
ip=split[0];
}
}else{
ip=remoteAddr;
}
//這裏就是把訂單信息封裝好了統一下單,微信會返回一個加完籤的結果,只要判斷一下結果的正確性就可以吧這個結果直接返回給前端,讓前端去調起支付了
//我把上邊提到的幾個參數封裝到了ConfigUtils裏,這裏你取你自己的 appid 微信商戶號 上邊數組封裝的值,自己對應 ip 微信回調地址(自己寫的,上邊提到的第二部) APP,看文檔,你是什麼支付就傳//什麼,body 上邊定義的字段,必傳,加簽的時候有body的解釋,也可以自己看微信官方文檔 自己封裝的appkey
result=WeiXinPay.weixinUnifiedOrder(ConfigUtils.WXAPPappid, ConfigUtils.WXmch_id,outTradeNo, "1", ip, ConfigUtils.WXnotify_url, "APP", "test", ConfigUtils.WXappkey);
if(result.get("prepay_id")!=null){
System.out.println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"+result);
// //result是微信返回的字段,詳細見微信開發者文檔
// Map<String, String> sign = WeiXinPay.getSign3(result);
// System.out.println("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"+sign);
result.put("out_trade_no", appPay.getOutTradeNo());
System.err.println("result:::::::+"+result);
return R.ok().put("data", result);
}else {
return R.error();
}
}else {
return R.error("簽名錯誤");
}
}
return R.error("錯誤");
}
/*
* 微信支付成功回調
* @param request
* @param response
* @return
* @throws Exception
*/
//微信支付要在服務起上正式環境調試,所以這個回調接口必須是在外網上可以訪問到的,不能加任何參數的
@ResponseBody
@RequestMapping(value="weiXinPayCallback")
//這裏是微信支付回調,支付完成後,微信會回調這個接口,確定是否支付成功,支付成功給微信返回一個success,讓微信知道成功支付了,不然微信會隔幾分鐘回調一次,對於你處理成功業務邏輯很麻煩
public String weiXinPayCallback(HttpServletRequest request,HttpServletResponse response) throws Exception{
//獲取微信支付回調結果
String result = WeiXinPayUtil.getPostStr(request);
System.out.println(result);
Map<String, String> resuleMap = WeiXinPayUtil.xmlToMap(result);
if(resuleMap.get("result_code")!=null && "SUCCESS".equals(resuleMap.get("result_code"))){
//成功
//獲取實際支付的錢數
String totalFee = resuleMap.get("total_fee");
//獲取訂單號
String orderNo = resuleMap.get("out_trade_no").split("_")[0];
//獲取交易流水號
String tradeNo = resuleMap.get("transaction_id");
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
AppPayRecord record = appPayService.queryRecordByOutTradeNo(request.getParameter("out_trade_no"),
null);
if (record == null) {
System.out.println("---------------------record is null");
return "failure";
}
if(record.getUserType()==1) {
Doctor doctor = doctorMapper.selectByPrimaryKey(record.getPayUser());
if(doctor==null) {
System.out.println("---------------------doctor is null");
return "failure";
}
}
if(record.getUserType()==2) {
Patient patient=patientMapper.selectByPrimaryKey(record.getPayUser());
if(patient==null) {
System.out.println("---------------------patient is null");
return "failure";
}
}
record.setBuyerLogonId(request.getParameter("buyer_logon_id"));
record.setTradeNo(request.getParameter("trade_no"));
record.setPayFee(Double.parseDouble(request.getParameter("total_amount")));
record.setStatus(1);
record.setPayTime(format.parse(request.getParameter("gmt_payment")));
// 更新支付記錄
Integer updateRecord = appPayService.updateRecord(record);
System.err.println("支付訂單11111111111111成功"+updateRecord);
if(updateRecord>0) {
return "success";
}else {
record.setStatus(0);
appPayService.updateRecord(record);
return "failure";
}
}
}
return "fail";
}
public String getOutTradeNo() {
Date d = new Date();
SimpleDateFormat format = new SimpleDateFormat("yyyyMMddHHmmss");
Double i = (Math.random() * 9 + 1) * 100000;
return "NSZDX" + format.format(d) + i.intValue();
}
public static String getIpAddress(HttpServletRequest request) {
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_CLIENT_IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return ip;
}
}
這裏訂單自己生成自己的商戶訂單 是需要傳給app端的
- 發起支付返回的參數
我碰到的坑 就是 生成的 WXappkey 有問題 害的我一直返回
<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[簽名錯誤]]></return_msg></xml>
微信返回結果又不做任何說明 我們只能一個一個排查 首先判斷 生成的簽名是不是正確的 知道找到
一個地方(別說 微信藏得夠深的 還真找不到 這地)
https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=20_1
這裏 可以驗證你的簽名是否生成有誤 微信文檔是真的坑 什麼提示都沒有 接下來就是 app端的坑
app 那邊也要生成簽名 不能用你生成的簽名