1200字范文,内容丰富有趣,写作的好帮手!
1200字范文 > JAVA微信支付(微信公众号支付JSAPI)

JAVA微信支付(微信公众号支付JSAPI)

时间:2019-10-18 07:58:51

相关推荐

JAVA微信支付(微信公众号支付JSAPI)

JAVA开发微信支付-公众号支付/微信浏览器支付(JSAPI)

写本篇博客其一是因为最近做的项目在用这个功能,通过本篇博客进行一个全局的梳理,其二呢,也就是想趁着思路清晰,把心得记录下来,分享给大家,也方便日后自己再用。再此之前,在网上搜索了很多关于微信公众号支付的博文,真正思路清晰,适合第一次接触微信支付的人去用的博文更是少之又少,所以,打算详细的记录一下,希望可以帮助到初次接触微信支付的人吧。

接下来进入正题:

步骤一:前提条件

进行微信公众号支付开发,就需要公众号和商户号两个账号,公众号比较好弄,商户号的话申请就比较麻烦了,一般情况下都是由客户之间提供好以上两个账号,提供这两个账号也是为了给我们提供微信支付的其中四个比较重要的参数:

微信公众号:1. APPID:wx455***********54532. APPSECRET:35502926************0c20e87b7fb8微信商户号1. MCH_ID(商户ID):14******422. API_KEY(API秘钥):2NEJNeXbTdy********q77fPKQIDAQAB

其中比较不好找的是商户的API密钥:在商户平台的账户中心下:需要用户自行下载证书及安装,(略)

步骤二:平台配置

公众号配置网页域名授权,在下图部分修改为域名

步骤三:开发流程

简单来说,就是调用微信支付的统一下单的接口,简单的步骤如下

凑足参数,请求统一下单接口编写回调接口,进行业务处理给微信返回信息

接下来看一下请求统一下单接口需要的参数

官方文档:https://pay./wiki/doc/api/jsapi.php?chapter=9_1

先看一遍所有的参数:

在这些参数里,其中有11个参数是必填的:

appid:公众账号IDmch_id:商户号nonce_str:随机字符串sign:签名(重点)body:商品描述out_trade_no:商户订单号total_fee:金额spbill_create_ip:终端IPnotify_url:回调接口地址trade_type:交易类型(本次为JSAPI)openid :支付人的微信公众号对应的唯一标识

接下来开始凑这十一个参数

appid:√mch_id:√nonce_str:随机字符串用WXPayUtil中的generateNonceStr()即可√sign:签名 用WXPayUtil中的generateSignature(finalMap<String, String> data, String key)方法,data是将除了sign外,其他10个参数放到map中,key是四大配置参数中的API秘钥(paternerKey)(这里不要着急管它,最后处理它);body:√out_trade_no:商户订单号(自主生成)√total_fee:金额(需要支付的金额)√spbill_create_ip:终端IP(测试用127.0.0.1即可,获取IP也很简单)√notify_url:回调地址,这是微信支付成功后,微信那边会带着一大堆XML格式的参数请求这个地址多次,在这个回调接口里我们要处理我们的业务逻辑,订单,流水,用户信息等,最终给微信返回一个XML格式的result_codetrade_type:交易类型(本次为JSAPI) √openid:支付人的微信公众号对应的唯一标识

这样的话还有openid、sign签名、回调接口(回调地址直接写接口地址就可以,只不过我们现在还没写接口里的内容)未凑齐。

步骤四:代码部分

1:获取openid

这部分在我的代码中其实用户信息里就已经存过用户的openid了,所以我直接获取了支付用户的用户信息拿到的openid,不过如果没有openid的话,下面我描述一下获取openid的方法,其实也就是微信授权的部分,有想详细了解的可以看我写过的另一篇博文JAVA实现微信授权登录(详解),简单说一下:

第一步:用户同意授权,获取code

