企业付款到零钱「微信小程序别样发放红包」

    xiaoxiao2023-09-25  167

    目录

    一、开发前的准备工作

    二、接入微信企业付款到零钱API

    1)接入「企业付款到零钱」API

    2)接入「查询企业付款」API

    三、开发过程的参数封装以及工具类封装

    四、调试注意事项汇总


     

    一、开发前的准备工作

    前往商户平台开通「企业付款到零钱」。配置好API密钥和生成API证书。

    「企业付款到零钱」介绍:

    开通注意事项见下图。详情请戳:企业付款场景介绍&操作指导

    二、接入微信企业付款到零钱API

    先附上微信官方文档:企业付款到零钱  & 查询企业付款

    1)接入「企业付款到零钱」API

    API 接入注意事项参见下方截图:  

    嗯,浏览官网文档后,相信应该已经找到感觉,哪怕一丁点都好! 接下来,总体梳理一下编码思路:

    读取对接必需配置项:appid & 商户号mch_id & 商户API密钥mchKey读取微信「企业付款到零钱」接口URL配置项SSL加载API证书组装「企业付款到零钱」接口所需的请求参数按照既定规则生成商户订单号生成签名正式请求「企业付款到零钱」API接收API响应结果,处理相关业务逻辑

     

    2)接入「查询企业付款」API

    嗯,还是梳理编码步骤:

    读取对接必需配置项:appid & 商户号mch_id & 商户API密钥mchKey读取微信「查询企业付款」接口URL配置项SSL加载API证书组装「查询企业付款」接口所需的请求参数(此处商户订单号跟付款API使用的商户订单号保持一致)生成签名正式请求「查询企业付款」API接收API响应结果,处理相关业务逻辑
    /** * @return java.util.Map<java.lang.String, java.lang.String> * @throws * @description 查询企业付款 * @params [partnerTradeNo] */ @Override public Map<String, String> getTransferInfo(String partnerTradeNo) throws IOException { // 加载API证书 SSLContext sslContext = initSSLContext(); SSLConnectionSocketFactory sslSkF = new SSLConnectionSocketFactory(sslContext, new String[]{"TLSv1"}, null, SSLConnectionSocketFactory.getDefaultHostnameVerifier()); SortedMap<String, Object> parameters = new TreeMap<>(); // 组装请求参数 parameters.put("partner_trade_no", partnerTradeNo); parameters.put("nonce_str", weChatUtils.gen32RandomString()); parameters.put("appid", wxEpProperties.getAppid()); parameters.put("mch_id", wxEpProperties.getMchId()); // 生成签名 String sign = weChatUtils.createSign(parameters, wxEpProperties.getMchKey()); parameters.put("sign", sign); try { // 查询企业付款 响应结果=> Xml格式 String entPaymentQueryRes = weChatUtils.executeHttpPost(wxEpProperties.getGetTransferInfoUrl(), parameters, sslSkF); log.info("WeChatEntPaymentServiceImpl.getTransferInfo ======== 查询企业付款响应结果:[{}] ======== ", entPaymentQueryRes); // XML => Map Map<String, String> resInfoMap = weChatUtils.transferXmlToMap(entPaymentQueryRes); log.info("WeChatEntPaymentServiceImpl.getTransferInfo ======== 查询企业付款响应结果Map结构:[{}] ======== ", resInfoMap.toString()); return resInfoMap; } catch (Exception e) { log.error("WeChatEntPaymentServiceImpl.getTransferInfo ======== 查询企业付款发生异常 ======== "); throw new CommonBusinessException("查询企业付款失败!"); } }

    ​​​​

    三、开发过程的参数封装以及工具类封装  

    import lombok.extern.slf4j.Slf4j; import org.apache.http.HttpResponse; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpGet; 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.HttpClients; import org.jdom2.Document; import org.jdom2.Element; import org.jdom2.JDOMException; import org.jdom2.input.SAXBuilder; import org.springframework.stereotype.Component; import javax.servlet.http.HttpServletRequest; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.SortedMap; import java.util.TreeMap; /** * @Description 微信生态常用工具方法 * @Author blake * @Date 2018/12/11 下午4:15 * @Version 1.0 */ @Component @Slf4j public class WeChatUtils { public String getRemoteHost(javax.servlet.http.HttpServletRequest request) { String ip = request.getHeader("x-forwarded-for"); if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("Proxy-Client-IP"); log.info("WeChatUtils.getRemoteHost ======= 第一处Value:[{}] ======== ",ip); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("WL-Proxy-Client-IP"); log.info("WeChatUtils.getRemoteHost ======= 第二处Value:[{}] ======== ",ip); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getRemoteAddr(); log.info("WeChatUtils.getRemoteHost ======= 第三处Value:[{}] ======== ",ip); } return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : ip; } /** * 执行 POST 方法的 HTTP 请求 * * @param url * @param parameters * @param sslsf * @return * @throws IOException */ public String executeHttpPost(String url, SortedMap<String, Object> parameters, SSLConnectionSocketFactory sslsf) throws IOException { HttpClient client = null; if (Objects.isNull(sslsf)) { client = HttpClients.createDefault(); } else { client = HttpClients.custom().setSSLSocketFactory(sslsf).build(); } HttpPost request = new HttpPost(url); request.setHeader("Content-type", "application/xml"); request.setHeader("Accept", "application/xml"); request.setEntity(new StringEntity(transferMapToXml(parameters), "UTF-8")); HttpResponse response = client.execute(request); return readResponse(response); } /** * 执行 GET 方法的 HTTP 请求 * * @param url * @return * @throws IOException */ public String executeHttpGet(String url) throws IOException { HttpClient client = HttpClients.createDefault(); HttpGet request = new HttpGet(url); request.setHeader("Content-type", "application/xml"); request.setHeader("Accept", "application/xml"); HttpResponse response = client.execute(request); return readResponse(response); } /** * 第一次签名 * * @param parameters 数据为服务器生成,下单时必须的字段排序签名 * @param key * @return */ public String createSign(SortedMap<String, Object> parameters, String key) { StringBuffer sb = new StringBuffer(); Set es = parameters.entrySet();//所有参与传参的参数按照accsii排序(升序) Iterator it = es.iterator(); while (it.hasNext()) { Map.Entry entry = (Map.Entry) it.next(); String k = (String) entry.getKey(); Object v = entry.getValue(); if (null != v && !"".equals(v) && !"sign".equals(k) && !"key".equals(k)) { sb.append(k + "=" + v + "&"); } } sb.append("key=" + key); return encodeMD5(sb.toString()); } /** * 第二次签名 * * @param result 数据为微信返回给服务器的数据(XML 的 String),再次签名后传回给客户端(APP)使用 * @param key 密钥 * @return * @throws IOException */ public Map<String, Object> createSign2(String result, String key) throws IOException { SortedMap<String, Object> map = new TreeMap<>(transferXmlToMap(result)); Map<String, Object> app = new HashMap<>(); app.put("signType", "MD5"); app.put("appId", map.get("appid")); app.put("nonceStr", map.get("nonce_str")); // 统一下单接口返回的prepay_id参数值 String packageStr = "prepay_id=" + map.get("prepay_id"); app.put("package", packageStr); // 当前时间戳 app.put("timeStamp", Long.toString(new Date().getTime() / 1000)); // 微信支付正式签名 app.put("paySign", createSign(new TreeMap<>(app), key)); return app; } /** * 验证签名是否正确 * * @return boolean * @throws Exception */ public boolean checkSign(SortedMap<String, Object> parameters, String key) throws Exception { String signWx = parameters.get("sign").toString(); if (signWx == null) return false; parameters.remove("sign"); // 需要去掉原 map 中包含的 sign 字段再进行签名 String signMe = createSign(parameters, key); return signWx.equals(signMe); } /** * 读取 request body 内容作为字符串 * * @param request * @return * @throws IOException */ public String readRequest(HttpServletRequest request) throws IOException { InputStream inputStream; StringBuffer sb = new StringBuffer(); inputStream = request.getInputStream(); String str; BufferedReader in = new BufferedReader(new InputStreamReader(inputStream, "UTF-8")); while ((str = in.readLine()) != null) { sb.append(str); } in.close(); inputStream.close(); return sb.toString(); } /** * 读取 response body 内容为字符串 */ public String readResponse(HttpResponse response) throws IOException { BufferedReader in = new BufferedReader( new InputStreamReader(response.getEntity().getContent())); String result = new String(); String line; while ((line = in.readLine()) != null) { result += line; } return result; } /** * 将 Map 转化为 XML * * @param map * @return */ public String transferMapToXml(SortedMap<String, Object> map) { StringBuffer sb = new StringBuffer(); sb.append("<xml>"); for (String key : map.keySet()) { sb.append("<").append(key).append(">") .append(map.get(key)) .append("</").append(key).append(">"); } return sb.append("</xml>").toString(); } /** * 将 XML 转化为 map * * @param strxml * @return * @throws JDOMException * @throws IOException */ public Map transferXmlToMap(String strxml) throws 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 = null; try { doc = builder.build(in); } catch (JDOMException e) { throw new IOException(e.getMessage()); // 统一转化为 IO 异常输出 } // 解析 DOM 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; } // 辅助 transferXmlToMap 方法递归提取子节点数据 private String getChildrenText(List<Element> children) { StringBuffer sb = new StringBuffer(); if (!children.isEmpty()) { Iterator<Element> it = children.iterator(); while (it.hasNext()) { Element e = (Element) it.next(); String name = e.getName(); String value = e.getTextNormalize(); List<Element> list = e.getChildren(); sb.append("<" + name + ">"); if (!list.isEmpty()) { sb.append(getChildrenText(list)); } sb.append(value); sb.append("</" + name + ">"); } } return sb.toString(); } /** * 生成 32 位随机字符串,包含:数字、字母大小写 * * @return */ public String gen32RandomString() { char[] dict = {'1', '2', '3', '4', '5', '6', '7', '8', '9', '0', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'}; StringBuffer sb = new StringBuffer(); for (int i = 0; i < 32; i++) { sb.append(String.valueOf(dict[(int) (Math.random() * 36)])); } return sb.toString(); } /** * MD5 签名 * * @param str * @return 签名后的字符串信息 */ public String encodeMD5(String str) { try { MessageDigest messageDigest = MessageDigest.getInstance("MD5"); byte[] inputByteArray = (str).getBytes(); messageDigest.update(inputByteArray); byte[] resultByteArray = messageDigest.digest(); return byteArrayToHex(resultByteArray); } catch (NoSuchAlgorithmException e) { return null; } } // 辅助 encodeMD5 方法实现 private String byteArrayToHex(byte[] byteArray) { char[] hexDigits = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; char[] resultCharArray = new char[byteArray.length * 2]; int index = 0; for (byte b : byteArray) { resultCharArray[index++] = hexDigits[b >>> 4 & 0xf]; resultCharArray[index++] = hexDigits[b & 0xf]; } // 字符数组组合成字符串返回 return new String(resultCharArray); } public static void main(String[] args) { } }
    /** * 初始化ssl. * * @return the ssl context * @throws CommonBusinessException the wx pay exception */ public SSLContext initSSLContext() throws CommonBusinessException, IOException { if (StringUtils.isBlank(wxEpProperties.getMchId())) { throw new CommonBusinessException("请确保商户号mchId已设置"); } // SpringBoot项目,API证书可直接放至类路径resources下 InputStream resourceAsStream = new ClassPathResource(wxEpProperties.getCertPath()).getInputStream(); byte[] bytes = IOUtils.toByteArray(resourceAsStream); ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes); try { KeyStore keystore = KeyStore.getInstance("PKCS12"); char[] partnerId2charArray = wxEpProperties.getMchId().toCharArray(); keystore.load(byteArrayInputStream, partnerId2charArray); return SSLContexts.custom().loadKeyMaterial(keystore, partnerId2charArray).build(); } catch (Exception e) { throw new CommonBusinessException("商户密钥不匹配或证书文件有问题,请核实!"); } finally { byteArrayInputStream.close(); resourceAsStream.close(); } }
    /** * @Description 微信企业付款到零钱 基础配置数据项 * @Author blake * @Date 2019-05-16 14:48 * @Version 1.0 */ @Configuration // SpringBoot读取配置项其中一种用法 public class WxEntPaymentProperties { /** * 设置商户号关联的appid */ @Value("${wechat.ent.payment.appid}") private String appid; /** * 商户号 */ @Value("${wechat.ent.payment.mchid}") private String mchId; /** * 商户密钥 */ @Value("${wechat.ent.payment.mchKey}") private String mchKey; /** * API证书存放路径 */ @Value("${wechat.ent.payment.certPath}") private String certPath; /** * 企业付款到零钱接口Url */ @Value("${wechat.ent.payment.transfers}") private String transfersUrl; /** * 查询企业付款接口Url */ @Value("${wechat.ent.payment.gettransferinfo}") private String getTransferInfoUrl; }

    四、调试注意事项汇总

    想保证调试过程顺利的话,强烈建议将项目部署至linux服务器,且保证外网可访问调试金额amount必须保证:1 <= amount <= 5000,单位:元

     

    最新回复(0)