很久之前做了微信支付,其中也趟过很多坑,现在有时间就做个自我梳理吧算是。
公众号开发的基本配置(不明白的可以参考https://blog.csdn.net/TOP__ONE/article/details/78183209),这里不再继续阐述。
如果以下代码涉及到微信工具类方法,而我没有提到的,请到链接下载:https://download.csdn.net/download/top__one/10875681
实现微信页面的分享自定义接口功能,需要先配置js-sdk以下数据项,所以需要先获取这些数据项。详细配置可以参考上一篇文章https://blog.csdn.net/TOP__ONE/article/details/85247401中的第一步后台参数准备配置,这里就不重复写了。
在页面配置好以下参数,同样是引用的js-1.2.0版本
<script type="text/javascript">
$(document).ready(function(){
var appId = $("#appId").val();
var timestamp = $("#timestamp").val();
var nonceStr = $("#nonceStr").val();
var signature = $("#signature").val();
//var jsonObj = eval('('+t+')');
wx.config({
debug: false,
appId: appId,
timestamp: timestamp,
nonceStr: nonceStr,
signature: signature,
jsApiList: [
'chooseWXPay',
'checkJsApi',
'closeWindow'
]
});
wx.error(function(res){
//alert(JSON.stringify(res));
// config信息验证失败会执行error函数,如签名过期导致验证失败,具体错误信息可以打开config的debug模式查看,也可以在返回的res参数中查看,对于SPA可以在这里更新签名。
});
wx.checkJsApi({
jsApiList: ['checkJsApi'], // 需要检测的JS接口列表,所有JS接口列表见附录2,
success: function(res) {
//alert(res);
// 以键值对的形式返回,可用的api值true,不可用为false
// 如:{"checkResult":{"chooseImage":true},"errMsg":"checkJsApi:ok"}
}
});
});
准备好页面参数配置好以后就可以进行下面程序了,进行 统一下单 微信统一下单操作:
$(function(){
//此方法就是为了获取统一订单id之类的信息,与后台配套
$("#paybt").click(function(){
$.ajax({
type: "POST",
url: "*/payGetPreId.action",
data:,
dataType : "html",
success : function(msg) {
msg=eval('(' + msg + ')');
if("fail"==msg.info){
alert(mgs.content);
return false;
}
wx.chooseWXPay({
timestamp : msg.timeStamp, // 支付签名时间戳,注意微信jssdk中的所有使用timestamp字段均为小写。但最新版的支付后台生成签名使用的timeStamp字段名需大写其中的S字符
nonceStr : msg.nonceStr, // 支付签名随机串,不长于 32 位
package : msg.prep, // 统一支付接口返回的prepay_id参数值,提交格式如:prepay_id=***)
signType : 'MD5', // 签名方式,默认为'SHA1',使用新版支付需传入'MD5'
paySign : msg.paySign, // 支付签名
success : function(res) {
$.ajax({
type: "POST",
async: false,
url: pay/success/",
data:,
dataType : "html",
success : function(msg) {
//alert(msg);
}
});
a();
},
cancel: function(res) {
//支付取消
alert('支付取消');
}
});
}
});
});
});
后台对应的payGetPreId方法
/**
* 获取统一下单的id
*
* @author wang
* @dateTime 上午10:17:51
* @param RenewalController
* .java
* @return
* @throws ServiceException
*
*/
@ResponseBody
@RequestMapping(value = "/payGetPreId", produces = { "application/json;charset=UTF-8" })
public String payGetPreId(Model model, @RequestParam Map<String, String> map) {
String buy_time = StringUtils.getNowTime();
// 生成订单号
String rStr = TokenGenerator.getPwd(6);
String id = buy_time.replace("-", "").replace(" ", "").replace(":", "")
+ rStr;
WxPaySendData data = new WxPaySendData();
String nonce_str = Sign.create_nonce_str();
String timep = Sign.create_timestamp();
String ip = GetIp.getLocalIp(this.getRequest());
data.setAppid(PropertieSingle.getInstance().getProperty("APPID"));
data.setMch_id(PropertieSingle.getInstance().getProperty("APP_MCH"));
data.setBody("IncallOrderPay");
data.setNonce_str(nonce_str);
// 支付回调url需要重新设置
data.setNotify_url("http://*/payBack.action");
logger.info("微信支付回调url地址======"+data.getNotify_url());
data.setOut_trade_no(id);
data.setTotal_fee((int) (Double.parseDouble(map.get("combo_price")) * 100));// 单位:分
//data.setTotal_fee(1);// 单位:分
data.setTrade_type("JSAPI");
data.setSpbill_create_ip(ip);// 获取本地ip
String openid = (String) this.getSession().getAttribute("openid");
data.setOpenid(openid);
logger.info("微信支付中的openid参数========"+data.getOpenid());
String sign = UnifiedorderService.unifiedOrder(data, PropertieSingle
.getInstance().getProperty("APP_SECRET"));
Map<String, String> m = new DOMXML().parse(sign);
String pre1 = m.get("prepay_id");
String prep = "prepay_id=" + pre1;
// 加 session
SortedMap<Object, Object> parameters = new TreeMap<Object, Object>();
parameters.put("appId",
PropertieSingle.getInstance().getProperty("APPID"));
parameters.put("timeStamp", timep);
parameters.put("nonceStr", nonce_str);
parameters.put("package", prep);
parameters.put("signType", "MD5");
this.getSession().setAttribute("p", parameters);
// FIXME 再获取一次singture 查看下文档
String signAgain = Sign.createSign(parameters, PropertieSingle
.getInstance().getProperty("APP_SECRET"));
String paySign = signAgain;
String nonceStr = (String) parameters.get("nonceStr");
String timeStamp = (String) parameters.get("timeStamp");
JSONObject json = new JSONObject();
json.put("info", "ok");
json.put("timeStamp", timeStamp);
json.put("nonceStr", nonceStr);
json.put("prep", prep);
json.put("paySign", paySign);
map.put("wxhost", PropertieSingle.getInstance().getProperty("WXNOTIFYHOST"));
json.put("map", map);
return json.toString();
}
/**
* 微信支付回调方法,用于验证微信发来的请求
*
* @param model
* @param map
*/
@ResponseBody
@RequestMapping(value = "/payBack", produces = { "application/json;charset=UTF-8" })
public void payBack(Model model, @RequestParam Map<String, String> map) {
InputStream inStream = null;
ByteArrayOutputStream outSteam = null;
String result = "";
try {
inStream = this.getRequest().getInputStream();
outSteam = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len = 0;
while ((len = inStream.read(buffer)) != -1) {
outSteam.write(buffer, 0, len);
}
result = new String(outSteam.toByteArray(), "utf-8");
} catch (Exception e) {
e.printStackTrace();
} finally {
if (outSteam != null) {
try {
outSteam.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (inStream != null) {
try {
inStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
// 解析微信发过来回调内容
Map<String, String> m = XMLUtil.doXMLParse(result);
if (m.get("result_code").equalsIgnoreCase("success")) {
//成功回调,可以进行自己的业务操作,参数可以从m中取
System.out.println(m.get("out_trade_no"));
//m.get("out_trade_no").substring(0,m.get("out_trade_no").length()-1));
try {
this.getResponse().getWriter()
.write(PayCommonUtil.setXML("SUCCESS", ""));
} catch (IOException e) {
e.printStackTrace();
}
// 告诉微信服务器,我收到信息了,不要在调用回调action了
System.out.println("-------------"
+ PayCommonUtil.setXML("SUCCESS", ""));
} else {
System.out.println("11111111111111111");
}
}
其中涉及到的工具类sign
package com.chinatsp.wechat.util;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Formatter;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.UUID;
public class Sign {
public static void main(String[] args) {
String jsapi_ticket = "jsapi_ticket";
// 注意 URL 一定要动态获取,不能 hardcode
String url = "http://example.com";
Map<String, String> ret = sign(jsapi_ticket, url);
for (Map.Entry entry : ret.entrySet()) {
System.out.println(entry.getKey() + ", " + entry.getValue());
}
};
/**
* 这是jssdk中需要的那个singture签名
* @param jsapi_ticket
* @param url
* @return
*/
public static Map<String, String> sign(String jsapi_ticket, String url) {
Map<String, String> ret = new HashMap<String, String>();
String nonce_str = create_nonce_str();//定义的字符串
String timestamp = create_timestamp();//同上
String string1;
String signature = "";
System.out.println(nonce_str);
System.out.println(timestamp);
System.out.println(jsapi_ticket);
System.out.println(url);
//注意这里参数名必须全部小写,且必须有序
string1 = "jsapi_ticket=" + jsapi_ticket +
"&noncestr=" + nonce_str +
"×tamp=" + timestamp +
"&url=" + url;
System.out.println(string1);
try
{
MessageDigest crypt = MessageDigest.getInstance("SHA-1");//加密方法
crypt.reset();
crypt.update(string1.getBytes("UTF-8"));
signature = byteToHex(crypt.digest());
}
catch (NoSuchAlgorithmException e)
{
e.printStackTrace();
}
catch (UnsupportedEncodingException e)
{
e.printStackTrace();
}
ret.put("url", url);
ret.put("jsapi_ticket", jsapi_ticket);
ret.put("nonceStr", nonce_str);
ret.put("timestamp", timestamp);
ret.put("signature", signature);
return ret;
}
/**
* 这是微信支付里面需要哪个签名
* @param parameters
* @param key
* @return
*/
private static String characterEncoding = "UTF-8";
@SuppressWarnings("rawtypes")
public static String createSign(SortedMap<Object,Object> parameters,String key){
StringBuffer sb = new StringBuffer();
Set es = parameters.entrySet();//所有参与传参的参数按照accsii排序(升序)
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=" + key);
String signature = MD5Util.MD5Encode(sb.toString(), characterEncoding).toUpperCase();
return signature;
}
//加密方法
private static String byteToHex(final byte[] hash) {
Formatter formatter = new Formatter();
for (byte b : hash)
{
formatter.format("%02x", b);
}
String result = formatter.toString();
formatter.close();
return result;
}
public static String create_nonce_str() {
return UUID.randomUUID().toString().replaceAll("-", "");
}
public static String create_timestamp() {
return Long.toString(System.currentTimeMillis() / 1000);
}
}
工具类DOMXML
package com.chinatsp.wechat.util;
import java.io.StringReader;
import java.util.HashMap;
import java.util.Map;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
public class DOMXML {
public Map<String,String> parse(String protocolXML) {
Map<String,String> m = new HashMap<String, String>();
try {
DocumentBuilderFactory factory = DocumentBuilderFactory
.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder
.parse(new InputSource(new StringReader(protocolXML)));
Element root = doc.getDocumentElement();
NodeList books = root.getChildNodes();
if (books != null) {
for (int i = 0; i < books.getLength(); i++) {
Node book = books.item(i);
// System.out.println("节点=" + book.getNodeName() + "\ttext="
// + book.getFirstChild().getNodeValue());
m.put(book.getNodeName(), book.getFirstChild().getNodeValue());
}
}
} catch (Exception e) {
e.printStackTrace();
}
return m;
}
}
GetIp工具类
package com.chinatsp.wechat.util;
import javax.servlet.http.HttpServletRequest;
public class GetIp{
/**
* 从Request对象中获得客户端IP,处理了HTTP代理服务器和Nginx的反向代理截取了ip
* @param request
* @return ip
*/
public static String getLocalIp(HttpServletRequest request) {
String remoteAddr = request.getRemoteAddr();
String forwarded = request.getHeader("X-Forwarded-For");
String realIp = request.getHeader("X-Real-IP");
String ip = null;
if (realIp == null) {
if (forwarded == null) {
ip = remoteAddr;
} else {
ip = forwarded.split(",")[0];
}
} else {
if (realIp.equals(forwarded)) {
ip = realIp;
} else {
if(forwarded != null){
forwarded = forwarded.split(",")[0];
}
ip = forwarded;
}
}
return ip;
}
}
UnifiedorderService.unifiedOrder工具类方法 重点
package com.chinatsp.wechat.util;
import java.io.IOException;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import weixin.util.HttpUtils;
import com.alibaba.fastjson.JSONObject;
import com.chinatsp.wechat.bean.WxPaySendData;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.xml.DomDriver;
import com.thoughtworks.xstream.io.xml.XmlFriendlyNameCoder;
public class UnifiedorderService {
private final static Logger logger = LoggerFactory.getLogger(UnifiedorderService.class);
public static String unifiedOrder(WxPaySendData data,String key){
//统一下单支付
String returnXml = null;
try {
//生成sign签名
SortedMap<Object,Object> parameters = new TreeMap<Object,Object>();
parameters.put("appid", data.getAppid());
parameters.put("body", data.getBody());
parameters.put("mch_id", data.getMch_id());
parameters.put("nonce_str", data.getNonce_str());
parameters.put("notify_url", data.getNotify_url());
parameters.put("out_trade_no", data.getOut_trade_no());
parameters.put("total_fee", data.getTotal_fee()+"");
parameters.put("trade_type", data.getTrade_type());
parameters.put("spbill_create_ip", data.getSpbill_create_ip());
parameters.put("openid", data.getOpenid());
// parameters.put("time_start", data.getTime_start());
// parameters.put("time_expire", data.getTime_expire());
logger.info("SIGN:"+Sign.createSign(parameters,key));
data.setSign(Sign.createSign(parameters,key));
XStream xs = new XStream(new DomDriver("UTF-8",new XmlFriendlyNameCoder("-_", "_")));
xs.alias("xml", WxPaySendData.class);
String xml = xs.toXML(data);
logger.info("统一下单xml为:\n" + xml);
returnXml = HttpUtils.doRequest("https://api.mch.weixin.qq.com/pay/unifiedorder","POST", xml);
logger.info("返回结果:" + returnXml);
System.out.println(returnXml);
} catch (Exception e) {
e.printStackTrace();
}
return returnXml;
}
public static void main(String[] args) {
WxPaySendData data = new WxPaySendData();
data.setAppid("wx1b");
data.setBody("wxgzzgzgufy");
data.setMch_id("13901");
data.setNonce_str("12345678");
data.setNonce_str(Sign.create_nonce_str());
data.setNotify_url("yy/testindex.action");
data.setOut_trade_no("122125112");
data.setTotal_fee(1);//单位:分
data.setTrade_type("JSAPI");
data.setSpbill_create_ip("192.16.");
data.setOpenid("oogDE");
String sign = UnifiedorderService.unifiedOrder(data, "pq19L**SwY5EB");
// String s = "<xml><appid><![CDATA[wx2c43b]]></appid><attach><![CDATA[支付测试]]></attach><bank_type><![CDATA[CFT]]></bank_type> <fee_type><![CDATA[CNY]]></fee_type> <is_subscribe><![CDATA[Y]]></is_subscribe> <mch_id><![CDATA[10000100]]></mch_id> <nonce_str><![CDATA[5d2b6c46e531c]]></nonce_str> <openid><![CDATA[oUpFkE]]></openid> <out_trade_no><![CDATA[14653]]></out_trade_no> <result_code><![CDATA[SUCCESS]]></result_code> <return_code><![CDATA[SUCCESS]]></return_code> <sign><![CDATA[B552ED6B278AB241]]></sign> <sub_mch_id><![CDATA[10000]]></sub_mch_id> <time_end><![CDATA[20140903131540]]></time_end> <total_fee>1</total_fee> <trade_type><![CDATA[JSAPI]]></trade_type> <transaction_id><![CDATA[100440079030005092168]]></transaction_id> </xml>";
// String xmlToJSON = XmlUtils.xmlToJSON(s);
// Map<String,String> map = (Map)JSONObject.toJSON(xmlToJSON);
// System.out.println(map.get("prepay_id"));
Map<String,String> m=XMLUtil.doXMLParse(sign);
System.out.println(m.size());
}
}
至此,微信公众号的支付功能就开发完毕了·、留此备忘一下~
具体的jsapi支付功能请参考详细文档https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_1