这两天 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 那边也要生成签名 不能用你生成的签名