- 首先看一下,微信支付关于jsapi的官方文档,相关接口,一共有下单,查询订单,关闭订单,调起jsapi支付,支付结果通知,申请退款,查询单笔退款,退款结果通知,申请交易账单,申请资金账单以及下载账单接口。其中稍微难一点的支付结果通知和退款结果通知,因为涉及到验签和解密报文,不过最新的官方已经给了示例,很方便的。
jsapi支付流程:
黄色部分是需要我们处理。
1、jsapi下单接口
HttpPost httpPost = new HttpPost(WxPayConstant.WX_PAY_DOMAIN.concat(WxApiType.JSAPI_PAY.getType()));httpPost.addHeader("Accept", "application/json");httpPost.addHeader("Content-type","application/json; charset=utf-8");ByteArrayOutputStream bos = new ByteArrayOutputStream();ObjectMapper objectMapper = new ObjectMapper();ObjectNode rootNode = objectMapper.createObjectNode();rootNode.put("mchid",WxPayConstant.WX_PAY_MERCHANT_ID).put("appid", WxPayConstant.WX_PAY_APPID).put("description", "测试").put("notify_url", WxPayConstant.WX_PAY_NOTIFY_DOMAIN.concat(WxNotifyType.JSAPI_NOTIFY.getType())).put("out_trade_no", wxPayQuery.getSn());rootNode.putObject("amount").put("total", totalPrice);rootNode.putObject("payer").put("openid", openid);objectMapper.writeValue(bos, rootNode);httpPost.setEntity(new StringEntity(bos.toString("UTF-8"), "UTF-8"));CloseableHttpResponse response = httpClient.execute(httpPost);try {String bodyAsString = EntityUtils.toString(response.getEntity());//响应体int statusCode = response.getStatusLine().getStatusCode();//响应状态码if (statusCode == 200) {log.info("成功, 返回结果 = " + bodyAsString);} else if (statusCode == 204) { //处理成功,无返回Bodylog.info("成功");} else {log.info("jsapi下单失败,响应码 = " + statusCode+ ",返回结果 = " + bodyAsString);throw new ServiceException("request failed");}//把数据返回前端//String substring = bodyAsString.substring(12, 50);HashMap<String, String> hashMap = new HashMap<>();hashMap.put("prepay_id",bodyAsString);ResultMessage<HashMap<String, String>> data = ResultUtil.data(hashMap);return data;} finally {response.close();}
2、获取签名
String prepayId = "prepay_id="+wxPayQuery.getPrepayId();//这个地方很容易出错//获取32位随机字符串String nonceStr = GeneratorUtil.randomSequence(32);System.out.println(nonceStr);//APPIDString wxPayAppid = WxPayConstant.WX_PAY_APPID;//时间戳Long time = (new Date().getTime())/1000;String timeStamp = time.toString();System.out.println(timeStamp);//构造签名串String source=wxPayAppid+"\n"+timeStamp+"\n"+nonceStr+"\n"+prepayId+"\n";String paySign = "";try {// 加载商户私钥(privateKey:私钥字符串)String privateKey = FileUtils.readFileToString(new File(WxPayConstant.WX_PAY_PRIVATE_KEY_PATH));System.out.println(privateKey);paySign = SignUtil.signBySHA256WithRSA(source, privateKey, "UTF-8");} catch (Exception e) {e.printStackTrace();}//组装数据Map<String, String> map = new HashMap<>();map.put("timeStamp",timeStamp);map.put("nonceStr",nonceStr);map.put("appId",wxPayAppid);map.put("package",prepayId);map.put("paySign",paySign);ResultMessage<Map<String, String>> data = ResultUtil.data(map);return data; }
3、处理支付结果通知
@ApiOperation(value = "支付结果通知") @PostMapping ("/jsapi/notify") public String jsapiNotify(HttpServletRequest httpServletRequest, HttpServletResponse response){log.info("支付结果通知");//创建应答对象Map<String, String> responseMap = new HashMap<>();Gson gson = new Gson();try {//处理通知参数String body = HttpUtils.readData(httpServletRequest);log.info("支付结果通知"+body);Map<String, Object> bodyMap = gson.fromJson(body, HashMap.class);log.info("支付通知的id ===> {}", bodyMap.get("id").toString());//验证签名PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(privateKey);// 获取证书管理器实例CertificatesManager certificatesManager = CertificatesManager.getInstance();// 向证书管理器增加需要自动更新平台证书的商户信息certificatesManager.putMerchant(WxPayConstant.WX_PAY_MERCHANT_ID,new WechatPay2Credentials(WxPayConstant.WX_PAY_MERCHANT_ID, new PrivateKeySigner(WxPayConstant.WX_PAY_MERCHANT_SERIAL_NUMBER,merchantPrivateKey)), WxPayConstant.WX_PAY_API_V3KEY.getBytes(StandardCharsets.UTF_8));// 从证书管理器中获取verifierVerifier verifier = certificatesManager.getVerifier(WxPayConstant.WX_PAY_MERCHANT_ID);String serialNumber = httpServletRequest.getHeader("Wechatpay-Serial");String nonce = httpServletRequest.getHeader("Wechatpay-Nonce");String timestamp = httpServletRequest.getHeader("Wechatpay-Timestamp");String signature = httpServletRequest.getHeader("Wechatpay-Signature");// 构建request,传入必要参数NotificationRequest request = new NotificationRequest.Builder().withSerialNumber(serialNumber).withNonce(nonce).withTimestamp(timestamp).withSignature(signature).withBody(body).build();NotificationHandler handler = new NotificationHandler(verifier, WxPayConstant.WX_PAY_API_V3KEY.getBytes(StandardCharsets.UTF_8));// 验签和解析请求体Notification notification = handler.parse(request);Assert.assertNotNull(notification);// 从notification中获取解密报文String decryptData = notification.getDecryptData();HashMap resource =gson.fromJson(decryptData,HashMap.class);log.info("验签成功");//订单处理wxPayService.processOrder(resource);//应答 成功response.setStatus(200);responseMap.put("code","SUCCESS");responseMap.put("message","成功");return gson.toJson(responseMap);} catch (Exception e) {log.info("验签失败");response.setStatus(500);responseMap.put("code","FAIL");responseMap.put("message","失败");return gson.toJson(responseMap);} }
4、关闭订单接口
log.info("关单接口的调用,订单号 ===> {}", orderSn);//创建远程请求对象 String url = String.format(WxApiType.CLOSE_ORDER_BY_NO.getType(), orderSn); url = WxPayConstant.WX_PAY_DOMAIN.concat(url); HttpPost httpPost = new HttpPost(url);//组装json请求体 Gson gson = new Gson(); Map<String, String> paramsMap = new HashMap<>(); paramsMap.put("mchid", WxPayConstant.WX_PAY_MERCHANT_ID); String jsonParams = gson.toJson(paramsMap); log.info("请求参数 ===> {}", jsonParams);//将请求参数设置到请求对象中 StringEntity entity = new StringEntity(jsonParams,"utf-8"); entity.setContentType("application/json"); httpPost.setEntity(entity); httpPost.setHeader("Accept", "application/json");//完成签名并执行请求 CloseableHttpResponse response = httpClient.execute(httpPost);try {int statusCode = response.getStatusLine().getStatusCode();//响应状态码if (statusCode == 204) { //处理成功log.info("微信支付取消订单成功");} else {log.info("微信支付取消订单失败,响应码 = " + statusCode);throw new IOException("request failed");}} finally {response.close(); }
5、根据订单号查询订单状态
String url = String.format(WxApiType.ORDER_QUERY_BY_NO.getType(), orderSn); url = WxPayConstant.WX_PAY_DOMAIN.concat(url).concat("?mchid=").concat(WxPayConstant.WX_PAY_MERCHANT_ID);HttpGet httpGet = new HttpGet(url); httpGet.setHeader("Accept", "application/json");//完成签名并执行请求 CloseableHttpResponse response = httpClient.execute(httpGet); String bodyAsString = EntityUtils.toString(response.getEntity());//响应体try {int statusCode = response.getStatusLine().getStatusCode();//响应状态码if (statusCode == 200) { //处理成功log.info("成功, 返回结果 = " + bodyAsString);} else if (statusCode == 204) { //处理成功,无返回Bodylog.info("成功");} else {log.info("查单接口调用,响应码 = " + statusCode+ ",返回结果 = " + bodyAsString);throw new IOException("request failed");}} finally {response.close();
6、申请退款
//调用统一下单API String url = WxPayConstant.WX_PAY_DOMAIN.concat(WxApiType.DOMESTIC_REFUNDS.getType()); HttpPost httpPost = new HttpPost(url);// 请求body参数 Gson gson = new Gson(); Map paramsMap = new HashMap(); paramsMap.put("out_trade_no", tradeSn);//订单编号 paramsMap.put("out_refund_no", order.getRefundNo());//退款单编号 paramsMap.put("reason",reason);//退款原因 paramsMap.put("notify_url", WxPayConstant.WX_PAY_NOTIFY_DOMAIN.concat(WxNotifyType.REFUND_NOTIFY.getType()));//退款通知地址Map amountMap = new HashMap();//把元转换为分 String flowPrice = order.getFlowPrice().toString(); String total = AmountUtils.changeY2F(flowPrice); Integer totalPrice = Integer.valueOf(total);amountMap.put("refund", totalPrice);//退款金额 amountMap.put("total", totalPrice);//原订单金额 amountMap.put("currency", "CNY");//退款币种 paramsMap.put("amount", amountMap);//将参数转换成json字符串 String jsonParams = gson.toJson(paramsMap); log.info("请求参数 ===> {}" + jsonParams);StringEntity entity = new StringEntity(jsonParams,"utf-8"); entity.setContentType("application/json");//设置请求报文格式 httpPost.setEntity(entity);//将请求报文放入请求对象 httpPost.setHeader("Accept", "application/json");//设置响应报文格式//完成签名并执行请求,并完成验签 CloseableHttpResponse response = httpClient.execute(httpPost);try {//解析响应结果String bodyAsString = EntityUtils.toString(response.getEntity());int statusCode = response.getStatusLine().getStatusCode();if (statusCode == 200) {log.info("成功, 退款返回结果 = " + bodyAsString);} else if (statusCode == 204) {log.info("成功");} else {throw new RuntimeException("退款异常, 响应码 = " + statusCode+ ", 退款返回结果 = " + bodyAsString);}} finally {response.close(); }
7、查询单笔退款
log.info("查询退款接口调用 ===> {}", refundNo);String url = String.format(WxApiType.DOMESTIC_REFUNDS_QUERY.getType(), refundNo); url = WxPayConstant.WX_PAY_DOMAIN.concat(url);//创建远程Get 请求对象 HttpGet httpGet = new HttpGet(url); httpGet.setHeader("Accept", "application/json");//完成签名并执行请求 CloseableHttpResponse response = httpClient.execute(httpGet);try {String bodyAsString = EntityUtils.toString(response.getEntity());int statusCode = response.getStatusLine().getStatusCode();if (statusCode == 200) {log.info("成功, 查询退款返回结果 = " + bodyAsString);} else if (statusCode == 204) {log.info("成功");} else {throw new RuntimeException("查询退款异常, 响应码 = " + statusCode+ ", 查询退款返回结果 = " + bodyAsString);}return ResultUtil.data(bodyAsString);} finally {response.close(); }
8、退款通知
@ApiOperation("退款结果通知") @PostMapping("/refunds/notify") public String refundsNotify(HttpServletRequest httpServletRequest, HttpServletResponse response){log.info("退款通知执行");Gson gson = new Gson();Map<String, String> map = new HashMap<>();//应答对象try {//处理通知参数String body = HttpUtils.readData(httpServletRequest);Map<String, Object> bodyMap = gson.fromJson(body, HashMap.class);String requestId = (String)bodyMap.get("id");log.info("支付通知的id ===> {}", requestId);//验证签名PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(privateKey);// 获取证书管理器实例CertificatesManager certificatesManager = CertificatesManager.getInstance();// 向证书管理器增加需要自动更新平台证书的商户信息certificatesManager.putMerchant(WxPayConstant.WX_PAY_MERCHANT_ID,new WechatPay2Credentials(WxPayConstant.WX_PAY_MERCHANT_ID, new PrivateKeySigner(WxPayConstant.WX_PAY_MERCHANT_SERIAL_NUMBER,merchantPrivateKey)), WxPayConstant.WX_PAY_API_V3KEY.getBytes(StandardCharsets.UTF_8));// 从证书管理器中获取verifierVerifier verifier = certificatesManager.getVerifier(WxPayConstant.WX_PAY_MERCHANT_ID);String serialNumber = httpServletRequest.getHeader("Wechatpay-Serial");String nonce = httpServletRequest.getHeader("Wechatpay-Nonce");String timestamp = httpServletRequest.getHeader("Wechatpay-Timestamp");String signature = httpServletRequest.getHeader("Wechatpay-Signature");// 构建request,传入必要参数NotificationRequest request = new NotificationRequest.Builder().withSerialNumber(serialNumber).withNonce(nonce).withTimestamp(timestamp).withSignature(signature).withBody(body).build();NotificationHandler handler = new NotificationHandler(verifier, WxPayConstant.WX_PAY_API_V3KEY.getBytes(StandardCharsets.UTF_8));// 验签和解析请求体Notification notification = handler.parse(request);//非空校验Assert.assertNotNull(notification);// 从notification中获取解密报文String decryptData = notification.getDecryptData();HashMap resource =gson.fromJson(decryptData,HashMap.class);log.info("通知验签成功");//处理退款单wxPayService.processRefund(resource);//成功应答response.setStatus(200);map.put("code", "SUCCESS");map.put("message", "成功");return gson.toJson(map);} catch (Exception e) {e.printStackTrace();//失败应答response.setStatus(500);map.put("code", "ERROR");map.put("message", "失败");return gson.toJson(map);} }
9、获取账单的url
log.warn("申请账单接口调用 {}", billDate);String url = ""; if("tradebill".equals(type)){url = WxApiType.TRADE_BILLS.getType(); }else if("fundflowbill".equals(type)){url = WxApiType.FUND_FLOW_BILLS.getType(); }else{throw new RuntimeException("不支持的账单类型"); }url = WxPayConstant.WX_PAY_DOMAIN.concat(url).concat("?bill_date=").concat(billDate);//创建远程Get 请求对象 HttpGet httpGet = new HttpGet(url); httpGet.addHeader("Accept", "application/json");//使用wxPayClient发送请求得到响应 CloseableHttpResponse response = httpClient.execute(httpGet);try {String bodyAsString = EntityUtils.toString(response.getEntity());int statusCode = response.getStatusLine().getStatusCode();if (statusCode == 200) {log.info("成功, 申请账单返回结果 = " + bodyAsString);} else if (statusCode == 204) {log.info("成功");} else {throw new RuntimeException("申请账单异常, 响应码 = " + statusCode+ ", 申请账单返回结果 = " + bodyAsString);}//获取账单下载地址Gson gson = new Gson();Map<String, String> resultMap = gson.fromJson(bodyAsString, HashMap.class);return resultMap.get("download_url");} finally {response.close();}
10、下载账单 注意返回的文件流 需要自己去处理
log.warn("下载账单接口调用 {}, {}", billDate, type);//获取账单url地址 String downloadUrl = this.queryBill(billDate, type); //创建远程Get 请求对象 HttpGet httpGet = new HttpGet(downloadUrl); httpGet.addHeader("Accept", "application/json");//使用wxPayClient发送请求得到响应 CloseableHttpResponse response = wxPayNoSignClient.execute(httpGet);try {String bodyAsString = EntityUtils.toString(response.getEntity());int statusCode = response.getStatusLine().getStatusCode();if (statusCode == 200) {log.info("成功, 下载账单返回结果 = " + bodyAsString);} else if (statusCode == 204) {log.info("成功");} else {throw new RuntimeException("下载账单异常, 响应码 = " + statusCode+ ", 下载账单返回结果 = " + bodyAsString);}return bodyAsString;} finally {response.close(); }
简单总结一下,最开始看微信开发文档的时候,简直是头皮发麻,需要配置的参数也很多,而且必须很仔细,特别是支付授权目录以及秘钥,要是配错了再在前端发起支付的时候会报验签错误,排查也比较麻烦,所以需要细心些。还有就是看验签和解密报文的时候,开始看简直打脑壳,不过多看一下也还好,上面的方法获取的数据就直接是解密报文里面的数据,后面就需要自己拿到数据根据自己业务场景去处理订单。