目录标题
- 微信支付之扫码Native支付与JSAPI支付
- 进入主题
- 一、Native支付
- 1. 使用场景
- 2. 开发步骤
- 3. 开始开发
- 二、JSAPI支付
- 1. 使用场景
- 2. 开发步骤
- 3. 开始开发
微信支付之扫码Native支付与JSAPI支付
在电商网站开发中,我们必不可少的功能环节就是“支付”了,我们可以根据公司或者自己的开发需求,选择不一样的支付方式,比如微信支付,支付宝支付,银联支付,以及跨境支付如PayPal等等的众多方式供你选择,今天,我将和大家聊聊微信支付之二微信扫码Native支付与微信JSAPI支付。
进入主题
根据微信官方提供的文档,我们来进行梳理。首先我们访问:微信支付-开发文档
进入如下界面:
一、Native支付
1. 使用场景
我们首先会调用微信提供的API得到二维码链接,这个链接可以通过前端qrcode.js将链接转换为二维码,也可以在后台通过谷歌的ZXing工具生成二维码。详细介绍如下:
2. 开发步骤
首先,你需要在微信支付的商户平台配置扫码支付的回调域名,具体位置为:商户平台–>产品中心–>开发配置。
当然,你也可以不用配置扫码支付的回调地址,而是在调用微信支付的统一下单API接口中传递参数notify_url。我选的是这种方式,可以将回调配置在yml文件中,自由定义,不需要关心商户平台的回调地址。
最后,还需要选择一种模式,我采用的是模式二,你也可以根据自己的需求,选择你认为合适的开发模式。如下图模式二说明了接下来我们编码的流程:
3. 开始开发
(1)商户后台系统根据用户选购的商品生成订单。
用户在商城平台提交订单,我们会生成初始订单。
商城平台生成订单之后,接着调用微信支付。进行第二,第三步骤,如下:
(2)用户确认支付后调用微信支付【统一下单API】生成预支付交易;
这一步骤,有一个关键的地方是生成商户单号。如下是我生成商户单号的方式。
生成商户单号
@Override@Transactional(rollbackFor = Exception.class)public String generateCode(KeSequence.Tag tag, String userMobile) {Long sequence = this.findSequence(Long.parseLong(tag.getCode()+""));this.updateSequence(Long.parseLong(tag.getCode()+""),sequence);return DateWithRandomUtils.numsToRandom()+ sequence+ ((userMobile.length()>4)?(userMobile.substring(userMobile.length()-4)):userMobile);}
调用工具方法
/*** 将生成的时间+num位随机数,打乱* @return*/public static String numsToRandom(){String randomNO = randomNO(4);char[] chars = randomNO.toCharArray();List<String> list = new ArrayList<>();for (char aChar : chars) {list.add(aChar+"");}Collections.shuffle(list);StringBuffer sb = new StringBuffer();list.forEach(ch->{sb.append(ch);});return sb.toString();}
工具方法细节:
/*** 唯一编号 = 点前时间年月日时分秒+num位随机数* @return*/public static String randomNO(int num){String newsNo = timeNoYMDHMS();long random = getRandom(num);return newsNo+random;}/*** 获取当前时间 yyyyMMddHHmmss* @return*/public static String timeNoYMDHMS(){//设置日期格式SimpleDateFormat df = new SimpleDateFormat("yyyyMMddHHmmss");// new Date()为获取当前系统时间,也可使用当前时间戳String newsNo = df.format(new Date());return newsNo;}/*** 生成固定长度随机码* @param n 长度*/public static long getRandom(long n) {long min = 1,max = 9;for (int i = 1; i < n; i++) {min *= 10;max *= 10;}long rangeLong = (((long) (new Random().nextDouble() * (max - min)))) + min ;return rangeLong;}
(3)微信支付系统收到请求后生成预支付交易单,并返回交易会话的二维码链接code_url。
生成商户单号,接着可以通过微信提供的SDK,调用统一下单的方法了,我的做法如下:
核心代码:
/*** 微信支付* @param keOrder* @return*/private ResultMsg WxUnifiedorder(KeOrder keOrder,String bizPayNo, Integer tradeType, String openid) throws Exception {//设置需要的参数WxParamQuery wxParamQuery = new WxParamQuery();//是wxParamQuery.setOut_trade_no(bizPayNo);//以分为单位wxParamQuery.setTotal_fee(keOrder.getActualTotal().multiply(BigDecimal.valueOf(100)).longValue()+"");String body = "";if(!StringUtils.isEmpty(keOrder.getProdName())){body = keOrder.getProdName().length()>15 ? keOrder.getProdName().substring(0,15)+"...":keOrder.getProdName();}wxParamQuery.setBody(body);wxParamQuery.setReceipt("N");// 注意:此处配置交易方式, JSPAI, NATIVE...wxParamQuery.setTrade_type(WxParamQuery.getTradeTypeMap(tradeType));//否wxParamQuery.setAttach(keOrder.getOrderNo());wxParamQuery.setProduct_id("");wxParamQuery.setOpenid(openid);wxParamQuery.setDetail("");wxParamQuery.setDevice_info("WEB");wxParamQuery.setUserId(keOrder.getUserId());return wxPayService.unifiedorder(wxParamQuery);}
调用统一下单API:
/*** 微信支付-统一下单* @param wxParamQuery* @return*/@Override@Transactionalpublic ResultMsg unifiedorder(WxParamQuery wxParamQuery) throws Exception {Map<String,String> data = new ConcurrentHashMap();data.put("device_info",wxParamQuery.getDevice_info());data.put("body",wxParamQuery.getBody());data.put("detail",wxParamQuery.getDetail());data.put("attach",wxParamQuery.getAttach());data.put("out_trade_no",wxParamQuery.getOut_trade_no());data.put("total_fee",wxParamQuery.getTotal_fee());data.put("trade_type",wxParamQuery.getTrade_type());data.put("product_id",wxParamQuery.getProduct_id());data.put("openid",wxParamQuery.getOpenid());data.put("receipt",wxParamQuery.getReceipt());data.put("spbill_create_ip", spbill_create_ip);if("JSAPI".equalsIgnoreCase(wxParamQuery.getTrade_type())){if(StringUtils.isEmpty(wxParamQuery.getOpenid())){return ResultMsg.fail("缺少参数openid",null);}}try {WXPayConfig payConfig = new PayConfig(appID,mchID,key,domain,primaryDomain, true);WXPay wxPay = new WXPay(payConfig,notify_url,true,false);data = wxPay.fillRequestData(data);//微信提供的统一下单方法一:Map<String,String> resMap = wxPay.unifiedOrder(data);//主动调微信统一下单方法二/*String xml = WXPayUtil.mapToXml(data);String res = HttpHelper.post(pay_url, xml, "text/plain;charset=UTF-8");Map<String, String> resMap = WXPayUtil.xmlToMap(res);*/if("SUCCESS".equalsIgnoreCase(resMap.get("return_code"))){if("SUCCESS".equalsIgnoreCase(resMap.get("result_code"))){// 获取二维码链接String code_url = resMap.get("code_url");String prepay_id = resMap.get("prepay_id");Map<String, String> payParam = wxUtils.getJsapiPayParam(prepay_id);if(wxParamQuery.getTrade_type().equals(WxParamQuery.TradeType.NATIVE.name())){return ResultMsg.suc(code_url);}else{return ResultMsg.suc(payParam);}}}String return_msg = resMap.get("return_msg");return ResultMsg.fail(return_msg,return_msg);} catch (Exception e) {e.printStackTrace();return ResultMsg.fail(e.getMessage(),e.getMessage());}}
调用成功之后,微信会返回code_url, 二维码链接,至此,我们可以将二维码链接转换为二维码图片,即可。
当用户扫码支付完成之后,会进入回调,我们可以在回调中修改订单的状态或者其他必要的操作。如下,是我的回调代码。
核心代码:
@RequestMapping(value = "/notifyUrl")public void notifyUrl(HttpServletRequest request, HttpServletResponse response) throws Exception {String resXml = "";try {log.info("微信notifyUrl回调方法,开始进入。。。");//读取参数StringBuffer sb = new StringBuffer();InputStream inputStream = request.getInputStream();BufferedReader in = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));String str = null ;while ((str = in.readLine()) != null){sb.append(str);}in.close();inputStream.close();//解析xml成mapMap<String, String> m = WXPayUtil.xmlToMap(sb.toString());//过滤空 设置 TreeMapSortedMap<String,String> packageParams = new TreeMap<>();Iterator<String> it = m.keySet().iterator();while (it.hasNext()) {String parameter = it.next();String parameterValue = m.get(parameter);String v = "";if(null != parameterValue) {v = parameterValue.trim();}packageParams.put(parameter, v);}log.info("微信支付返回回来的参数:"+packageParams);try {// 支付日志入库iKePayLogService.saveWxPayLog(packageParams);}catch (Exception e){e.printStackTrace();}//判断签名是否正确if(WXPayUtil.isSignatureValid(m, key, WXPayConstants.SignType.HMACSHA256)) {//------------------------------//处理业务开始//------------------------------if("SUCCESS".equalsIgnoreCase( packageParams.get("return_code")) &&"SUCCESS".equals(packageParams.get("result_code"))){// 这里是支付成功//执行自己的业务逻辑开始String app_id = packageParams.get("appid");String mch_id = packageParams.get("mch_id");String openid = packageParams.get("openid");//是否关注公众号String is_subscribe = packageParams.get("is_subscribe");//附加参数【订单号】String attach = packageParams.get("attach");//商户订单号String out_trade_no = packageParams.get("out_trade_no");//付款金额【以分为单位】String total_fee = packageParams.get("total_fee");//微信生成的交易订单号String transaction_id = packageParams.get("transaction_id");//支付完成时间String time_end= packageParams.get("time_end");log.info("app_id:"+app_id);log.info("mch_id:"+mch_id);log.info("openid:"+openid);log.info("is_subscribe:"+is_subscribe);log.info("out_trade_no:"+out_trade_no);log.info("total_fee:"+total_fee);log.info("额外参数_attach:"+attach);log.info("time_end:"+time_end);//执行自己的业务逻辑开始log.info("支付成功。。。执行自己的业务逻辑开始");OrderPayDTO orderPayDTO = new OrderPayDTO();orderPayDTO.setTransaction_id(transaction_id);orderPayDTO.setOut_trade_no(out_trade_no);orderPayDTO.setMch_id(mch_id);orderPayDTO.setApp_id(app_id);orderPayDTO.setAttach(attach);orderPayDTO.setTime_end(LocalDateTime.now());orderPayDTO.setOpenid(openid);orderPayDTO.setIs_subscribe(is_subscribe);orderPayDTO.setTotal_fee(BigDecimal.valueOf(Double.parseDouble(total_fee)));orderPayDTO.setPayType(KeOrder.orderPayType.WECHAT_PAY.getCode());//调用业务处理方法iKeOrderService.orderPaySuc(orderPayDTO);//执行自己的业务逻辑结束//通知微信.异步确认成功.必写.不然会一直通知后台.八次之后就认为交易失败了.resXml = notifyToWx("SUCCESS","OK");} else {log.info("支付失败,错误信息:" + packageParams.get("err_code"));resXml = notifyToWx("FAIL",packageParams.get("err_code"));}} else{resXml = notifyToWx("FAIL","签名验证失败");}}catch (Exception e){e.printStackTrace();resXml = notifyToWx("FAIL","程序异常");}//------------------------------//处理业务完毕//------------------------------BufferedOutputStream out = new BufferedOutputStream(response.getOutputStream());out.write(resXml.getBytes());out.flush();out.close();}public String notifyToWx(String return_code, String return_msg){return "<xml>" + "<return_code><![CDATA["+return_code+"]]></return_code>"+ "<return_msg><![CDATA["+return_msg+"]]></return_msg>" + "</xml> ";}
二、JSAPI支付
JSAPI支付的后端实现逻辑和Native之后一样,只需要多传入一个必传参数openid,上面已经列举,我就不必再次重复说明了。 接下来,我将说明和Native不同的实现逻辑。
1. 使用场景
我们开发公众号时,需要调用微信支付,这时候就是需要JSPAPI支付了,JSAPI需要通过网页授权,获取用户的openid,也就是上述步骤中调用微信的统一下单需要的步骤,然后再改变trade_type为JSAPI,如下
其余步骤与Native一致。
2. 开发步骤
JSAPI支付必须要在商户平台配置支付目录,所谓支付目录就是指:前端调起微信支付的当前URL地址。 设置如下:
3. 开始开发
调起支付,需要配置jsconfig,我用的是vue, 以下我将介绍vue的方式发起支付。
首先前端配置如下:
mounted() {this.axios.get("/kuais/essleyWeb/essley/wx/getWxConfigParams",{params: {webUrl: this.webUrl}}).then(res => {var data = res.data.data;console.log(data)wx.config({debug: false, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。appId: data.appId, // 必填,公众号的唯一标识timestamp: data.timestamp, // 必填,生成签名的时间戳nonceStr: data.nonceStr, // 必填,生成签名的随机串signature: data.signature, // 必填,签名,见附录1jsApiList: ["chooseWXPay"]});wx.ready(function() {wx.checkJsApi({jsApiList: ["chooseWXPay"],success: function(res) {console.log("seccess");console.log(res);},fail: function(res) {console.log("fail");console.log(res);}});});});},
我们需要在后端,获取token,然后获取jsapi_ticket。 得到初始化参数之后,返回给前端,配置jsconfig。 可以通过微信开发者工具中查看是否配置成功,(有时候你看JS-SDK中提示签名错误,其实也是可以调起微信支付的)。 代码如下:
/*** 签名算法签名生成规则如下:参与签名的字段包括noncestr(随机字符串),有效的jsapi_ticket, timestamp(时间戳),url(当前网页的URL,不包含#及其后面部分) 。对所有待签名参数按照字段名的ASCII 码从小到大排序(字典序)后,使用URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串string1。这里需要注意的是所有参数名均为小写字符。对string1作sha1加密,字段名和字段值都采用原始值,不进行URL 转义。*/public Map<String,String> wxConfigParams(String webUrl){String jsapi_ticket = jsapiTicketFromRedis();String noncestr = WXPayUtil.generateNonceStr();String timestamp = WXPayUtil.getCurrentTimestamp()+"";String url = webUrl;String[] arr = new String[]{jsapi_ticket,noncestr, timestamp,url};String string1 = "jsapi_ticket="+jsapi_ticket+"&noncestr="+noncestr+"×tamp="+timestamp+"&url="+url;log.info("拼接好的string1: "+string1);String sha1Hex = DigestUtils.sha1Hex(string1.getBytes());log.info("jsapi_ticket签名: "+sha1Hex);Map<String,String> resMap = new HashMap<>();resMap.put("appId", WebAppID);resMap.put("timestamp", timestamp);resMap.put("nonceStr", noncestr);resMap.put("signature", sha1Hex);return resMap;}
jsconfig配置成功之后,接下来就可以调用微信统一下单API了, 与Native扫码支付不同的是,它需要返回如下参数:
appId: data.appId,
timestamp: data.timeStamp, // 支付签名时间戳,注意微信jssdk中的所有使用timestamp字段均为小写。但最新版的支付后台生成签名使用的timeStamp字段名需大写其中的S字符
nonceStr: data.nonceStr, // 支付签名随机串,不长于 32
package: data.package, // 统一支付接口返回的prepay_id参数值,提交格式如:prepay_id=\*\*\*)
signType: data.signType, // 签名方式,默认为'SHA1',使用新版支付需传入'MD5'
paySign: data.paySign, // 支付签名
如果后端返回的参数没有问题,这时候,就可以发起微信支付了。输入密码,就可以支付完成。