一、微信支付太坑爹,废话不说了,下面是我的服务端微信支付开发过程和代码记录
二、首先去微信申请账户,这里有两个平台
1、微信公众平台
2、微信开放平台(https://open.weixin.qq.com)这里选择第二个
三、账户开通、开发者认证之后就可以进行微信支付开发了
1、微信统一下单接口调用获取预支付id
/**
* 获取微信支付所需信息(统一下单接口调用)
* @param backURL
* @param mDealerOrderEntity
* @param mCarInfoEntity
* @return
* @throws JSONException
* @throws IOException
* @throws JDOMException
*/
public Map<String, String> getWechatOrderInfo(String notifyUrl, MDealerOrderEntity mDealerOrderEntity, String body, HttpServletRequest request, HttpServletResponse response) throws Exception {
Map<String, String> resultMap = new HashMap<String, String>();
//生成payPreId
Map<String, String> payPreIdMap = WechatUtil.getPayPreId(mDealerOrderEntity.getGoodorderno(), body, notifyUrl, request.getRemoteAddr(), String.valueOf((int)(mDealerOrderEntity.getMoney()*100)));
String prePayId = payPreIdMap.get("prepay_id");
if(StringUtils.isNotEmpty(prePayId)) {
//生成调用微信APP参数
resultMap = WechatUtil.genPayReq(prePayId);
}
return resultMap;
}
此方法返回的数据如下
{
"appid": "123132131",
"noncestr": "416e5cf0acb7e553a880b7647903da6e",
"packageValue ": "Sign=WXPay",
"partnerid ": "1276000000",
"prepayid ": "wx2015101611341514a3cbbbf90572184370",
"timestamp ": "1444966497",
"sign": "1DD72B07607B0B41D2827954150D89E9"
}
2、服务器端接受微信支付结果通知
/**
* 处理微信支付通知
* @param request
* @return
* @throws Exception
*/
public String saveWechatNotify(HttpServletRequest request, HttpServletResponse response) throws Exception {
Map<String, String> noticeMap = XMLUtil.parseXml(request);
String transactionId = noticeMap.get("transaction_id");
MWechatInfoEntity wechatInfoEntity = this.findEntityByProperty(MWechatInfoEntity.class, "transactionId", transactionId);
//如果wechatInfoEntity存在,说明请求已经处理过,直接返回
if(wechatInfoEntity != null) {
return "SUCCESS";
}
String sign = noticeMap.get("sign");
noticeMap.remove("sign");
// 验签通过
if (WechatUtil.getSignVeryfy(noticeMap, sign)) {
// 通信成功此字段是通信标识,非交易标识,交易是否成功需要查看result_code来判断
if ("SUCCESS".equals(noticeMap.get("return_code"))) {
// 交易成功
if ("SUCCESS".equals(noticeMap.get("result_code"))) {
// 商户订单号
String goodorderno = noticeMap.get("out_trade_no");
MDealerOrderEntity mDealerOrderEntity = this.findEntityByProperty(MDealerOrderEntity.class, "goodorderno", goodorderno);
MCarInfoEntity mCarInfoEntity = this.get(MCarInfoEntity.class, mDealerOrderEntity.getCarid());
// 订单更新时间
mDealerOrderEntity.setUpdatetime(new Date());
// ------------------------------
// 处理业务开始
// ------------------------------
// 这里写自己业务相关
// ------------------------------
// 处理业务完毕
// ------------------------------
noticeMap.put("sign", sign);
this.common99Service.saveWechatInfo(noticeMap, mDealerOrderEntity.getId());
} else {
// 错误时,返回结果未签名,记录retcode、retmsg看失败详情。
System.out.println("查询验证签名失败或业务错误");
System.out.println("retcode:" + noticeMap.get("retcode") + " retmsg:" + noticeMap.get("retmsg"));
}
return "SUCCESS";
} else {
System.out.println("后台调用通信失败");
}
return "SUCCESS";
} else {
System.out.println("通知签名验证失败");
}
return null;
}
3、上面代码用到的工具方法都在WechatUtil.java工具类中
package com.jim.iweb.haocheok.tenpay.util;
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Random;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.http.NameValuePair;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.jdom2.JDOMException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class WechatUtil {
private static Logger logger = LoggerFactory.getLogger(WechatUtil.class);
public static final String TAG = "Wechat.Util";
private static final int timeout = 5000;
public static byte[] httpPost(String url, String entity) throws URISyntaxException, IOException {
if (url == null || url.length() == 0) {
logger.info(TAG, "httpPost, url is null");
return null;
}
CloseableHttpClient httpClient = HttpClients.createDefault();
URIBuilder uriBuilder = new URIBuilder(url);
HttpPost httpPost = new HttpPost(uriBuilder.build());
RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(timeout).setConnectionRequestTimeout(timeout).setConnectTimeout(timeout).build();
httpPost.setConfig(requestConfig);
// 避免汉字乱码导致请求失败,
httpPost.setEntity(new StringEntity(entity, "UTF-8"));
CloseableHttpResponse resp = null;
try {
resp = httpClient.execute(httpPost);
if (resp.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
logger.info(TAG, "httpGet fail, status code = " + resp.getStatusLine().getStatusCode());
return null;
}
return EntityUtils.toByteArray(resp.getEntity());
} catch (Exception e) {
logger.info(TAG, "httpPost exception, e = " + e.getMessage());
e.printStackTrace();
return null;
} finally {
if (httpClient != null) {
httpClient.close();
}
if (resp != null) {
resp.close();
}
}
}
/**
* 把数组所有元素排序,并按照“参数=参数值”的模式用“&”字符拼接成字符串
*
* @param params
* 需要排序并参与字符拼接的参数组
* @return 拼接后字符串
*/
public static String createLinkString(Map<String, String> params) {
List<String> keys = new ArrayList<String>(params.keySet());
Collections.sort(keys);
String prestr = "";
for (int i = 0; i < keys.size(); i++) {
String key = keys.get(i);
String value = params.get(key);
if (i == keys.size() - 1) {// 拼接时,不包括最后一个&字符
prestr = prestr + key + "=" + value;
} else {
prestr = prestr + key + "=" + value + "&";
}
}
return prestr;
}
/**
* 根据反馈回来的信息,生成签名结果
*
* @param Params
* 通知返回来的参数数组
* @param sign
* 比对的签名结果
* @return 生成的签名结果
*/
public static boolean getSignVeryfy(Map<String, String> Params, String sign) {
// 过滤空值、sign与sign_type参数
// Map<String, String> sParaNew = AlipayCore.paraFilter(Params);
// 获取待签名字符串
String preSignStr = createLinkString(Params);
preSignStr += "&key=" + ConstantUtil.API_KEY;
// 获得签名验证结果
String resultSign = MD5.getMessageDigest(preSignStr.getBytes()).toUpperCase();
// String resultSign = MD5Util.MD5Encode(preSignStr.toString(), "UTF-8").toLowerCase();
if (sign.equals(resultSign)) {
return true;
} else {
return false;
}
}
/**
* 装配xml,生成请求prePayId所需参数
*
* @param params
* @return
*/
public static String toXml(List<NameValuePair> params) {
StringBuilder sb = new StringBuilder();
sb.append("<xml>");
for (int i = 0; i < params.size(); i++) {
sb.append("<" + params.get(i).getName() + ">");
sb.append(params.get(i).getValue());
sb.append("</" + params.get(i).getName() + ">");
}
sb.append("</xml>");
return sb.toString();
}
/**
* 生成签名
*/
public static String genPackageSign(List<NameValuePair> params) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < params.size(); i++) {
sb.append(params.get(i).getName());
sb.append('=');
sb.append(params.get(i).getValue());
sb.append('&');
}
sb.append("key=");
sb.append(ConstantUtil.API_KEY);
String packageSign = MD5.getMessageDigest(sb.toString().getBytes()).toUpperCase();
return packageSign;
}
/**
*
* @param goodOrderNo
* @param body
* @param noticeUrl
* @param ip
* @param totalFee
* @return
*/
public static String genProductArgs(String goodOrderNo, String body, String noticeUrl, String ip, String totalFee) {
StringBuffer xml = new StringBuffer();
try {
String nonceStr = getNonceStr();
xml.append("</xml>");
List<NameValuePair> packageParams = new LinkedList<NameValuePair>();
packageParams.add(new BasicNameValuePair("appid", ConstantUtil.APP_ID));
packageParams.add(new BasicNameValuePair("body", body));
packageParams.add(new BasicNameValuePair("mch_id", ConstantUtil.MCH_ID));
packageParams.add(new BasicNameValuePair("nonce_str", nonceStr));
packageParams.add(new BasicNameValuePair("notify_url", noticeUrl));
packageParams.add(new BasicNameValuePair("out_trade_no", goodOrderNo));
packageParams.add(new BasicNameValuePair("spbill_create_ip", ip));
packageParams.add(new BasicNameValuePair("total_fee", totalFee));
packageParams.add(new BasicNameValuePair("trade_type", "APP"));
String sign = genPackageSign(packageParams);
packageParams.add(new BasicNameValuePair("sign", sign));
String xmlstring = toXml(packageParams);
return xmlstring;
} catch (Exception e) {
logger.info("genProductArgs fail, ex = " + e.getMessage());
return null;
}
}
/**
* 生成app支付签名
*
* @param params
* @return
*/
public static String genAppSign(List<NameValuePair> params) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < params.size(); i++) {
sb.append(params.get(i).getName());
sb.append('=');
sb.append(params.get(i).getValue());
sb.append('&');
}
sb.append("key=");
sb.append(ConstantUtil.API_KEY);
String appSign = MD5.getMessageDigest(sb.toString().getBytes()).toUpperCase();
logger.info("orion", appSign);
return appSign;
}
/**
* 生成调用微信app支付所需参数
*
* @param prepayId
* @return
*/
public static Map<String, String> genPayReq(String prepayId) {
Map<String, String> resultMap = new HashMap<String, String>();
String timeStamp = getTimeStamp();
String nonceStr = getNonceStr();
List<NameValuePair> signParams = new LinkedList<NameValuePair>();
signParams.add(new BasicNameValuePair("appid", ConstantUtil.APP_ID));
signParams.add(new BasicNameValuePair("noncestr", nonceStr));
signParams.add(new BasicNameValuePair("package", "Sign=WXPay"));
signParams.add(new BasicNameValuePair("partnerid", ConstantUtil.MCH_ID));
signParams.add(new BasicNameValuePair("prepayid", prepayId));
signParams.add(new BasicNameValuePair("timestamp", timeStamp));
String sign = genAppSign(signParams);
resultMap.put("appid", ConstantUtil.APP_ID);
resultMap.put("noncestr", nonceStr);
resultMap.put("packageValue", "Sign=WXPay");
resultMap.put("partnerid", ConstantUtil.MCH_ID);
resultMap.put("prepayid", prepayId);
resultMap.put("timestamp", timeStamp);
resultMap.put("sign", sign);
return resultMap;
}
/**
* 微信支付生成预支付订单
*
* @throws IOException
* @throws JDOMException
*/
public static Map<String, String> getPayPreId(String goodOrderNo, String body, String noticeUrl, String ip, String totalFee) throws Exception {
String paramsXml = genProductArgs(goodOrderNo, body, noticeUrl, ip, totalFee);
logger.info("orion", paramsXml);
byte[] buf = WechatUtil.httpPost(ConstantUtil.URL, paramsXml);
String contentXml = new String(buf);
Map<String, String> resultMap = XMLUtil.doXMLParse(contentXml);
return resultMap;
}
public static String getNonceStr() {
Random random = new Random();
return MD5.getMessageDigest(String.valueOf(random.nextInt(10000)).getBytes());
}
public static String getTimeStamp() {
return String.valueOf(System.currentTimeMillis() / 1000);
}
}
4、下面是用到的配置类
package com.jim.iweb.haocheok.tenpay.util;
public class ConstantUtil {
/**
* 商家可以考虑读取配置文件
*/
//初始化
public static String APP_ID = "wxsdfsdfsf5fdbc";//微信开发平台应用id
public static String APP_SECRET = "aab95csdfsdfsffdcsdfsfs0df34";//应用对应的凭证
//商户号
public static String MCH_ID = "1233312201";
public static String PARTNER = "1233312201";//财付通商户号
public static String API_KEY = "KgjyjirmjajdfjsdjfsjffVpT6RMbrB";
public static String PARTNER_KEY = "KgjyjirmjajdfjsdjfsjffVpT6RMbrB";//商户号对应的密钥
public static String URL = "https://api.mch.weixin.qq.com/pay/unifiedorder";//获取预支付id的接口url
}
5、xml 解析工具类
package com.jim.iweb.haocheok.tenpay.util;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.dom4j.io.SAXReader;
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.JDOMException;
import org.jdom2.input.SAXBuilder;
/**
* xml工具类
*
* @author miklchen
*
*/
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();
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 + ">");
}
}
return sb.toString();
}
/**
* 将requestxml通知结果转出啊成map
* @param request
* @return
* @throws Exception
*/
public static Map<String, String> parseXml(HttpServletRequest request) throws Exception {
// 解析结果存储在HashMap
Map<String, String> map = new HashMap<String, String>();
InputStream inputStream = request.getInputStream();
// 读取输入流
SAXReader reader = new SAXReader();
org.dom4j.Document document = reader.read(inputStream);
// 得到xml根元素
org.dom4j.Element root = document.getRootElement();
// 得到根元素的所有子节点
List<org.dom4j.Element> elementList = root.elements();
// 遍历所有子节点
for (org.dom4j.Element e : elementList)
map.put(e.getName(), e.getText());
// 释放资源
inputStream.close();
inputStream = null;
return map;
}
}
6、MWechatInfoEntity类
import java.io.Serializable;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
/**
* The persistent class for the m_wechat_info database table.
*
*/
@Entity
@Table(name = "m_wechat_info")
public class MWechatInfoEntity implements Serializable {
private static final long serialVersionUID = 1L;
private Integer id;
//微信支付分配的终端设备号
private String deviceInfo;
//随机字符串,不长于32位
private String nonceStr;
//SUCCESS/FAIL
private String resultCode;
//错误返回的信息描述
private String errCode;
//错误返回的信息描述
private String errCodeDes;
//用户是否关注公众账号,Y-关注,N-未关注,仅在公众账号类型支付有效
private String isSubscribe;
//交易类型 (JSAPI、NATIVE、APP)
private String tradeType;
//银行类型,采用字符串类型的银行标识
private String bankType;
// 订单总金额,单位为分
private Integer totalFee;
//货币类型,符合ISO4217标准的三位字母代码,默认人民币:CNY,其他值列表详见货币类型
private String feeType;
//现金支付金额订单现金支付金额,详见支付金额
private Integer cashFee;
//货币类型,符合ISO4217标准的三位字母代码,默认人民币:CNY,其他值列表详见货币类型
private String cashFeeType;
//代金券或立减优惠金额<=订单总金额,订单总金额-代金券或立减优惠金额=现金支付金额,详见支付金额
private Integer couponFee;
//代金券或立减优惠使用数量
private Integer couponCount;
//代金券或立减优惠ID,$n为下标,从0开始编号
private String couponId;
private String sign;
// private String signType;
//微信支付订单号
private String transactionId;
//商户订单号
private String outTradeNo;
//商家数据包
private String attach;
//支付完成时间
private Date timeEnd;
private Date createTime;
private Date updateTime;
private Integer dealerOrderId;
public MWechatInfoEntity() {
}
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name ="id",nullable=false, unique=true)
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
@Column(name ="device_info",nullable=true)
public String getDeviceInfo() {
return deviceInfo;
}
public void setDeviceInfo(String deviceInfo) {
this.deviceInfo = deviceInfo;
}
@Column(name ="nonce_str",nullable=true)
public String getNonceStr() {
return nonceStr;
}
public void setNonceStr(String nonceStr) {
this.nonceStr = nonceStr;
}
@Column(name ="result_code",nullable=true)
public String getResultCode() {
return resultCode;
}
public void setResultCode(String resultCode) {
this.resultCode = resultCode;
}
@Column(name ="err_code",nullable=true)
public String getErrCode() {
return errCode;
}
public void setErrCode(String errCode) {
this.errCode = errCode;
}
@Column(name ="err_code_des",nullable=true)
public String getErrCodeDes() {
return errCodeDes;
}
public void setErrCodeDes(String errCodeDes) {
this.errCodeDes = errCodeDes;
}
@Column(name ="is_subscribe",nullable=true)
public String getIsSubscribe() {
return isSubscribe;
}
public void setIsSubscribe(String isSubscribe) {
this.isSubscribe = isSubscribe;
}
@Column(name ="trade_type",nullable=true)
public String getTradeType() {
return tradeType;
}
public void setTradeType(String tradeType) {
this.tradeType = tradeType;
}
@Column(name ="bank_type",nullable=true)
public String getBankType() {
return bankType;
}
public void setBankType(String bankType) {
this.bankType = bankType;
}
@Column(name ="total_fee",nullable=true)
public Integer getTotalFee() {
return totalFee;
}
public void setTotalFee(Integer totalFee) {
this.totalFee = totalFee;
}
@Column(name ="fee_type",nullable=true)
public String getFeeType() {
return feeType;
}
public void setFeeType(String feeType) {
this.feeType = feeType;
}
@Column(name ="cash_fee",nullable=true)
public Integer getCashFee() {
return cashFee;
}
public void setCashFee(Integer cashFee) {
this.cashFee = cashFee;
}
@Column(name ="cash_fee_type",nullable=true)
public String getCashFeeType() {
return cashFeeType;
}
public void setCashFeeType(String cashFeeType) {
this.cashFeeType = cashFeeType;
}
@Column(name ="coupon_fee",nullable=true)
public Integer getCouponFee() {
return couponFee;
}
public void setCouponFee(Integer couponFee) {
this.couponFee = couponFee;
}
@Column(name ="coupon_count",nullable=true)
public Integer getCouponCount() {
return couponCount;
}
public void setCouponCount(Integer couponCount) {
this.couponCount = couponCount;
}
@Column(name ="coupon_id",nullable=true)
public String getCouponId() {
return couponId;
}
public void setCouponId(String couponId) {
this.couponId = couponId;
}
@Column(name ="sign",nullable=true)
public String getSign() {
return sign;
}
public void setSign(String sign) {
this.sign = sign;
}
// @Column(name ="sign_type",nullable=true)
// public String getSignType() {
// return signType;
// }
//
//
// public void setSignType(String signType) {
// this.signType = signType;
// }
@Column(name ="transaction_id",nullable=true)
public String getTransactionId() {
return transactionId;
}
public void setTransactionId(String transactionId) {
this.transactionId = transactionId;
}
@Column(name ="out_trade_no",nullable=true)
public String getOutTradeNo() {
return outTradeNo;
}
public void setOutTradeNo(String outTradeNo) {
this.outTradeNo = outTradeNo;
}
@Column(name ="attach",nullable=true)
public String getAttach() {
return attach;
}
public void setAttach(String attach) {
this.attach = attach;
}
@Column(name ="time_end",nullable=true)
public Date getTimeEnd() {
return timeEnd;
}
public void setTimeEnd(Date timeEnd) {
this.timeEnd = timeEnd;
}
@Column(name ="createtime",nullable=true)
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
@Column(name ="updatetime",nullable=true)
public Date getUpdateTime() {
return updateTime;
}
public void setUpdateTime(Date updateTime) {
this.updateTime = updateTime;
}
@Column(name ="dealer_order_id",nullable=true)
public Integer getDealerOrderId() {
return dealerOrderId;
}
public void setDealerOrderId(Integer dealerOrderId) {
this.dealerOrderId = dealerOrderId;
}
}