这次系统的需求是要求在微信公众号上支付完成后,并将该笔订单按照规定的方法进行分账。
首先将JSAPI支付和分账功能开通。微信浏览器中只支持JSAPI支付,不支持H5,所以选择JSAPI支付。分账功能开通需要知道自己的平台是微信服务商平台还是微信商户平台,不能混淆,因为接口是不一样的。我这用的是商户平台。
分账功能开通后,可以在分账管理哪里添加分帐人,切记一定要选本商户,不然加不上去的,提示 “无分账权限”。
openid的话,在公众号中,让用户授权哪里可以取到。分账到个人需要用到openid的。
openid获取的方法如下。其中YMurl是自己的域名。
import java.net.URLEncoder;
@GetMapping("/login")
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
//回调地址(必须在公网进行访问)
String backUrl=YMurl+"/WxLogin/callBack";
String url="https://open.weixin.qq.com/connect/oauth2/authorize?appid="+appid
+ "&redirect_uri="+ URLEncoder.encode(backUrl)
+ "&response_type=code"
+ "&scope=snsapi_userinfo"
+ "&state=STATE#wechat_redirect";
//重定向
resp.sendRedirect(url);
}
@GetMapping("/callBack")
protected void callBack(HttpServletRequest req, HttpServletResponse resp) {
/**
* 3.获取code
*/
String code=req.getParameter("code");
String url="https://api.weixin.qq.com/sns/oauth2/access_token?appid="+appid
+ "&secret="+secret
+ "&code="+code
+ "&grant_type=authorization_code";
JSONObject jsonObject;
jsonObject = Utils.doGetJson(url);
String openid=jsonObject.getString("openid");
String token=jsonObject.getString("access_token");
/**
* 4.拉取用户信息
*/
String infoUrl="https://api.weixin.qq.com/sns/userinfo?access_token="+token
+ "&openid="+openid
+ "&lang=zh_CN";
JSONObject userInfo=Utils.doGetJson(infoUrl);
//userInfo 则是该用户的信息
}
//Utils下的doGetJson方法,这里我就放一起了,自己注意下,修改下
public static JSONObject doGetJson(String url) throws Exception, IOException {
JSONObject jsonObject=null;
//初始化httpClient
DefaultHttpClient client=new DefaultHttpClient();
//用Get方式进行提交
HttpGet httpGet=new HttpGet(url);
//发送请求
HttpResponse response= client.execute(httpGet);
//获取数据
HttpEntity entity=response.getEntity();
//格式转换
if (entity!=null) {
String result= EntityUtils.toString(entity,"UTF-8");
jsonObject=JSONObject.parseObject(result);
}
//释放链接
httpGet.releaseConnection();
return jsonObject;
}
接着开始整个流程
首先从JSAPI支付开始
1.发起预支付 这一步的逻辑是,先发起预支付请求给微信,然后得到orderStr,然后请参数整合转义出sign后一起返回给前端,前端再去发起支付请求。 openid,orderid=订单id 之类的数据请根据自己实际情况填写。
@GetMapping("/test")
public String view(@RequestParam(value = "user_id")String user_id, Model model,
HttpServletRequest request){
String openid = "";
Date date = new Date();
DateFormat format = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
Integer sum = weChatPayService.FindWxOrderCount(format.format(date));
String orderId = Utils.buildOrderCode(sum+1); //订单id,根据自己实际情况填写
String payXml ="";
try {
payXml = weChatConfig.fillRequestData(request,openid,orderId);
String orderStr = HttpUtils.doPost("https://api.mch.weixin.qq.com/pay/unifiedorder",payXml,4000);
if(StringUtils.isEmpty(orderStr)){
return null;
}
log.info("orderStr: "+orderStr);
Map<String, String> OrderMap =WXPayUtil.xmlToMap(orderStr);
if(!OrderMap.get("result_code").equals("SUCCESS")){
log.error(OrderMap.get("err_code_des"));
return "pay/500";
}
long timeNew = System.currentTimeMillis()/ 1000;
OrderMap.put("time",String.valueOf(timeNew));
WxOrderPay wxOrderPay = new WxOrderPay();
if(OrderMap != null) {
String paySign = weChatConfig.GetSing(OrderMap.get("prepay_id"),OrderMap.get("nonce_str"),String.valueOf(timeNew));
OrderMap.put("paySign",paySign);
//这的订单号、随机数跟预支付单号等要和前面一致。不然报错
model.addAttribute("timeStamp",String.valueOf(timeNew));
model.addAttribute("nonceStr",OrderMap.get("nonce_str"));
model.addAttribute("prepay_id",OrderMap.get("prepay_id"));
model.addAttribute("paySign",paySign);
model.addAttribute("order_id",orderId);
model.addAttribute("user_id",user_id);
return "pay/wxbuy";
}
} catch (Exception e) {
e.printStackTrace();
}
return "pay/back";
}
http的POST请求接口
import org.apache.http.HttpEntity;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.*;
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;
public class HttpUtils {
public static String doPost(String url, String data, int timeout){
CloseableHttpClient httpClient = HttpClients.createDefault();
//超时设置
RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(timeout) //连接超时
.setConnectionRequestTimeout(timeout)//请求超时
.setSocketTimeout(timeout)
.setRedirectsEnabled(true) //允许自动重定向
.build();
HttpPost httpPost = new HttpPost(url);
httpPost.setConfig(requestConfig);
httpPost.addHeader("Content-Type","application/json;charset=utf-8");
if(data != null && data instanceof String){ //使用字符串传参
StringEntity stringEntity = new StringEntity(data,"UTF-8");
httpPost.setEntity(stringEntity);
}
try{
CloseableHttpResponse httpResponse = httpClient.execute(httpPost);
HttpEntity httpEntity = httpResponse.getEntity();
System.out.println(httpResponse.getStatusLine().getStatusCode());
System.out.println(httpResponse.getStatusLine().getReasonPhrase());
if(httpResponse.getStatusLine().getStatusCode() == 200){
String result = EntityUtils.toString(httpEntity, "UTF-8");
return result;
}
}catch (Exception e){
e.printStackTrace();
}finally {
try{
httpClient.close();
}catch (Exception e){
e.printStackTrace();
}
}
return null;
}
//string转Map格式,前面代码中放其他工具类中,博文我放这里方便显示了,自行调整位置
public static Map<String, String> xmlToMap(String strXML) throws Exception {
try {
Map<String, String> data = new HashMap<String, String>();
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
InputStream stream = new ByteArrayInputStream(strXML.getBytes("UTF-8"));
org.w3c.dom.Document doc = documentBuilder.parse(stream);
doc.getDocumentElement().normalize();
NodeList nodeList = doc.getDocumentElement().getChildNodes();
for (int idx = 0; idx < nodeList.getLength(); ++idx) {
Node node = nodeList.item(idx);
if (node.getNodeType() == Node.ELEMENT_NODE) {
org.w3c.dom.Element element = (org.w3c.dom.Element) node;
data.put(element.getNodeName(), element.getTextContent());
}
}
try {
stream.close();
} catch (Exception ex) {
}
return data;
} catch (Exception ex) {
throw ex;
}
}
}
预下单接口和转义sign接口`
/**
* 转义sign
*/
public String GetSing(String prepay_id,String nonce_str,String timestamp ) throws Exception {
SortedMap<String,String> params = new TreeMap<>();
params.put("appId", appid);
params.put("timeStamp",timestamp);
params.put("nonceStr", nonce_str); // 随机字符串, 请求参数需要
params.put("package", "prepay_id="+prepay_id); // 订单号
params.put("signType","MD5");
String sign = WXPayUtil.createSign(params, key);
params.put("sign",sign);
System.out.println(WXPayUtil.mapToXml(params));
return sign;
}
/**
* JSAPI的预下单请求xml
* @return
*/
public String fillRequestData( HttpServletRequest request,String openid,String orderId ) throws Exception {
String requestIp = CommonUtils.getIpAddr(request);
SortedMap<String,String> reqData = new TreeMap<>();
reqData.put("appid", appid);
reqData.put("mch_id", merId);
reqData.put("nonce_str", CommonUtils.generateUUID()); //随机数
reqData.put("attach","功能态内容查看");
reqData.put("body","功能态内容查看");
reqData.put("notify_url",redirect_url); //返回路径
reqData.put("openid",openid); //用户的open id
reqData.put("out_trade_no",orderId); //订单
reqData.put("spbill_create_ip",requestIp); //终端ip
reqData.put("total_fee","10"); //价钱 1=0.01
reqData.put("trade_type","JSAPI"); //支付方式
reqData.put("profit_sharing","Y"); //是否支持分账 默认是不支持 Y是支持
String sign = WXPayUtil.createSign(reqData, key);
reqData.put("sign", sign);
return mapToXml(reqData);
}
//WXPayUtil下的createSign方法,自行调整下
public static String createSign(SortedMap<String, String> params, String key){
StringBuilder sb = new StringBuilder();
Set<Map.Entry<String, String>> es = params.entrySet();
Iterator<Map.Entry<String, String>> it = es.iterator();
while (it.hasNext()){
Map.Entry<String, String> entry = (Map.Entry<String, String>)it.next();
String k = (String)entry.getKey();
String v = (String)entry.getValue();
if(null != v && !"".equals(v) && !"sign".equals(k) && !"key".equals(k)){
sb.append(k+"="+v+"&");
}
}
sb.append("key=").append(key);
String sign = CommonUtils.MD5(sb.toString()).toUpperCase();
return sign;
}
前端js发起JSAPI支付
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>JSAPI支付</title>
</head>
<body>
</body>
<script type="text/javascript" th:inline="javascript">
function first2(orderid,userid) {
//支付完成后触发的接口地址,如点击确认或者关闭页面时触发
window.location.href="http://*********/wechat/PayComplete?order_id="+orderid+"&user_id="+userid;
}
function onBridgeReady(){
var single = [[${paySign}]];
var time = [[${timeStamp}]];
var nonceStr2 =[[${nonceStr}]];
var prepay_id2 =[[${prepay_id}]];
var order_id =[[${order_id}]];
var user_id =[[${user_id}]];
WeixinJSBridge.invoke(
'getBrandWCPayRequest', {
"appId":"", //公众号ID,由商户传入
"timeStamp": time, //时间戳,自1970年以来的秒数
"nonceStr": nonceStr2, //随机串
"package":"prepay_id="+prepay_id2,
"signType":"MD5", //微信签名方式:
"paySign": single //微信签名
},
function(res){
if(res.err_msg == "get_brand_wcpay_request:ok" ){
// 使用以上方式判断前端返回,微信团队郑重提示:
first2(order_id,user_id);
}else {
}
});
}
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();
}
</script>
</html>
1微信支付完成确认接口和2微信支付回调接口。这2个接口功能分别是:
1:前端在用户支付完成后,点击确认或者关闭页面访问后的访问的接口,用于确定订单是否完成。我这边将分账业务写在此处.
2:前面发起微信预制付时所填写的接口,微信会对该接口发起请求,我们需要回应,如果没有回应,微信会对这个接口进行多次的请求访问。在这里我们可以将微信返给我们的订单信息保存下来,如该订单的商户支付订单号:transaction_id,后面分账需要用上。
代码如下:2微信支付回调接口
@ResponseBody
@PostMapping("/backll")
public String backll(HttpServletRequest request) throws Exception {
InputStream in = request.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader( in, "UTF-8"));
StringBuffer result = new StringBuffer();
String line = "";
while ((line = br.readLine()) != null )
{
result.append(line);
}
Map<String, String> Map = WXPayUtil.xmlToMap(result.toString());
log.info("返回状态-----------:"+Map.get("return_code"));
String out_trade_no = Map.get("out_trade_no"); //商户订单号
String gmt_payment = Map.get("time_end"); //支付完成时间
String transaction_id = Map.get("transaction_id"); //微信支付订单号
if(Map.get("return_code").equals("SUCCESS")){
String noticeStr = weChatConfig.setXML("SUCCESS", "OK");
return noticeStr;
}else {
log.warn("支付失败");
String noticeStr = weChatConfig.setXML("FAIL", "error");
return noticeStr;
}
}
public static String setXML(String return_code, String return_msg) {
return "<xml><return_code><![CDATA[" + return_code + "]]></return_code><return_msg><![CDATA["+return_msg+"]]></return_msg></xml>";
}
微信支付完成确认接口代码:
/**
* 支付完成以后,点击确认 调用此接口来确认订单
*/
@GetMapping("/PayComplete")
public String PayComplete(@RequestParam("order_id")String order_id,
@RequestParam("user_id")String user_id) throws UnsupportedEncodingException {
JSONObject json = new JSONObject();
json.put("msg","ok");
json.put("status",true);
//我这边业务逻辑是自己先在微信服务器调用我服务时,将订单信息先存下来,这里根据订单id在查询出来,然后进行分账。
wxUserServer.UpdatePayComplete(user_id);
WxOrderPay wxOrderPay = weChatPayService.FindMemberByOrderId(order_id);
WxUser wxUser2 = wxUserServer.FindRecommend(wxOrderPay.getMobile());
String createOrderURL = "https://api.mch.weixin.qq.com/secapi/pay/profitsharing"; //请求分账接口
log.error(wxOrderPay.getTransaction_id());
try {
weChatConfig.doRefund(wxOrderPay.getTransaction_id(), wxUser2.getOpenid(), createOrderURL);
}catch (Exception e){
log.error(e.getMessage());
}
WxUser wxUser = wxUserServer.findWxUser(user_id);
//返回给前端页面了 整个过程结束了。
return "redirect:http://**********?name="+
URLEncoder.encode(wxUser.getName(),"UTF-8")+
"&mobile="+wxUser.getMobile()+"&member_id="+wxUser.getUser_id();
}
微信分账的方法,我这里只分给了一个用户。请求单次分账。证书需要自己在那个商户平台中的api安全中设置得到。
分账API地址
/**
* 将本笔订单进行分账处理
*/
public Map doRefund(String transaction_id,String accout, String url) throws Exception {
Date date = new Date();
SimpleDateFormat format = new SimpleDateFormat("YYYYMMddhhmmss");
SortedMap<String,String> map = new TreeMap<>();
String out_order_no = "P"+format.format(date);
String nonce_str =CommonUtils.generateUUID();
map.put("mch_id", merId); //商户ID
map.put("appid", appid); //应用ID
map.put("nonce_str", nonce_str); //随机数
map.put("transaction_id", transaction_id); // 商家订单号
map.put("out_order_no",out_order_no); //分账单号
List<Object> list = new ArrayList<>();
JSONObject json = new JSONObject();
json.put("type","PERSONAL_OPENID"); //分账的接收方类型:个人还是商户
json.put("account",accout); //分账接收方账号
json.put("amount",1); //分账金额 //分账金额 0.01 :1
json.put("description","分给个体户A"); //备注
list.add(json);
map.put("receivers", list.toString());
String sign = WXPayUtil.HMAC_SHA256_Sign(map,key);
map.put("sign",sign);
String xml = WXPayUtil.mapToXml(map);
System.out.println(xml);
KeyStore keyStore = KeyStore.getInstance("PKCS12");//证书格式
//FileInputStream读取本地证书文件
FileInputStream fis=new FileInputStream("E:\\WXCertUtil\\cert\\apiclient_cert.p12");
try {
//这里写密码..默认是你的MCHID
keyStore.load(fis, merId.toCharArray());
} finally {
fis.close();
}
SSLContext sslcontext = SSLContexts.custom()
.loadKeyMaterial(keyStore, merId.toCharArray())
.build();
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
sslcontext,
SSLConnectionSocketFactory.getDefaultHostnameVerifier());
CloseableHttpClient httpclient = HttpClients.custom()
.setSSLSocketFactory(sslsf)
.build();
try {
HttpPost httpost = new HttpPost(url);
httpost.setEntity(new StringEntity(xml, "UTF-8"));
CloseableHttpResponse response = httpclient.execute(httpost);
try {
HttpEntity entity = response.getEntity();
//接受到返回信息
String jsonStr = EntityUtils.toString(response.getEntity(), "UTF-8");
EntityUtils.consume(entity);
System.out.println(jsonStr);
Map<String, String> map2 = WXPayUtil.xmlToMap(jsonStr);
return map2;
} finally {
response.close();
}
} finally {
httpclient.close();
}
}
整个业务流程差不多就是这样了,上面代码有些需要自己改动下。有什么问题可以提出来,我会及时解答的。
HMAC_SHA256_Sign 方法
public static String HMAC_SHA256_Sign(SortedMap<String, String> params, String key) throws Exception {
StringBuilder sb = new StringBuilder();
Set<Map.Entry<String, String>> es = params.entrySet();
Iterator<Map.Entry<String, String>> it = es.iterator();
while (it.hasNext()){
Map.Entry<String, String> entry = (Map.Entry<String, String>)it.next();
String k = (String)entry.getKey();
String v = (String)entry.getValue();
if(null != v && !"".equals(v) && !"sign".equals(k) && !"key".equals(k)){
sb.append(k+"="+v+"&");
}
}
sb.append("key=").append(key);
String sign = com.github.wxpay.sdk.WXPayUtil.HMACSHA256(sb.toString(),key).toUpperCase();//CommonUtils.MD5(sb.toString()).toUpperCase();
return sign;
}