官方网址:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_1
第一步:就是将生成支付的金额页面或者是支付需要扫的二维码
例如:
第二步:
①用户通过操作前端页面向后台发送下单请求,传入支付金额等所必须的参数,
②随后后台调用微信的统一下单API进行,并对API响应的参数,进行金额,签名等进行验证,所有验证通过后,
③给前端返回所需要的参数,前端给微信发送请求,将后台返回的参数进行配置,配置成功即能进行支付操作
①请求参数(必填中‘是’的参数,统一下单时必须传)
例如参数:
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 notifyUrl = portalURL + ":" + serverPort + "/recharge/notify";
AppConfig config = new AppConfig();
WXPay wxpay = new WXPay(config, notifyUrl, false, false);
Map data = new TreeMap<>();
//put
data.put("appid", appId);
data.put("mch_id", mchId);
data.put("body", prepareOrderBean.getBody() + new Date());
data.put("product_id", "12");
data.put("device_info", "WEB");
data.put("fee_type", "CNY");
data.put("nonce_str", PayCommonUtil.CreateNoncestr());
data.put("notify_url", notifyUrl);
data.put("openid", prepareOrderBean.getOpenId());
data.put("out_trade_no", prepareOrderBean.getOutTradeNo());
data.put("spbill_create_ip", "123.12.12.123");
data.put("total_fee", "1");
data.put("trade_type", "JSAPI");
//调用统一下单api
Map unifiedOrder = wxpay.unifiedOrder(data);
下单成功后会返应的一些参数,例如:
验证数据:我们需要对返回的一些参数进行判断,判断下单是否成功,对签名进行验证,防止第三方恶意篡改签名
下单成功后向前端返回相应的参数,这里注意的是签名问题:这里签名是根据你要向前端返回的这些参数,根据一定的算法策略生成,不是使用下单成功后返回的那个签名,这里注意,有点坑
算法基本实现过程,根据你Map中所有put的数据,通过ASCLL码进行排序之后通过相应的算法进行加密,生成sign(下载sdk,生成sign的源码很容易看懂)
AppConfig config = new AppConfig();
AppPackageBean appPackageBean = new AppPackageBean();
Map map = new HashMap<>();
map.put("appId", config.getAppID());
map.put("nonceStr", unifiedOrderMap.get("nonce_str"));
map.put("package", "prepay_id=" + unifiedOrderMap.get("prepay_id"));
map.put("signType", WXPayConstants.HMACSHA256);
map.put("timeStamp", DateUtil.getCurrentTimestamp10());// 10 位时间戳
String requestXml = PayCommonUtil.getRequestXml(map);
log.debug(requestXml);
/*
* final Map data, String key, SignType signType
* 校验签名
* */
String signature = WXPayUtil.generateSignature(map, config.getKey(), WXPayConstants.SignType.HMACSHA256);
前端接收到响应的参数,请求微信端进行参数配置,配置成功,即可使用支付功能进行支付
这里官方有提供参考代码
function onBridgeReady(){
WeixinJSBridge.invoke(
'getBrandWCPayRequest', {
"appId":"wx2421b1c4370ec43b", //公众号名称,由商户传入
"timeStamp":"1395712654", //时间戳,自1970年以来的秒数
"nonceStr":"e61463f8efa94090b1f366cccfbbb444", //随机串
"package":"prepay_id=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();
}
第三步,微信端进行回调下单前传入的notifyurl,通知支付结果,这里基本的支付完成,后面涉及关闭失败订单,和查询未回调成功的订单等问题
支付成功后,微信会根据之前统一下单时,传入的notifyurl进行回调,根据判断是否支付成功
1
根据微信官方文档提供的所有验证的参数,如:支付金额,签名,等参数进行验证,如参数验证成功则返回如下格式的告知微信端
通知微信端,接收到微信支付结果通知,并停止回调,这里要注意的是,如果在接收到回调是5秒内没有给微信端返回消息,微信会根据一定的回调策略进行多次回调
这里又有一个坑人的地方,就是再最开始进行sandbox测试时,不会进行回调,到真实支付情景下,才会去进行回调
并且注意回调的notifyurl,异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数。
try {
log.debug(request.toString());
//解析请求参数
InputStream inStream = request.getInputStream();
ByteArrayOutputStream outSteam = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len = 0;
while ((len = inStream.read(buffer)) != -1) {
outSteam.write(buffer, 0, len);
}
String weiXinCallback = new String(outSteam.toByteArray(), "utf-8");
outSteam.close();//关流
inStream.close();
log.debug("weiXinCallback" + weiXinCallback);
//将字符串转换为sortedMap
Map sortedMap = XMLUtil.doXMLParse(weiXinCallback);
log.info("微信支付回调: " + sortedMap.toString());
String outTradeNo = sortedMap.get("out_trade_no");
Recharge recharge = rechargeRepository.findByOutTradeNo(outTradeNo);
/* 防重复回调校验 */
if (recharge.getStatus() == 2) {
Map hashMap = new HashMap<>();
hashMap.put("return_code", "SUCCESS");
hashMap.put("return_msg", "OK");
return PayCommonUtil.getRequestXml(hashMap);
}
//验证签名
if (!checkSign(weiXinCallback)) {
log.debug("微信回调失败,签名可能被第三方篡改");
Map hashMap = new HashMap<>();
hashMap.put(PayErrorCodeMessage.ERROR_SIGN.getIndex(),
PayErrorCodeMessage.ERROR_SIGN.getName());
//关闭订单
if (CloseOrderManager.closeOrder(outTradeNo)) {
log.info("关闭订单");
//充值记录
updateUser(recharge, 3, "支付失败,原因:" + PayErrorCodeMessage.ERROR_SIGN.getName());
}
return PayCommonUtil.getRequestXml(hashMap);
}
//验证支付状态
if (!"SUCCESS".equals(sortedMap.get("result_code"))) {
log.debug("微信回调失败,失败原因: " + weiXinCallback);
Map hashMap = new HashMap<>();
hashMap.put(PayErrorCodeMessage.RESULT_CODE_FAIL.getIndex(),
PayErrorCodeMessage.RESULT_CODE_FAIL.getName());
//关闭订单
if (CloseOrderManager.closeOrder(outTradeNo)) {
log.info("关闭订单");
//充值记录
updateUser(recharge, 3, "订单支付失败,原因:" + PayErrorCodeMessage.RESULT_CODE_FAIL.getName());
}
return PayCommonUtil.getRequestXml(hashMap);
}
if (!"SUCCESS".equals(sortedMap.get("return_code"))) {
log.debug("微信回调失败,失败原因: " + weiXinCallback);
Map hashMap = new HashMap<>();
hashMap.put(PayErrorCodeMessage.RETURN_CODE_FAIL.getIndex(),
PayErrorCodeMessage.RETURN_CODE_FAIL.getName());
if (CloseOrderManager.closeOrder(outTradeNo)) {
log.info("关闭订单");
//充值记录
updateUser(recharge, 3, "订单支付失败,原因:" + PayErrorCodeMessage.RETURN_CODE_FAIL.getName());
}
return PayCommonUtil.getRequestXml(hashMap);
} else {
String totalFee = sortedMap.get("total_fee");
int fee = Integer.valueOf(totalFee) * 100;
if (Integer.valueOf(totalFee) == recharge.getAmount()) {
Map hashMap = new HashMap<>();
hashMap.put("return_code", "SUCCESS");
hashMap.put("return_msg", "OK");
log.debug("校验完成________________________________");
//有可能出现重复回调,保证充值积分正确
if (recharge.getStatus() != 2) {
//充值记录
updateUser(recharge, 2, "订单支付成功");
User user = userRepository.findById(recharge.getUserId());
//todo 用于测试积分
user.setGold(user.getGold() + fee / 100);
user.setLastModifyTime(new Date());
userRepository.save(user);
}
return PayCommonUtil.getRequestXml(hashMap);
} else {
Map hashMap = new HashMap<>();
hashMap.put(WXPayConstants.FAIL, "update bussiness outTrade fail");
//关闭订单
if (CloseOrderManager.closeOrder(outTradeNo)) {
log.info("关闭订单");
//充值记录
updateUser(recharge, 3, "订单支付失败,失败原因:支付金额错误");
}
return PayCommonUtil.getRequestXml(hashMap);
}
}
} catch (Exception e) {
log.error("回调异常,错误信息:" + e.getMessage());
Map hashMap = new HashMap<>();
hashMap.put(WXPayConstants.FAIL, "weixin pay server exception");
return PayCommonUtil.getRequestXml(hashMap);
}
进行到这里基本简单支付也就如此了,
当然这里还涉及到关闭订单,如客户支付失败,或未支付的订单等或未成功的订单进行关闭
AppConfig config = new AppConfig();
WXPay wxpay = new WXPay(config, false, false);
Map map = new HashMap<>();
map.put("appid", appId);
map.put("mch_id", mchId);
map.put("out_trade_no", outTradeNo);
Map closeOrder = wxpay.closeOrder(map);
第四步:
①之后涉及到服务器不稳定或宕机等不确定因素,需要在这一时段客户支付的进行确认并记录,
①使用到spring中定时器的注解,没两秒对数据库中状态未明确的订单进行查询,
②如该订单超过五分钟并且未支付或支付失败则关闭该订单,
这里涉及到JAVA 发送 http请求
public static String interfaceUtil(String path, String data) {
HttpURLConnection conn = null;
InputStream is = null;
try {
URL url = new URL(path);
//打开和url之间的连接
// conn = (HttpURLConnection) url.openConnection();
conn = (HttpURLConnection) url.openConnection();
/**设置URLConnection的参数和普通的请求属性****start***/
conn.setRequestProperty("accept", "*/*");
conn.setRequestProperty("connection", "Keep-Alive");
conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)");
conn.setDoOutput(true);
conn.setDoInput(true);
conn.setRequestMethod("GET");//GET和POST必须全大写
/**GET方法请求*****start*/
conn.connect();
//获取URLConnection对象对应的输入流
is = conn.getInputStream();
//构造一个字符流缓存
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String str = "";
while ((str = br.readLine()) != null) {
str = new String(str.getBytes(), "UTF-8");//解决中文乱码问题
return str;
}
//关闭流
return str;
//断开连接,最好写上,disconnect是在底层tcp socket链接空闲时才切断。如果正在被其他线程使用就不切断。
//固定多线程的话,如果不disconnect,链接会增多,直到收发不出信息。写上disconnect后正常一些。
} catch (Exception e) {
e.printStackTrace();
return "";
} finally {
try {
if (is != null) {
is.close();
}
if (conn != null) {
conn.disconnect();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
①将xml格式的字符串解析map
package com.demeter.utils;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.input.SAXBuilder;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Iterator;
import java.util.List;
import java.util.SortedMap;
import java.util.TreeMap;
/**
* @desc:XML 解析工具
*/
@SuppressWarnings("all")
public class XMLUtil {
/**
* 解析xml,返回第一级元素键值对。
* 如果第一级元素有子节点,
* 则此节点的值是子节点的xml数据。
*
* @param strxml
* @return
* @throws JDOMException
* @throws IOException
*/
public static SortedMap doXMLParse(String strxml)
throws JDOMException, IOException {
strxml = strxml.replaceFirst("encoding=\".*\"", "encoding=\"UTF-8\"");
if (null == strxml || "".equals(strxml)) {
return null;
}
SortedMap map = new TreeMap();
InputStream in = new ByteArrayInputStream(strxml.getBytes("UTF-8"));
SAXBuilder builder = new SAXBuilder();
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 key = e.getName();
String value = "";
List children = e.getChildren();
if (children.isEmpty()) {
value = e.getTextNormalize();
} else {
value = XMLUtil.getChildrenText(children);
}
map.put(key, value);
}
// 关闭流
in.close();
return map;
}
/**
* 获取子结点的xml
* @param children
* @return
*/
public static String getChildrenText(List children) {
StringBuffer sb = new StringBuffer();
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(XMLUtil.getChildrenText(list));
}
sb.append(value);
sb.append("");
}
}
return sb.toString();
}
}
② 获取时间戳
/*
* 获取当前系统的时间戳
* */
public static String getCurrentTimestamp() {
long timeStamp = new Date().getTime();
return String.valueOf(timeStamp);
}
public static String getCurrentTimestamp10() {
long timeStamp = new Date().getTime() / 1000;
String timestr = String.valueOf(timeStamp);
return timestr;
}
public static String getTimeStamp() {
int time = (int) (System.currentTimeMillis() / 1000);
return String.valueOf(time);
}