当时公司告诉我做微信公众号支付的需求,个人根据微信支付官网以及网上的demo来做完成支付功能。微信公众号支付分为以下步骤:
第一步:配置
1.公司需注册微信公众号支付,提供给后台开发人员以下字段:
appid:微信公众号id==登陆微信公众号后台-开发-基本配置
secret:微信公众号密钥id
mch_id:微信支付商户号==登陆微信支付后台,即可看到
key:商户号对应的密钥
2.设置支付目录:(具体步骤如微信支付官网:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_3) 需在商户平台设置授权域名:xx.xxx.cn (注:这个域名相当于Java项目运行时的ip:端口号) 另外在商户平台设置支付目录:http://域名//项目名
3.测试环境:测试必须在服务器上测试,可以根据微信公众平台文档下载“微信开发者工具”如:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1455784140
首先:公司公众号登录管理后台,邀请开发者微信号为好友,同时开发者的微信要确认。
然后:在微信开发者工具上测试看测试条件是否满足:
(1)用开发者微信登录微信开发者工具。
(2)在浏览器上浏览获取code的链接: https://open.weixin.qq.com/connect/oauth2/authorize? appid=wxxxxxxxxxxxxxxx&redirect_uri=http://域名/项目名/获取openid的接口名&response_type=code&scope=snsapi_base&state=STATE#wechat_redirect ,若环境成功,浏览器上会自动跳到重定向(redirect_uri)的地址上,并自带code和state的值如:http://域名/项目名/获取openid的接口名&code=06jjjjjxxxxxxxxxxxxxxF1q2wmF1b6jmx&state=STATE 这时开发者的测试环境成功。注:redirect_uri地址可以进行urlEncode编码也可以不进行编码。
第二步(后台开发代码编写):
1.获取openid的接口:
要获取openid先获取code,而code值需在前端获取,前端浏览获取code的微信链接:https://open.weixin.qq.com/connect/oauth2/authorize?
前端代码:index.jsp如下:
<html>
<head>
<meta charset="UTF-8">
<meta name="x5-orientation" content="portrait">
<link rel="stylesheet" href="/flowsweb/css/weui.min.css">
<title>微信公众号支付测试</title>
</head>
<body>
<div class="container" id="container">
<a href=" https://open.weixin.qq.com/connect/oauth2/authorize? appid=wxxxxxxxxxxxxxxx&redirect_uri=http://域名/项目名/获取openid的接口名&response_type=code&scope=snsapi_base&state=STATE#wechat_redirect " class="weui-btn weui-btn_primary" style="font-size:40px; 100px;">
立即支付
</a>
</div>
</body>
</html>
获取openid接口代码:
public class wxPayOpenId
{
public static Log LOG = LogFactory.getLog(wxPayOpenId.class);
private static String APPID = "wxxxxxxxxxxxxxxxxxxxx51"; //微信公众号id
private static String SECRET = "2xxxxxxxxxxxxxxxxxc"; //微信公众号密钥id
private static String MCHID = "1xxxxxxxxx"; //微信支付商户号
private static String KEY = "xxxxxxxxxxxxxxxxxxxxx"; //商户号对应的密钥
// private static String CODEURL = "https://open.weixin.qq.com/connect/oauth2/authorize?"; //前端获取code的微信链接
private static String OPENIDURL = "https://api.weixin.qq.com/sns/oauth2/access_token?"; //获取微信openid的链接
private static String UNURL = "https://api.mch.weixin.qq.com/pay/unifiedorder"; //微信统一下单链接
private static String TRADETYPE="JSAPI";//jsapi代表公众号支付
public String execute(Context context) throws BPException {
HttpServletRequest request = (HttpServletRequest) context.getRequest();
//获取用户的code
String code = (String) context.getValue("code"); // 从前端请求获取code, 针对你的框架 String code = request.getParameter("code");
System.out.println("得到用户code值");
System.out.println(code);
//获取openid
String openId=getOpenId(code); //要把openid放在缓存里,因为“微信统一下单接口”需要用openid
context.setValue("openId", String.valueOf(openId));
return "0";
}
// 获取openId
public String getOpenId(String code) {
System.out.println("此时code不为空");
String openIdUrl = OPENIDURL + "appid=" + APPID + "&secret=" + SECRET + "&code=" + code + "&grant_type=authorization_code";
try {
HttpRequest request = HttpRequest.post(openIdUrl).contentType("application/json;charset=utf-8");
String res = request.body();
if (res != null) {
JSONObject obj = JSON.parseObject(res);
System.out.println("输出调用获取openid链接的值~~~~~~~");
System.out.println(obj);
String access_token=obj.getString("access_token");
System.out.println("输出access_token的值~~~~~~~");
System.out.println(access_token);
return obj.getString("openid");
}
return null;
}catch (Exception e) {
return null;
}
return null;
}
}
2.微信统一下单接口:
import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.security.MessageDigest;
import java.util.Iterator;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.blade.kit.http.HttpRequest;
import org.dom4j.*;
import org.dom4j.io.SAXReader;
import sun.util.logging.resources.logging;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.HttpResponse;
public class wxPayHttp{
private static String APPID = "wxxxxxxxxxxxxxxxxxxxx51"; //微信公众号id
private static String SECRET = "2xxxxxxxxxxxxxxxxxc"; //微信公众号密钥id
private static String MCHID = "1xxxxxxxxx"; //微信支付商户号
private static String KEY = "xxxxxxxxxxxxxxxxxxxxx"; //商户号对应的密钥
// private static String CODEURL = "https://open.weixin.qq.com/connect/oauth2/authorize?"; //前端获取code的微信链接
private static String OPENIDURL = "https://api.weixin.qq.com/sns/oauth2/access_token?"; //获取openid的链接
private static String UNURL = "https://api.mch.weixin.qq.com/pay/unifiedorder"; //微信统一下单链接
private static String TRADETYPE="JSAPI";//jsapi代表公众号支付
public String execute(Context context) throws BPException {
HttpServletRequest request = (HttpServletRequest) context.getRequest();
String money=(String) context.getValue("money");
String body=(String) context.getValue("body");
String openid=(String) context.getValue("openid"); //openid要从缓存中获取
String xml=getxml(openid,money,body,request);
System.out.println("输出进行签名的数据xml:~~~~~~~~~~~~~~~");
System.out.println(xml);
String resxml=unifiedorder(xml);//解析xml字符串,取出preparepayid
System.out.println("输出调用微信统一下单所返回的数据resxml:~~~~~~~~~~~~~~~~~~");
System.out.println(resxml); try {
Map<String, String> resultMap=WXPayUtil.xmlToMap(resxml); //WXPayUtil类将在下面展示出来
String return_code=resultMap.get("return_code");
if ("SUCCESS".equalsIgnoreCase(return_code)) {
String prepay_id= resultMap.get("prepay_id");
System.out.println("输出prepay_id的值");
System.out.println(prepay_id);
String nonce_str= System.currentTimeMillis()+""+((int)(Math.random()*90000)+10000);//随机生成18位数字
SortedMap<Object, Object> finalpackage = new TreeMap<Object, Object>();
String timestamp = String.valueOf(System.currentTimeMillis() / 1000); //时间戳
String packages = "prepay_id="+prepay_id; finalpackage.put("appId", APPID);
finalpackage.put("timeStamp", timestamp);
finalpackage.put("nonceStr", nonce_str);
finalpackage.put("package", packages);
finalpackage.put("signType", "MD5");
String finalsign=WXPayUtil.creatSign(finalpackage,KEY); //进行签名
CollList data=(CollList)context.get("data");
Entity entity= new Entity(); //把如下数据放在集合data里传给前端
entity.addElement("appId", APPID);
entity.addElement("timeStamp",timestamp);
entity.addElement("nonceStr", nonce_str);
entity.addElement("package",packages );
entity.addElement("paySign",finalsign );
entity.addElement("signType","MD5");
//entity.addElement("success","ok" );
data.addEntity(entity);
} else {
return "-1";
}
} catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); return "-1"; }
return "0";
}
//组装统一下订单参数并进行签名
public static String getxml(String openid,String money,String body,HttpServletRequest request){
System.out.println("对订单参数进行签名+++++++++");
String returnXml = null;
try {
String nonce_str= System.currentTimeMillis()+""+((int)(Math.random()*90000)+10000);//随机生成18位数字
String notify_url="http://域名/项目名/回调时的接口名";//微信处理完统一订单后回调的接口url
String out_trade_no =WXPayUtil.getCurrTime()+WXPayUtil.getRandomString2(5); //商户订单号
int total_fee=(int)(Double.parseDouble(money)*100); //注意费用total_fee一定要是int型,微信支付官网规定的
SortedMap<Object,Object> parameters = new TreeMap<Object,Object>();
parameters.put("appid", APPID);
parameters.put("mch_id", MCHID);
parameters.put("nonce_str", nonce_str);
//parameters.put("attach", "微信支付");
parameters.put("body", body);
parameters.put("out_trade_no", out_trade_no);
parameters.put("total_fee", total_fee); //注意费用total_fee一定要是int型,微信支付官网规定的
parameters.put("spbill_create_ip", request.getRemoteAddr());//获取订单生成的机器IP
parameters.put("notify_url", notify_url);
parameters.put("trade_type",TRADETYPE );
parameters.put("openid", openid);
//parameters.put("device_info", "WEB");
String sign=WXPayUtil.creatSign(parameters,KEY);
returnXml="<xml>"+
"<appid>"+ APPID+"</appid>"+
"<mch_id>"+ MCHID+"</mch_id>"+
"<nonce_str>"+nonce_str+"</nonce_str>"+
"<sign>"+sign+"</sign>"+
"<body>"+body+"</body>"+
"<out_trade_no>"+out_trade_no+"</out_trade_no>"+
"<total_fee>"+total_fee+"</total_fee>"+
"<spbill_create_ip>"+request.getRemoteAddr()+"</spbill_create_ip>"+
"<notify_url>"+notify_url+"</notify_url>"+
"<trade_type>"+TRADETYPE+"</trade_type>"+
"<openid>"+openid+"</openid>"+
"</xml>";
return returnXml;
} catch (Exception e) { return returnXml; }
}
//调用微信统一下单接口
public static String unifiedorder(String xml){
System.out.println("调用微信统一下单接口+++++++++unifiedorder");
String returnxml=null;
try {
HttpRequest request = HttpRequest.post(UNURL).contentType( "application/json;charset=utf-8").send(xml);
returnxml=request.body();
System.out.println("微信统一下单接口返回值");
System.out.println(returnxml);
return returnxml;
} catch (Exception e) { return returnxml;}
}
}
3.微信回调的接口:
public class wxNotify
{ HttpServletRequest request = (HttpServletRequest) context.getRequest();
try{
boolean flag=false;
ServletInputStream is=null;
InputStreamReader isr = null;
BufferedReader br=null;
try {
is = request.getInputStream();
isr = new InputStreamReader(is);
br =new BufferedReader(isr);
StringBuilder stb = new StringBuilder();
String s = "";
//String return_msg=(String) context.getValue("return_msg");
String return_code = "";
while ((s = br.readLine()) != null) {
stb.append(s);
}
Document document = DocumentHelper.parseText(stb.toString());
List<Element> returnCodeList = document.selectNodes("//return_code");
for (Element element : returnCodeList) {
return_code = element.getText();
}
if(return_code.equals("SUCCESS"))
{
List<Element> prepayIdList=document.selectNodes("//out_trade_no");
String out_trade_no = "";//订单ID
for (Element element : prepayIdList) {
out_trade_no = element.getText();
}
if(StringUtils.isNotBlank(out_trade_no)){
flag=true;
}
}
}catch (Exception e) {
e.printStackTrace();
}finally{
try {
if (is != null) {
is.close();
}
if (isr != null) {
isr.close();
}
if (br != null) {
br.close();
}
} catch (IOException e) {
flag = true;
}
}
if (flag) {
context.setValue("return_code", "SUCCESS");
context.setValue("return_msg", "OK");
System.out.println("通知微信支付结果成功!!!!!!!");
// return "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>";
} else {
context.setValue("return_code", "FAIL");
context.setValue("return_msg", "return_code_err");
// return "<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[return_code_err]]></return_msg></xml>";
System.out.println("通知微信支付结果失败!!!!!!!!!");
}
} catch (NumberFormatException e) {
e.printStackTrace();
return "-1";
}
return "0";
}
}
注意:公用的类 WXPayUtil .java
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.StringWriter;
import java.text.SimpleDateFormat;
import java.util.*;
import java.security.MessageDigest;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class WXPayUtil {
/**
* XML格式字符串转换为Map
*
* @param strXML XML字符串
* @return XML数据转换后的Map
* @throws Exception
*/
public static Map<String, String> xmlToMap(String strXML) throws Exception {
try {
Map<String, String> data = new HashMap<String, String>();
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
InputStream stream = new ByteArrayInputStream(strXML.getBytes("UTF-8"));
org.w3c.dom.Document doc = documentBuilder.parse(stream);
doc.getDocumentElement().normalize();
NodeList nodeList = doc.getDocumentElement().getChildNodes();
for (int idx = 0; idx < nodeList.getLength(); ++idx) {
Node node = nodeList.item(idx);
if (node.getNodeType() == Node.ELEMENT_NODE) {
org.w3c.dom.Element element = (org.w3c.dom.Element) node;
data.put(element.getNodeName(), element.getTextContent());
}
}
try {
stream.close();
} catch (Exception ex) {
// do nothing
}
return data;
} catch (Exception ex) {
WXPayUtil.getLogger().warn("Invalid XML, can not convert to map. Error message: {}. XML content: {}", ex.getMessage(), strXML);
throw ex;
}
}
/**
* 将Map转换为XML格式的字符串
*
* @param data Map类型数据
* @return XML格式的字符串
* @throws Exception
*/
public static String mapToXml(Map<String, String> data) throws Exception {
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder documentBuilder= documentBuilderFactory.newDocumentBuilder();
org.w3c.dom.Document document = documentBuilder.newDocument();
org.w3c.dom.Element root = document.createElement("xml");
document.appendChild(root);
for (String key: data.keySet()) {
String value = data.get(key);
if (value == null) {
value = "";
}
value = value.trim();
org.w3c.dom.Element filed = document.createElement(key);
filed.appendChild(document.createTextNode(value));
root.appendChild(filed);
}
TransformerFactory tf = TransformerFactory.newInstance();
Transformer transformer = tf.newTransformer();
DOMSource source = new DOMSource(document);
transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
StringWriter writer = new StringWriter();
StreamResult result = new StreamResult(writer);
transformer.transform(source, result);
String output = writer.getBuffer().toString(); //.replaceAll("\n|\r", "");
try {
writer.close();
}
catch (Exception ex) {
}
return output;
}
/**
* 日志
* @return
*/
public static Logger getLogger() {
Logger logger = LoggerFactory.getLogger("wxpay java sdk");
return logger;
}
/**
* md5加密
* @param str
* @return
*/
public static String md5(String str) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(str.getBytes());
byte b[] = md.digest();
int i;
StringBuffer buf = new StringBuffer("");
for (int offset = 0; offset < b.length; offset++) {
i = b[offset];
if (i < 0) {
i += 256;
}
if (i < 16) {
buf.append("0");
}
buf.append(Integer.toHexString(i));
}
str = buf.toString();
} catch (Exception e) {
e.printStackTrace();
}
return str;
}
/**
* 签名算法
* @param str
* @return
*/
public static String creatSign(SortedMap<Object, Object> parameters,String Key){
StringBuffer sb = new StringBuffer();
Set es = parameters.entrySet();
Iterator<?> it = es.iterator();
while (it.hasNext()) {
@SuppressWarnings("rawtypes")
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 sign =md5(sb.toString())
.toUpperCase();
return sign;
}
//自动生成out_trade_no
public static String getCurrTime(){
Date now=new Date();
SimpleDateFormat outFormat=new SimpleDateFormat("yyyyMMddHHmmss");
String s=outFormat.format(now);
return s;
}
//产生任一位数随机数
public static String getRandomString2(int length){
//产生随机数
Random random=new Random();
StringBuffer sb=new StringBuffer();
//循环length次
for(int i=0; i<length; i++){
//产生0-2个随机数,既与a-z,A-Z,0-9三种可能
int number=random.nextInt(3);
long result=0;
switch(number){
//如果number产生的是数字0;
case 0:
//产生A-Z的ASCII码
result=Math.round(Math.random()*25+65);
//将ASCII码转换成字符
sb.append(String.valueOf((char)result));
break;
case 1:
//产生a-z的ASCII码
result=Math.round(Math.random()*25+97);
sb.append(String.valueOf((char)result));
break;
case 2:
//产生0-9的数字
sb.append(String.valueOf
(new Random().nextInt(10)));
break;
}
}
return sb.toString();
}
}