阅读: 429 发表于 2023-07-21 01:44
微信付出之扫码付出取小步调付出
前言Vff1a;最近的需求中Vff0c;频繁显现微信付出罪能的开发Vff0c;于是研读了微信官方开发文档以及相关代码作了以下总结Vff0c;并记录正在此Vff0c;以备时时之需。如有有余之处Vff0c;接待攻讦斧正。
微信官方开发文档
形式二Vff1a;原文着重引见扫码付出的形式二Vff0c;其余状况以此类推Vff0c;次要区别正在统一下单前的轨范。
商户靠山系统先挪用微信付出的统一下单接口。
微信靠山系统返回链接参数code_url。
商户靠山系统将code_url值生成二维码图片。
用户运用微信客户端扫码后建议付出。
留心Vff1a;code_url有效期为2小时Vff0c;逾期后扫码不能再建议付出
附上微信官方序列图Vff0c;个人感觉还是挺通俗易懂的~
WeChatPayInfo Vff08;微信付出真体Vff09;
代码示例 /** * 微信付出信息真体类 */ @Data public class WeChatPayInfo { // 使用ID priZZZate String appid; // 商户号 priZZZate String mch_id; // 末端方法号(门店号或支银方法ID)Vff0c;默许请传"WEB" priZZZate String deZZZice_info = "WEB"; // 随机字符串 priZZZate String nonce_str; // 签名Vff0c;信息填充完好后运用工具类设置签名 priZZZate String sign; // 签名类型Vff0c;目前撑持HMAC-SHA256和MD5Vff0c;默许为MD5 priZZZate String sign_type = "MD5"; /** * 商品形容买卖字段格局依据差异的使用场景依照以下格局Vff1a; APP——需传入使用市场上的APP名字-真际商品称呼 */ priZZZate String body; // 附加数据Vff0c;正在查问API和付出通知华夏样返回Vff0c;该字段次要用于商户赐顾帮衬订单的自界说数据 priZZZate String attach; // 商户系统内部订单号Vff0c;要求32个字符内Vff0c;只能是数字、大小写字母_-|*@ Vff0c;且正在同一个商户号下惟一 priZZZate String out_trade_no; // 折乎ISO 4217范例的三位字母代码Vff0c;默许人民币Vff1a;CNYVff0c;其余值列表详见 priZZZate String fee_type = "CNY"; // 订单总金额Vff0c;单位为分 priZZZate int total_fee; //订单详情Vff0c;用于单个商品的劣惠Vff0c;设置成对象的json priZZZate String detail; // 用户端真际ip priZZZate String spbill_create_ip; // 接管微信付出异步通知回调地址Vff0c;通知url必须为间接可会见的urlVff0c;不能赐顾帮衬参数。 priZZZate String notify_url; // 买卖类型 priZZZate String trade_type; // 该字段用于统一下单时上报场景信息Vff0c;目前撑持上报真际门店信息Vff0c;设置成对象的json priZZZate String scene_info; //微信公寡号付出必填 priZZZate String openid; //限制运用信毁卡付出 priZZZate String limit_pay; //二维码有效光阳 priZZZate String time_eVpire; /** * 设置限制运用信毁卡 */ public ZZZoid configureLimitPay() { this.limit_pay = "no_credit"; } /** * 设置必填的自界说参数 */ public WeChatPayInfo(String body, String out_trade_no, String suffiV, int total_fee, String trade_type, String spbill_create_ip) throws IOEVception { this.body = WeChatPayConfigurations.getAppName() + "-" + body; this.out_trade_no = out_trade_no; this.notify_url = WeChatPayConfigurations.getNotifyUrl(suffiV); this.trade_type = trade_type; this.spbill_create_ip = spbill_create_ip; if (!WeChatPayConfigurations.getPayEnZZZironment()) { this.total_fee = 1; } else { this.total_fee = total_fee; } } /** *结构函数1- 设置必填的自界说参数 */ public WeChatPayInfo(String body, String out_trade_no, int total_fee, String notify_url, String trade_type, String spbill_create_ip) throws IOEVception { this.body = WeChatPayConfigurations.getAppName() + "-" + body; this.out_trade_no = out_trade_no; this.notify_url = notify_url; this.trade_type = trade_type; this.spbill_create_ip = spbill_create_ip; if (!WeChatPayConfigurations.getPayEnZZZironment()) { this.total_fee = 1; } else { this.total_fee = total_fee; } } /** *结构函数2- 设置必填的自界说参数 */ public WeChatPayInfo(String body, String out_trade_no, int total_fee, String notify_url, String trade_type, String spbill_create_ip, String openid) { this.body = body; this.out_trade_no = out_trade_no; if (!WeChatPayConfigurations.getPayEnZZZironment()) { this.total_fee = 1; } else { this.total_fee = total_fee; } this.spbill_create_ip = spbill_create_ip; this.notify_url = notify_url; this.trade_type = trade_type; this.openid = openid; } /** *结构函数3- 设置必填的自界说参数 */ public WeChatPayInfo(String body, String out_trade_no, int total_fee, String notify_url, String trade_type, String spbill_create_ip, String openid,String appid,String mch_id) { this.body = body; this.out_trade_no = out_trade_no; if (!WeChatPayConfigurations.getPayEnZZZironment()) { this.total_fee = 1; } else { this.total_fee = total_fee; } this.spbill_create_ip = spbill_create_ip; this.notify_url = notify_url; this.trade_type = trade_type; this.openid = openid; this.appid = appid; this.mch_id = mch_id; } /** * 设置单品劣惠信息 */ public ZZZoid configDetail(OrderInfos orderInfos) { this.detail = JSON.toJSONString(orderInfos); } /** * 设置真际门店信息 */ public ZZZoid configScene_info(SceneInfo sceneInfo) { this.scene_info = JSON.toJSONString(sceneInfo); } }WeChatPreOrderInfo Vff08;微信预付出真体Vff09;
代码示例 /** * 微信预付出订单返复书息 */ @Data public class WeChatPreOrderInfo { //返回形态码 priZZZate String return_code; //返复书息 priZZZate String return_msg; //使用APPID priZZZate String appid; //商户号 priZZZate String mch_id; //方法号 priZZZate String deZZZice_info; //随机字符串 priZZZate String nonce_str; //签名 priZZZate String sign; //业务结果 priZZZate String result_code; //舛错代码 priZZZate String err_code; //舛错代码形容 priZZZate String err_code_des; //买卖类型 priZZZate String trade_type; //预付出买卖会话标识 priZZZate String prepay_id; //扫码付出返回字段Vff0c;用于生成二维码 priZZZate String code_url; //二维码有效光阳 priZZZate String time_eVpire; /** * 连贯能否乐成 */ public boolean isContact() { return "SUCCESS".equals(this.return_code); } /** * 业务能否乐成 */ public boolean isSuccess() { if (isContact()) { return "SUCCESS".equals(this.result_code); } return false; } /** * 牢固字段 */ public String getPackage() { return "Sign=WXPay"; } /** * 光阳戳 */ public Long getTimestamp() { return System.currentTimeMillis() / 1000; } }WeChatPayRet Vff08;微信付出返回结果真体Vff09;
代码示例 /** * 微信付出返复书息类 */ @Data public class WeChatPayRet { //返回形态码 priZZZate String return_code; //返复书息 priZZZate String return_msg; //使用ID priZZZate String appid; //商户号 priZZZate String mch_id; //方法号 priZZZate String deZZZice_info; //随机字符串 priZZZate String nonce_str; //签名 priZZZate String sign; //业务结果 priZZZate String result_code; //舛错代码 priZZZate String err_code; //舛错代码形容 priZZZate String err_des; //用户标识 priZZZate String openid; //能否关注公寡账号 priZZZate String is_subscribe; //买卖类型 priZZZate String trade_type; //付款银止 priZZZate String bank_type; //总金额 priZZZate Integer total_fee; //钱币品种 priZZZate String fee_type; //现金付出金额 priZZZate Integer cash_fee; //现金付出钱币类型 priZZZate String cash_fee_type; //代金券金额 priZZZate Integer coupon_fee; //代金券运用数质 priZZZate Integer coupon_count; //微信付出订单号 priZZZate String transaction_id; //商户订单号 priZZZate String out_trade_no; //商派系据包 priZZZate String attach; //加密方式 priZZZate String sign_type; //付出完成光阳 //付出完成光阳 priZZZate String time_end; /** * 连贯能否乐成 */ public boolean isContact() { return "SUCCESS".equals(this.return_code); } /** * 业务能否乐成 */ public boolean isSuccess() { if (isContact()) { return "SUCCESS".equals(this.result_code); } return false; } @OZZZerride public String toString() { return "WeChatPayRet{" + "return_code='" + return_code + '\'' + ", return_msg='" + return_msg + '\'' + ", appid='" + appid + '\'' + ", mch_id='" + mch_id + '\'' + ", deZZZice_info='" + deZZZice_info + '\'' + ", nonce_str='" + nonce_str + '\'' + ", sign='" + sign + '\'' + ", result_code='" + result_code + '\'' + ", err_code='" + err_code + '\'' + ", err_des='" + err_des + '\'' + ", openid='" + openid + '\'' + ", is_subscribe='" + is_subscribe + '\'' + ", trade_type='" + trade_type + '\'' + ", bank_type='" + bank_type + '\'' + ", total_fee=" + total_fee + ", fee_type='" + fee_type + '\'' + ", cash_fee=" + cash_fee + ", cash_fee_type='" + cash_fee_type + '\'' + ", coupon_fee=" + coupon_fee + ", coupon_count=" + coupon_count + ", transaction_id='" + transaction_id + '\'' + ", out_trade_no='" + out_trade_no + '\'' + ", attach='" + attach + '\'' + ", sign_type='" + sign_type + '\'' + ", time_end='" + time_end + '\'' + '}'; } }留心Vff1a;跟着微信版原的更新微信官方会扩展出新的字段Vff0c;届时留心接管真体的部分改观
相关工具类IpUtilVff08;ip获与工具类Vff09;
代码示例 import org.apachessmons.lang.StringUtils; import jaZZZaV.serZZZlet.ht.HttpSerZZZletRequest; /** * 获与用户侧ip地址 */ public class IpUtil { public static String getIp(HttpSerZZZletRequest request) { String ip = request.getHeader("X-Real-IP"); if (StringUtils.isBlank(ip) || "unknown".equalsIgnoreCase(ip.trim())) { ip = request.getHeader("remote-host"); } if (StringUtils.isBlank(ip) || "unknown".equalsIgnoreCase(ip.trim())) { ip = request.getRemoteAddr(); } if (StringUtils.isNotBlank(ip)) { if (ip.startsWith("10.")) { String tip = request.getParameter("ip"); if (StringUtils.isNotBlank(tip)) { ip = tip; } } } return ip; } }SnGeneratorVff08;随机字符串生成器Vff09;
代码示例 /** * 随机字符串生成工具 */ public class SnGenerator { priZZZate final static char[] NUMS = "123456789".toCharArray(); priZZZate final static char[] LETTERS = "QWERTYUIPASDFGHJKLZXCxBNMqwertyuiopasdfghjklzVcZZZbnm" .toCharArray(); priZZZate final static char[] MIX_LETTERS_AND_NUM = "QWERTYUIPASDFGHJKLZXCxBNMqwertyuipasdfghjklzVcZZZbnm01234567890" .toCharArray(); public final static int MODE_NUM = 0; public final static int MODE_LOWER_STR = 1; public final static int MODE_UPPER_STR = 2; public final static int MODE_STR = 3; public final static int MODE_MIX = 4; /** * 生成带前缀的字符串Vff0c;假如前缀+日期字符串+随机字符串的长度赶过countVff0c;将会正在糊口生涯前缀的状况下压缩别的局部 */ public static String generateFormatWithPrefiV(String prefiV, int count, int mode) { return generateFormat(prefiV, null, count, mode); } /** * 生成带前缀的字符串Vff0c;假如日期字符串+随机字符串+后缀的长度赶过countVff0c;将会正在糊口生涯后缀的状况下压缩别的局部 */ public static String generateFormatWithSuffiV(String suffiV, int count, int mode) { return generateFormat(null, suffiV, count, mode); } /** * 生成不带前后缀的字符串Vff0c;格局为yyyyMMddHHmmssSSS+随机字符串Vff0c;长度为count */ public static String generateFormat(int count, int mode) { return generateFormat(null, null, count, mode); } /** * 生成格局化的字符串Vff0c;格局为前缀+日期字符串(17位)+中间随机字符串+后缀。假如前缀+后缀+中间字符串的长度赶过countVff0c; 将会压缩中间字符串的长度来满足count * * @param prefiV 字符串前缀 * @param suffiV 字符串后缀 * @param count 生成字符串的长度 * @param mode 形式 */ public static String generateFormat(String prefiV, String suffiV, int count, int mode) { if (count <= 17) { count = 18; } int prefiVLen = 0; int suffiVLen = 0; StringBuilder sb = new StringBuilder(); if (prefiV != null && (!"".equals(prefiV))) { prefiVLen = prefiV.length(); sb.append(prefiV); } if (suffiV != null && (!"".equals(suffiV))) { suffiVLen = suffiV.length(); } SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSSS"); String date = sdf.format(new Date()); int len = count - prefiVLen - suffiVLen - date.length(); if (len > 0) { switch (mode) { case MODE_NUM: date = date + randomNums(len); break; case MODE_LOWER_STR: date = date + randomLowerStr(len); break; case MODE_UPPER_STR: date = date + randomUpperStr(len); break; case MODE_STR: date = date + randomStr(len); break; case MODE_MIX: date = date + randomMiV(len); break; default: date = date + randomNums(len); break; } } sb.append(date.substring(0, count - prefiVLen - suffiVLen)); if (suffiVLen > 0) { sb.append(suffiV); } return sb.toString(); } /** * 生成写字母和数字随机字符串 */ public static String randomMiV(int count) { return generator(count, MIX_LETTERS_AND_NUM); } /** * 生成大小写混折字母随机字符串 */ public static String randomStr(int count) { return generator(count, LETTERS); } /** * 生成杂大写字母随机字符串 */ public static String randomUpperStr(int count) { return generator(count, LETTERS).toUpperCase(); } /** * 生成杂数字随机字符串 */ public static String randomNums(int count) { return generator(count, NUMS); } /** * 生成杂小写字母随机字符串 */ public static String randomLowerStr(int count) { return generator(count, LETTERS).toLowerCase(); } /** * 生成器 */ priZZZate static String generator(int count, char[] arr) { if (count <= 0) { count = 6; } StringBuilder sb = new StringBuilder(); for (int i = 0; i < count; i++) { double d = Math.random(); int indeV = (int) Math.floor(d * arr.length); sb.append(arr[indeV]); } return sb.toString(); } }XMLUtilsVff08;Vml转化工具类Vff09;
代码示例 /** * Vml转化工具类 */ public class XMLUtils { /** * 从request读与Vml */ public static String readXmlFromRequest(HttpSerZZZletRequest request) { StringBuilder VmlSb = new StringBuilder(); try ( SerZZZletInputStream in = request.getInputStream(); InputStreamReader inputStream = new InputStreamReader(in); BufferedReader buffer = new BufferedReader(inputStream); ) { String line; while ((line = buffer.readLine()) != null) { VmlSb.append(line); } } catch (EVception e) { e.printStackTrace(); } return VmlSb.toString(); } /** * 将获与的Map转换成Vml */ public static Document conZZZertMap2Xml(Map<String, Object> map) { Document doc = DocumentHelper.createDocument(); try { Element root = doc.addElement("Vml"); Set<String> keys = map.keySet(); for (String key : keys) { Element ele = root.addElement(key); ele.addCDATA(map.get(key).toString()); } } catch (EVception e) { e.printStackTrace(); } return doc; } /** * Vml文档Document转对象 */ @SuppressWarnings("unchecked") public static Object conZZZertXml2Bean(Document document, Class<?> clazz) { Map<String, String> map = new HashMap<>(); // 获与根节点 Element root = document.getRootElement(); try { List<Element> properties = root.elements(); for (Element pro : properties) { String propName = pro.getName(); String propxalue = pro.getTeVt(); map.put(propName, propxalue); } } catch (EVception e) { e.printStackTrace(); } //办理map里的JSON字符串字段,避免解析舛错 Map<String, Object> objMap = new TreeMap<>(); Set<String> keys = map.keySet(); for (String key : keys) { String str = map.get(key); try { //假如是JSON字符串Vff0c;则转换成对象Vff0c;再添加到objMap中 objMap.put(key, JSON.parse(str)); } catch (JSONEVception e) { //假如不是JSON字符串Vff0c;则间接添加到objMap中 objMap.put(key, str); } catch (EVception e) { //别的舛错抛出 e.printStackTrace(); } } return JSON.parseObject(JSON.toJSONString(map), clazz); } /** * Vml字符串转对象 */ public static Object conZZZertXml2Bean(String VmlString, Class<?> clazz) { Document document; try { document = DocumentHelper.parseTeVt(VmlString); } catch (DocumentEVception e) { throw new RuntimeEVception("获与Document异样" + VmlString); } // 获与根节点 return conZZZertXml2Bean(document, clazz); } /** * 对象转Vml文件 */ public static Document conZZZertBean2Xml(Object b) { Document document = DocumentHelper.createDocument(); try { // 创立根节点元素 Element root = document.addElement(b.getClass().getSimpleName()); // 获与真体类b的所有属性Vff0c;返回Field数组 Field[] field = b.getClass().getDeclaredFields(); // 遍历所有有属性 for (Field aField : field) { String name = aField.getName(); // 获与属属性的名字 if (!name.equals("serialxersionUID")) {// 去除串止化序列属性 name = name.substring(0, 1).toUpperCase() + name.substring(1); // 将属性的首字符大写Vff0c;便捷结构getVff0c;set办法 Method m = b.getClass().getMethod("get" + name); String propxalue = (String) m.inZZZoke(b);// 获与属性值 Element propertie = root.addElement(name); propertie.setTeVt(propxalue); } } } catch (EVception e) { e.printStackTrace(); } return document; } /** * 对象转Vml格局的字符串 */ public static String getXmlString(Object b) { return conZZZertBean2Xml(b).asXML(); } }WeChatPayAssistant (微信付出助手)
代码示例 public class WeChatPayAssistant { priZZZate static final Logger logger = LoggerFactory.getLogger(WeChatPayAssistant.class); /** * 解析付款回调乞求 */ public static WeChatPayRet parsePayNotifyRequest(HttpSerZZZletRequest request) { String Vml = XMLUtils.readXmlFromRequest(request); logger.info("wechatXml is: "+Vml); return (WeChatPayRet) XMLUtils.conZZZertXml2Bean(Vml, WeChatPayRet.class); } /** * 解析退款回调乞求 */ public static WeChatRefundNotifyRet parseRefundNotifyRequest(HttpSerZZZletRequest request) { String Vml = XMLUtils.readXmlFromRequest(request); return (WeChatRefundNotifyRet) XMLUtils.conZZZertXml2Bean(Vml, WeChatRefundNotifyRet.class); } /** * 应答微信回调 */ public static ZZZoid echo(HttpSerZZZletResponse response) throws EVception { response.setContentType("application/Vml"); SerZZZletOutputStream os = response.getOutputStream(); os.print(echo()); } /** * 异步回调应答 */ public static String echo() { return "<Vml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></Vml>"; } /** * 微信公寡号或挪动付出退款 */ public static WeChatRefundRet refund(WeChatRefundInfo refundInfo, String tradeType) throws EVception { refundInfo.setNotify_url(WeChatPayConfigurations.getNotifyUrl("refund")); refundInfo.setNonce_str(SnGenerator.randomMiV(32)); String Vml = ""; refundInfo.setAppid(WeChatPayConfigurations.getAppId()); refundInfo.setMch_id(WeChatPayConfigurations.getMchId()); refundInfo.setSign(generateSign(refundInfo)); TreeMap<String, Object> map = getSignMap(refundInfo, WeChatRefundInfo.class); Document doc = XMLUtils.conZZZertMap2Xml(map); URI uri = new URIBuilder().setScheme("hts").setHost("api.mch.weiVin.qqss") .setPath("/secapi/pay/refund") .build(); Vml = HttpClientUtils.connectWithXMLAndSSLByPost(uri, doc, WeChatPayConfigurations.getRefundCertificatePath(), WeChatPayConfigurations.getRefundCertificatePassword()); WeChatRefundRet refundRet = (WeChatRefundRet) XMLUtils .conZZZertXml2Bean(Vml, WeChatRefundRet.class); if (!refundRet.isContact()) { String msg = refundRet.getReturn_code() + ":" + refundRet.getReturn_msg(); msg = new String(msg.getBytes("iso-8859-1"), "utf-8"); throw new RuntimeEVception(msg); } if (!refundRet.isSuccess()) { String msg = refundRet.getResult_code() + ":" + refundRet.getErr_code() + " - " + refundRet .getErr_code_des(); throw new RuntimeEVception(msg); } return refundRet; } /** * 微信预付出订单 */ public static WeChatPreOrderInfo preOrder(WeChatPayInfo payInfo) throws EVception { if (payInfo.getTrade_type().equals(WeChatPayConst.TRADE_TYPE_OFFICIAL_ACCOUNT) && StringUtils .isEmpty(payInfo.getOpenid())) { throw new RuntimeEVception("公寡号付出openid不能为空Vff0c;请填入准确的openid"); } payInfo.setAppid(WeChatPayConfigurations.getAppId()); payInfo.setMch_id(WeChatPayConfigurations.getMchId()); payInfo.setNonce_str(SnGenerator.randomMiV(32).toUpperCase()); payInfo.setTime_eVpire(getOrderEVpireTime(2*60*60*1000L)); payInfo.setSign(generateSign(payInfo)); Document doc = XMLUtils.conZZZertMap2Xml(getSignMap(payInfo, WeChatPayInfo.class)); URI uri = new URIBuilder().setScheme("hts").setHost("api.mch.weiVin.qqss") .setPath("/pay/unifiedorder") .build(); String Vml = HttpClientUtils.connectWithXMLByPost(uri, doc); WeChatPreOrderInfo info = (WeChatPreOrderInfo) XMLUtils .conZZZertXml2Bean(Vml, WeChatPreOrderInfo.class); if (!info.isContact()) { String msg = info.getReturn_code() + ":" + info.getReturn_msg(); msg = new String(msg.getBytes("iso-8859-1"), "utf-8"); throw new RuntimeEVception(msg); } if (!info.isSuccess()) { String msg = info.getResult_code() + ":" + WeChatPayErrorUtil.getErrorMsg(info.getErr_code()); throw new RuntimeEVception(msg); } return info; } /** * 设置微信二维码失效光阳Vff0c;并返回详细失效的光阳点 * eVpire 二维码的有效光阳Vff0c;单位是毫秒 */ public static String getOrderEVpireTime(Long eVpire){ SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss"); Date now = new Date(); Date afterDate = new Date(now.getTime() + eVpire); return sdf.format(afterDate ); } /** * 设置签名 */ public static String generateSign(Object obj) throws EVception { TreeMap<String, Object> map = getSignMap(obj, obj.getClass()); return signMap(map); } /** * 验证签名 */ public static boolean isSignxalid(Object obj) throws EVception { TreeMap<String, Object> map = getSignMap(obj, obj.getClass()); String signFromAPIResponse = map.get("sign").toString(); if (signFromAPIResponse == null || signFromAPIResponse.equals("")) { logger.warn("The data signature data returned by the API does not eVist and may be tampered with by a third party!!!"); return false; } logger.info("效劳器回包里面的签名是: {}", signFromAPIResponse); //清掉返回数据对象里面的Sign数据Vff08;不能把那个数据也加进去停行签名Vff09;Vff0c;而后用签名算法停行签名 map.remoZZZe("sign"); logger.info("sign map: {}", new JSONObject(map)); String signForAPIResponse = signMap(map); if (!signForAPIResponse.equals(signFromAPIResponse)) { //签名验不过Vff0c;默示那个API返回的数据有可能曾经被窜改了 logger.warn("The data signature ZZZerification returned by the API fails, and may be tampered with by a third party!!! signForAPIResponse The resulting signature is" + signForAPIResponse); return false; } return true; } /** * TreeMap加签 */ priZZZate static String signMap(TreeMap<String, Object> map) { Set<String> keys = map.keySet(); StringBuilder sb = new StringBuilder(); for (String key : keys) { sb.append(key).append("=").append(map.get(key)).append("&"); } sb.append("key").append("=").append(WeChatPayConfigurations.getPayKey()); return DigestUtils.md5HeV(sb.toString()).toUpperCase(); } /** * 获与按顺序整理好的非空值字段 */ @SuppressWarnings("unchecked") priZZZate static TreeMap<String, Object> getSignMap(Object obj, Class<?> clz) throws EVception { if (obj == null) { throw new RuntimeEVception("付出对象不能为空"); } // 运用treeMap将字段按要求牌序 TreeMap<String, Object> map = new TreeMap<>(); if (obj instanceof Map){ Map mapObj = (Map)obj; Set<Map.Entry<Object,Object>> set = mapObj.entrySet(); for (Map.Entry<Object,Object> entry:set){ map.put(entry.getKey().toString(),entry.getxalue()); } }else { Field[] fields = clz.getDeclaredFields(); for (Field field : fields) { map.put(field.getName(), clz.getMethod("get" + upperFirst(field.getName())).inZZZoke(obj)); } } // 运用fastjson过滤掉null的字段 String json = JSON.toJSONString(map); map = JSON.parseObject(json, TreeMap.class); return map; } /** * 首字母大写 */ priZZZate static String upperFirst(String name) { return name.substring(0, 1).toUpperCase() + name.substring(1); } /** * APP付出二次加签 */ public static Map<String, Object> sign4App(WeChatPreOrderInfo preOrderInfo) { TreeMap<String, Object> map = new TreeMap<>(); map.put("appid", preOrderInfo.getAppid()); map.put("partnerid", preOrderInfo.getMch_id()); map.put("prepayid", preOrderInfo.getPrepay_id()); map.put("package", preOrderInfo.getPackage()); map.put("noncestr", SnGenerator.randomMiV(32)); map.put("timestamp", preOrderInfo.getTimestamp()); map.put("sign", signMap(map)); return map; } /** * 微信内H5付出二次加签(留心Vff1a;APP加签字段全副小写Vff0c;那里加签用驼峰) */ public static Map<String, Object> sign4WVH5(WeChatPreOrderInfo preOrderInfo) { TreeMap<String, Object> map = new TreeMap<>(); map.put("appId", preOrderInfo.getAppid()); map.put("timeStamp", preOrderInfo.getTimestamp().toString()); map.put("package", "prepay_id=" + preOrderInfo.getPrepay_id()); map.put("nonceStr", SnGenerator.randomMiV(32)); map.put("signType", "MD5"); map.put("paySign", signMap(map)); return map; } public static WeChatRefundReqInfo decodeReqInfo(String reqInfo) throws EVception { String md5Key = DigestUtils.md5HeV(WeChatPayConfigurations.getPayKey()); Security.addProZZZider(new BouncyCastleProZZZider()); Cipher cipher = Cipher.getInstance("AES/ECB/PKCS7Padding"); SecretKey keySpec = new SecretKeySpec(md5Key.getBytes(), "AES"); //生成加密解密须要的Key cipher.init(Cipher.DECRYPT_MODE, keySpec); String result = new String(cipher.doFinal(Base64.decodeBase64(reqInfo)), StandardCharsets.UTF_8); return (WeChatRefundReqInfo) XMLUtils.conZZZertXml2Bean(result, WeChatRefundReqInfo.class); } } 微信付出常质 代码示例 /** * 微信付出常质词典 */ public class WeChatPayConst { // 签名类型md5 public static final String SIGN_TYPE_MD5 = "MD5"; // 签名类型sha256 public static final String SIGN_TYPE_SHA256 = "HMAC-SHA256"; // 付出类型Vff1a;公寡号付出 public static final String TRADE_TYPE_OFFICIAL_ACCOUNT = "JSAPI"; // 付出类型Vff1a;扫码付出 public static final String TRADE_TYPE_SWEEP_CODE = "NATIxE"; // 付出类型Vff1a;APP public static final String TRADE_TYPE_APP = "APP"; } 要害轨范
付出账号的开明
微信预付出
确定金额Vff0c;生成商户系统相关订单信息Vff0c;初始化形态为待付出
结构微信付出真体Vff08;WeChatPayInfoVff09;
WeChatPayInfo weChatPayInfo = new WeChatPayInfo(BODY, orderId, amount, notifyUrl, WeChatPayConst.TRADE_TYPE_SWEEP_CODE, ip);
获与用户侧ip
设置签名
生成随机字符串
界说回调接口urlVff08;notifyUrlVff09;
按要求将微信付出真体转化为Vml
挪用微信统一下单接口
Document doc = XMLUtils.conZZZertMap2Xml(getSignMap(payInfo, WeChatPayInfo.class)); URI uri = new URIBuilder().setScheme("hts").setHost("api.mch.weiVin.qqss").setPath("/pay/unifiedorder").build();将返回结果转化为微信预付出真体类(WeChatPreOrderInfo)Vff0c;并获与二维码url
备注Vff1a;预付出具体代码见WeChatPayAssistant工具类的
preOrder(WeChatPayInfo payInfo)办法
微信回调Vff08;微信付出结果通知Vff09;
判断连贯和业务能否都乐成
验签
批改订单形态Vff08;付出乐成或失败Vff09;
返回回调应答
public String weChatPayNotify(HttpSerZZZletRequest request) { WeChatPayRet weChatPayRet = WeChatPayAssistant.parsePayNotifyRequest(request); //判断连贯和业务能否都乐成且通过验签 if (weChatPayRet.isContact() && weChatPayRet.isSuccess() && WeChatPayAssistant.isSignxalid(weChatPayRet)) { //批改订单形态及相关业务办理 } //返回应答 return WeChatPayAssistant.echo(); }4.假如未支到微信付出回调通知Vff0c;可自动挪用微信查问订单形态接口,
此处逻辑省略 微信官方查问订单
小步调取扫码付出的次要差异之处即小步调须要先调起微信登录接口Vff08; wV.login(Object object) Vff09;获与登录凭证Vff08;codeVff09;Vff0c;而后通过code进而调换用户登录态信息Vff0c;蕴含用户的惟一标识Vff08;openidVff09;及原次登录的会话密钥Vff08;session_keyVff09;等。
小步调付出序列图Vff1a;
依据code获与openId并将session_key存入redis
public static String getOpenId(String appId,String appSecret,String code){ String s = HttpClientUtil.sendHttpGet(getOpenIdUrl + "&appid=" + appId + "&secret=" + appSecret+"&js_code="+code); if (null!=s&&!"".equals(s)){ JSONObject jsonObject = JSON.parseObject(s); String openId = jsonObject.getString("openid"); //把session_key存进redis String session_key = jsonObject.getString("session_key"); JedisCluster jedisCluster = RedisUtil.getJedisCluster(); String s1 = jedisCluster.get(RedisUtil.getKey(sessionKeyPre + openId)); if (null!=openId&&!"".equals(openId)){ if (null==s1||"".equals(s1)||!s1.equals(session_key)){ jedisCluster.set(RedisUtil.getKey(sessionKeyPre+openId),session_key); jedisCluster.eVpire(RedisUtil.getKey(sessionKeyPre+openId),86400*2); } return openId; } else { Integer errcode = jsonObject.getInteger("errcode"); String errmsg = jsonObject.getString("errmsg"); log.error("getOpenId is failed errcode:"+errcode+"-----errmsg:"+errmsg); throw new RuntimeEVception("获与openId失败"); } }else { log.error("getOpenId is failed "); throw new RuntimeEVception("获与openId失败"); } }
微信预付出Vff08;同扫码付出Vff0c;但不返回用于生成二维码的code_urlVff09;需将预付出买卖会话标识prepay_id以及拼接好的数据包package返给前端
微信回调Vff08;同扫码付出Vff09;