先登陆微信公众平台进入“公众号设置”的“功能设置”里填写“JS接口安全域名”。
复制代码
wx.config({
debug: true, // 开启调试模式,调用的全部api的返回值会在客户端alert出来,若要查看传入的参数,能够在pc端打开,参数信息会经过log打出,仅在pc端时才会打印。
appId: '', // 必填,公众号的惟一标识
timestamp: , // 必填,生成签名的时间戳
nonceStr: '', // 必填,生成签名的随机串
signature: '',// 必填,签名
jsApiList: [] // 必填,须要使用的JS接口列表
});
复制代码
//经过appid,secret获取access_token
if(StringUtils.isEmpty(appid)|| StringUtils.isEmpty(secret))
{
DEBUG_LOGGER.error("appid or secret is null");
return null;
}
GetAccessTokenRsp getAccessTokenRsp = new GetAccessTokenRsp();
try
{
String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid="+appid+"&secret="+secret;
CloseableHttpClient httpClient = HttpClients.createDefault();
HttpGet get =new HttpGet(url);
CloseableHttpResponse response =httpClient.execute(get);
int execute =response.getStatusLine().getStatusCode();
System.out.println("execute:"+execute);
HttpEntity entity =response.getEntity();
String entityString = EntityUtils.toString(entity,"utf-8");
JSONObject addjson = JSONObject.fromObject(entityString);
// System.out.println(addjson.get("access_token"));
getAccessTokenRsp.setAccessToken(addjson.getString("access_token"));
//关闭httpclient
response.close();
httpClient.close();
}
catch (IOException e)
{
DEBUG_LOGGER.error("getAccessToken failed,desc:::"+e);
e.printStackTrace();
}
return getAccessTokenRsp;
复制代码
//再经过access_token获取jsapi_ticket
String requestUrl = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=ACCESS_TOKEN&type=jsapi";
requestUrl = requestUrl.replace("ACCESS_TOKEN", accessToken);
JSONObject jsonObject = CommonUtil
.httpsRequest(requestUrl, "GET", null);
String jsapi_ticket = jsonObject.getString("ticket");
//这里是用jsapi_ticket和你前端配置config的网址生成签名
return Sign.sign(jsapi_ticket, url);
复制代码
//这里是生成签名的方法,这个是微信提供的,能够到公众号开发文档下载
public class Sign {
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 = "";
//注意这里参数名必须所有小写,且必须有序
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;
}
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();
}
public static String create_timestamp() {
return Long.toString(System.currentTimeMillis() / 1000);
}
}
复制代码
HttpUtil.get("wechat/getJSSDKToken.do", { url: url }, function(data) {
_this.$wechat.config({
debug: false,
appId: data.appId,
timestamp: data.timestamp,
nonceStr: data.nonceStr,
signature: data.signature,
jsApiList: ["chooseWXPay"]
});
});
复制代码
到这里,config完成前端
WXJSSDKPay wxpay = null;
IWxPayConfig iWxPayConfig = new IWxPayConfig();
Map<String, String> result = new HashMap<String,String>();
try {
// ******************************************
//
// 统一下单
//
// ******************************************
wxpay = new WXJSSDKPay(iWxPayConfig); // *** 注入本身实现的微信配置类, 建立WXPay核心类, WXPay 包括统一下单接口
Map<String, String> data = new HashMap<String, String>();
data.put("body", "下单");
data.put("out_trade_no", billCode); // 订单惟一编号, 不容许重复
data.put("total_fee", String.valueOf(Math.round(sum*100))); // 订单金额, 单位分
data.put("spbill_create_ip", "10.215.70.30"); // 下单ip
data.put("openid", addjson.getString("openid")); // 微信公众号统一标示openid
data.put("notify_url", "http://app.jishengsoft.com/PSSWeb/cosmetics/api/bill/payCallback.do"); // 订单结果通知, 微信主动回调此接口
data.put("trade_type", "JSAPI"); // 固定填写
logger.error("发起微信支付下单接口, request={}");
logger.error( data);
Map<String, String> response = wxpay.unifiedOrder(data); // 微信sdk集成方法, 统一下单接口unifiedOrder, 此处请求 MD5加密 加密方式
logger.error("微信支付下单成功, 返回值 response={}");
logger.error( response);
String returnCode = response.get("return_code");
logger.error(returnCode);
String resultCode = response.get("result_code");
logger.error(resultCode);
String prepay_id = response.get("prepay_id");
logger.error(prepay_id);
// ******************************************
//
// 前端调起微信支付必要参数
//
// ******************************************
String packages = "prepay_id=" + prepay_id;
Map<String, String> wxPayMap = new HashMap<String, String>();
wxPayMap.put("appId", iWxPayConfig.getAppID());
wxPayMap.put("timeStamp", Sign.create_timestamp());
wxPayMap.put("nonceStr", Sign.create_nonce_str());
wxPayMap.put("package", packages);
wxPayMap.put("signType", "MD5");
// 加密串中包括 appId timeStamp nonceStr package signType 5个参数, 经过sdk WXPayUtil类加密, 注意, 此处使用 MD5加密 方式
String sign = WXPayUtil.generateSignature(wxPayMap, iWxPayConfig.getKey());
// ******************************************
//
// 返回给前端调起微信支付的必要参数
//
// ******************************************
result.put("prepay_id", prepay_id);
result.put("sign", sign);
result.putAll(wxPayMap);
result.put("state", "success");
logger.error(result);
} catch (Exception e) {
}
复制代码
//IWxPayConfig的实现,其它代码去微信公众开发文档下载
public class IWxPayConfig extends WXPayConfig {
@Override
public String getAppID() {
// TODO Auto-generated method stub
return config.getWechatAppId();
}
@Override
String getMchID() {
// TODO Auto-generated method stub
return config.getWechatMchID();
}
@Override
public String getKey() {
// TODO Auto-generated method stub
return config.getWechatKey();
}
@Override
InputStream getCertStream() {
// TODO Auto-generated method stub
return null;
}
@Override
IWXPayDomain getWXPayDomain() {
// TODO Auto-generated method stub
IWXPayDomain iwxPayDomain = new IWXPayDomain() {
@Override
public void report(String domain, long elapsedTimeMillis, Exception ex) {
}
@Override
public DomainInfo getDomain(WXPayConfig config) {
return new IWXPayDomain.DomainInfo(WXPayConstants.DOMAIN_API, true);
}
};
return iwxPayDomain;
}
}
复制代码
这里有一个大坑,微信提供的demo里面,wxpay这个类,这里要改一行代码,要否则一直提示签名出错spring
public WXJSSDKPay(final WXPayConfig config, final String notifyUrl, final boolean autoReport, final boolean useSandbox) throws Exception {
this.config = config;
this.notifyUrl = notifyUrl;
this.autoReport = autoReport;
this.useSandbox = useSandbox;
if (useSandbox) {
this.signType = SignType.MD5; // 沙箱环境
}
else {
this.signType = SignType.MD5; //这一行必须这么改,demo里面不是这样的
}
this.wxPayRequest = new WXPayRequest(config);
}
复制代码
wx.chooseWXPay({
timestamp: 0, // 支付签名时间戳,注意微信jssdk中的全部使用timestamp字段均为小写。但最新版的支付后台生成签名使用的timeStamp字段名需大写其中的S字符
nonceStr: '', // 支付签名随机串,不长于 32 位
package: '', // 统一支付接口返回的prepay_id参数值,提交格式如:prepay_id=\*\*\*)
signType: '', // 签名方式,默认为'SHA1',使用新版支付需传入'MD5'
paySign: '', // 支付签名
success: function (res) {
// 支付成功后的回调函数
}
});
复制代码
_this.$wechat.chooseWXPay({
timestamp: data.timeStamp, // 支付签名时间戳,注意微信jssdk中的全部使用timestamp字段均为小写。但最新版的支付后台生成签名使用的timeStamp字段名需大写其中的S字符
nonceStr: data.nonceStr, // 支付签名随机串,不长于 32 位
package: data.package, // 统一支付接口返回的prepay_id参数值,提交格式如:prepay_id=\*\*\*)
signType: 'MD5', // 签名方式,默认为'SHA1',使用新版支付需传入'MD5'
paySign: data.sign, // 支付签名
success: function (res) {
// 支付成功后的回调函数
console.log(JSON.stringify(res));
_this.$vux.toast.show({
text: "报货成功",
type: "success"
});
_this.$router.push("/order");
}
});
复制代码
@ResponseBody
@RequestMapping(value = "/payCallback", method = RequestMethod.POST)
public void payCallback(HttpServletRequest request, HttpServletResponse response) {
logger.info("进入微信支付异步通知");
String resXml="";
try{
//
InputStream is = request.getInputStream();
//将InputStream转换成String
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
StringBuilder sb = new StringBuilder();
String line = null;
try {
while ((line = reader.readLine()) != null) {
sb.append(line + "\n");
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
resXml=sb.toString();
logger.error("微信支付异步通知请求包: {}");
logger.error( resXml);
String result= payBack(resXml);
//这里又是一个坑,要使用这种方式,使用springmvc的@ResponseBody是不行的,微信接收不到,会不停的给你发回调消息
response.getWriter().write(result);
}catch (Exception e){
logger.error("微信支付回调通知失败",e);
String result = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[报文为空]]></return_msg>" + "</xml> ";
}
}
public String payBack(String notifyData) {
logger.error("payBack() start, notifyData={}");
logger.error( notifyData);
String xmlBack="";
Map<String, String> notifyMap = null;
try {
IWxPayConfig iWxPayConfig = new IWxPayConfig();
WXJSSDKPay wxpay = new WXJSSDKPay(iWxPayConfig);
notifyMap = WXPayUtil.xmlToMap(notifyData); // 转换成map
if (wxpay.isPayResultNotifySignatureValid(notifyMap)) {
// 签名正确
// 进行处理。
// 注意特殊状况:订单已经退款,但收到了支付结果成功的通知,不该把商户侧订单状态从退款改为支付成功
String return_code = notifyMap.get("return_code");//状态
String out_trade_no = notifyMap.get("out_trade_no");//订单号
String total_fee = notifyMap.get("total_fee");//金额
double total = Double.parseDouble(total_fee)/100;
if (out_trade_no == null) {
logger.error("微信支付回调失败订单号: {}");
logger.error( notifyMap);
xmlBack = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[报文为空]]></return_msg>" + "</xml> ";
return xmlBack;
}
wbs.saveOrderPay(out_trade_no, total);
// 业务逻辑处理 ****************************
logger.error("微信支付回调成功订单号: {}");
logger.error( notifyMap);
xmlBack = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>" + "<return_msg><![CDATA[SUCCESS]]></return_msg>" + "</xml> ";
logger.error( xmlBack);
return xmlBack;
} else {
logger.error("微信支付回调通知签名错误");
xmlBack = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[报文为空]]></return_msg>" + "</xml> ";
return xmlBack;
}
} catch (Exception e) {
logger.error("微信支付回调通知失败",e);
xmlBack = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[报文为空]]></return_msg>" + "</xml> ";
}
return xmlBack;
}
复制代码
一个支付,花了3天才搞好,中间确实有不少坑,有一说一,这个微信的接口确实没有支付宝作的友好json
还好有不少前辈在网上发了不少教程,不少代码都是从网上整理的,但愿可让后来人少踩点坑后端