java实现jsapi方式的微信支付
要使用jsapi进行微信支付,首先要从微信获得一个prepay_id,然后通过调用微信的jsapi完成支付,js api的返回结果get_brand_wcpay_request:ok仅在用户成功完成支付时返回。由于前端交互复杂,get_brand_wcpay_request:cancel或者get_brand_wcpay_request:fail可以统一处理为用户遇到错误或者主动放弃,不必细化区分。示例代码如下:
function onbridgeready(){ weixinjsbridge.invoke( 'getbrandwcpayrequest', { "appid" : "wx2421b1c4370ec43b", //公众号名称,由商户传入
"timestamp":" 1395712654", //时间戳,自1970年以来的秒数 "noncestr" : "e61463f8efa94090b1f366cccfbbb444",
//随机串 "package" : "u802345jgfjsdfgsdg888", "signtype" : "md5",
//微信签名方式: "paysign" : "70ea570631e4bb79628fbca90534c63ff7fadd89" //微信签名 },
function(res){ if(res.err_msg == "get_brand_wcpay_request:ok" ) {}
// 使用以上方式判断前端返回,微信团队郑重提示:res.err_msg将在用户支付成功后返回 ok,但并不保证它绝对可靠。
} ); }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();}
以上传入的参数package,即为prepay_id详细文档见:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_7
下面讲的是获得参数来调用jsapi我们调用jsapi时,必须获得用户的openid,(trade_type=jsapi,openid为必填参数。)首先定义一个请求的对象:
package com.unstoppedable.protocol;import com.thoughtworks.xstream.annotations.xstreamalias;import com.unstoppedable.common.configure;import com.
unstoppedable.common.randomstringgenerator;import com.unstoppedable.common.signature;import java.lang.reflect.field;import java.util.hashmap;
import java.util.map;@xstreamalias("xml")
public class unifiedorderreqdata
{
private string appid;
private string mch_id;
private string device_info;
private string nonce_str;
private string sign;
private string body;
private string detail;
private string attach;
private string out_trade_no;
private string fee_type;
private int total_fee;
private string spbill_create_ip;
private string time_start;
private string time_expire;
private string goods_tag;
private string notify_url;
private string trade_type;
private string product_id;
private string limit_pay;
private string openid;
private unifiedorderreqdata(unifiedorderreqdatabuilder builder)
{
this.appid = builder.appid;
this.mch_id = builder.mch_id;
this.device_info = builder.device_info;
this.nonce_str = randomstringgenerator.getrandomstringbylength(32);
this.body = builder.body;
this.detail = builder.detail;
this.attach = builder.attach;
this.out_trade_no = builder.out_trade_no;
this.fee_type = builder.fee_type;
this.total_fee = builder.total_fee;
this.spbill_create_ip = builder.spbill_create_ip;
this.time_start = builder.time_start;
this.time_expire = builder.time_expire;
this.goods_tag = builder.goods_tag;
this.notify_url = builder.notify_url;
this.trade_type = builder.trade_type;
this.product_id = builder.product_id;
this.limit_pay = builder.limit_pay;
this.openid = builder.openid;
this.sign = signature.getsign(tomap());
}
public string getappid()
{
return appid;
}
public string getmch_id()
{
return mch_id;
}
public string getdevice_info()
{ return device_info;
}
public string getnonce_str()
{
return nonce_str;
}
public string getsign()
{
return sign;
}
public string getbody()
{
return body;
}
public string getdetail()
{
return detail;
}
public string getattach()
{
return attach;
}
public string getout_trade_no()
{
return out_trade_no;
}
public string getfee_type()
{
return fee_type;
}
public int gettotal_fee()
{
return total_fee;
}
public string getspbill_create_ip()
{
return spbill_create_ip;
}
public string gettime_start()
{
return time_start;
}
public string gettime_expire()
{
return time_expire;
}
public string getgoods_tag()
{
return goods_tag;
}
public string getnotify_url()
{ return notify_url;
}
public string gettrade_type()
{ return trade_type;
}
public string getproduct_id()
{ return product_id;
}
public string getlimit_pay()
{ return limit_pay;
}
public string getopenid()
{
return openid;
}
public map<string, object> tomap()
{
map<string, object> map = new hashmap<string, object>();
field[] fields = this.getclass().getdeclaredfields();
for (field field : fields)
{
object obj;
try {
obj = field.get(this);
if (obj != null) {
map.put(field.getname(), obj);
}
}
catch (illegalargumentexception e)
{
e.printstacktrace();
}
catch (illegalaccessexception e)
{
e.printstacktrace();
}
}
return map;
}
public static class unifiedorderreqdatabuilder
{
private string appid;
private string mch_id;
private string device_info;
private string body;
private string detail;
private string attach;
private string out_trade_no;
private string fee_type;
private int total_fee;
private string spbill_create_ip;
private string time_start;
private string time_expire;
private string goods_tag;
private string notify_url;
private string trade_type;
private string product_id;
private string limit_pay;
private string openid;
/**
* 使用配置中的 appid 和 mch_id
*
* @param body
* @param out_trade_no
* @param total_fee
* @param spbill_create_ip
* @param notify_url
* @param trade_type
*/
public unifiedorderreqdatabuilder(string body, string out_trade_no, integer total_fee,
string spbill_create_ip, string notify_url, string trade_type)
{
this(configure.getappid(),
configure.getmchid(),
body,
out_trade_no,
total_fee,
spbill_create_ip, notify_url, trade_type);
}
public unifiedorderreqdatabuilder(string appid, string mch_id, string body, string out_trade_no, integer total_fee,
string spbill_create_ip, string notify_url, string trade_type)
{
if (appid == null)
{
throw new illegalargumentexception("传入参数appid不能为null");
}
if (mch_id == null) {
throw new illegalargumentexception("传入参数mch_id不能为null");
}
if (body == null) {
throw new illegalargumentexception("传入参数body不能为null");
}
if (out_trade_no == null) {
throw new illegalargumentexception("传入参数out_trade_no不能为null");
}
if (total_fee == null) {
throw new illegalargumentexception("传入参数total_fee不能为null");
}
if (spbill_create_ip == null) {
throw new illegalargumentexception("传入参数spbill_create_ip不能为null");
}
if (notify_url == null) {
throw new illegalargumentexception("传入参数notify_url不能为null");
}
if (trade_type == null) {
throw new illegalargumentexception("传入参数trade_type不能为null");
}
this.appid = appid;
this.mch_id = mch_id;
this.body = body;
this.out_trade_no = out_trade_no;
this.total_fee = total_fee;
this.spbill_create_ip = spbill_create_ip;
this.notify_url = notify_url;
this.trade_type = trade_type;
}
public unifiedorderreqdatabuilder setdevice_info(string device_info)
{
this.device_info = device_info;
return this;
}
public unifiedorderreqdatabuilder setdetail(string detail)
{
this.detail = detail;
return this;
}
public unifiedorderreqdatabuilder setattach(string attach)
{
this.attach = attach;
return this;
}
public unifiedorderreqdatabuilder setfee_type(string fee_type)
{
this.fee_type = fee_type;
return this;
}
public unifiedorderreqdatabuilder settime_start(string time_start)
{
this.time_start = time_start;
return this;
}
public unifiedorderreqdatabuilder settime_expire(string time_expire)
{
this.time_expire = time_expire;
return this;
}
public unifiedorderreqdatabuilder setgoods_tag(string goods_tag)
{
this.goods_tag = goods_tag;
return this;
}
public unifiedorderreqdatabuilder setproduct_id(string product_id)
{
this.product_id = product_id;
return this;
}
public unifiedorderreqdatabuilder setlimit_pay(string limit_pay)
{
this.limit_pay = limit_pay;
return this;
}
public unifiedorderreqdatabuilder setopenid(string openid)
{
this.openid = openid;
return this;
}
public unifiedorderreqdata build()
{
if ("jsapi".equals(this.trade_type) && this.openid == null)
{
throw new illegalargumentexception("当传入trade_type为jsapi时,openid为必填参数");
}
if ("native".equals(this.trade_type) && this.product_id == null)
{
throw new illegalargumentexception("当传入trade_type为native时,product_id为必填参数");
}
return new unifiedorderreqdata(this);
}
}
}
因为有些参数为必填,有些参数为选填。而且sign要等所有参数传入之后才能计算的出,所以这里用了builder模式。关于builder模式。关于每个参数的定义,参考说明文档https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1
我们选用httpclient进行网络传输。
package com.
unstoppedable.
common;
import com.
thoughtworks.
xstream.
xstream;import com.
thoughtworks.
xstream.io.xml.
domdriver;import com.
thoughtworks.xstream.io.xml.xmlfriendlynamecoder;import org.apache.commons.logging.log;import org.apache.commons.logging.logfactory;import org.apache.
http.httpentity;import org.apache.http.httpresponse;import org.apache.http.client.clientprotocolexception;import org.apache.http.client.responsehandler;
import org.apache.http.client.config.requestconfig;import org.apache.http.client.methods.httpget;import org.apache.http.client.methods.httppost;import org.
apache.http.conn.connecttimeoutexception;import org.apache.http.conn.connectionpooltimeoutexception;import org.apache.http.conn.ssl.sslconnectionsocketfactory;
import org.apache.http.conn.ssl.sslcontexts;import org.apache.http.entity.stringentity;import org.apache.http.impl.client.closeablehttpclient;import org.apache.
http.impl.client.httpclients;import org.apache.http.util.entityutils;import javax.net.ssl.sslcontext;import java.io.file;import java.io.fileinputstream;
import java.io.ioexception;import java.net.sockettimeoutexception;import java.security.keystore;
/**
* created by hupeng on 2015/7/28.
*/
public class httpservice
{
private static log logger = logfactory.getlog(httpservice.class);
private static closeablehttpclient httpclient = buildhttpclient(); //连接超时时间,默认10秒
private static int sockettimeout = 5000; //传输超时时间,默认30秒
private static int connecttimeout = 5000;
private static int requesttimeout = 5000;
public static closeablehttpclient buildhttpclient()
{
try {
keystore keystore = keystore.getinstance("pkcs12");
fileinputstream instream = new fileinputstream(new file(configure.getcertlocalpath()));//加载本地的证书进行https加密传输
try {
keystore.load(instream, configure.getcertpassword().tochararray());//设置证书密码
} finally {
instream.close();
}
// trust own ca and all self-signed certs
sslcontext sslcontext = sslcontexts.custom()
.loadkeymaterial(keystore, configure.getcertpassword().tochararray())
.build();
// allow tlsv1 protocol only
sslconnectionsocketfactory sslsf = new sslconnectionsocketfactory(
sslcontext,
new string[]{"tlsv1"},
null,
sslconnectionsocketfactory.browser_compatible_hostname_verifier);
requestconfig requestconfig = requestconfig.custom()
.setconnecttimeout(connecttimeout)
.setconnectionrequesttimeout(requesttimeout)
.setsockettimeout(sockettimeout).build();
httpclient = httpclients.custom()
.setdefaultrequestconfig(requestconfig)
.setsslsocketfactory(sslsf)
.build();
return httpclient;
} catch (exception e) {
throw new runtimeexception("error create httpclient......", e);
}
}
public static string doget(string requesturl) throws exception {
httpget httpget = new httpget(requesturl);
try {
logger.debug("executing request " + httpget.getrequestline());
// create a custom response handler
responsehandler<string> responsehandler = new responsehandler<string>() {
@override
public string handleresponse(
final httpresponse response) throws clientprotocolexception, ioexception
{
int status = response.getstatusline().getstatuscode();
if (status >= 200 && status < 300)
{
httpentity entity = response.getentity();
return entity != null ? entityutils.tostring(entity) : null;
} else {
throw new clientprotocolexception("unexpected response status: " + status);
}
}
};
return httpclient.execute(httpget, responsehandler);
} finally {
httpget.releaseconnection();
}
}
public static string dopost(string url, object object2xml) {
string result = null;
httppost httppost = new httppost(url);
//解决xstream对出现双下划线的bug
xstream xstreamforrequestpostdata = new xstream(new domdriver("utf-8", new xmlfriendlynamecoder("-_", "_")));
//将要提交给api的数据对象转换成xml格式数据post给api
string postdataxml = xstreamforrequestpostdata.toxml(object2xml);
logger.info("api,post过去的数据是:");
logger.info(postdataxml);
//得指明使用utf-8编码,否则到api服务器xml的中文不能被成功识别
stringentity postentity = new stringentity(postdataxml, "utf-8");
httppost.addheader("content-type", "text/xml");
httppost.setentity(postentity); //设置请求器的配置
logger.info("executing request" + httppost.getrequestline());
try {
httpresponse response = httpclient.execute(httppost);
httpentity entity = response.getentity();
result = entityutils.tostring(entity, "utf-8");
} catch (connectionpooltimeoutexception e) {
logger.error("http get throw connectionpooltimeoutexception(wait time out)", e);
} catch (connecttimeoutexception e) {
logger.error("http get throw connecttimeoutexception", e);
} catch (sockettimeoutexception e) {
logger.error("http get throw sockettimeoutexception", e);
} catch (exception e) {
logger.error("http get throw exception", e);
} finally {
httppost.abort();
}
return result;
}}
然后是我们的总入口:
package com.unstoppedable.service;import com.unstoppedable.common.configure;import com.unstoppedable.common.httpservice;import com.unstoppedable.common.
xmlparser;import com.unstoppedable.protocol.unifiedorderreqdata;import org.xml.sax.saxexception;import javax.xml.parsers.parserconfigurationexception;
import java.io.ioexception;import java.util.map;
/**
* created by hupeng on 2015/7/28.
*/
public class wxpayapi
{
public static map<string,object> unifiedorder(unifiedorderreqdata reqdata) throws ioexception, saxexception, parserconfigurationexception {
string res = httpservice.dopost(configure.unified_order_api, reqdata);
return xmlparser.getmapfromxml(res);
}
public static void main(string[] args) throws exception
{
unifiedorderreqdata reqdata = new unifiedorderreqdata.unifiedorderreqdatabuilder
("appid", "mch_id", "body", "out_trade_no", 1, "spbill_create_ip", "notify_url", "jsapi").setopenid("openid").build();
system.out.println(unifiedorder(reqdata));
}}
返回的xml为:
<xml>
<return_code>
<![cdata[success]]>
</return_code>
<return_msg>
<![cdata[ok]]>
</return_msg>
<appid>
<![cdata[wx2421b1c4370ec43b]]>
</appid>
<mch_id>
<![cdata[10000100]]>
</mch_id>
<nonce_str>
<![cdata[iitri8iabbblz1jc]]>
</nonce_str>
<sign>
<![cdata[7921e432f65eb8ed0ce9755f0e86d72f]]>
</sign>
<result_code>
<![cdata[success]]>
</result_code>
<prepay_id>
<![cdata[wx201411101639507cbf6ffd8b0779950874]]>
</prepay_id>
<trade_type>
<![cdata[jsapi]]>
</trade_type>
</xml>
return_code 和result_code都为success的时候会返回我们需要的prepay_id。。。
然后在jsapi中使用他就可以了。。
以上就是java实现jsapi方式的微信支付的内容。