-----------------------------------------------------------------------------------------------1.微信 手机网页支付 流程图------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------2.前台页面--------------------------------------------------------------------------------------------------
根据上面的流程,下面一步一步的实现这个微信支付的逻辑。
前台页面 userPayView.jsp
<%@ page language="java" import="java.util.*" pageEncoding="ISO-8859-1"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<base href="<%=basePath%>">
<title>模拟支付页面--微信支付/支付宝支付</title>
<meta http-equiv="pragma" content="no-cache">
<meta http-equiv="cache-control" content="no-cache">
<meta http-equiv="expires" content="0">
<meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
<meta http-equiv="description" content="This is my page">
</head>
<body>
<input type="text" value="" name="openID"/> <!-- 微信 所需OpenID -->
<input type="text" value="" name="orderID"/><!-- 微信 支付宝[out_trade_no] -->
<input type="number" value="" name="money"/><!-- 微信[分为单位,不允许小数] 支付宝[total_amount 元为单位,精确到小数点后2位] 商品价格 -->
<input type="text" value="" name="subject"/><!-- 支付宝 商品的标题/交易标题/订单标题/订单关键字等。 -->
<input type="text" value="" name="product_code"/><!-- 支付宝 销售产品码,商家和支付宝签约的产品码。该产品请填写固定值:QUICK_WAP_WAY。 -->
<input type="text" value="" name="body"/><!-- 支付宝 商品描述 -->
<button class="payButton">微信支付</button>
<button class="alipayButton">支付宝支付</button>
</body>
<script type="text/javascript" src="/resources/bootstrap-3.3.5-dist/js/jquery-1.10.2.min.js"></script>
<script type="text/javascript" src="/wx/pay/pay.js"></script>
</html>
View Code
微信支付按钮的点击事件 pay.js
/**
* 微信支付按钮的点击事件
*/
$(".payButton").click(function(){
var openID = $("input[name='openID']").val();
var orderID = $("inpuut[name='orderID']").val();
var money = $("input[name='money']").val();
/**
* ①ajax 微信支付按钮点击事件
*/
$.ajax({
url: "/wx/PayOrder/unifiedOrder.jhtml",
type:"GET",
data: {"openId":openID,"orderId":orderID,"money":money },
traditional:true,
success: function(response){
if(response.length > 0){
/**
* ⑤获取到prepayId 继续ajax请求商户服务器
*/
$.ajax({
url: "/wx/PayOrder/createPayConfig.jhtml",
type:"GET",
data: {"prepayId":response },
traditional:true,
success: function(response){
var data = eval('(' + response + ')');
var obj = data.msg;
/**
* ⑦ 根据支付配置,使用微信浏览器内置对象WeixinJSBridge 调用支付页面,完成支付操作,将支付结果返回给①中配置的回调函数
*/
WeixinJSBridge.invoke('getBrandWCPayRequest',{
"appId" : obj.appId, //公众号名称,由商户传入
"timeStamp":obj.timestamp, //时间戳,自 1970 年以来的秒数
"nonceStr" : obj.nonce, //随机串
"package" : obj.packageName, //商品包信息 prepay_Id拼接值
"signType" : obj.signType, //微信签名方式
"paySign" : obj.signature //微信签名
},function(res){
if(res.err_msg == "get_brand_wcpay_request:ok" ) {
alert('支付成功');
}else if(res.err_msg == "get_brand_wcpay_request:cancel"){
alert("支付过程中用户取消");
}else if(res.err_msg == "get_brand_wcpay_request:fail"){
alert("支付失败");
}
});
}
});
}
}});
});
View Code
----------------------------------------------------------------------------------------------3.获取prepay_id时的配置实体类 也就是统一下单实体类-------------------------------------------------------------------------------------------------
统一下单 入参 参考文档:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1
package net.shopxx.wx.pay;
/**
* 微信支付 统一下单 接口提供必填参数的实体类
* @author SXD
*
*/
public class Unifiedorder {
/**
* 公众账号ID
*/
private String appid;
/**
* 商户号
*/
private String mch_id;
/**
* 随机字符串,长度要求在32位以内
*/
private String nonce_str;
/**
* 签名 通过签名算法计算得出的签名值
*/
private String sign;
/**
* 商品描述
*/
private String body;
/**
* 商户系统内部订单号 要求32个字符内
*/
private String out_trade_no;
/**
* 本次支付总金额 单位为分 不能带小数点
*/
private Integer total_fee;
/**
* 用户IP
*/
private String spbill_create_ip;
/**
* 通知地址
* 异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数
*/
private String notify_url;
/**
* 交易类型
* 取值如下:JSAPI,NATIVE,APP等
* JSAPI 公众号支付
* NATIVE 扫描二维码支付
* APP app支付
*/
private String trade_type;
/**
* 用户标识
* trade_type=JSAPI时(即公众号支付),此参数必传,此参数为微信用户在商户对应appid下的唯一标识
*/
private String openid;
public Unifiedorder() {
this.trade_type = "JSAPI";//公众号支付的方式
}
public String getAppid() {
return appid;
}
public void setAppid(String appid) {
this.appid = appid;
}
public String getMch_id() {
return mch_id;
}
public void setMch_id(String mch_id) {
this.mch_id = mch_id;
}
public String getNonce_str() {
return nonce_str;
}
public void setNonce_str(String nonce_str) {
this.nonce_str = nonce_str;
}
public String getSign() {
return sign;
}
public void setSign(String sign) {
this.sign = sign;
}
public String getBody() {
return body;
}
public void setBody(String body) {
this.body = body;
}
public String getOut_trade_no() {
return out_trade_no;
}
public void setOut_trade_no(String out_trade_no) {
this.out_trade_no = out_trade_no;
}
public Integer getTotal_fee() {
return total_fee;
}
public void setTotal_fee(Integer total_fee) {
this.total_fee = total_fee;
}
public String getSpbill_create_ip() {
return spbill_create_ip;
}
public void setSpbill_create_ip(String spbill_create_ip) {
this.spbill_create_ip = spbill_create_ip;
}
public String getNotify_url() {
return notify_url;
}
public void setNotify_url(String notify_url) {
this.notify_url = notify_url;
}
public String getTrade_type() {
return trade_type;
}
public void setTrade_type(String trade_type) {
this.trade_type = trade_type;
}
public String getOpenid() {
return openid;
}
public void setOpenid(String openid) {
this.openid = openid;
}
}
View Code
----------------------------------------------------------------------------------------------4.微信支付的实体类 也就是JS-SDK使用的配置信息实体类----------------------------------------------------------------------------------------------
微信内H5调起支付 配置信息 入参 参考文档:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_7&index=6
package net.shopxx.wx.pay;
/**
* 微信支付
* JS-SDK使用的配置信息 实体
* @author SXD
*
*/
public class JsAPIConfig {
/**
* 公众号id
*/
private String appId;
/**
* 时间戳
*/
private String timeStamp;
/**
* 随机字符串
*/
private String nonceStr;
/**
* 订单详情扩展字符串 prepay_id=***
*/
private String packageStr;
/**
* 签名方式 暂支持MD5
*/
private String signType;
/**
* 签名
*
* 签名算法
* 第一步,设所有发送或者接收到的数据为集合M,将集合M内非空参数值的参数按照参数名ASCII码从小到大排序(字典序),使用URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串stringA。
特别注意以下重要规则:
◆ 参数名ASCII码从小到大排序(字典序);
◆ 如果参数的值为空不参与签名;
◆ 参数名区分大小写;
◆ 验证调用返回或微信主动通知签名时,传送的sign参数不参与签名,将生成的签名与该sign值作校验。
◆ 微信接口可能增加字段,验证签名时必须支持增加的扩展字段
第二步,在stringA最后拼接上key得到stringSignTemp字符串,并对stringSignTemp进行MD5运算,再将得到的字符串所有字符转换为大写,得到sign值signValue。
*
*/
private String paySign;
public JsAPIConfig(){
signType = "MD5";
}
public String getAppId() {
return appId;
}
public void setAppId(String appId) {
this.appId = appId;
}
public String getTimeStamp() {
return timeStamp;
}
public void setTimeStamp(String timeStamp) {
this.timeStamp = timeStamp;
}
public String getNonceStr() {
return nonceStr;
}
public void setNonceStr(String nonceStr) {
this.nonceStr = nonceStr;
}
public String getPackageStr() {
return packageStr;
}
public void setPackageStr(String packageStr) {
this.packageStr = packageStr;
}
public String getSignType() {
return signType;
}
public void setSignType(String signType) {
this.signType = signType;
}
public String getPaySign() {
return paySign;
}
public void setPaySign(String paySign) {
this.paySign = paySign;
}
}
View Code
----------------------------------------------------------------------------------------------5.商户处理后同步返回给微信 订单成功的 实体类--------------------------------------------------------------------------------------------------------
返回信息实体 参考文档:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1 返回结果
package net.shopxx.wx.pay;
/**
* 微信支付 商户处理后同步返回给微信 订单成功的 实体
* @author SXD
*
*/
public class PayCallback {
private String return_code;
private String return_msg;
public PayCallback() {
this.return_code = "SUCCESS";
this.return_msg = "OK";
}
public String getReturn_code() {
return return_code;
}
public void setReturn_code(String return_code) {
this.return_code = return_code;
}
public String getReturn_msg() {
return return_msg;
}
public void setReturn_msg(String return_msg) {
this.return_msg = return_msg;
}
}
View Code
----------------------------------------------------------------------------------------------6.HTTP通信类 在后台访问微信服务器的 通信工具类----------------------------------------------------------------------------------------------------
package net.shopxx.wx.pay;
import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.util.Iterator;
import java.util.Map;
import javax.activation.MimetypesFileTypeMap;
import org.springframework.stereotype.Component;
/**
* 微信支付
* HTTP通信类
*
*/
@Component
public class HttpConnection {
/**
* 向指定URL发送GET方法的请求
* @param url 发送请求的URL
* @param param 请求参数,请求参数应该是name1=value1&name2=value2的形式。
* @return URL所代表远程资源的响应
*/
public String get(String url, String param) throws Exception {
String urlName = url + "?" + param;
return get(urlName);
}
/**
* 向指定URL发送GET方法的请求
*
* @param url
* 发送请求的URL
* @return URL所代表远程资源的响应
*/
public String get(String url) throws Exception {
String result = "";
BufferedReader in = null;
URL realUrl = new URL(url);
URLConnection conn = realUrl.openConnection();
conn.setRequestProperty("accept", "*/*");
conn.setRequestProperty("Accept-Language", "zh-CN,zh;q=0.8");
conn.setRequestProperty("Content-Type", "text/xml;charset=utf-8");
conn.setRequestProperty("connection", "keep-alive");
conn.setRequestProperty("user-agent",
"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)");
// 建立实际的连接
conn.connect();
// 定义BufferedReader输入流来读取URL的响应
in = new BufferedReader(new InputStreamReader(conn.getInputStream(), "UTF-8"));
String line;
while ((line = in.readLine()) != null) {
result += line;
}
in.close();
return result;
}
/**
* 向指定URL发送POST方法的请求
* @param url 发送请求的URL
* @param content 内容
* @return URL所代表远程资源的响应
* @throws Exception
*/
public String post(String url,String content) throws Exception{
String result = "";
URL postUrl = new URL(url);
HttpURLConnection connection = (HttpURLConnection) postUrl
.openConnection();
connection.setDoOutput(true);
connection.setDoInput(true);
connection.setRequestMethod("POST");
connection.setUseCaches(false);
connection.setInstanceFollowRedirects(true);
connection.setRequestProperty("Content-Type",
"application/x-www-form-urlencoded");
connection.connect();
DataOutputStream out = new DataOutputStream(connection
.getOutputStream());
// out.writeBytes(content);
out.write(content.getBytes("UTF-8"));
out.flush();
out.close(); // flush and close
BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream(),"utf-8"));//设置编码,否则中文乱码
String line="";
while ((line = reader.readLine()) != null){
result += line;
}
reader.close();
connection.disconnect();
return result;
}
/**
* 向指定URL发送POST方法的请求
* @Title: post
* @Description: TODO
* @param @param url
* @param @param textMap
* @param @return
* @return String
* @throws
*/
public String post(String url, Map<String, String> textMap){
String res = "";
HttpURLConnection conn = null;
String BOUNDARY = "---------------------------123821742118716"; //boundary就是request头和上传文件内容的分隔符
try {
URL postUrl = new URL(url);
conn = (HttpURLConnection) postUrl.openConnection();
conn.setConnectTimeout(5000);
conn.setReadTimeout(30000);
conn.setDoOutput(true);
conn.setDoInput(true);
conn.setUseCaches(false);
conn.setRequestMethod("POST");
conn.setRequestProperty("Connection", "Keep-Alive");
conn.setRequestProperty("User-Agent",
"Mozilla/5.0 (Windows; U; Windows NT 6.1; zh-CN; rv:1.9.2.6)");
conn.setRequestProperty("Content-Type",
"multipart/form-data; boundary=" + BOUNDARY);
OutputStream out = new DataOutputStream(conn.getOutputStream());
// text
if (textMap != null) {
StringBuffer strBuf = new StringBuffer();
Iterator iter = textMap.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry entry = (Map.Entry) iter.next();
String inputName = (String) entry.getKey();
String inputValue = (String) entry.getValue();
if (inputValue == null) {
continue;
}
strBuf.append("\r\n").append("--").append(BOUNDARY).append(
"\r\n");
strBuf.append("Content-Disposition: form-data; name=\""
+ inputName + "\"\r\n\r\n");
strBuf.append(inputValue);
}
out.write(strBuf.toString().getBytes());
}
byte[] endData = ("\r\n--" + BOUNDARY + "--\r\n").getBytes();
out.write(endData);
out.flush();
out.close();
// 读取返回数据
StringBuffer strBuf = new StringBuffer();
BufferedReader reader = new BufferedReader(new InputStreamReader(
conn.getInputStream()));
String line = null;
while ((line = reader.readLine()) != null) {
strBuf.append(line).append("\n");
}
res = strBuf.toString();
reader.close();
reader = null;
} catch (Exception e) {
System.out.println("发送POST请求出错。" + url);
e.printStackTrace();
} finally {
if (conn != null) {
conn.disconnect();
conn = null;
}
}
return res;
}
/**
* 向指定URL发送POST方法的请求 (带文件)
* @param url 发送请求的URL
* @param textMap 文本参数键值
* @param fileMap 文件键值
* @return URL所代表远程资源的响应
* @throws Exception
*/
public String filePost(String url, Map<String, String> textMap,
Map<String, String> fileMap) {
String res = "";
HttpURLConnection conn = null;
String BOUNDARY = "---------------------------123821742118716"; //boundary就是request头和上传文件内容的分隔符
try {
URL postUrl = new URL(url);
conn = (HttpURLConnection) postUrl.openConnection();
conn.setConnectTimeout(5000);
conn.setReadTimeout(30000);
conn.setDoOutput(true);
conn.setDoInput(true);
conn.setUseCaches(false);
conn.setRequestMethod("POST");
conn.setRequestProperty("Connection", "Keep-Alive");
conn.setRequestProperty("User-Agent",
"Mozilla/5.0 (Windows; U; Windows NT 6.1; zh-CN; rv:1.9.2.6)");
conn.setRequestProperty("Content-Type",
"multipart/form-data; boundary=" + BOUNDARY);
OutputStream out = new DataOutputStream(conn.getOutputStream());
// text
if (textMap != null) {
StringBuffer strBuf = new StringBuffer();
Iterator iter = textMap.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry entry = (Map.Entry) iter.next();
String inputName = (String) entry.getKey();
String inputValue = (String) entry.getValue();
if (inputValue == null) {
continue;
}
strBuf.append("\r\n").append("--").append(BOUNDARY).append(
"\r\n");
strBuf.append("Content-Disposition: form-data; name=\""
+ inputName + "\"\r\n\r\n");
strBuf.append(inputValue);
}
out.write(strBuf.toString().getBytes());
}
// file
if (fileMap != null) {
Iterator iter = fileMap.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry entry = (Map.Entry) iter.next();
String inputName = (String) entry.getKey();
String inputValue = (String) entry.getValue();
if (inputValue == null) {
continue;
}
File file = new File(inputValue);
String filename = file.getName();
String contentType = new MimetypesFileTypeMap()
.getContentType(file);
if (filename.endsWith(".png")) {
contentType = "image/png";
}
if (contentType == null || contentType.equals("")) {
contentType = "application/octet-stream";
}
StringBuffer strBuf = new StringBuffer();
strBuf.append("\r\n").append("--").append(BOUNDARY).append(
"\r\n");
strBuf.append("Content-Disposition: form-data; name=\""
+ inputName + "\"; filename=\"" + filename
+ "\"\r\n");
strBuf.append("Content-Type:" + contentType + "\r\n\r\n");
out.write(strBuf.toString().getBytes());
DataInputStream in = new DataInputStream(
new FileInputStream(file));
int bytes = 0;
byte[] bufferOut = new byte[1024];
while ((bytes = in.read(bufferOut)) != -1) {
out.write(bufferOut, 0, bytes);
}
in.close();
}
}
byte[] endData = ("\r\n--" + BOUNDARY + "--\r\n").getBytes();
out.write(endData);
out.flush();
out.close();
// 读取返回数据
StringBuffer strBuf = new StringBuffer();
BufferedReader reader = new BufferedReader(new InputStreamReader(
conn.getInputStream()));
String line = null;
while ((line = reader.readLine()) != null) {
strBuf.append(line).append("\n");
}
res = strBuf.toString();
reader.close();
reader = null;
} catch (Exception e) {
System.out.println("发送POST请求出错。" + url);
e.printStackTrace();
} finally {
if (conn != null) {
conn.disconnect();
conn = null;
}
}
return res;
}
}
View Code
----------------------------------------------------------------------------------------------7.微信 签名 规则 工具类-------------------------------------------------------------------------------------------------------------------------------------------
签名算法 规则:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=4_3
package net.shopxx.wx.pay;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
import javax.net.ssl.SSLContext;
import javax.security.cert.CertificateException;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.http.ssl.SSLContexts;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
/**
* 微信支付 微信公众号发红包
* 工具类
* @author SXD
*
*/
public class WeXinUtil {
/**
* 获取用户IP
* @param request
* @return
*/
public static String getIp(HttpServletRequest request){
String ipAddress = null;
if (request.getHeader("x-forwarded-for") == null) {
ipAddress = request.getRemoteAddr();
}else{
if(request.getHeader("x-forwarded-for").length() > 15){
String [] aStr = request.getHeader("x-forwarded-for").split(",");
ipAddress = aStr[0];
} else{
ipAddress = request.getHeader("x-forwarded-for");
}
}
return ipAddress;
}
/**
* 签名算法,生成统一下单中 必填项签名
* @param unifiedOrder 1.将统一下单实体中各个字段拼接 2.MD5加密 3.全部转化为大写
* @return 返回经过签名算法生成的签名 sign
* 第一步的规则
* ◆ 参数名ASCII码从小到大排序(字典序);
* ◆ 如果参数的值为空不参与签名;
* ◆ 参数名区分大小写;
* ◆ 验证调用返回或微信主动通知签名时,传送的sign参数不参与签名,将生成的签名与该sign值作校验。
* ◆ 微信接口可能增加字段,验证签名时必须支持增加的扩展字段
*/
/* 手动拼接方式
public String createUnifiedOrderSign(Unifiedorder unifiedOrder){
StringBuffer sign = new StringBuffer();
sign.append("appid=").append(unifiedOrder.getAppid());
sign.append("&body=").append(unifiedOrder.getBody());
sign.append("&mch_id=").append(unifiedOrder.getMch_id());
sign.append("&nonce_str=").append(unifiedOrder.getNonce_str());
sign.append("¬ify_url=").append(unifiedOrder.getNotify_url());
sign.append("&openid=").append(unifiedOrder.getOpenid());
sign.append("&out_trade_no=").append(unifiedOrder.getOut_trade_no());
sign.append("&spbill_create_ip=").append(unifiedOrder.getSpbill_create_ip());
sign.append("&total_fee=").append(unifiedOrder.getTotal_fee());
sign.append("&trade_type=").append(unifiedOrder.getTrade_type());
sign.append("&key=").append(KEY);
return DigestUtils.md5Hex(sign.toString()).toUpperCase();
}
*/
/**
* 拼接生成sign 签名
* @param unifiedOrder
* @param KEY
* @return
* @throws Exception
*/
public static String createUnifiedOrderSign(Object object,String KEY) throws Exception{
StringBuffer sign = new StringBuffer();
Map<String, String> map = getSortMap(object);
boolean isNotFirst = false;
for (Map.Entry<String, String> entry : map.entrySet()) {
if(isNotFirst == true){
sign.append("&");
}else{
isNotFirst = true;
}
sign.append(entry.getKey()).append("=").append(entry.getValue());
}
sign.append("&key=").append(KEY);
return DigestUtils.md5Hex(sign.toString()).toUpperCase();
}
/**
* 使用java反射机制,动态获取对象的属性和参数值,排除值为null的情况,并按字典序排序
* @param object
* @return
* @throws Exception
*/
private static Map<String, String> getSortMap(Object object) throws Exception{
Field[] fields = object.getClass().getDeclaredFields();
Map<String, String> map = new HashMap<String, String>();
for(Field field : fields){
String name = field.getName();
String methodName = "get" + name.replaceFirst(name.substring(0, 1), name.substring(0, 1)
.toUpperCase());
// 调用getter方法获取属性值
// Method getter = object.getClass().getMethod(methodName);
// String value = getter.invoke(object)+"";
field.setAccessible(true);
Object value = field.get(object);
if (value != null){
map.put(name, value.toString());
}
}
Map<String, String> sortMap = new TreeMap<String, String>(
new Comparator<String>() {
@Override
public int compare(String arg0, String arg1) {
return arg0.compareTo(arg1);
}
});
sortMap.putAll(map);
return sortMap;
}
public static SSLContext getSSL() throws KeyStoreException, IOException, CertificateException, NoSuchAlgorithmException, UnrecoverableKeyException, KeyManagementException, java.security.cert.CertificateException {
KeyStore keyStore = KeyStore.getInstance("PKCS12");
//证书位置 放在自己的项目下面
Resource resource = new ClassPathResource("apiclient_cert.p12");
InputStream instream = resource.getInputStream();
try {
keyStore.load(instream, "填写证书密码,默认为商户号".toCharArray());
} finally {
instream.close();
}
SSLContext sslcontext = SSLContexts.custom()
.loadKeyMaterial(keyStore, "填写证书密码,默认为商户号".toCharArray())
.build();
return sslcontext;
}
}
View Code
----------------------------------------------------------------------------------------------8.封装/解析xml消息的工具类-------------------------------------------------------------------------------------------------------------------------------------
package net.shopxx.wx.pay;
import java.io.Writer;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.core.util.QuickWriter;
import com.thoughtworks.xstream.io.naming.NoNameCoder;
import com.thoughtworks.xstream.io.xml.PrettyPrintWriter;
import com.thoughtworks.xstream.io.xml.XppDriver;
/**
* 微信支付 微信公众号发红包
* 封装/解析xml消息的工具类
* @author SXD
*
*/
public class XmlUtil {
public XStream getXstreamInclueUnderline(){
XStream stream = new XStream(new XppDriver(new NoNameCoder()) {
@Override
public PrettyPrintWriter createWriter(Writer out) {
return new PrettyPrintWriter(out) {
// 对所有xml节点的转换都增加CDATA标记
boolean cdata = true;
@Override
@SuppressWarnings("rawtypes")
public void startNode(String name, Class clazz) {
super.startNode(name, clazz);
}
@Override
public String encodeNode(String name) {
return name;
}
@Override
protected void writeText(QuickWriter writer, String text) {
if (cdata) {
writer.write("<![CDATA[");
writer.write(text);
writer.write("]]>");
} else {
writer.write(text);
}
}
};
}
});
return stream;
}
/**
* 根据字符串 解析XML map集合
* @param xml
* @return
* @throws DocumentException
*/
public Map<String, String> parseXML(String xml) throws DocumentException{
Document document = DocumentHelper.parseText(xml);
Element element =document.getRootElement();
List<Element> childElements = element.elements();
Map<String,String> map = new HashMap<String, String>();
map = getAllElements(childElements,map);
map.forEach((k,v)->{
System.out.println(k+">>>>"+v);
});
return map;
}
/**
* 获取 子节点的被迭代方法
* @param childElements
* @param mapEle
* @return
*/
private Map<String, String> getAllElements(List<Element> childElements,Map<String,String> mapEle) {
for (Element ele : childElements) {
if(ele.elements().size()>0){
mapEle = getAllElements(ele.elements(), mapEle);
}else{
mapEle.put(ele.getName(), ele.getText());
}
}
return mapEle;
}
}
View Code
----------------------------------------------------------------------------------------------9.微信支付 商户服务器 逻辑处理中心------------------------------------------------------------------------------------------------------------------------
package net.shopxx.wx.pay;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.util.Map;
import java.util.UUID;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import com.thoughtworks.xstream.XStream;
/**
* 微信支付 逻辑处理
* @author SXD
*
*/
@Controller
@RequestMapping("/wx/PayOrder")
public class PayOrderController {
/**
* 公众账号ID
*/
@Value("${member.appid}")
private String APPID;
/**
* 商户号 暂未配置
*/
private String MCHID;
/**
* key设置路径:微信商户平台(pay.weixin.qq.com)-->账户设置-->API安全-->密钥设置
* 暂未配置
*/
private String KEY;
/**
* 统一下单 URL
*/
@Value("${unifiedOrderUrl}")
private String unifiedOrderUrl;
private XmlUtil xmlUtil = new XmlUtil();
private HttpConnection httpConnection = new HttpConnection();
/**
* ② 用户支付按钮后,先进入统一下单这个方法获取prepay_id
*进行统一下单 ,获取预支付订单的prepay_id
* @param request
*
* @param openId
* @param orderId
* @param money
* @return
* @throws Exception
*/
@ResponseBody
@RequestMapping("/unifiedOrder")
public String unifiedOrder(HttpServletRequest request,String openId,String orderId,int money) throws Exception{
Unifiedorder unifiedOrder = new Unifiedorder();
unifiedOrder.setAppid(APPID);
unifiedOrder.setMch_id(MCHID);
String nonce = UUID.randomUUID().toString().replaceAll("-", "");
unifiedOrder.setNonce_str(nonce);
unifiedOrder.setBody("商品的描述");
unifiedOrder.setOut_trade_no(orderId);
unifiedOrder.setTotal_fee(money);
unifiedOrder.setSpbill_create_ip(WeXinUtil.getIp(request));
unifiedOrder.setNotify_url("http://weixin.myagen.com.cn/wx/PayOrder/wechatPayNotify");
unifiedOrder.setOpenid(openId);
String sign = WeXinUtil.createUnifiedOrderSign(unifiedOrder,KEY);
unifiedOrder.setSign(sign);
/**
* 转成XML格式 微信可接受的格式
*/
xmlUtil.getXstreamInclueUnderline().alias("xml", unifiedOrder.getClass());
String xml = xmlUtil.getXstreamInclueUnderline().toXML(unifiedOrder);
/**
* ③请求微信服务器 返回结果 获取prepay_id
*/
String response = httpConnection.post(unifiedOrderUrl, xml);
System.out.println(response);
Map<String, String> responseMap = xmlUtil.parseXML(response);
/**
* ④商户服务器将prepay_id返回给前台ajax
*/
return responseMap.get("prepay_id");
}
/**
* ⑥根据获取到的prepay_Id ,组装支付需要的相关配置参数,返回给前台网页
* 获取支付 配置
* @param prepayId
* @return
* @throws Exception
*/
@ResponseBody
@RequestMapping("/createPayConfig")
public JsAPIConfig createPayConfig(String prepayId) throws Exception {
JsAPIConfig config = new JsAPIConfig();
String nonce = UUID.randomUUID().toString().replaceAll("-", "");
String timestamp = Long.toString(System.currentTimeMillis() / 1000);
String packageName = "prepay_id="+prepayId;
/**
* 生成签名
*/
StringBuffer sign = new StringBuffer();
sign.append("appId=").append(APPID);
sign.append("&nonceStr=").append(nonce);
sign.append("&package=").append(packageName);
sign.append("&signType=").append(config.getSignType());
sign.append("&timeStamp=").append(timestamp);
sign.append("&key=").append(KEY);
String signature = DigestUtils.md5Hex(sign.toString()).toUpperCase();
config.setAppId(APPID);
config.setNonceStr(nonce);
config.setPackageStr(packageName);
config.setTimeStamp(timestamp);
config.setPaySign(signature);
return config;
}
/**
* ⑧ ⑨回调方法 接收到支付完成后的相关信息,根据是否支付成功 进行商户服务器端的业务逻辑操作,并在最后返回给微信服务器 已经确认成功的信息
* 回调方法
* 异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数
* @param request
* @return
*/
@ResponseBody
@RequestMapping("/wechatPayNotify")
public String wechatPayNotify (HttpServletRequest request){
try {
Map<String, String> map = getCallbackParams(request);
if (map.get("result_code").toString().equalsIgnoreCase("SUCCESS")) {
//这里写成功后的业务逻辑
// String orderId = map.get("out_trade_no");
// orderService.updateConfirm(orderId);
}
}catch(Exception e){
System.out.println(e);
}
/**
* ⑩返回给微信服务器 确认交易完成
*/
return getPayCallback();
}
/**
* 接收支付完成后 微信服务器返回的request,解析返回字符串为键值对
* @param request
* @return
* @throws Exception
*/
public Map<String, String> getCallbackParams(HttpServletRequest request)
throws Exception {
InputStream inStream = request.getInputStream();
ByteArrayOutputStream outSteam = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len = 0;
while ((len = inStream.read(buffer)) != -1) {
outSteam.write(buffer, 0, len);
}
System.out.println("~~~~~~~付款成功~~~~~~~~~");
outSteam.close();
inStream.close();
String result = new String(outSteam.toByteArray(), "utf-8");
return xmlUtil.parseXML(result);
}
/**
* https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_7
* 商户处理后同步返回给微信参数
* @return
*/
public String getPayCallback(){
PayCallback callback = new PayCallback();
XStream stream = new XStream();
stream.alias("xml",callback.getClass() );
String xml = stream.toXML(callback);
return xml;
}
}
View Code
--------------------------------------------------------------------------------------------------------------如上,整个的微信 手机网页内支付-------------------------------------------------------------------------------------------------