zoukankan      html  css  js  c++  java
  • 微信公众号支付备忘及填坑之路-java

    一、背景  

      最近公司给第三方开发了一个公众号,其中最重要的功能是支付,由于是第一次开发,遇到的坑特别的多,截止我写博客时,支付已经完成,在这里我把遇到的坑记录一下(不涉及退款)。不得不吐槽一下,腾讯这么大的公司,写的代码真的烂,变量命名不规范(后来又开发了公众号分享,发现对于同一个变量,两个地方的变量名不同),文档写的垃圾,而且好多时候,有返回的错误码但是文档上没说明,另外线上线下测试极其不方便。

    二、参考资料

      1、JSAPI支付开发文档:主要是支付开发过程中接口、流程、注意事项、常见问题等;  

      2、微信公众平台:里面有公众号的运维配置,当然也有开发配置,咱们主要是用到了开发配置;  

      3、微信商户平台:要支付必须有微信商户,里面需配置开发设置;  

      4、花生壳:主要是用到花生壳的内网穿透,由于微信支付开发过程中,需要公网的微信服务器访问本地开发的电脑,包括重定向之类的,所以需要内网穿透,这样微信才能访问到局域网电脑;花生壳可以免费注册使用,但是呢,免费的带宽为1M,由于带宽太小,根本穿透不进来,所以我们后来购买了带宽,发现带宽为3M时,足以应付日场使用。当然Ngrok也可以内网穿透,但是带宽太低了;  

      5、微信公众平台支付接口调试工具:这个平台可以校验自己生成的sign与微信服务器生成的sign是否一致,很有用,尤其是提示签名失败时。

     

    三、说明  

      1、在支付接入过程中,如果不顺利,不要怀疑微信服务器有问题,多想想自己哪儿可能出问题了,因为接入微信支付的商户已经达好几亿了,微信不可能出问题的;

      2、微信的开发文档中说到,为保证商户接入质量,提升交易安全及用户体验,微信做了一个支付仿真沙箱环境,还说什么“微信支付的合作服务商在正式上线交易前,必须先根据本文指引完成验收”。这个别听微信的,他们的这个沙箱环境根本不成熟,用这个是瞎折腾,咱们可以直接用真实环境测试;

      3、微信支付开发文档中提到“协议规则”,首先传输方式是HTTPS,是指微信服务器和商户服务器通讯协议是HTTPS,但是不要求微信客户端与商户服务器之间的通讯协议是HTTPS,用HTTP也行,也就是说商户服务器不用自己准备SSL证书。其次所有微信支付有关的接口都用POST。最后,提交和返回的数据都是XML格式,不是目前普标接受的JSON格式,这个要注意。 

     

    四、微信支付流程

      第3步:微信客户端请求商户后台系统,主要是携带商品的价格,其次如有必要携带商品的描述,或者传递一个参数可以让服务器唯一确定商品价格与商品描述;

      第4步:这一步是在我们商户后台的数据库的"订单表"里面插入一条数据,数据里包含的基本信息有:用户id,订单金额,订单商品及描述,支付状态;

      第5步:整个微信公众号支付流程图中,最重要的一步,这一步的目的是返回预支付订单号prepay_id,这一步起到承上启下的作用;这一步的详细代码见后文 “统一下单”;开发文档 ;

      第6步:根据第5步形成的prepay_id附带其他参数一起发送给微信客户端,开发文档

      第7步:携带第6步的参数请求微信,这一步也是次重要的一步,开发文档常见问题

      第10步、第11步:支付完成后,微信服务器通知商户后台系统,后台系统验证完毕后,需要改变商户订单表里订单的状态,即第4步的订单状态。之后需要后台系统给微信服务器返回特定的信息。但是开发文档中也说了,绝大部分情况下,我们支付成功,能接收到微信的通知,但是有时收不到,这时我们需要主动调用微信支付【查询订单API】确认订单状态 。这一步的详细代码见后文 “微信支付结果通知及商户服务器响应”,开发文档 。

     

     

    五、微信公众号开通微信支付

      见参考文档:微信公众号怎么申请微信支付。开通之后,从公众号内可以看到已经关联的商户(图1),同时从微信商户里可以看到关联的微信公众号(图2)。 

     

    六、公众号开发参数配置

      1、设置——公众号设置——功能设置:设置网页授权域名

      微信是这么说的“用户在网页授权页同意授权给公众号后,微信会将授权数据传给一个回调页面,回调页面需在此域名下,以确保安全可靠”,我是这里理解的:网页需要重定向时才可以跳转到该域名下的地址。由于是在微信里面嵌套h5网页,所以网页的域名需要受到微信的监管。注意这里的设置的是域名,不是ip地址之类的,也不能带有端口号。这里就需要用到花生壳的内网穿透了。

        假如服务器是tomcat,端口号是8080,微信给的文件是xxx.txt,花生壳地址是http://123asd.zicp.vip

        1)首先你将微信给的xxx.txt文件下载到ROOT目录,然后启动tomcat,在本地浏览器用localhost:8080/xxx.txt访问,如果正常不能正常访问,那就是tomcat启动失败了,自己检查下log日志看看;

        2)启动花生壳客户端,并且配置内网主机为localhost:8080,在本地浏览器访问http://123asd.zicp.vip/xxx.txt,这个受制于带宽的原因,可能需要等一会时间,也可能很快,如果不能访问成功,花生壳重启试试;

        3)配置网页授权域名为123asd.zicp.vip。

        

     

    2、开发——基本配置。

      1)这里需要配置开发者密码(特别重要)、ip白名单(通过开发者ID及密码调用获取access_token接口时,需要设置访问来源IP为白名单)。

      2)服务器配置,验证token。这一步也是微信防止没有经过鉴权的服务器访问自己的服务器做的一个验证吧,可以看做是为了安全考虑。这个需要服务端代码支持,代码见后面。这个配置有时候需要多试几次,有时提示“系统繁忙”。

     

    七、微信商户开发参数配置

      这里的支付授权目录配置的就是微信公众号调起支付页面的URL,和后端服务器无关。这里有坑,详见下面的填坑之路。

     

    八、后端代码

      1、微信常量类

     1 public class WeixinConstant {
     2 
     3     /**
     4      * 公众号-id
     5      */
     6     public static final String WX_CON_APPID = "xxx";
     7     /**
     8      * 公众号-秘钥
     9      */
    10     public static final String WX_CON_APPSECRET = "yyy";
    11 
    12 
    13     /**
    14      * 商户-商户号
    15      */
    16     public static final String WX_CON_MCH_ID = "zzz";
    17     /**
    18      * 商户-商户密钥
    19      */
    20     public static final String WX_CON_MCHSECRET = "uuu";
    21 
    22 
    23     /**
    24      * 请求结果-成功
    25      */
    26     public static final String RESULT_SUCCESS = "SUCCESS";
    27     /**
    28      * 请求结果-失败
    29      */
    30     public static final String RESULT_FAIL = "FAIL";
    31 
    32 
    33     /**
    34      * 微信支付成功回调响应Map
    35      */
    36     public static final Map<String, String> WX_CON_PAY_RESPMAP = new HashMap<String, String>() {{
    37         put("return_code", "SUCCESS");
    38         put("return_msg", "OK");
    39     }};
    40 
    41 
    42     /**
    43      * 接口-用户-通过code换取网页授权access_token URL
    44      */
    45     public static final String WX_URL_USER_GETOPENIDANDTOKEN = "https://api.weixin.qq.com/sns/oauth2/access_token";
    46 
    47     /**
    48      * 接口-用户-拉取用户信息
    49      */
    50     public static final String WX_URL_USER_GETUSERINFO = "https://api.weixin.qq.com/sns/userinfo";
    51 
    52     /**
    53      * 接口-支付-统一下单URL
    54      */
    55     public static final String WX_URL_PAY_UNIFIEDORDER = "https://api.mch.weixin.qq.com/pay/unifiedorder";
    56 
    57     /**
    58      * 接口-支付-查询订单URL
    59      */
    60     public static final String WX_URL_PAY_ORDERQUERY = "https://api.mch.weixin.qq.com/pay/orderquery";
    61 
    62 }
    View Code

      2、工具类/方法

      2.1、map与xml转化工具(微信工具类提供的。因为我们熟悉使用map,所以我们把请求参数放在有序map里,然后再将map转化为xml)

      1 public class WXPayUtil {
      2 
      3     private static final String SYMBOLS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
      4 
      5     private static final Random RANDOM = new SecureRandom();
      6 
      7     /**
      8      * XML格式字符串转换为Map
      9      *
     10      * @param strXML XML字符串
     11      * @return XML数据转换后的Map
     12      * @throws Exception
     13      */
     14     public static Map<String, String> xmlToMap(String strXML) throws Exception {
     15         try {
     16             Map<String, String> data = new HashMap<String, String>();
     17             DocumentBuilder documentBuilder = WXPayXmlUtil.newDocumentBuilder();
     18             InputStream stream = new ByteArrayInputStream(strXML.getBytes("UTF-8"));
     19             org.w3c.dom.Document doc = documentBuilder.parse(stream);
     20             doc.getDocumentElement().normalize();
     21             NodeList nodeList = doc.getDocumentElement().getChildNodes();
     22             for (int idx = 0; idx < nodeList.getLength(); ++idx) {
     23                 Node node = nodeList.item(idx);
     24                 if (node.getNodeType() == Node.ELEMENT_NODE) {
     25                     org.w3c.dom.Element element = (org.w3c.dom.Element) node;
     26                     data.put(element.getNodeName(), element.getTextContent());
     27                 }
     28             }
     29             try {
     30                 stream.close();
     31             } catch (Exception ex) {
     32                 // do nothing
     33             }
     34             return data;
     35         } catch (Exception ex) {
     36             WXPayUtil.getLogger().warn("Invalid XML, can not convert to map. Error message: {}. XML content: {}", ex.getMessage(), strXML);
     37             throw ex;
     38         }
     39 
     40     }
     41 
     42     /**
     43      * 将Map转换为XML格式的字符串
     44      *
     45      * @param data Map类型数据
     46      * @return XML格式的字符串
     47      * @throws Exception
     48      */
     49     public static String mapToXml(Map<String, String> data) throws Exception {
     50         org.w3c.dom.Document document = WXPayXmlUtil.newDocument();
     51         org.w3c.dom.Element root = document.createElement("xml");
     52         document.appendChild(root);
     53         for (String key: data.keySet()) {
     54             String value = data.get(key);
     55             if (value == null) {
     56                 value = "";
     57             }
     58             value = value.trim();
     59             org.w3c.dom.Element filed = document.createElement(key);
     60             filed.appendChild(document.createTextNode(value));
     61             root.appendChild(filed);
     62         }
     63         TransformerFactory tf = TransformerFactory.newInstance();
     64         Transformer transformer = tf.newTransformer();
     65         DOMSource source = new DOMSource(document);
     66         transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
     67         transformer.setOutputProperty(OutputKeys.INDENT, "yes");
     68         StringWriter writer = new StringWriter();
     69         StreamResult result = new StreamResult(writer);
     70         transformer.transform(source, result);
     71         String output = writer.getBuffer().toString(); //.replaceAll("
    |
    ", "");
     72         try {
     73             writer.close();
     74         }
     75         catch (Exception ex) {
     76         }
     77         return output;
     78     }
     79 
     80 
     81     /**
     82      * 生成带有 sign 的 XML 格式字符串
     83      *
     84      * @param data Map类型数据
     85      * @param key API密钥
     86      * @return 含有sign字段的XML
     87      */
     88     public static String generateSignedXml(final Map<String, String> data, String key) throws Exception {
     89         return generateSignedXml(data, key, SignType.MD5);
     90     }
     91 
     92     /**
     93      * 生成带有 sign 的 XML 格式字符串
     94      *
     95      * @param data Map类型数据
     96      * @param key API密钥
     97      * @param signType 签名类型
     98      * @return 含有sign字段的XML
     99      */
    100     public static String generateSignedXml(final Map<String, String> data, String key, SignType signType) throws Exception {
    101         String sign = generateSignature(data, key, signType);
    102         data.put(WXPayConstants.FIELD_SIGN, sign);
    103         return mapToXml(data);
    104     }
    105 
    106 
    107     /**
    108      * 判断签名是否正确
    109      *
    110      * @param xmlStr XML格式数据
    111      * @param key API密钥
    112      * @return 签名是否正确
    113      * @throws Exception
    114      */
    115     public static boolean isSignatureValid(String xmlStr, String key) throws Exception {
    116         Map<String, String> data = xmlToMap(xmlStr);
    117         if (!data.containsKey(WXPayConstants.FIELD_SIGN) ) {
    118             return false;
    119         }
    120         String sign = data.get(WXPayConstants.FIELD_SIGN);
    121         return generateSignature(data, key).equals(sign);
    122     }
    123 
    124     /**
    125      * 判断签名是否正确,必须包含sign字段,否则返回false。使用MD5签名。
    126      *
    127      * @param data Map类型数据
    128      * @param key API密钥
    129      * @return 签名是否正确
    130      * @throws Exception
    131      */
    132     public static boolean isSignatureValid(Map<String, String> data, String key) throws Exception {
    133         return isSignatureValid(data, key, SignType.MD5);
    134     }
    135 
    136     /**
    137      * 判断签名是否正确,必须包含sign字段,否则返回false。
    138      *
    139      * @param data Map类型数据
    140      * @param key API密钥
    141      * @param signType 签名方式
    142      * @return 签名是否正确
    143      * @throws Exception
    144      */
    145     public static boolean isSignatureValid(Map<String, String> data, String key, SignType signType) throws Exception {
    146         if (!data.containsKey(WXPayConstants.FIELD_SIGN) ) {
    147             return false;
    148         }
    149         String sign = data.get(WXPayConstants.FIELD_SIGN);
    150         return generateSignature(data, key, signType).equals(sign);
    151     }
    152 
    153     /**
    154      * 生成签名
    155      *
    156      * @param data 待签名数据
    157      * @param key API密钥
    158      * @return 签名
    159      */
    160     public static String generateSignature(final Map<String, String> data, String key) throws Exception {
    161         return generateSignature(data, key, SignType.MD5);
    162     }
    163 
    164     /**
    165      * 生成签名. 注意,若含有sign_type字段,必须和signType参数保持一致。
    166      *
    167      * @param data 待签名数据
    168      * @param key API密钥
    169      * @param signType 签名方式
    170      * @return 签名
    171      */
    172     public static String generateSignature(final Map<String, String> data, String key, SignType signType) throws Exception {
    173         Set<String> keySet = data.keySet();
    174         String[] keyArray = keySet.toArray(new String[keySet.size()]);
    175         Arrays.sort(keyArray);
    176         StringBuilder sb = new StringBuilder();
    177         for (String k : keyArray) {
    178             if (k.equals(WXPayConstants.FIELD_SIGN)) {
    179                 continue;
    180             }
    181             if (data.get(k).trim().length() > 0) // 参数值为空,则不参与签名
    182                 sb.append(k).append("=").append(data.get(k).trim()).append("&");
    183         }
    184         sb.append("key=").append(key);
    185         if (SignType.MD5.equals(signType)) {
    186             return MD5(sb.toString()).toUpperCase();
    187         }
    188         else if (SignType.HMACSHA256.equals(signType)) {
    189             return HMACSHA256(sb.toString(), key);
    190         }
    191         else {
    192             throw new Exception(String.format("Invalid sign_type: %s", signType));
    193         }
    194     }
    195 
    196 
    197     /**
    198      * 获取随机字符串 Nonce Str
    199      *
    200      * @return String 随机字符串
    201      */
    202     public static String generateNonceStr() {
    203         char[] nonceChars = new char[32];
    204         for (int index = 0; index < nonceChars.length; ++index) {
    205             nonceChars[index] = SYMBOLS.charAt(RANDOM.nextInt(SYMBOLS.length()));
    206         }
    207         return new String(nonceChars);
    208     }
    209 
    210 
    211     /**
    212      * 生成 MD5
    213      *
    214      * @param data 待处理数据
    215      * @return MD5结果
    216      */
    217     public static String MD5(String data) throws Exception {
    218         MessageDigest md = MessageDigest.getInstance("MD5");
    219         byte[] array = md.digest(data.getBytes("UTF-8"));
    220         StringBuilder sb = new StringBuilder();
    221         for (byte item : array) {
    222             sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
    223         }
    224         return sb.toString().toUpperCase();
    225     }
    226 
    227     /**
    228      * 生成 HMACSHA256
    229      * @param data 待处理数据
    230      * @param key 密钥
    231      * @return 加密结果
    232      * @throws Exception
    233      */
    234     public static String HMACSHA256(String data, String key) throws Exception {
    235         Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
    236         SecretKeySpec secret_key = new SecretKeySpec(key.getBytes("UTF-8"), "HmacSHA256");
    237         sha256_HMAC.init(secret_key);
    238         byte[] array = sha256_HMAC.doFinal(data.getBytes("UTF-8"));
    239         StringBuilder sb = new StringBuilder();
    240         for (byte item : array) {
    241             sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
    242         }
    243         return sb.toString().toUpperCase();
    244     }
    245 
    246     /**
    247      * 日志
    248      * @return
    249      */
    250     public static Logger getLogger() {
    251         Logger logger = LoggerFactory.getLogger("wxpay java sdk");
    252         return logger;
    253     }
    254 
    255     /**
    256      * 获取当前时间戳,单位秒
    257      * @return
    258      */
    259     public static long getCurrentTimestamp() {
    260         return System.currentTimeMillis()/1000;
    261     }
    262 
    263     /**
    264      * 获取当前时间戳,单位毫秒
    265      * @return
    266      */
    267     public static long getCurrentTimestampMs() {
    268         return System.currentTimeMillis();
    269     }
    270 
    271 }
    View Code

      2.2、服务器token验证工具

     1 public class CheckoutUtil {
     2 
     3 
     4     // 与接口配置信息中的Token要一致
     5     private static String token = "83cea0ca4c9ee2dd60c15f2df17de4a2";
     6 
     7     /**
     8      * 验证签名
     9      *
    10      * @param signature
    11      * @param timestamp
    12      * @param nonce
    13      * @return
    14      */
    15     public static boolean checkSignature(String signature, String timestamp, String nonce) {
    16         String[] arr = new String[] { token, timestamp, nonce };
    17         // 将token、timestamp、nonce三个参数进行字典序排序
    18         // Arrays.sort(arr);
    19         sort(arr);
    20         StringBuilder content = new StringBuilder();
    21         for (int i = 0; i < arr.length; i++) {
    22             content.append(arr[i]);
    23         }
    24         MessageDigest md = null;
    25         String tmpStr = null;
    26 
    27         try {
    28             md = MessageDigest.getInstance("SHA-1");
    29             // 将三个参数字符串拼接成一个字符串进行sha1加密
    30             byte[] digest = md.digest(content.toString().getBytes());
    31             tmpStr = byteToStr(digest);
    32         } catch (NoSuchAlgorithmException e) {
    33             e.printStackTrace();
    34         }
    35         content = null;
    36         // 将sha1加密后的字符串可与signature对比,标识该请求来源于微信
    37         return tmpStr != null ? tmpStr.equals(signature.toUpperCase()) : false;
    38     }
    39 
    40     /**
    41      * 将字节数组转换为十六进制字符串
    42      *
    43      * @param byteArray
    44      * @return
    45      */
    46     private static String byteToStr(byte[] byteArray) {
    47         String strDigest = "";
    48         for (int i = 0; i < byteArray.length; i++) {
    49             strDigest += byteToHexStr(byteArray[i]);
    50         }
    51         return strDigest;
    52     }
    53 
    54     /**
    55      * 将字节转换为十六进制字符串
    56      *
    57      * @param mByte
    58      * @return
    59      */
    60     private static String byteToHexStr(byte mByte) {
    61         char[] Digit = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
    62         char[] tempArr = new char[2];
    63         tempArr[0] = Digit[(mByte >>> 4) & 0X0F];
    64         tempArr[1] = Digit[mByte & 0X0F];
    65         String s = new String(tempArr);
    66         return s;
    67     }
    68     public static void sort(String a[]) {
    69         for (int i = 0; i < a.length - 1; i++) {
    70             for (int j = i + 1; j < a.length; j++) {
    71                 if (a[j].compareTo(a[i]) < 0) {
    72                     String temp = a[i];
    73                     a[i] = a[j];
    74                     a[j] = temp;
    75                 }
    76             }
    77         }
    78     }
    79 
    80 }
    View Code

      2.3、支付加签工具

     1 public class WeixinSignUtil {
     2 
     3     private final static Log logger = LogFactory.getLog(WeixinSignUtil.class);
     4 
     5 
     6     /**
     7      * @param requestDataMap
     8      * @return
     9      * @function 微信参数签名
    10      * @remark flag=true 加密的参数为sign  flag=false 加密的参数是paySign
    11      */
    12     public static void getWeixinSign(Map<String, String> requestDataMap, Boolean isRequestFlag) {
    13         String paramStrTemp = "";
    14         Iterator<Map.Entry<String, String>> entries = requestDataMap.entrySet().iterator();
    15         while (entries.hasNext()) {
    16             Map.Entry<String, String> entry = entries.next();
    17             paramStrTemp += (entry.getKey() + "=" + entry.getValue() + "&");
    18         }
    19         String paramStr = paramStrTemp + "key=" + WeixinConstant.WX_CON_MCHSECRET;
    20         logger.info("签名前字符串:" + paramStr);
    21         String sign = DigestUtils.md5DigestAsHex(paramStr.getBytes()).toUpperCase();
    22         if (isRequestFlag) {
    23             logger.info("加签为sign:" + sign);
    24             requestDataMap.put("sign", sign);
    25         } else {
    26             logger.info("加签为paySign:" + sign);
    27             requestDataMap.put("paySign", sign);
    28         }
    29     }
    30 
    31 }
    View Code

      2.4、post方式提交xml数据工具(微信支付这样要求的)

     1 public Map<String, String> wxPayRequest(LinkedHashMap<String, String> requestDataMap, String requestUrl) {
     2         Map<String, String> wxResponseMap;
     3         try {
     4             URLConnection con = new URL(requestUrl).openConnection();
     5             con.setDoOutput(true);
     6             con.setRequestProperty("Cache-Control", "no-cache");
     7             con.setRequestProperty("Content-Type", "text/xml");
     8 
     9             OutputStreamWriter out = new OutputStreamWriter(con.getOutputStream());
    10             out.write(new String(WXPayUtil.mapToXml(requestDataMap).getBytes("UTF-8")));
    11             out.flush();
    12             out.close();
    13 
    14             String line = "";
    15             StringBuffer wxResponseStr = new StringBuffer();
    16             BufferedReader br = new BufferedReader(new InputStreamReader(con.getInputStream()));
    17             for (line = br.readLine(); line != null; line = br.readLine()) {
    18                 wxResponseStr.append(line);
    19             }
    20 
    21             logger.info("wxRespStr::" + wxResponseStr);
    22             wxResponseMap = WXPayUtil.xmlToMap(wxResponseStr.toString());
    23             logger.info("wxRespMap:" + wxResponseMap);
    24             return wxResponseMap;
    25         } catch (IOException e) {
    26             e.printStackTrace();
    27             return null;
    28         } catch (Exception e) {
    29             e.printStackTrace();
    30             return null;
    31         }
    32     }
    View Code

      3、服务器token验证(配合上文的   六、公众号开发参数配置——> 2)  

     1 @GetMapping("weixinVerifyUrl")
     2     @LoggerManage(logDescription = "系统-微信校验URL的合法性")
     3     public void tokenVarify(HttpServletRequest request, HttpServletResponse response) {
     4 
     5         // 微信加密签名
     6         String signature = request.getParameter("signature");
     7         // 时间戳
     8         String timestamp = request.getParameter("timestamp");
     9         // 随机数
    10         String nonce = request.getParameter("nonce");
    11         // 随机字符串
    12         String echostr = request.getParameter("echostr");
    13 
    14         // 通过检验signature对请求进行校验,若校验成功则原样返回echostr,表示接入成功,否则接入失败
    15         if (signature != null && CheckoutUtil.checkSignature(signature, timestamp, nonce)) {
    16             try {
    17                 response.getWriter().write(echostr);
    18             } catch (IOException e) {
    19                 e.printStackTrace();
    20             }
    21             return;
    22         }
    23         return;
    24     }
    View Code

      4、统一下单

     1 public Map<String, Object> weixinPay(DealRecord dealRecord, Byte memberGrade, String userId) {
     2         Map<String, Object> retMap = new HashMap<>(2);
     3 
     4         /**
     5          * 1、用户背景校验
     6          */
     7        
     8 
     9         /**
    10          * 2、准备请求参数
    11          */
    12         LinkedHashMap<String, String> requestDataMap = new LinkedHashMap<>();
    13         requestDataMap.put("appid", WeixinConstant.WX_CON_APPID);
    14         requestDataMap.put("attach", memberGrade + "");
    15         requestDataMap.put("body", dealRecord.getGoodsName() + "-" + memberGrade);
    16         requestDataMap.put("device_info", "WEB");
    17         requestDataMap.put("fee_type", "CNY");
    18         requestDataMap.put("mch_id", WeixinConstant.WX_CON_MCH_ID);
    19         requestDataMap.put("nonce_str", UUIDUtil.getUUID());
    20         requestDataMap.put("notify_url", weixinPayNotifyUrl);
    21         requestDataMap.put("openid", dealRecord.getOpenid());
    22         requestDataMap.put("out_trade_no", dealRecord.getId());
    23         requestDataMap.put("sign_type", "MD5");
    24         requestDataMap.put("spbill_create_ip", dealRecord.getDeviceIp());
    25         requestDataMap.put("time_start", DateUtil.formatDate(dealRecord.getCreateTime(), DateUtil.SDF_yyyyMMddHHmmss));
    26         // 虽然文档上total_fee为int,但是String也可以
    27         requestDataMap.put("total_fee", dealRecord.getAmount() + "");
    28         requestDataMap.put("trade_type", "JSAPI");
    29 
    30         /**
    31          * 3、微信请求参数签名+下单
    32          */
    33         WeixinSignUtil.getWeixinSign(requestDataMap, true);
    34         Map<String, String> wxResponseMap = weixinService.wxPayRequest(requestDataMap, WeixinConstant.WX_URL_PAY_UNIFIEDORDER);
    35 
    36         /**
    37          * 4、逻辑处理
    38          */
    39         String return_code = wxResponseMap.get("return_code");
    40         if (WeixinConstant.RESULT_FAIL.equals(return_code)) {
    41             retMap.put("result", 0);
    42             retMap.put("data", wxResponseMap);
    43             return retMap;
    44         } else if (WeixinConstant.RESULT_SUCCESS.equals(return_code)) {
    45             String prepay_id = wxResponseMap.get("prepay_id");
    46 
    47             LinkedHashMap<String, String> wxDataMap = new LinkedHashMap<>(6);
    48             wxDataMap.put("appId", WeixinConstant.WX_CON_APPID);
    49             wxDataMap.put("nonceStr", UUIDUtil.getUUID());
    50             wxDataMap.put("package", "prepay_id=" + prepay_id);
    51             wxDataMap.put("signType", "MD5");
    52             wxDataMap.put("timeStamp", String.valueOf(System.currentTimeMillis() / 1000));
    53 
    54             // 微信请求参数签名
    55             WeixinSignUtil.getWeixinSign(wxDataMap, false);
    56             
    57             retMap.put("result", 0);
    58             retMap.put("data", wxDataMap);
    59             return retMap;
    60         }
    61         retMap.put("result", 9);
    62         return retMap;
    63     }
    View Code

      5、微信支付结果通知及商户服务器响应

     1 /**
     2      * @param request
     3      * @function 微信/支付通知
     4      */
     5     @RequestMapping(value = "/weixin/pay/notify")
     6     @LoggerManage(logDescription = "用户-微信/支付/回调通知")
     7     public void weixinPayNotify(HttpServletRequest request, HttpServletResponse response) {
     8         BufferedReader reader = null;
     9         StringBuilder wxRequestStr = new StringBuilder();
    10         Map<String, String> wxRequestMap = null;
    11         try {
    12             reader = new BufferedReader(new InputStreamReader(request.getInputStream(), "UTF-8"));
    13             String line = null;
    14             while ((line = reader.readLine()) != null) {
    15                 wxRequestStr.append(line);
    16             }
    17 
    18             logger.info("wxNotifyReqStr:" + wxRequestStr.toString());
    19             wxRequestMap = WXPayUtil.xmlToMap(wxRequestStr.toString());
    20             logger.info("wxNotifyReqMap:" + wxRequestMap);
    21         } catch (IOException e) {
    22             e.printStackTrace();
    23         } catch (Exception e) {
    24             e.printStackTrace();
    25         } finally {
    26             try {
    27                 if (null != reader) {
    28                     reader.close();
    29                 }
    30             } catch (IOException e) {
    31             }
    32         }
    33 
    34         String return_code = wxRequestMap.get("return_code");
    35         if (WeixinConstant.RESULT_FAIL.equals(return_code)) {
    36             // 支付失败
    37             logger.info("交易失败!");
    38         } else if (WeixinConstant.RESULT_SUCCESS.equals(return_code)) {
    39             
    40             try {
    41                 BufferedOutputStream out = new BufferedOutputStream(response.getOutputStream());
    42                 out.write(WXPayUtil.mapToXml(WeixinConstant.WX_CON_PAY_RESPMAP).getBytes("UTF-8"));
    43                 out.flush();
    44                 out.close();
    45             } catch (IOException e) {
    46                 e.printStackTrace();
    47             } catch (Exception e) {
    48                 e.printStackTrace();
    49             }
    50         }
    51     }
    View Code

    九、填坑之路

      1、统一下单接口,请求微信,提示签名失败

    1)仔细检查统一下单接口里携带的参数,参数大小写、参数是否必须携带。比如是appid而不是appId;openid文档上写的是“”,但是它在备注里写到trade_type=JSAPI时(即JSAPI支付),此参数必传,这个注意啊;

    2)加密方式是否正确,可以把自己生成的sign与微信公众平台支付接口调试工具生成的sign做对比,如果不一致,那证明你的加签方式不正确,请仔细阅读加签安全规范这篇文章,比如里面提到的按照参数名ASCII字典序排序、拼接商户密钥、MD5加密并转化为大写;

    3)如果第二步没问题,好吧,不要犹豫,去重置商户秘钥吧,注意是商户秘钥,因为支付加签时和商户秘钥有关,我当时就是卡在这一步了好久,至今记忆深刻;

    4)如果第三步没问题,那么就是加签方法错误了,推荐使用微信工具包里面的加签方法。

      2、微信内h5调起支付,提示“支付验证签名失败”

    1)仔细检查参数,参数大小写、参数是否必须携带。比如是appId而不是appid(统一下单与这个正好相反);package的value必须是“prepay_id=xxx”形式;

    2)统一下单的签名的key是sign,而微信调起h5的签名的key是paySign,这个要注意;

    3)加密方式是否正确,可以把自己生成的sign与微信公众平台支付接口调试工具生成的sign做对比,如果不一致,那证明你的加签方式不正确,请仔细阅读加签安全规范这篇文章,比如里面提到的按照参数名ASCII字典序排序、拼接商户密钥、MD5加密并转化为大写;

    4)如果第三步没问题,好吧,不要犹豫,去重置商户秘钥吧。

      3、微信内h5调起支付,提示“当前页面未注册 http://xxx”

    1)检查微信商户平台–>产品中心–>开发配置–>支付授权目录是否配置;

    2)如果第一步没问题,但还是报错,那就得注意配置了。比如你的支付页是http://www.abc.com/pay/wxjspay/id/50.html,那配置为http://www.abc.com/pay/wxjspay/id/;假如是http://www.abc.com/pay/wxjspay.php,那配置为http://www.abc.com/pay/。规律就是去掉最后一个反斜杠的内容。

      

    在全栈的道路上,积极向上、成熟稳重、谦虚好学、怀着炽热的心向前方的走得更远。
  • 相关阅读:
    vscode debugger 调试服务
    巴克斯诺尔范式 && 乔姆斯基谱系,词法 && 语法
    推荐好用的建站系统以及各网站系统优缺点介绍
    解决emlog默认导航不能修改的问题以及修改后台登录地址的方法
    易企CMS主要模板文件介绍
    易企CMS模板调用标签列表
    易企CMS仿站标签说明
    使用Custom scrollbar(彩色滚动条)插件实现WordPress滚动条变色的方法
    2018给网页滚动条变色的新方法
    javascript实现双击网页自动滚动,单击滚动停止
  • 原文地址:https://www.cnblogs.com/DDgougou/p/10812418.html
Copyright © 2011-2022 走看看