我是在前台请求的这个自己写的接口,然后在后台此接口中调用微信授权接口,调用此接口需要有三个注意的地方

APPID:公众号的appidREDIRECT_URI:这个地址是调用微信授权后的回调接口,在回调接口中我们可以拿到用户的信息,特别注意:redirect_uri地址需要使用 urlEncode 对链接进行处理SCOPE:此处填snsapi_userinfo,需要用户点授权

/*** Tea微信登录* @throws IOException */@ApiOperation(value = "微信登录接口")@IgnoreAuth@RequestMapping("wx_login")public void wxLogin(HttpServletResponse response) throws IOException{//域名String sym = WxConstant.DOMAINNAME;//这里是回调的urlString redirect_uri = URLEncoder.encode(sym+"/front/auth/callBack", "UTF-8");String url = "https://open./connect/oauth2/authorize?" +"appid=APPID" +"&redirect_uri=REDIRECT_URI"+"&response_type=code" +"&scope=SCOPE" +"&state=123#wechat_redirect";response.sendRedirect(url.replace("APPID",WxConstant.APPID).replace("REDIRECT_URI",redirect_uri).replace("SCOPE","snsapi_userinfo"));}

接下来就是微信授权的回调接口

1、获取code

2、根据code获取openid和access_token

3、根据access_token和openid获取用户的微信信息(昵称,性别,地址等)

/*** Tea微信授权成功的回调函数* * @param request* @param response* @throws ClientProtocolException* @throws IOException* @throws ServletException*/@ApiOperation(value = "微信授权回调接口")@IgnoreAuth@RequestMapping("/callBack")protected void deGet(HttpServletRequest request, HttpServletResponse response)throws Exception {//获取回调地址中的codeString code = request.getParameter("code");//拼接URLString url = "https://api./sns/oauth2/access_token?appid=" + WxConstant.APPID + "&secret="+ WxConstant.APPSECRET + "&code=" + code + "&grant_type=authorization_code";JSONObject jsonObject = AuthUtil.doGetJson(url);//获取openIdString openid = jsonObject.getString("openid");//获取tokenString access_token = jsonObject.getString("access_token");String infoUrl = "https://api./sns/userinfo?access_token=" + access_token + "&openid=" + openid+ "&lang=zh_CN";//获取用户信息JSONObject userInfo = AuthUtil.doGetJson(infoUrl);}

补上刚刚用的工具类AuthUtil

package com.platform.util;import java.io.IOException;import org.apache.http.HttpEntity;import org.apache.http.HttpResponse;import org.apache.http.client.ClientProtocolException;import org.apache.http.client.methods.HttpGet;import org.apache.http.impl.client.DefaultHttpClient;import org.apache.http.util.EntityUtils;import net.sf.json.JSONObject;public class AuthUtil {public static JSONObject doGetJson(String url) throws ClientProtocolException, IOException {JSONObject jsonObject = null;DefaultHttpClient client = new DefaultHttpClient();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.fromObject(result);}httpGet.releaseConnection();return jsonObject;}}

这样我们就拿到openId了

接下来就只剩下签名sign还没有了,不过这个时候其他参数我们都已经凑完了,开始生成签名

用WXPayUtil中的generateSignature(final Map<String, String> data, Stringkey)方法,data是将除了sign外,其他10个参数放到map中,key是四大配置参数中的API秘钥(paternerKey)

拿到sign后需要将上面我们凑的这十一个参数放到一个map里,参数凑齐了,我们开始请求统一下单接口吧

如果不出意外的话,你将会拿到如下所述的一个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><openid><![CDATA[oUpF8uMuAJO_M2pxb1Q9zNjWeS6o]]></openid><sign><![CDATA[7921E432F65EB8ED0CE9755F0E86D72F]]></sign><result_code><![CDATA[SUCCESS]]></result_code><prepay_id><![CDATA[wx11101639507cbf6ffd8b0779950874]]></prepay_id><trade_type><![CDATA[JSAPI]]></trade_type></xml>

官方文档如下:

到此为止,我们千辛万苦费劲拿到的范围值,仅仅是为了一个prepay_id。

看看前台都需要接收哪些值吧

这次是要将6个参数返回给前台,逐个分析一下

appId:四大参数之一的APPID;timestamp:时间戳(newDate()即可)nonceStr:随机字符串,再次用WXPayUtil中的generateNonceStr()即可;package:就tm是它用到了prepay_id,但是还不是直接取值,还非要固定格式的,值的格式例如:”prepay_id= wx…250…9981…666”signType:写MD5就好、paySign:又来了还是签名算法 ,按照上面的方法,用WXPayUtil中的publicstatic String generateSignature(final Map<String, String> data, Stringkey)方法,data是将除了paySign外,其他5个参数放到map中,key是四大配置参数中的API秘钥(paternerKey),得到了paySign后,不要忘记再将paySign put到只有5个参数的map中,这样才能凑齐最后的第6个参数。);

