最近项目需要微信支付,然后看了下微信公众号支付,虽然不难,但是细节还是需要注意的,用了大半天时间写了个demo,并且完整的测试了一下支付流程,下面分享一下微信公众号支付的经验。
一、配置公众号微信支付
需要我们配置微信公众号支付地址和测试白名单。
比如:支付JS页面的地址为 http://www.xxx.com/shop/pay/ 那此处配置www.xxx.com/shop/pay/
二、开发流程
借用微信公众号支付api(地址 ),我们需要开发的为红色标记出的。如下:
三、向微信服务器端下订单
调用统一下单接口,这样就能获取微信支付的prepay_id()。
在调用该接口前有几个字段是H5支付必须填写的openid
3.1 获取openid
可以通过网页授权形式()
在微信中发送如下链接
3.2 后台支付 代码如下,包含预处理订单,支付订单等接口。
import javax.servlet.ServletInputStream; import org.apache.commons.codec.digest.DigestUtils; import com.fasterxml.jackson.databind.JsonNode; @Controller @RequestMapping(value = "wxprepay") String appid = "wx16691fcb0523c1a4"; String out_trade_no = getTradeNo(); // 统一下单 https://api.mch.weixin.qq.com/pay/unifiedorder String xml = ArrayToXml(paraMap,false); String xmlStr = HttpKit.post(url,xml); // 预付商品id if (xmlStr.indexOf("SUCCESS") != -1) { Map<String,String> payMap = new HashMap<String,String>(); payMap.put("pg",prepay_id); WebUtil.response(response,WebUtil.packJsonp(callback,JsonUtil.warpJsonNodeResponse(JsonUtil.objectToJsonNode(payMap)).toString())); @RequestMapping(value = "appPay") String appid = "wx16691fcb0523c1a4"; String out_trade_no = getTradeNo(); // 预付商品id Map<String,String> map = doXMLParse(xmlStr); String result_code = map.get("result_code"); payMap.put("sign",paySign); WebUtil.response(response,JsonUtil.warpJsonNodeResponse(JsonUtil.objectToJsonNode(payMap)).toString())); @RequestMapping("/appPay_notify") Map<String,String> map = doXMLParse(xmlMsg); map = new HashMap<String,String>(); //响应xml @RequestMapping("/orderquery.do") String url = "https://api.mch.weixin.qq.com/pay/orderquery"; String appid = "wx16691fcb0523c1a4"; Map<String,String> map = new HashMap<String,String>(); String xml = ArrayToXml(map,false); Map<String,String> orderMap = doXMLParse(xmlStr); WebUtil.response(response,JsonUtil.warpJsonNodeResponse(JsonUtil.objectToJsonNode(orderMap)).toString())); /** // 获取openId private String create_nonce_str() { private String getAddrIp(HttpServletRequest request){ private String create_timestamp() { private String getTradeNo(){ private String getSign(Map<String,String> params,String paternerKey ) private Map<String,String> doXMLParse(String xml) InputStream inputStream = new ByteArrayInputStream(xml.getBytes()); Map<String,String> map = null; XmlPullParser pullParser = XmlPullParserFactory.newInstance() pullParser.setInput(inputStream,"UTF-8"); // 为xml设置要解析的xml数据 int eventType = pullParser.getEventType(); while (eventType != XmlPullParser.END_DOCUMENT) { case XmlPullParser.START_TAG: String value = pullParser.nextText(); break; case XmlPullParser.END_TAG: } eventType = pullParser.next(); } return map; } wxprepay.shtm接口是预处理订单接口向微信服务器下订单。
appPay.shtml接口是支付接口。
appPay_notify.shtml接口是微信支付后异步通知结果接口。
orderquery.shtml接口是订单查询接口
3.3、涉及到的工具类
SessionUtil.java工具类
public class SessionUtil { public static void removeAtt(HttpServletRequest request,String key){ public static String getAtt(HttpServletRequest request,String key){ public static Object getAttObj(HttpServletRequest request,String key){ public static String optAtt(HttpServletRequest request,String value){ } HttpKit 网络请求工具类
if (headers != null && !headers.isEmpty()) { /** /** /** public static String post(String url,String s) throws IOException,InterruptedException { } 支付工具类pay.java
/** /** /** /** /** /** /** public static boolean delivernotify(String access_token,String transid,String out_trade_no) throws IOException,InterruptedException { 流转化Tools.java工具类
StringBuffer out = new StringBuffer(); public static final boolean checkSignature(String token,String signature,String timestamp,String nonce){ 相应前端数据工具WebUtil.java工具类
try { public static void response(HttpServletResponse response,String result) { } public static void response(HttpServletResponse response,ResponseMessage result) { public static String packJsonp(String callback,String json) { return callback + "&&" + callback + '(' + json + ')'; public static String packJsonp(String callback,ResponseMessage response) { return callback + "&&" + callback + '(' + json + ')'; Json转换工具JsonUtil.java
public static JsonNode objectToJsonNode(Object obj){ }
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Random;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory;
import com.gson.oauth.Oauth;
import com.gson.oauth.Pay;
import com.gson.util.HttpKit;
import com.gson.util.Tools;
import org.andy.util.DatetimeUtil;
import org.andy.util.JsonUtil;
import org.andy.util.SessionUtil;
import org.andy.util.WebUtil;
@RequestMapping("/pay")
public class WXPayController {
public void jspay(HttpServletRequest request,HttpServletResponse response,String callback) throws Exception {
// 获取openid
String openId = SessionUtil.getAtt(request,"openId");
if (openId == null) {
openId = getUserOpenId(request);
}
String partnerid = "22223670";
String paternerKey = "fjfjfjfjf1234567FFFFFFFFF1234567";
Map<String,String> paraMap = new HashMap<String,String>();
paraMap.put("appid",appid);
paraMap.put("attach","测试支付");
paraMap.put("body","测试购买Beacon支付");
paraMap.put("mch_id",partnerid);
paraMap.put("nonce_str",create_nonce_str());
paraMap.put("openid",openId);
paraMap.put("out_trade_no",out_trade_no);
paraMap.put("spbill_create_ip",getAddrIp(request));
paraMap.put("total_fee","1");
paraMap.put("trade_type","JSAPI");
paraMap.put("notify_url","http://www.xxx.co/wxpay/pay/appPay_notify.shtml");
String sign = getSign(paraMap,paternerKey);
paraMap.put("sign",sign);
String url = "https://api.mch.weixin.qq.com/pay/unifiedorder";
String prepay_id = "";
Map<String,String> map = doXMLParse(xmlStr);
prepay_id = (String) map.get("prepay_id");
}
payMap.put("appId",appid);
payMap.put("timeStamp",create_timestamp());
payMap.put("nonceStr",create_nonce_str());
payMap.put("signType","MD5");
payMap.put("package","prepay_id=" + prepay_id);
String paySign = getSign(payMap,paternerKey);
payMap.put("paySign",paySign);
}
public void appPay(HttpServletRequest request,String body,String detail,String total_fee,String spbill_create_ip,String notify_url,String trade_type,String callback) throws Exception {
String partnerid = "22223670";
String paternerKey = "fjfjfjfjf1234567FFFFFFFFF1234567";
Map<String,appid);
paraMap.put("body",body);
paraMap.put("mch_id",create_nonce_str());
paraMap.put("out_trade_no",spbill_create_ip);
paraMap.put("total_fee",total_fee);
paraMap.put("trade_type",trade_type);
paraMap.put("notify_url",notify_url);
String sign = getSign(paraMap,xml);
String prepay_id = "";
if (xmlStr.indexOf("SUCCESS") != -1) {
prepay_id = (String) map.get("prepay_id");
}
String err_code_des = map.get("err_code_des");
Map<String,String>();
payMap.put("appid",appid);
payMap.put("partnerid",partnerid);
payMap.put("prepayid",prepay_id);
payMap.put("package","Sign=WXPay");
payMap.put("noncestr",create_nonce_str());
payMap.put("timestamp",create_timestamp());
String paySign = getSign(payMap,paternerKey);
payMap.put("result_code",result_code);
payMap.put("err_code_des",err_code_des);
}
public void appPayNotify(HttpServletRequest request,HttpServletResponse response) throws Exception{
//String xml = "
response.setCharacterEncoding("UTF-8");
response.setContentType("text/xml");
ServletInputStream in = request.getInputStream();
String xmlMsg = Tools.inputStream2String(in);
String return_code = map.get("return_code");
String return_msg = map.get("return_msg");
map.put("return_code",return_code);
map.put("return_msg",return_msg);
String resXml = ArrayToXml(map,true);
response.getWriter().write(resXml);
}
public void orderquery(HttpServletRequest request,String transaction_id,String out_trade_no,String callback) throws Exception{
String partnerid = "22223670";
String paternerKey = "fjfjfjfjf1234567FFFFFFFFF1234567";
map.put("appid",appid);
map.put("mch_id",partnerid);
if(transaction_id != null && !transaction_id.equals("")){
map.put("transaction_id",transaction_id);
}else {
map.put("out_trade_no",out_trade_no);
}
map.put("nonce_str",create_nonce_str());
String paySign = getSign(map,paternerKey);
map.put("sign",paySign);
String xmlStr = HttpKit.post(url,xml);
}
*/
public String ArrayToXml(Map<String,String> parm,boolean isAddCDATA) {
StringBuffer strbuff = new StringBuffer(
if (parm != null ) {
for (Entry<String,String> entry : parm.entrySet()) {
strbuff.append("<").append(entry.getKey()).append(">");
if (isAddCDATA) {
strbuff.append(<![CDATA[).append(entry.getValue()).append(]]>);
}else {
strbuff.append(entry.getValue());
}
strbuff.append("<").append(entry.getKey()).append(">");
}
}
return strbuff.append(
}
private String getUserOpenId(HttpServletRequest request) throws Exception {
String code = request.getParameter("code");
if (code == null) {
String openId = request.getParameter("openId");
return openId;
}
Oauth o = new Oauth();
String token = o.getToken(code);
JsonNode node = JsonUtil.StringToJsonNode(token);
String openId = node.get("openid").asText();
return openId;
}
String chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
String res = "";
for (int i = 0; i < 16; i++) {
Random rd = new Random();
res += chars.charAt(rd.nextInt(chars.length() - 1));
}
return res;
}
return request.getRemoteAddr();
}
return Long.toString(System.currentTimeMillis() / 1000);
}
String timestamp = DatetimeUtil.formatDate(new Date(),DatetimeUtil.DATETIME_PATTERN);
return "HZNO" + timestamp;
}
throws UnsupportedEncodingException {
String string1 = Pay.createSign(params,false);
String stringSignTemp = string1 + "&key=" + paternerKey;
String signValue = DigestUtils.md5Hex(stringSignTemp).toUpperCase();
return signValue;
}
throws XmlPullParserException,IOException {
.newPullParser();
switch (eventType) {
case XmlPullParser.START_DOCUMENT:
map = new HashMap<String,String>();
break;
String key = pullParser.getName();
if (key.equals("xml"))
break;
map.put(key,value);
break;
}
public static void addAtt(HttpServletRequest request,String key,Object value){
request.getSession().setAttribute(key,value);
}
request.getSession().removeAttribute(key);
}
return (String)request.getSession().getAttribute(key);
}
return request.getSession().getAttribute(key);
}
String r = (String)request.getSession().getAttribute(key);
if (r == null){
r = value;
}
return r;
}
Set
for (String key : keys) {
builder.addHeader(key,params.get(key));
}
}
Future
String body = f.get().getResponseBody(DEFAULT_CHARSET);
http.close();
return body;
}
*/
public static String get(String url) throws KeyManagementException,NoSuchAlgorithmException,NoSuchProviderException,UnsupportedEncodingException,IOException,InterruptedException {
return get(url,null);
}
*/
public static String get(String url,String> params) throws KeyManagementException,params,null);
}
*/
public static String post(String url,String> params) throws IOException,InterruptedException {
AsyncHttpClient http = new AsyncHttpClient();
AsyncHttpClient.BoundRequestBuilder builder = http.preparePost(url);
builder.setBodyEncoding(DEFAULT_CHARSET);
if (params != null && !params.isEmpty()) {
Set
for (String key : keys) {
builder.addParameter(key,params.get(key));
}
}
Future
String body = f.get().getResponseBody(DEFAULT_CHARSET);
http.close();
return body;
}
AsyncHttpClient http = new AsyncHttpClient();
AsyncHttpClient.BoundRequestBuilder builder = http.preparePost(url);
builder.setBodyEncoding(DEFAULT_CHARSET);
builder.setBody(s);
Future
String body = f.get().getResponseBody(DEFAULT_CHARSET);
http.close();
return body;
}
private static final String DELIVERNOTIFY_URL = "https://api.weixin.qq.com/pay/delivernotify?access_token=";
*/
public static String getPackage(Map<String,String> params) throws UnsupportedEncodingException {
String partnerKey = ConfKit.get("partnerKey");
String partnerId = ConfKit.get("partnerId");
String notifyUrl = ConfKit.get("notify_url");
// 公共参数
params.put("bank_type","WX");
params.put("attach","yongle");
params.put("partner",partnerId);
params.put("notify_url",notifyUrl);
params.put("input_charset","UTF-8");
return packageSign(params,partnerKey);
}
*/
public static String createSign(Map<String,boolean encode) throws UnsupportedEncodingException {
Set
Object[] keys = keysSet.toArray();
Arrays.sort(keys);
StringBuffer temp = new StringBuffer();
boolean first = true;
for (Object key : keys) {
if (first) {
first = false;
} else {
temp.append("&");
}
temp.append(key).append("=");
Object value = params.get(key);
String valueString = "";
if (null != value) {
valueString = value.toString();
}
if (encode) {
temp.append(URLEncoder.encode(valueString,"UTF-8"));
} else {
temp.append(valueString);
}
}
return temp.toString();
}
*/
private static String packageSign(Map<String,String paternerKey) throws UnsupportedEncodingException {
String string1 = createSign(params,false);
String stringSignTemp = string1 + "&key=" + paternerKey;
String signValue = DigestUtils.md5Hex(stringSignTemp).toUpperCase();
String string2 = createSign(params,true);
return string2 + "&sign=" + signValue;
}
*/
public static String paySign(String timestamp,String noncestr,String packages) throws UnsupportedEncodingException {
Map<String,String> paras = new HashMap<String,String>();
paras.put("appid",ConfKit.get("AppId"));
paras.put("timestamp",timestamp);
paras.put("noncestr",noncestr);
paras.put("package",packages);
paras.put("appkey",ConfKit.get("paySignKey"));
// appid、timestamp、noncestr、package 以及 appkey。
String string1 = createSign(paras,false);
String paySign = DigestUtils.shaHex(string1);
return paySign;
}
*/
public static boolean verifySign(long timestamp,String openid,int issubscribe,String appsignature) throws UnsupportedEncodingException {
Map<String,ConfKit.get("AppId"));
paras.put("appkey",ConfKit.get("paySignKey"));
paras.put("timestamp",String.valueOf(timestamp));
paras.put("noncestr",noncestr);
paras.put("openid",openid);
paras.put("issubscribe",String.valueOf(issubscribe));
// appid、appkey、productid、timestamp、noncestr、openid、issubscribe
String string1 = createSign(paras,false);
String paySign = DigestUtils.shaHex(string1);
return paySign.equalsIgnoreCase(appsignature);
}
*/
private static String deliverSign(Map<String,String> paras) throws UnsupportedEncodingException {
paras.put("appkey",ConfKit.get("paySignKey"));
String string1 = createSign(paras,false);
String paySign = DigestUtils.shaHex(string1);
return paySign;
}
*/
Map<String,ConfKit.get("AppId"));
paras.put("openid",openid);
paras.put("transid",transid);
paras.put("out_trade_no",out_trade_no);
paras.put("deliver_timestamp",(System.currentTimeMillis() / 1000) + "");
paras.put("deliver_status","1");
paras.put("deliver_msg","ok");
// 签名
String app_signature = deliverSign(paras);
paras.put("app_signature",app_signature);
paras.put("sign_method","sha1");
String json = HttpKit.post(DELIVERNOTIFY_URL.concat(access_token),JSONObject.toJSONString(paras));
if (StringUtils.isNotBlank(json)) {
JSONObject object = JSONObject.parseObject(json);
if (object.containsKey("errcode")) {
int errcode = object.getIntValue("errcode");
return errcode == 0;
}
}
return false;
}
}
if(in == null)
return "";
byte[] b = new byte[4096];
for (int n; (n = in.read(b)) != -1;) {
out.append(new String(b,n,"UTF-8"));
}
return out.toString();
}
List
params.add(token);
params.add(timestamp);
params.add(nonce);
Collections.sort(params,new Comparator
@Override
public int compare(String o1,String o2) {
return o1.compareTo(o2);
}
});
String temp = params.get(0)+params.get(1)+params.get(2);
return SHA1.encode(temp).equals(signature);
}
}
Object ret = null;
ret = req.getSession(false).getAttribute(key);
} catch (Exception e) {
}
return ret;
}
try {
response.setContentType("application/json;charset=utf-8");
response.getWriter().write(result);
} catch (IOException e) {
e.printStackTrace();
}
try {
response.setContentType("application/json;charset=utf-8");
response.getWriter().write(JsonUtil.objectToJsonNode(result).toString());
} catch (Exception e) {
e.printStackTrace();
}
}
if (json == null) {
json = "";
}
if (callback == null || callback.isEmpty()) {
return json;
}
}
String json = null;
if (response == null) {
json = "";
} else {
json = JsonUtil.objectToJsonNode(response).toString();
}
if (callback == null || callback.isEmpty()) {
return json;
}
}
}
ObjectNode objectNode=createObjectNode();
objectNode.put("code",1);
objectNode.put("response",obj);
return objectNode;
}
try {
ObjectMapper objectMapper = new ObjectMapper();
String objJson=objectMapper.writeValueAsString(obj);
JsonNode jsonNode = objectMapper.readTree(objJson);
return jsonNode;
} catch (JsonProcessingException e) {
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
} 四、微信H5调起支付
这个url需要后台实现,其实现功能如下: 1、接受微信服务器端发送的支付结果。 2、向微信服务器发送支付结果
具体 参考微信aip() 具体代码如下: 4.1、授权向后台发起生成统一下订单页面 wxrepay.jsp
<div class="branch_con">
<script type="text/javascript" src="../js/common.js?t=<%=t%>">
首先是请求服务端wxprepay.shml接口,后台向微信支付平台获取支付订单信息,返回前台,wxpay.jsp页面 4.2、确认支付页面
<div class="branch_con">
<script type="text/javascript" src="../js/common.js?t=<%=t%>">
<script type="text/javascript">
var appId = urlparameter("appId");
var timeStamp = urlparameter("timeStamp");
var nonceStr = urlparameter("nonceStr");
var pg = urlparameter("pg");
var signType = urlparameter("signType");
var paySign = urlparameter("paySign");
function onBridgeReady(){
WeixinJSBridge.invoke(
'getBrandWCPayRequest',{
"appId" : appId,//公众号名称,由商户传入
"timeStamp": timeStamp,//时间戳,自1970年以来的秒数
"nonceStr" : nonceStr,//随机串
"package" : "prepay_id=" + pg,"signType" : signType,//微信签名方式:
"paySign" : paySign //微信签名
},function(res){
if(res.err_msg == "get_brand_wcpay_request:ok" ) {
alert("支付成功");
} // 使用以上方式判断前端返回,微信团队郑重提示:res.err_msg将在用户支付成功后返回 ok,但并不保证它绝对可靠。
}
);
}
function pay(){
if (typeof WeixinJSBridge == "undefined"){
if( document.addEventListener ){
document.addEventListener('WeixinJSBridgeReady',onBridgeReady,false);
}else if (document.attachEvent){
document.attachEvent('WeixinJSBridgeReady',onBridgeReady);
document.attachEvent('onWeixinJSBridgeReady',onBridgeReady);
}
}else{
onBridgeReady();
}
}