第一:首先有两个条件:
第二:大致步骤:
获取金额->构造商户自己的订单->签名认证->服务端请求微信获取微信订单->构造移动端所需参数->移动端客户支付->微信回调->商户自己的业务逻辑实现
第三:具体实现:
构造移动端所需参数
/**
* 构造客户端所需的参数(这里是业务层代码实现,控制层不再罗列)
* return 返回map的JSON串
*/
public String getOnlinePayInfo(HttpServletRequest request) throws Exception {
String orderId = "orderId"; // 这里构造自己的订单
String totalFee = request.getParameter("totalFee"); //金额由客户端获取还是服务端生成根据自己的需求而定
String noncestr = WeChatUtil.getRandomString();
String notify_url = Config.ReadStringPropertie("serverPath") + "/" + request.getContextPath()
+ "/onlinePay/callBackWXpay";
int fee = (int) (Float.parseFloat(totalFee + "F") * 100); // 微信支付的金额,微信支付金额的单位是分,这里需要自行转换
SortedMap reqMap = new TreeMap<>();
reqMap.put("appid", WeChatConfig.AppId); // APPID
reqMap.put("mch_id", WeChatConfig.MchId); // 商户ID
reqMap.put("nonce_str", noncestr); // 32位随机字符串
reqMap.put("body","我要支付"); // 商品描述
reqMap.put("out_trade_no", orderId); //商户系统内部的订单号,
reqMap.put("total_fee", fee + ""); //订单总金额,微信支付的单位为分
reqMap.put("spbill_create_ip", WeChatUtil.getHostIp()); //用户端实际ip
reqMap.put("notify_url", notify_url); //回调通知地址
reqMap.put("trade_type", "APP"); //交易类型
reqMap.put("sign", WeChatUtil.createSign(WeChatConfig.input_charset, reqMap));// 签名
// 请求微信,目的是获取prepay_id,必需先转换成xml格式;需要对xml进行转码,不然会报编码格式的问题
String retStr = HttpClientUtil.postHttplient(WeChatConfig.PrepayUrl, new String(WeChatUtil.getRequestXml(reqMap).toString().getBytes("utf-8")));
// 构造客户端需要的参数
Map wxpayRet = WeChatUtil.doXMLParse(retStr);
SortedMap retMap = new TreeMap<>();
if (wxpayRet.get("return_code").equals("SUCCESS")) {
retMap.put("appid", WeChatConfig.AppId);
retMap.put("noncestr", noncestr);
retMap.put("partnerid", WeChatConfig.MchId);
retMap.put("prepayid", wxpayRet.get("prepay_id"));
retMap.put("package", "Sign=WXPay");
retMap.put("timestamp", System.currentTimeMillis() / 1000); // 时间戳为10位
retMap.put("sign", WeChatUtil.createSign(WeChatConfig.input_charset, retMap));
}
return JSONUtil.writeMapJSON(retMap);
}
移动端支付后的回调
/**
* 微信支付的回调方法
*
* @param request 请求
* @param response 响应
*/
@RequestMapping(value = "/callBackWXpay", method = {RequestMethod.GET, RequestMethod.POST})
public synchronized void callBackWXpay(HttpServletRequest request, HttpServletResponse response) {
SortedMap ret = new TreeMap<>();
try {
if (service.callBackWXpay(request)) {
ret.put("return_code", "SUCCESS");
ret.put("return_msg", "OK");
response.getWriter().write(WeChatUtil.getRequestXml(ret));
} else {
ret.put("return_code", "FALSE");
ret.put("return_msg", "支付失败");
response.getWriter().write(WeChatUtil.getRequestXml(ret));
}
} catch (Exception e) {
e.printStackTrace();
}
}
// service中callBackWXpay的实现
@Override
public boolean callBackWXpay(HttpServletRequest request) throws Exception {
String inputLine;
String notityXml = "";
Map retMap = new HashMap();
// 取出回调中的数据
while (null != (inputLine = request.getReader().readLine())) {
notityXml += inputLine;
}
request.getReader().close();
Map responseMap = WeChatUtil.doXMLParse(notityXml);
String return_code = responseMap.get("return_code");
String orderId = responseMap.get("out_trade_no"); // 商户订单号
// 验证签名,判断支付是否成功
if (WeChatUtil.checkSign(responseMap)) {
if (StringUtils.isNotBlank(return_code) && return_code.equals("SUCCESS")) {
// 这里处理支付成功的业务逻辑
return true;
}
}
} else {
// 这里处理支付失败的业务逻辑
}
return false;
}
用到的工具类
/**
* 微信支付的配置信息
* Created by zhangcg on 2017/4/5.
*/
public class WeChatConfig {
/**
* 预支付请求地址
*/
public static final String PrepayUrl = "https://api.mch.weixin.qq.com/pay/unifiedorder";
/**
* 查询订单地址
*/
public static final String OrderUrl = "https://api.mch.weixin.qq.com/pay/orderquery";
/**
* 关闭订单地址
*/
public static final String CloseOrderUrl = "https://api.mch.weixin.qq.com/pay/closeorder";
/**
* 申请退款地址
*/
public static final String RefundUrl = "https://api.mch.weixin.qq.com/secapi/pay/refund";
/**
* 查询退款地址
*/
public static final String RefundQueryUrl = "https://api.mch.weixin.qq.com/pay/refundquery";
/**
* 下载账单地址
*/
public static final String DownloadBillUrl = "https://api.mch.weixin.qq.com/pay/downloadbill";
/**
* 商户APPID
*/
public static final String AppId = "***";
/**
* 商户账户 获取支付能力后,从邮件中得到
*/
public static final String MchId = "***";
/**
* 商户秘钥 32位,在微信商户平台中设置
*/
public static final String AppSercret = "***";
/**
* 编码格式
*/
public static final String input_charset = "UTF-8";
/**
* 商品描述
*/
public static final String body ="***";
}
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.input.SAXBuilder;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.*;
/**
* 微信支付的工具类
* Created by zhangcg on 2017/4/5.
*/
public class WeChatUtil {
/**
* 组装请求的xml
*
* @param parameters 集合参数
* @return
*/
public static String getRequestXml(SortedMap parameters) {
StringBuffer sb = new StringBuffer();
sb.append("");
Set es = parameters.entrySet();
Iterator it = es.iterator();
while (it.hasNext()) {
Map.Entry entry = (Map.Entry) it.next();
String k = (String) entry.getKey();
String v = (String) entry.getValue();
if ("sign".equalsIgnoreCase(k)) {
} else if ("attach".equalsIgnoreCase(k) || "body".equalsIgnoreCase(k)) {
sb.append("" + "" + k + ">");
} else {
sb.append("" + v + "" + k + ">");
}
}
sb.append("" + "" + "sign" + ">");
sb.append("");
return sb.toString();
}
/**
* 得到随机字符串
*
* @return
*/
public static String getRandomString() {
int length = 32;
String str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
Random random = new Random();
StringBuffer sb = new StringBuffer();
for (int i = 0; i < length; ++i) {
int number = random.nextInt(62);//[0,62)
sb.append(str.charAt(number));
}
return sb.toString();
}
/**
* 定义签名,微信根据参数字段的ASCII码值进行排序 加密签名,故使用SortMap进行参数排序
*
* @param characterEncoding 编码格式
* @param parameters 集合参数
* @return 生成带签名的串
*/
public static String createSign(String characterEncoding, SortedMap 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=" + WeChatConfig.AppSercret);//最后加密时添加商户密钥,由于key值放在最后,所以不用添加到SortMap里面去,单独处理,编码方式采用UTF-8
String sign = MD5Util.MD5Encode(sb.toString(), characterEncoding).toUpperCase();
return sign;
}
public static boolean checkSign(Map map) {
String charset = "UTF-8";
String signFromAPIResponse = map.get("sign");
if (null == signFromAPIResponse || "".equals(signFromAPIResponse)) {
return false;
}
SortedMap packageParams = new TreeMap<>();
for (String parameter : map.keySet()) {
String parameterValue = map.get(parameter);
String v = "";
if (null != parameterValue) {
v = parameterValue.trim();
}
packageParams.put(parameter, v);
}
StringBuffer stringBuffer = new StringBuffer();
Set es = packageParams.entrySet();
Iterator it = es.iterator();
while (it.hasNext()) {
Map.Entry entry = (Map.Entry) it.next();
String k = (String) entry.getKey();
String v = (String) entry.getValue();
if (!"sign".equals(k) && null != v && !"".equals(v)) {
stringBuffer.append(k + "=" + v + "&");
}
}
stringBuffer.append("key=" + WeChatConfig.AppSercret);
String resultSign = "";
String tobesign = stringBuffer.toString();
if (null == charset || "".equals(charset)) {
resultSign = MD5Util.MD5Encode(tobesign, WeChatConfig.input_charset).toUpperCase();
} else {
resultSign = MD5Util.MD5Encode(tobesign, WeChatConfig.input_charset).toUpperCase();
}
String tenpaySign = (packageParams.get("sign")).toUpperCase();
return tenpaySign.equals(resultSign);
}
/**
* 解析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 = getChildrenText(children);
}
m.put(k, v);
}
//关闭流
in.close();
return m;
}
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("");
if (!list.isEmpty()) {
sb.append(getChildrenText(list));
}
sb.append(value);
sb.append("" + name + ">");
}
}
return sb.toString();
}
/**
* 得到本地机器的IP
*
* @return
*/
public static String getHostIp() {
String ip = "";
try {
ip = InetAddress.getLocalHost().getHostAddress();
} catch (UnknownHostException e) {
e.printStackTrace();
}
return ip;
}
}
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.methods.StringRequestEntity;
import org.apache.commons.httpclient.params.HttpMethodParams;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLContexts;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import javax.net.ssl.SSLContext;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.security.KeyStore;
import java.util.Map;
/**
* Created by zhangcg on 2017/4/5.
*/
public class HttpClientUtil {
public static String post(String url, Map headMap, Map params) {
try {
HttpClient httpclient = new HttpClient();
httpclient.getParams().setParameter(HttpMethodParams.HTTP_CONTENT_CHARSET, "UTF-8");
PostMethod httpPost = new PostMethod(url);
if (null != headMap) {
for (String key : headMap.keySet()) {
httpPost.setRequestHeader(key, headMap.get(key));
}
}
if (null != params) {
for (String pkey : params.keySet()) {
httpPost.addParameter(pkey, params.get(pkey));
}
}
httpclient.executeMethod(httpPost);
BufferedReader reader = new BufferedReader(new InputStreamReader(httpPost.getResponseBodyAsStream()));
StringBuffer stringBuffer = new StringBuffer();
String str = "";
while ((str = reader.readLine()) != null) {
stringBuffer.append(str);
}
reader.close();
return stringBuffer.toString();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static String postHttplient(String url, String xmlInfo) {
try {
HttpClient httpclient = new HttpClient();
httpclient.getParams().setParameter(HttpMethodParams.HTTP_CONTENT_CHARSET, "UTF-8");
PostMethod httpPost = new PostMethod(url);
httpPost.setRequestEntity(new StringRequestEntity(xmlInfo));
httpclient.executeMethod(httpPost);
BufferedReader reader = new BufferedReader(new InputStreamReader(httpPost.getResponseBodyAsStream()));
StringBuffer stringBuffer = new StringBuffer();
String str = "";
while ((str = reader.readLine()) != null) {
stringBuffer.append(str);
}
reader.close();
return stringBuffer.toString();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static String postHttplientNeedSSL(String url, String xmlInfo, String cretPath, String mrchId)
throws Exception {
//选择初始化密钥文件格式
KeyStore keyStore = KeyStore.getInstance("PKCS12");
//得到密钥文件流
FileInputStream instream = new FileInputStream(new File(cretPath));
try {
//用商户的ID 来解读文件
keyStore.load(instream, mrchId.toCharArray());
} finally {
instream.close();
}
//用商户的ID 来加载
SSLContext sslcontext = SSLContexts.custom().loadKeyMaterial(keyStore, mrchId.toCharArray()).build();
// Allow TLSv1 protocol only
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslcontext, new String[]{"TLSv1"}, null, SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
//用最新的httpclient 加载密钥
CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(sslsf).build();
StringBuffer ret = new StringBuffer();
try {
HttpPost httpPost = new HttpPost(url);
httpPost.setEntity(new StringEntity(xmlInfo));
CloseableHttpResponse response = httpclient.execute(httpPost);
try {
HttpEntity entity = response.getEntity();
if (entity != null) {
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(entity.getContent()));
String text;
while ((text = bufferedReader.readLine()) != null) {
ret.append(text);
}
}
EntityUtils.consume(entity);
} finally {
response.close();
}
} finally {
httpclient.close();
}
return ret.toString();
}
}
import java.security.MessageDigest;
/**
* 微信MD5加密工具
* Created by zhangcg on 2017/4/5.
*/
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"};
}