该文仅对于中间这种支付方式有参考价值哟
一、开发背景
在微信公众号中,需要进行微信支付且为微信公众号网页支付。
二、准备工作
1.申请商户平台
step1. 登录公众平台,点击左侧菜单中的微信支付,如图,
step2. 进入支付申请,按照指引填写相关的信息并提交;
step3. 资料审核通过后,支付宝会向登记的银行帐号打一笔款,收到款后,两次登录到公众平台填写正确的数字,签约成功,如图,
注意:在申请支付功能的时候,不要申请成了服务商,需要的是商户。虽然服务商也具备支付功能,但在调用 接口时会比商户复杂一点,申请过程也繁琐一点。另外服务商和商户是具备不同的业务功能的,常见的微商场用的更多是商户平台。对于服务商和商户的区别我理解的是,服务商是对商户提供技术支持的第三方团队。
finally , 通过各种审核后,支付宝会向你的邮箱发送以下信息,保存好,在调用接口时会用到
2.配置支付参数
如下图,进入公众平台-微信支付-开发配置,设置支付授权目录及支付回调。
如果配置的是测试目录则还需要添加测试微信号进入测试白名单;
支付授权目录的要求:
1、所有使用公众号支付方式发起支付请求的链接地址,都必须在支付授权目录之下;
2、最多设置3个支付授权目录,且域名必须通过ICP备案;
3、头部要包含http或https,须细化到二级或三级目录,以左斜杠“/”结尾。
支付回调URl:头部要包括http或https,当公众平台接到扫码支付请求时,会回调此URL传递订单信息。
这里指的是支付成功后,支付宝服务器会直接调用商户系统的服务器对订单进行处理,比如修改状态等,用户是无感知的,可以当作一个方法,该方法中不支付session. 如果需要传递参数怎么办了,tell you later.
二、调用接口步骤
1)获取用户授权(获取openid, 获取方式可以参考微信公众号网页授权)
2)调用统一下单接口获取预支付id(prepay_id)
3)H5调起微信支付的内置JS
4)支付完成后,微信回调URL的处理
看文字觉得麻烦的同志请参考以下稍显形象的手绘图,
step1. 获取用户授权
step2. 获取Prepay_id
根据文档(https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1)要求,将传递的参数拼接成要求的xml格式到请求地址:https://api.mch.weixin.qq.com/pay/unifiedorder,获得prepay_id.
如果连图也懒得看的同志直接copy 代码吧,贴到eclipse慢慢晕,每个参数的意义都已经加以说明
highlight的参数注意一下,容易被坑。
在加密时还要注意变量key。这里提个问,整个过程中加密多少次?
String sign = PayCommonUtils.createSign("UTF-8",packageParams);
key设置路径:微信商户平台(pay.weixin.qq.com)-->账户设置-->API安全-->密钥设置
/**
* 构建微信统一支付请求
*/
public static SortedMap wechatPayment(HttpServletRequest request, HttpServletResponse response,) throws JDOMException, IOException {
SortedMap packageParams = new TreeMap();
//nonce_str
String currTime = CommonUtils.getCurrTime();
String strTime = currTime.substring(8, currTime.length());
String strRandom = CommonUtils.buildRandom(4) + "";
//10位序列号,可以自行调整。
String strReq = strTime + strRandom;
String nonceStr = strReq;
String signType = "MD5";
String spbillCreateIp = request.getLocalAddr();//客户IP
/* 设置package订单参数 */
packageParams.put("appid", Constants.APPID);//微信分配的公众账号ID
packageParams.put("mch_id", Constants.MCH_ID);//微信支付分配的商户号
packageParams.put("device_info", "WEB");//公众号内支付请传"WEB"
packageParams.put("nonce_str", CommonUtils.CreateNoncestr(16));//随机字符串,不长于32位
packageParams.put("sign_type", signType);//签名类型,目前支持HMAC-SHA256和MD5,默认为MD5
packageParams.put("body", companyName);
packageParams.put("out_trade_no", orderNo);//商户系统内部的订单号,32个字符内
packageParams.put("attach", companyId + "&" + orderIds);//传递的参数,在返回时会原样 返回
packageParams.put("total_fee", CommonUtils.changeY2F(totalPrice));//订单总金额,单位为分
packageParams.put("total_fee", "1");//订单总金额 //测试用
packageParams.put("fee_type", "CNY");//符合ISO 4217标准的三位字母代码,默认人民币:CNY
packageParams.put("notify_url", Constants.WECHAT_NOTIFY_URL);//接收微信支付异步通知回调地址
packageParams.put("spbill_create_ip", spbillCreateIp);//APP和网页支付提交用户端ip,Native支付填调用微信支付API的机器IP
//packageParams.put("time_start", new Date().toString());//订单生成时间
packageParams.put("trade_type", "JSAPI");//JSAPI,NATIVE,APP
packageParams.put("openid", openId);//openid ,关于openid的获取请参考公众号网页授权
String sign = PayCommonUtils.createSign("UTF-8", packageParams);//将以上参数进行签名
packageParams.put("sign", sign);
packageParams.put("attach", companyId + "&" + orderIds);
String requestXML = PayCommonUtils.getRequestXml(packageParams);//生成接口需要的数据样式
System.out.println("request xml::" + requestXML);
//请求的数据样式
//
wx2421b1c4370ec43b
支付测试
JSAPI支付测试
10000100
1add1a30ac87aa2db72f57a2375d8fec
http://wxpay.wxutil.com/pub_v2/pay/notify.v2.php
oUpF8uMuAJO_M2pxb1Q9zNjWeS6o
1415659990
14.23.150.211
1
JSAPI
0CB01533B8C1EF103065174F50BCA001
String responseStr = HttpUtils.httpsRequest(“https://api.mch.weixin.qq.com/pay/unifiedorder”, "POST", requestXML);//发送请求
//返回的数据样式
//responseStr= "";
System.out.println("response str::" + responseStr);
Map map = XMLUtils.doXMLParse(responseStr);//解析微信返回的信息,以Map形式存储便于取值
String returnCode = map.get("return_code");
System.out.println("get prepay_id result::" + returnCode);
SortedMap finalpackage = new TreeMap();
if ("SUCCESS".equals(returnCode)) {
/*生成最终提交订单参数*/
String timestamp = CommonUtils.getTimeStamp();
String prepay_id = map.get("prepay_id");
System.out.println("prepay_id is :: " + prepay_id);//取得prepay_id,用于在H5调用支付api
String packages = "prepay_id=" + prepay_id;
finalpackage.put("appId", Constants.APPID);
finalpackage.put("timeStamp", timestamp);
finalpackage.put("nonceStr", nonceStr);
finalpackage.put("package", packages);
finalpackage.put("signType", signType);
String paySign = PayCommonUtils.createSign("UTF-8", finalpackage);
System.out.println("paySign is ::" + paySign);
finalpackage.put("paySign", paySign);
finalpackage.put("sendUrl", Constants.WECHAT_NOTIFY_URL); //付款成功后跳转的页面
finalpackage.put("errorCode", map.get("err_code_des"));
String responseJsonStr = JSONObject.toJSONString(finalpackage);
} else {
finalpackage.put("errorCode", map.get("return_msg"));
finalpackage.put("result", "failed");
}
return finalpackage;
}
step3. 网页端调起支付api
将上一步取得的prepay_id存到package中,在微信js接口中将以下参数传递。
$('.btn-info').click(function () {
if ("1" == mPaymentType) { //微信支付
$.ajax({
type: "post",
dataType: "json",
url: contextPath + "/order/order_payment_wechat.html",
data: {
mPaymentType: mPaymentType,
companyId: companyId,
},
}).done(function (response) {
console.log(response);
if (!isNull(response.errorCode)) {
alert(errorCode);
return false;
}
var obj = response.data;
WeixinJSBridge.invoke('getBrandWCPayRequest', {
"appId": obj.appId, //公众号名称,由商户传入
"timeStamp": obj.timeStamp, //时间戳
"nonceStr": obj.nonceStr, //随机串
"package": obj.package, //商品包信息
"signType": obj.signType, //微信签名方式:
"paySign": obj.paySign //微信签名
}, function (res) {
if (res.err_msg == "get_brand_wcpay_request:ok") {
alert('支付成功!');
} else if (res.err_msg == "get_brand_wcpay_request:cancel") {
alert("支付取消");
} else {
alert("支付失败!");
}
});
});
}
js调用接口时会将微信支付窗口拉起,
支付成功会跳转到如图页面,点击完成页面会返回到回调函数中,
if (res.err_msg == "get_brand_wcpay_request:ok") {
alert('支付成功!');
}
在js调用过程中,还是遇到一些错误,不要惊慌,将错误提示alert出来,或者在微信web开发者工具中console.log()出,方便调错
step4:处理支付返回值
支付成功后,支付宝会调用之前配置的回调函数,我们只需要用response去接受
/**
* 微信支付成功回调, 这个方法是我配置的回调函数
*
*@return
*/
@RequestMapping("modify_order_status_wechat.html")
public voidmodifyOrderStatusWechat(HttpServletRequest request,HttpServletResponse response)throwsIOException {
System.out.println("i am in modify_order_status_wechat begin");
try{
String paymentResponse = WechatUtils.paymentNotify(request,response);
System.out.println("paymentResponse:"+ paymentResponse);
Map map = XMLUtils.doXMLParse(paymentResponse);
if("SUCCESS".equals(map.get("result_code"))) {
String attach = map.get("attach");// 在step1中传递的参数原样取回
String attachTmp[] = attach.split("&");
Integer companyId = Integer.parseInt(attachTmp[0]);
System.out.println("companyId is :"+ companyId);
String orderIdTmp[] = attachTmp[1].split(",");
}
}catch(Exception e) {
e.printStackTrace();
}
}
/**
* 获取支付返回值
*/
public static String paymentNotify(HttpServletRequest request,HttpServletResponse response)throwsIOException,JDOMException {
System.out.println("~~~~~~~~~~~~~~~~I am in paymentNotify~~~~~~~~~");
InputStream inStream = request.getInputStream();
ByteArrayOutputStream outSteam =newByteArrayOutputStream();
byte[] buffer =new byte[1024];
intlen =0;
while((len = inStream.read(buffer)) != -1) {
outSteam.write(buffer,0,len);
}
System.out.println("~~~~~~~~~~~~~~~~付款成功~~~~~~~~~");
outSteam.close();
inStream.close();
String result =newString(outSteam.toByteArray(),"utf-8");//获取微信调用我们notify_url的返回信息
System.out.println("pay result is::"+ result);
Mapmap = XMLUtils.doXMLParse(result);
for(Object keyValue : map.keySet()) {
System.out.println(keyValue +"="+ map.get(keyValue));
}
if(map.get("result_code").toString().equalsIgnoreCase("SUCCESS")) {
response.getWriter().write(PayCommonUtils.setXML("SUCCESS",""));//告诉微信服务器,收到信息,不要在调用回调action了
System.out.println("-------------"+ PayCommonUtils.setXML("SUCCESS",""));
PayCommonUtils.setXML("SUCCESS","");
}else{
PayCommonUtils.setXML("Failed","");
}
return result;
}
三、测试
如果页面还没备好,只是想测试下参数是否传递成功,可以使用微信提供的在线调试工具:https://pay.weixin.qq.com/wiki/tools/signverify/。
因为微信接口对客户端有一些限制,所有搭建测试环境有点费时,可以参考微信公众号网页授权summer版
四、工具类共享
在调用接口中多次使用到xml的解析,md5加密,http请求以及对金额的处理。他们都在这里,不能上传附件,贴出来有点乱,但总有那么一句可能你会用到。
xml 解析(XMLUtils):
packagecom.hj.custsys.wechat.util;
importorg.jdom.Document;
importorg.jdom.Element;
importorg.jdom.JDOMException;
importorg.jdom.input.SAXBuilder;
importjava.io.ByteArrayInputStream;
importjava.io.IOException;
importjava.io.InputStream;
importjava.util.HashMap;
importjava.util.Iterator;
importjava.util.List;
importjava.util.Map;
/**
* Created by LiuBo on 2016/12/5.
*/
public classXMLUtils {
/**
* 解析xml,返回第一级元素键值对。如果第一级元素有子节点,则此节点的值是子节点的xml数据。
*
*@paramstrxml
*@return
*@throwsJDOMException
*@throwsIOException
*/
public staticMapdoXMLParse(String strxml)throwsJDOMException,IOException {
strxml = strxml.replaceFirst("encoding=\".*\"","encoding=\"UTF-8\"");
if(null== strxml ||"".equals(strxml)) {
return null;
}
Map m =newHashMap();
InputStream in =newByteArrayInputStream(strxml.getBytes("UTF-8"));
SAXBuilder builder =newSAXBuilder();
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 = XMLUtils.getChildrenText(children);
}
m.put(k,v);
}
//关闭流
in.close();
returnm;
}
/**
* 获取子结点的xml
*
*@paramchildren
*@returnString
*/
public staticStringgetChildrenText(List children) {
StringBuffer sb =newStringBuffer();
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("<"+ name +">");
if(!list.isEmpty()) {
sb.append(XMLUtils.getChildrenText(list));
}
sb.append(value);
sb.append("");
}
}
returnsb.toString();
}
}
签名:
/**
*@paramcharacterEncoding编码格式
*@paramparameters请求参数
*@return
*@Description:sign签名
*/
public staticStringcreateSign(String characterEncoding,SortedMap parameters) {
StringBuffer sb =newStringBuffer();
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="+ Constants.WECHAT_KEY);
String sign = MD5SignUtils.MD5Encode(sb.toString(),characterEncoding);
returnsign;
}
public staticStringsetXML(String return_code,String return_msg) {
return"
+"]]>
+"]]>";
}
金额的处理:
/**
* 金额为分的格式
*/
public static finalStringCURRENCY_FEN_REGEX="\\-?[0-9]+";
/**
* 将分为单位的转换为元并返回金额格式的字符串 (除100)
*
*@paramamount
*@return
*@throwsException
*/
public staticStringchangeF2Y(Long amount)throwsException {
if(!amount.toString().matches(CURRENCY_FEN_REGEX)) {
throw newException("金额格式有误");
}
intflag =0;
String amString = amount.toString();
if(amString.charAt(0) =='-') {
flag =1;
amString = amString.substring(1);
}
StringBuffer result =newStringBuffer();
if(amString.length() ==1) {
result.append("0.0").append(amString);
}else if(amString.length() ==2) {
result.append("0.").append(amString);
}else{
String intString = amString.substring(0,amString.length() -2);
for(inti =1;i <= intString.length();i++) {
if((i -1) %3==0&& i !=1) {
result.append(",");
}
result.append(intString.substring(intString.length() - i,intString.length() - i +1));
}
result.reverse().append(".").append(amString.substring(amString.length() -2));
}
if(flag ==1) {
return"-"+ result.toString();
}else{
returnresult.toString();
}
}
/**
* 将分为单位的转换为元 (除100)
*
*@paramamount
*@return
*@throwsException
*/
public staticStringchangeF2Y(String amount)throwsException {
if(!amount.matches(CURRENCY_FEN_REGEX)) {
throw newException("金额格式有误");
}
returnBigDecimal.valueOf(Long.valueOf(amount)).divide(newBigDecimal(100)).toString();
}
/**
* 将元为单位的转换为分 (乘100)
*
*@paramamount
*@return
*/
public staticStringchangeY2F(doubleamount) {
returnBigDecimal.valueOf(amount).multiply(newBigDecimal(100)).toString();
}
/**
* 将元为单位的转换为分 替换小数点,支持以逗号区分的金额
*
*@paramamount
*@return
*/
public staticStringchangeY2F(String amount) {
String currency = amount.replaceAll("\\$|\\¥|\\,","");// 处理包含, ¥
// 或者$的金额
intindex = currency.indexOf(".");
intlength = currency.length();
Long amLong =0l;
if(index == -1) {
amLong = Long.valueOf(currency +"00");
}else if(length - index >=3) {
amLong = Long.valueOf((currency.substring(0,index +3)).replace(".",""));
}else if(length - index ==2) {
amLong = Long.valueOf((currency.substring(0,index +2)).replace(".","") +0);
}else{
amLong = Long.valueOf((currency.substring(0,index +1)).replace(".","") +"00");
}
returnamLong.toString();
}
http请求(HttpUtils)
packagecom.hj.custsys.wechat.util.http;
importjava.io.*;
importjava.net.ConnectException;
importjava.net.MalformedURLException;
importjava.net.URL;
importjava.net.URLConnection;
importjava.util.*;
importjava.util.regex.Matcher;
importjava.util.regex.Pattern;
importjavax.net.ssl.SSLSocketFactory;
importorg.apache.commons.httpclient.NameValuePair;
importorg.apache.commons.httpclient.methods.GetMethod;
importorg.apache.commons.httpclient.methods.PostMethod;
importorg.apache.commons.httpclient.params.HttpMethodParams;
importorg.apache.http.HttpEntity;
importorg.apache.http.HttpResponse;
importorg.apache.http.HttpStatus;
importorg.apache.http.ParseException;
importorg.apache.http.client.ClientProtocolException;
importorg.apache.http.client.HttpClient;
importorg.apache.http.client.entity.UrlEncodedFormEntity;
importorg.apache.http.client.methods.HttpGet;
importorg.apache.http.client.methods.HttpPost;
importorg.apache.http.client.methods.HttpUriRequest;
importorg.apache.http.impl.client.DefaultHttpClient;
importorg.apache.http.message.BasicNameValuePair;
importorg.apache.http.protocol.HTTP;
importorg.apache.http.util.EntityUtils;
importjavax.net.ssl.TrustManager;
importjavax.net.ssl.HttpsURLConnection;
importjavax.net.ssl.SSLContext;
importjavax.servlet.http.HttpServletRequest;
public classHttpUtils{
public staticStringgetUrl(String url) {
String result =null;
try{
// 根据地址获取请求
HttpGet request =newHttpGet(url);
// 获取当前客户端对象
HttpClient httpClient =newDefaultHttpClient();
// 通过请求对象获取响应对象
HttpResponse response = httpClient.execute(request);
// 判断网络连接状态码是否正常(0--200都数正常)
if(response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
result = EntityUtils.toString(response.getEntity());
}
}catch(Exception e) {
//TODO Auto-generated catch block
e.printStackTrace();
}
returnresult;
}
/**
* 发送https请求
*
*@paramrequestUrl请求地址
*@paramrequestMethod请求方式(GET、POST)
*@paramoutputStr提交的数据
*@return返回微信服务器响应的信息
*/
public staticStringhttpsRequest(String requestUrl,String requestMethod,
String outputStr) {
try{
// 创建SSLContext对象,并使用我们指定的信任管理器初始化
TrustManager[] tm = {newTrustAnyTrustManager()};
SSLContext sslContext = SSLContext.getInstance("SSL","SunJSSE");
sslContext.init(null,tm, newjava.security.SecureRandom());
// 从上述SSLContext对象中得到SSLSocketFactory对象
SSLSocketFactory ssf = sslContext.getSocketFactory();
URL url =newURL(requestUrl);
HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
conn.setSSLSocketFactory(ssf);
conn.setDoOutput(true);
conn.setDoInput(true);
conn.setUseCaches(false);
// 设置请求方式(GET/POST)
conn.setRequestMethod(requestMethod);
conn.setRequestProperty("content-type",
"application/x-www-form-urlencoded");
// 当outputStr不为null时向输出流写数据
if(null!= outputStr) {
OutputStream outputStream = conn.getOutputStream();
// 注意编码格式
outputStream.write(outputStr.getBytes("UTF-8"));
outputStream.close();
}
// 从输入流读取返回内容
InputStream inputStream = conn.getInputStream();
InputStreamReader inputStreamReader =newInputStreamReader(
inputStream,"utf-8");
BufferedReader bufferedReader =newBufferedReader(
inputStreamReader);
String str =null;
StringBuffer buffer =newStringBuffer();
while((str = bufferedReader.readLine()) !=null) {
buffer.append(str);
}
// 释放资源
bufferedReader.close();
inputStreamReader.close();
inputStream.close();
inputStream =null;
conn.disconnect();
returnbuffer.toString();
}catch(ConnectException ce) {
ce.printStackTrace();
}catch(Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 发起https请求并获取结果
*/
public staticStringhttpRequest(String urlStr,String xmlFileName) {
String resultStr ="";
try{
URL url =newURL(urlStr);
URLConnection con = url.openConnection();
con.setDoOutput(true);
OutputStreamWriter out =newOutputStreamWriter(con.getOutputStream());
String xmlInfo = xmlFileName;
System.out.println("urlStr="+ urlStr);
System.out.println("xmlInfo="+ xmlInfo);
out.flush();
out.close();
BufferedReader br =newBufferedReader(newInputStreamReader(con
.getInputStream()));
String line ="";
StringBuffer sb =newStringBuffer();
for(line = br.readLine();line !=null;line = br.readLine()) {
sb.append(line);
}
resultStr = sb.toString();
}catch(MalformedURLException e) {
e.printStackTrace();
}catch(IOException e) {
e.printStackTrace();
}
returnresultStr;
}
public staticStringpost(String url,Map params) {
DefaultHttpClient httpclient =newDefaultHttpClient();
String body =null;
HttpPost post =postForm(url,params);
body =invoke(httpclient,post);
httpclient.getConnectionManager().shutdown();
returnbody;
}
public staticStringget(String url) {
DefaultHttpClient httpclient =newDefaultHttpClient();
String body =null;
HttpGet get =newHttpGet(url);
body =invoke(httpclient,get);
httpclient.getConnectionManager().shutdown();
returnbody;
}
private staticStringinvoke(DefaultHttpClient httpclient,
HttpUriRequest httpost) {
HttpResponse response =sendRequest(httpclient,httpost);
String body =paseResponse(response);
returnbody;
}
private staticStringpaseResponse(HttpResponse response) {
HttpEntity entity = response.getEntity();
String charset = EntityUtils.getContentCharSet(entity);
String body =null;
try{
body = EntityUtils.toString(entity,"UTF-8");
}catch(ParseException e) {
e.printStackTrace();
}catch(IOException e) {
e.printStackTrace();
}
returnbody;
}
private staticHttpResponsesendRequest(DefaultHttpClient httpclient,
HttpUriRequest httpost) {
HttpResponse response =null;
try{
response = httpclient.execute(httpost);
}catch(ClientProtocolException e) {
e.printStackTrace();
}catch(IOException e) {
e.printStackTrace();
}
returnresponse;
}
private staticHttpPostpostForm(String url,Map params) {
HttpPost httpost =newHttpPost(url);
List nvps =newArrayList();
Set keySet = params.keySet();
for(String key : keySet) {
nvps.add(newBasicNameValuePair(key,params.get(key)));
}
try{
httpost.setEntity(newUrlEncodedFormEntity(nvps,HTTP.UTF_8));
}catch(UnsupportedEncodingException e) {
e.printStackTrace();
}
returnhttpost;
}
}
在html中调用微信js接口时,我对官方提供的写法很没信心,心想没有引入任何js文件怎么可能引用到方法,抱着试一试的想法,呀,居然有反映。置疑和相信都存在时,带着置疑大胆去try try try...at last , you'll trust the truth,哈哈哈...。另外,在测试过程中时常会遇到一些偶发的bug, 从最初的小侥幸到现在的事出必有因不知放掉了多少bug,现在明白选择相信科学错出来也不会太离谱。