java实现微信扫码支付——模式二

xiaoxiao2021-03-01  25

大家可以先看下微信官方文档流程以及参数说明,本文介绍的是模式二支付,个人比较建议,只因为比较简单,如果对模式一有兴趣的同学也可以自己在微信官方文档上看一看。

官网地址:https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=6_5

可能有些人第一次看官方文档看的不是很明白(比如我),所以我极简模式说一下(但是签名的规则还有一些校验我就不说了,官网很详细,这里就是流程):

1.用户请求系统支付功能接口,将必要的参数封装成,后台请求微信统一下单接口

https://api.mch.weixin.qq.com/pay/unifiedorder

2.统一下单接口返回的是code_url用于生成二维码,也就是用户真正扫的二维码

3.支付完毕后,微信会把相关的支付结果及用户信息返回

特别提醒:商户系统对于支付结果通知的内容一定要做签名验证,并校验返回的订单金额是否与商户侧的订单金额一致,防止数据泄漏导致出现“假通知”,造成资金损失。

不多说,上代码

以下代码中凡是出现*号的地方都要替换为自己的信息。

实体类(其中有些是小程序中用到的,可自行修改没什么好说的)。

public class PayParams implements Serializable { private String appId;//微信小程序appid private String secret;//微信小程序密钥 private String grantType;//填写为 authorization_code private String mchId;//商户号 private String notifyUrl;//支付成功后的服务器回调url private String tradeType;//支付类型 private String payKey;//微信支付商户秘钥 private String payUrl;//统一下单接口地址 private String signType;//MD5 private String openId;//openid private String productName;//商品名称 private String orderId;//订单号 private String money;//金额 必须为字符串类型,否则签名报错 private String addressIp;//终端的IP //微信扫码支付 private String wxAppId;//公众号账号ID private String wxNotify_url;//回调通知地址 private String wxTradeType;//交易类型 NATIVE 扫码支付 private String wxNameUrl;//微信签名url https://api.mch.weixin.qq.com/pay/unifiedorder public String getWxNameUrl() { return "https://api.mch.weixin.qq.com/pay/unifiedorder"; } public String getWxAppId() { return "*********"; } public String getWxNotify_url() { return "http://"*********";"; } public String getWxTradeType() { return "NATIVE"; } public String getAppId() { return ""*********";"; } public String getSecret() { return ""*********";"; } public String getGrantType() { return "authorization_code"; } public String getMchId() { return ""*********";"; } public String getNotifyUrl() { return "http://"*********";"; } public String getTradeType() { return "JSAPI"; } public String getPayKey() { return ""*********";"; } public String getPayUrl() { return "https://api.mch.weixin.qq.com/pay/unifiedorder"; } public String getSignType() { return "MD5"; } public String getOpenId() { return openId; } public void setOpenId(String openId) { this.openId = openId; } public String getProductName() { return productName; } public void setProductName(String productName) { this.productName = productName; } public String getOrderId() { return orderId; } public void setOrderId(String orderId) { this.orderId = orderId; } public String getMoney() { return money; } public void setMoney(String money) { this.money = money; } public String getAddressIp() { return addressIp; } public void setAddressIp(String addressIp) { this.addressIp = addressIp; } public PayParams() { } public PayParams(String openId, String productName, String orderId, String money, String addressIp) { this.openId = openId; this.productName = productName; this.orderId = orderId; this.money = money; this.addressIp = addressIp; } }

定义接口:

public interface PayService { /** * 微信扫码支付 */ ResultInfo createOrderByWx(PayParams payParams); }

响应类:

public class ResultInfo implements Serializable { private boolean success; private String msg; private Object data; public boolean isSuccess() { return success; } public void setSuccess(boolean success) { this.success = success; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } public Object getData() { return data; } public void setData(Object data) { this.data = data; } }

接口实现类:

public class PayServiceImpl implements PayService { private final static Logger logger = LoggerFactory.getLogger(PayServiceImpl.class); @Override public ResultInfo createOrderByWx(PayParams payParams) { logger.info("进入微信扫码支付"); ResultInfo rtf = new ResultInfo(); try { //生成的随机字符串 String randomStr = StrUtil.getRandomStringByLength(32); Map<String, String> packageParams = new HashMap<String, String>(); packageParams.put("appid", payParams.getWxAppId()); packageParams.put("mch_id", payParams.getMchId()); packageParams.put("nonce_str", randomStr); packageParams.put("body", payParams.getProductName()); packageParams.put("total_fee",payParams.getMoney()); packageParams.put("out_trade_no", payParams.getOrderId()); packageParams.put("spbill_create_ip", payParams.getAddressIp()); packageParams.put("notify_url", payParams.getWxNotify_url()); packageParams.put("trade_type", payParams.getWxTradeType()); packageParams.put("openid",payParams.getOpenId()); packageParams = PayUtil.paraFilter(packageParams); String prestr = PayUtil.createLinkString(packageParams); //MD5生成签名,调用统一下单接口 String mysign = PayUtil.sign(prestr, payParams.getPayKey(), "utf-8").toUpperCase(); //拼接统一下单接口使用的xml数据 String xml = "<xml>" + "<appid>" + payParams.getWxAppId() + "</appid>" + "<body><![CDATA[" + payParams.getProductName() + "]]></body>" + "<mch_id>" + payParams.getMchId() + "</mch_id>" + "<nonce_str>" + randomStr + "</nonce_str>" + "<notify_url>" + payParams.getWxNotify_url() + "</notify_url>" + "<openid>" + payParams.getOpenId() + "</openid>" + "<out_trade_no>" + payParams.getOrderId() + "</out_trade_no>" + "<spbill_create_ip>" + payParams.getAddressIp() + "</spbill_create_ip>" + "<total_fee>" + payParams.getMoney() + "</total_fee>" + "<trade_type>" + payParams.getWxTradeType() + "</trade_type>" + "<sign>" + mysign + "</sign>" + "</xml>"; //调用统一下单接口,接收返回的结果 String result = PayUtil.httpRequest(payParams.getWxNameUrl(), "POST", xml); // 将解析结果存储在HashMap中 Map map = PayUtil.doXMLParse(result); String code_url = (String)map.get("code_url"); if(StringUtils.isEmpty(code_url)){ rtf.setSuccess(false); rtf.setMsg("发起失败"); }else{rtf.setSuccess(true); rtf.setData(code_url); logger.info(rtf.toString()); } }catch (Exception e){ e.printStackTrace(); rtf.setSuccess(false); rtf.setMsg("发起失败"); } return rtf; } }

扫码支付的Controller,返回给前端的就是用户付款的二维码

/** * PC 微信扫码支付 * @param request * @param response * @return */ @ResponseBody @RequestMapping(value = {"api/pay/wxPay"}, method = RequestMethod.GET) public ResultInfo wxPay(HttpServletRequest request, HttpServletResponse response, PayParams payParams){ String ipAddr = IpUtil.getIpAddr(request); PayParams p = new PayParams(); p.setProductName("微信扫码商品名称"); p.setMoney("1"); p.setAddressIp("192.168.58.1"); p.setOpenId("*******"); p.setOrderId(NonceStrUtil.getRandomStr()); ResultInfo orderByWx = payService.createOrderByWx(p); String str = orderByWx.getData().toString(); logger.info(str); if (!StringUtils.isEmpty(str)) { try { QRCodeUtil.writeToStream(str, response.getOutputStream()); } catch (IOException e) { e.printStackTrace(); } catch (WriterException e) { e.printStackTrace(); } } else { orderByWx.setSuccess(false); orderByWx.setMsg("请求有误请重试"); } return orderByWx; }

扫码支付完成后微信会发送通知,后端进行接收通知和校验签名

/** * PC 微信接口回调 * @param request * @param response */ @ResponseBody @RequestMapping(value = {"account/wxCallBack"}, method = RequestMethod.POST) public void wxCallBack(HttpServletRequest request, HttpServletResponse response) { logger.info(" -------------微信回调-------------- "); //读取参数 InputStream inputStream; StringBuffer sb = new StringBuffer(); PayParams payParams = new PayParams(); try { inputStream = request.getInputStream(); String s; BufferedReader in = new BufferedReader(new InputStreamReader(inputStream, "UTF-8")); while ((s = in.readLine()) != null) { sb.append(s); } logger.info("微信返回数据:"+sb.toString()); in.close(); inputStream.close(); //解析xml成map Map<String, String> m = new HashMap<String, String>(); m = PayUtil.doXMLParse(sb.toString()); //过滤空 设置 TreeMap SortedMap<Object,Object> packageParams = new TreeMap<Object,Object>(); Iterator it = m.keySet().iterator(); while (it.hasNext()) { String parameter = (String) it.next(); String parameterValue = m.get(parameter); String v = ""; if(null != parameterValue) { v = parameterValue.trim(); } packageParams.put(parameter, v); } //账号信息 String key = payParams.getPayKey(); // key String out_trade_no = (String)packageParams.get("out_trade_no"); //判断签名是否正确 if(PayUtil.isTenpaySign("UTF-8", packageParams,key)) { logger.info("处理业务开始"); //处理业务开始 String resXml = ""; if("SUCCESS".equals((String)packageParams.get("result_code"))){ logger.info("判断签名正确 "); logger.info("SUCCESS".equals((String)packageParams.get("result_code"))+""); // 支付成功,执行实际的业务逻辑 logger.info("执行生产的实际逻辑 "); } else { logger.info("支付失败,错误信息:" + packageParams.get("err_code")+ "----->订单号:"+out_trade_no+"----->支付失败时间:" +new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())); String err_code = (String)packageParams.get("err_code"); resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[报文为空]]></return_msg>" + "</xml> "; logger.info(resXml); } //处理完毕 BufferedOutputStream out = new BufferedOutputStream(response.getOutputStream()); out.write(resXml.getBytes()); out.flush(); out.close(); } else{ logger.info("通知签名验证失败---时间:"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())); } } catch (Exception e) { e.printStackTrace(); } }

以上正文结束,下面是本次用的所有工具类:

public class IpUtil { /** * IpUtils工具类方法 * 获取真实的ip地址 * @param request * @return */ public static String getIpAddr(HttpServletRequest request) { String ip = request.getHeader("x-forwarded-for"); if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("Proxy-Client-IP"); } if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("WL-Proxy-Client-IP"); } if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getRemoteAddr(); } return ip.equals("0:0:0:0:0:0:0:1")?"127.0.0.1":ip; } } public class NonceStrUtil { public static String getRandomStr(){ String base = "0123456789"; Random random = new Random(); StringBuffer sb = new StringBuffer(); for (int i = 0; i < 19; i++) { int number = random.nextInt(base.length()); sb.append(base.charAt(number)); } long time = new Date().getTime(); String randomStr = sb.append(time).toString(); return randomStr; } } public class PayUtil { /** * 签名字符串 * text需要签名的字符串 * key 密钥 * input_charset编码格式 * 签名结果 */ public static String sign(String text, String key, String input_charset) { text = text + "&key=" + key; return DigestUtils.md5Hex(getContentBytes(text, input_charset)); } /** * 签名字符串 * text需要签名的字符串 * sign 签名结果 * key密钥 * input_charset 编码格式 * 签名结果 */ public static boolean verify(String text, String sign, String key, String input_charset) { text = text + key; String mysign = DigestUtils.md5Hex(getContentBytes(text, input_charset)); if (mysign.equals(sign)) { return true; } else { return false; } } /** * @param content * @param charset * @return * @throws SignatureException * @throws UnsupportedEncodingException */ public static byte[] getContentBytes(String content, String charset) { if (charset == null || "".equals(charset)) { return content.getBytes(); } try { return content.getBytes(charset); } catch (UnsupportedEncodingException e) { throw new RuntimeException("MD5签名过程中出现错误,指定的编码集不对,您目前指定的编码集是:" + charset); } } /** * 生成6位或10位随机数 param codeLength(多少位) * @return */ public static String createCode(int codeLength) { String code = ""; for (int i = 0; i < codeLength; i++) { code += (int) (Math.random() * 9); } return code; } private static boolean isValidChar(char ch) { if ((ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z')) return true; if ((ch >= 0x4e00 && ch <= 0x7fff) || (ch >= 0x8000 && ch <= 0x952f)) return true;// 简体中文汉字编码 return false; } /** * 除去数组中的空值和签名参数 * @param sArray 签名参数组 * @return 去掉空值与签名参数后的新签名参数组 */ public static Map<String, String> paraFilter(Map<String, String> sArray) { Map<String, String> result = new HashMap<String, String>(); if (sArray == null || sArray.size() <= 0) { return result; } for (String key : sArray.keySet()) { String value = sArray.get(key); if (value == null || value.equals("") || key.equalsIgnoreCase("sign") || key.equalsIgnoreCase("sign_type")) { continue; } result.put(key, value); } return result; } /** * 把数组所有元素排序,并按照“参数=参数值”的模式用“&”字符拼接成字符串 * @param params 需要排序并参与字符拼接的参数组 * @return 拼接后字符串 */ public static String createLinkString(Map<String, String> params) { List<String> keys = new ArrayList<String>(params.keySet()); Collections.sort(keys); String prestr = ""; for (int i = 0; i < keys.size(); i++) { String key = keys.get(i); String value = params.get(key); if (i == keys.size() - 1) {// 拼接时,不包括最后一个&字符 prestr = prestr + key + "=" + value; } else { prestr = prestr + key + "=" + value + "&"; } } return prestr; } /** * requestUrl请求地址 * requestMethod请求方法 * outputStr参数 */ public static String httpRequest(String requestUrl,String requestMethod,String outputStr){ // 创建SSLContext StringBuffer buffer = null; try{ URL url = new URL(requestUrl); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod(requestMethod); conn.setDoOutput(true); conn.setDoInput(true); conn.connect(); //往服务器端写内容 if(null !=outputStr){ OutputStream os=conn.getOutputStream(); os.write(outputStr.getBytes("utf-8")); os.close(); } // 读取服务器端返回的内容 InputStream is = conn.getInputStream(); InputStreamReader isr = new InputStreamReader(is, "utf-8"); BufferedReader br = new BufferedReader(isr); buffer = new StringBuffer(); String line = null; while ((line = br.readLine()) != null) { buffer.append(line); } br.close(); }catch(Exception e){ e.printStackTrace(); } return buffer.toString(); } public static String urlEncodeUTF8(String source){ String result=source; try { result=java.net.URLEncoder.encode(source, "UTF-8"); } catch (UnsupportedEncodingException e) { // TODO Auto-generated catch block e.printStackTrace(); } return result; } /** *解析xml,返回第一级元素键值对。如果第一级元素有子节点,则此节点的值是子节点的xml数据。 *strxml *JDOMException *IOException */ public static Map doXMLParse(String strxml) throws Exception { if(null == strxml || "".equals(strxml)) { return null; } Map m = new HashMap(); InputStream in = String2Inputstream(strxml); 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 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; } /** * 获取子结点的xml * @param children * @return String */ 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(getChildrenText(list)); } sb.append(value); sb.append("</" + name + ">"); } } return sb.toString(); } public static InputStream String2Inputstream(String str) { return new ByteArrayInputStream(str.getBytes()); } /** * 是否签名正确,规则是:按参数名称a-z排序,遇到空值的参数不参加签名。 * @return boolean */ public static boolean isTenpaySign(String characterEncoding, SortedMap<Object, Object> packageParams, String API_KEY) { StringBuffer sb = new StringBuffer(); Set es = packageParams.entrySet(); Iterator it = es.iterator(); while(it.hasNext()) { Map.Entry entry = (Map.Entry)it.next(); String k = (String)entry.getKey(); String v = (String)entry.getValue(); if(!"sign".equals(k) && null != v && !"".equals(v)) { sb.append(k + "=" + v + "&"); } } sb.append("key=" + API_KEY); //算出摘要 String mysign = PayUtil.MD5Encode(sb.toString(), characterEncoding).toLowerCase(); String tenpaySign = ((String)packageParams.get("sign")).toLowerCase(); //System.out.println(tenpaySign + " " + mysign); return tenpaySign.equals(mysign); } private static String MD5Encode(String origin, String charsetname) { String resultString = null; try { resultString = new String(origin); MessageDigest md = MessageDigest.getInstance("MD5"); if (charsetname == null || "".equals(charsetname)) resultString = byteArrayToHexString(md.digest(resultString .getBytes())); else resultString = byteArrayToHexString(md.digest(resultString .getBytes(charsetname))); } catch (Exception exception) { } return resultString; } private static String byteArrayToHexString(byte b[]) { StringBuffer resultSb = new StringBuffer(); for (int i = 0; i < b.length; i++) resultSb.append(byteToHexString(b[i])); return resultSb.toString(); } private static String byteToHexString(byte b) { int n = b; if (n < 0) n += 256; int d1 = n / 16; int d2 = n % 16; return hexDigits[d1] + hexDigits[d2]; } private static final String hexDigits[] = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f" }; } public class QRCodeUtil { private static final int BLACK = 0xFF000000; private static final int WHITE = 0xFFFFFFFF; private QRCodeUtil() {} public static BufferedImage toBufferedImage(BitMatrix matrix) { int width = matrix.getWidth(); int height = matrix.getHeight(); BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { image.setRGB(x, y, matrix.get(x, y) ? BLACK : WHITE); } } return image; } public static void writeToFile(BitMatrix matrix, String format, File file) throws IOException { BufferedImage image = toBufferedImage(matrix); if (!ImageIO.write(image, format, file)) { throw new IOException("Could not write an image of format " + format + " to " + file); } } public static void writeToStream(String content,OutputStream stream)throws IOException, WriterException { int width = 300; int height = 300; String format = "gif"; Hashtable hints = new Hashtable(); //内容所使用编码 hints.put(EncodeHintType.CHARACTER_SET, "utf-8"); BitMatrix matrix = new MultiFormatWriter().encode(content, BarcodeFormat.QR_CODE, width, height, hints); BufferedImage image = toBufferedImage(matrix); if (!ImageIO.write(image, format, stream)) { throw new IOException("Could not write an image of format " + format); } } } public class StrUtil extends StringUtils{ /** * StringUtils工具类方法 * 获取一定长度的随机字符串,范围0-9,a-z * @param length:指定字符串长度 * @return 一定长度的随机字符串 */ public static String getRandomStringByLength(int length) { String base = "abcdefghijklmnopqrstuvwxyz0123456789"; Random random = new Random(); StringBuffer sb = new StringBuffer(); for (int i = 0; i < length; i++) { int number = random.nextInt(base.length()); sb.append(base.charAt(number)); } return sb.toString(); } }

部分maven的坐标:

<!-- httpClient --> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.5.2</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>4.0.0</version> </dependency> <!-- google 二维码 --> <dependency> <groupId>com.google.zxing</groupId> <artifactId>core</artifactId> <version>2.1</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.4</version> </dependency> <dependency> <groupId>jdom</groupId> <artifactId>jdom</artifactId> <version>1.0</version> </dependency>

 

转载请注明原文地址: https://www.6miu.com/read-4050029.html

最新回复(0)