下面贴一下我自己可以运行的代码吧,大家参考一下

一:前台的VUE的js代码(支付部分用到的是goldMember方法,其他的无用)

var vm = new Vue({el: '#rrapp',data: {user: getLocalUserInfo()},methods: {getInfo: function () {ApiAjax.request({url: "/api/user/detail",type: "POST",async: true,successCallback: function (r) {vm.user = r.data;console.log(vm.user);}});},goldMember: function () {var totalAmount = '0.01';//充值金额var description = '充值:xxx元'//充值描述ApiAjax.request({url: '/api/weixin/goldMember/'+totalAmount+'/'+description,type: "get",async: true,successCallback: function (date) {WeixinJSBridge.invoke('getBrandWCPayRequest',{"appId" : date.appId,//公众号名称,由商户传入 "timeStamp":date.timeStamp, //时间戳,自 1970 年以来的秒数 "nonceStr" : date.nonceStr, //随机串 "package" : date.package,//商品包信息"signType" : date.signType, //微信签名方式"paySign" : date.paySign //微信签名 },function(res){if(res.err_msg == "get_brand_wcpay_request:ok" ) {$.toast('支付成功');window.location.href = '/front/my.index.html';}else{$.toast('支付失败');ApiAjax.request({url: '/api/goldMember/weixinPay/fail',type: "POST",async: true,params: {trade_no: date.trade_no,},successCallback: function (r) {}});}});}});}},mounted: function() {if(this.user == null)window.location.href = '/front/login.html';this.getInfo();}});

二:刚刚前台请求的接口

/*** @param totalAmount 支付金额* @param description 描述* @param openId 微信公众号openId (可以前端传code,然后后台再通过微信对应接口换取openId)* @param request -* @return -*/@RequestMapping(value = "/weixin/goldMember/{totalAmount}/{description}",produces = MediaType.APPLICATION_JSON_VALUE)@ResponseBodypublic SortedMap<String, String> ToPay(@LoginUser UserVo loginUser, @PathVariable BigDecimal totalAmount, @PathVariable String description, HttpServletRequest request) {/*** 业务逻辑部分*///------------------//拼接回调地址,private static String wxnotify = "/api/json/money/goldMember/succ";String sym = request.getRequestURL().toString().split("/api/")[0];String notifyUrl = sym + wxnotify;String attach = null;String openId = (String) request.getSession().getAttribute("openid");//返回预支付参数到前台(此方法中凑齐了十一个参数请求统一下单接口并将6个值返回给了前台,代码在后面工具类中贴出来)return PayCommonUtil.WxPublicPay(tradeNo, totalAmount, description, attach, notifyUrl ,loginUser.getWeixin_openid(), request);}

三:支付成功回调接口

此接口中处理支付成功的业务逻辑,一定要判断该次订单或者支付是否已经处理过防止微信重复回调接口导致我们重复处理业务逻辑

/*** 支付成功回调地址* @param request* @return* @throws Exception */@IgnoreAuth@Transactional@RequestMapping(value = "/json/money/goldMember/succ",produces = MediaType.APPLICATION_JSON_VALUE)public String wxpaySucc(@LoginUser UserVo loginUser,HttpServletRequest request) throws Exception {System.out.println("微信支付回调");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 resultxml = new String(outSteam.toByteArray(), "utf-8");System.out.println("微信支付返回结果集:"+resultxml);Map<String, String> params = null;try {params = PayCommonUtil.doXMLParse(resultxml);System.out.println(params);} catch (JDOMException e) {e.printStackTrace();}//获取订单号(可以围绕订单号处理业务)String orderNo = params.get("out_trade_no");outSteam.close();inStream.close();try {if (PayCommonUtil.isTenpaySign(params)){if("SUCCESS".equals(params.get("return_code"))){Map map = new HashMap<String,String>();map.put("return_code", "SUCCESS");map.put("return_msg", "ok");String resultCode = params.get("result_code");if("SUCCESS".equals(resultCode)) {// --- 处理支付成功逻辑 ---//判断该业务逻辑是否已经处理过,防止微信重复回调//判断该业务逻辑是否已经处理过,防止微信重复回调//判断该业务逻辑是否已经处理过,防止微信重复回调return WXPayUtil.mapToXml(map);}else {// 充值失败业务逻辑↓return WXPayUtil.mapToXml(map);}}else{Map map = new HashMap<String,String>();map.put("return_code", "FILE");map.put("return_msg", "ok");// 充值失败业务逻辑↓return WXPayUtil.mapToXml(map);}}}catch(Exception e) {e.printStackTrace();TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();throw new RuntimeException();}return null;}/*** 支付失败回调地址* @param request* @return* @throws Exception */@RequestMapping(value = "/goldMember/weixinPay/fail",method = RequestMethod.POST)@ResponseBodypublic String wxpayFail(HttpServletRequest request,String trade_no) throws Exception {/**** 充值失败业务逻辑↓*/return null;}

四:补上刚刚请求统一下单的那个工具类

package com.platform.config.wxpay;import java.io.BufferedReader;import java.io.ByteArrayInputStream;import java.io.File;import java.io.FileInputStream;import java.io.IOException;import java.io.InputStream;import java.io.InputStreamReader;import java.io.OutputStream;import java.math.BigDecimal;import .ConnectException;import .HttpURLConnection;import .URL;import java.security.KeyStore;import java.util.HashMap;import java.util.List;import java.util.Map;import java.util.Random;import java.util.Set;import java.util.SortedMap;import java.util.TreeMap;import .ssl.SSLContext;import javax.servlet.http.HttpServletRequest;import org.apache.http.Consts;import org.apache.http.HttpEntity;import org.apache.http.client.methods.CloseableHttpResponse;import org.apache.http.client.methods.HttpPost;import org.apache.http.conn.ssl.SSLConnectionSocketFactory;import org.apache.http.entity.StringEntity;import org.apache.http.impl.client.CloseableHttpClient;import org.apache.http.impl.client.HttpClients;import org.apache.http.ssl.SSLContexts;import org.apache.http.util.EntityUtils;import org.jdom.Document;import org.jdom.Element;import org.jdom.JDOMException;import org.jdom.input.SAXBuilder;import com.platform.utils.HttpClientUtil;public class PayCommonUtil {//微信参数配置(就用到前三个)public static String API_KEY = "2NEJNeXbTdy8u5p35ARq77fPKQIDAQAB";public static String APPID = "wxb29f520d09a81ed9";public static String MCH_ID = "1560164811";public static String APP_NAME = "APP_NAME ";public static String BUNDLE_ID = "bundle_id";//bundle_idpublic static String PACKAGE_NAME = "包名";//包名public static String WAP_NAME = "WAP 网站名";//WAP 网站名public static String WAP_URL = "";//WAP网站URL地址public static String SUCCESS = "SUCCESS";/*** 微信公众号支付* @param trade_no订单号* @param totalAmount 支付金额* @param description 文字内容说明* @param attach 自定义参数 length=127* @param openId 微信公众号openId* @param wxnotify回调地址* @param request -* @return -*/public static SortedMap<String, String> WxPublicPay(String trade_no, BigDecimal totalAmount, String description, String attach,String wxnotify, String openid, HttpServletRequest request) {Map<String, String> map = weixinAppPrePay(trade_no,totalAmount,description,attach,wxnotify,openid,request);SortedMap<String, String> finalpackage = new TreeMap<>();finalpackage.put("appId", PayCommonUtil.APPID);finalpackage.put("timeStamp", String.valueOf(System.currentTimeMillis() / 1000));finalpackage.put("nonceStr", getRandomString(32));finalpackage.put("package", "prepay_id=" + map.get("prepay_id"));finalpackage.put("signType", "MD5");String sign = PayCommonUtil.createSign2(finalpackage);finalpackage.put("paySign", sign);finalpackage.put("trade_no", trade_no);return finalpackage;}/*** -* @param trade_no订单号* @param totalAmount 支付金额* @param description 文字内容说明* @param attach 自定义参数 length=127* @param openid 微信公众号openId* @param wxnotify回调地址* @param request -* @return -*/public static Map<String, String> weixinAppPrePay(String trade_no, BigDecimal totalAmount, String description, String attach, String wxnotify, String openid ,HttpServletRequest request) {SortedMap<String, String> parameterMap = new TreeMap<>();parameterMap.put("appid", PayCommonUtil.APPID);parameterMap.put("mch_id", PayCommonUtil.MCH_ID);parameterMap.put("nonce_str", getRandomString(32));parameterMap.put("body", description);parameterMap.put("out_trade_no", trade_no);parameterMap.put("fee_type", "CNY");BigDecimal total = totalAmount.multiply(new BigDecimal(100));java.text.DecimalFormat df = new java.text.DecimalFormat("0");parameterMap.put("total_fee", df.format(total).toString());parameterMap.put("spbill_create_ip", request.getRemoteAddr());parameterMap.put("notify_url", wxnotify);parameterMap.put("trade_type", "JSAPI");//trade_type为JSAPI是 openid为必填项parameterMap.put("openid", openid);String sign = PayCommonUtil.createSign2(parameterMap);parameterMap.put("sign", sign);Map<String, String> map = null;try {//此部分就是生成签名请求参数。可以不用这个工具类String requestXML = WXPayUtil.generateSignedXml(parameterMap, PayCommonUtil.API_KEY);String result = PayCommonUtil.httpsRequest("https://api.mch./pay/unifiedorder", "POST", requestXML);System.out.println(result);map = PayCommonUtil.doXMLParse(result);} catch (Exception e) {e.printStackTrace();}return map;}//随机字符串生成public static String getRandomString(int length) {//length表示生成字符串的长度String base = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";Random random = new Random();StringBuilder sb = new StringBuilder();for (int i = 0; i < length; i++) {int number = random.nextInt(base.length());sb.append(base.charAt(number));}return sb.toString();}//请求xml组装public static String getRequestXml(SortedMap<String, String> parameterMap){StringBuilder sb = new StringBuilder();sb.append("<xml>");Set es = parameterMap.entrySet();for (Object e : es) {Map.Entry entry = (Map.Entry) e;String key = (String) entry.getKey();String value = (String) entry.getValue();if ("attach".equalsIgnoreCase(key) || "body".equalsIgnoreCase(key) || "sign".equalsIgnoreCase(key)) {sb.append("<").append(key).append(">").append("<![CDATA[").append(value).append("]]></").append(key).append(">");} else {sb.append("<").append(key).append(">").append(value).append("</").append(key).append(">");}}sb.append("</xml>");return sb.toString();}//生成签名public static String createSign2(SortedMap<String, String> parameters){StringBuilder sb = new StringBuilder();Set es = parameters.entrySet();for (Object e : es) {Map.Entry entry = (Map.Entry) e;String k = (String) entry.getKey();Object v = entry.getValue();if (null != v && !"".equals(v)&& !"sign".equals(k) && !"key".equals(k)) {sb.append(k).append("=").append(v).append("&");}}sb.append("key=").append(API_KEY);System.out.println(sb.toString());return MD5Util.MD5Encode(sb.toString(), "UTF-8").toUpperCase();}/*** 验证回调签名* @param map* @return*/public static boolean isTenpaySign(Map<String, String> map) {String charset = "utf-8";String signFromAPIResponse = map.get("sign");if (signFromAPIResponse == null || signFromAPIResponse.equals("")) {System.out.println("API返回的数据签名数据不存在,有可能被第三方篡改!!!");return false;}System.out.println("服务器回包里面的签名是:" + signFromAPIResponse);//过滤空 设置 TreeMapSortedMap<String,String> packageParams = new TreeMap<>();for (String parameter : map.keySet()) {String parameterValue = map.get(parameter);String v = "";if (null != parameterValue) {v = parameterValue.trim();}packageParams.put(parameter, v);}StringBuilder sb = new StringBuilder();Set es = packageParams.entrySet();for (Object e : es) {Map.Entry entry = (Map.Entry) e;String k = (String) entry.getKey();String v = (String) entry.getValue();if (!"sign".equals(k) && null != v && !"".equals(v)) {sb.append(k).append("=").append(v).append("&");}}sb.append("key=").append(API_KEY);//将API返回的数据根据用签名算法进行计算新的签名,用来跟API返回的签名进行比较//算出签名String tobesign = sb.toString();String resultSign = MD5Util.MD5Encode(tobesign, "utf-8").toUpperCase();String tenpaySign = packageParams.get("sign").toUpperCase();return tenpaySign.equals(resultSign);}//请求方法public static String httpsRequest(String requestUrl, String requestMethod, String outputStr) {try {URL url = new URL(requestUrl);HttpURLConnection conn = (HttpURLConnection) url.openConnection();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 = new InputStreamReader(inputStream, "utf-8");BufferedReader bufferedReader = new BufferedReader(inputStreamReader);String str = null;StringBuilder buffer = new StringBuilder();while ((str = bufferedReader.readLine()) != null) {buffer.append(str);}// 释放资源bufferedReader.close();inputStreamReader.close();inputStream.close();conn.disconnect();return buffer.toString();} catch (ConnectException ce) {System.out.println("连接超时:{}"+ ce);} catch (Exception e) {System.out.println("https请求异常:{}"+ e);}return null;}//xml解析public static Map doXMLParse(String strxml) throws JDOMException, IOException {strxml = strxml.replaceFirst("encoding=\".*\"", "encoding=\"UTF-8\"");if(null == strxml || "".equals(strxml)) {return null;}Map m = new HashMap();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();for (Object aList : list) {Element e = (Element) aList;String k = e.getName();String v = "";List children = e.getChildren();if (children.isEmpty()) {v = e.getTextNormalize();} else {v = getChildrenText(children);}m.put(k, v);}//关闭流in.close();return m;}private static String getChildrenText(List children) {StringBuilder sb = new StringBuilder();if(!children.isEmpty()) {for (Object aChildren : children) {Element e = (Element) aChildren;String name = e.getName();String value = e.getTextNormalize();List list = e.getChildren();sb.append("<").append(name).append(">");if (!list.isEmpty()) {sb.append(getChildrenText(list));}sb.append(value);sb.append("</").append(name).append(">");}}return sb.toString();}}

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。