Przeglądaj źródła

feat : 小程序支付模拟

bilingfeng 2 lat temu
rodzic
commit
21b24bd6de
17 zmienionych plików z 445 dodań i 124 usunięć
  1. 29 6
      game-module/game-common/src/main/java/com/zanxiang/common/enums/PayDeviceEnum.java
  2. 1 0
      game-module/game-manage/src/main/java/com/zanxiang/manage/service/Impl/PayMerchantServiceImpl.java
  3. 59 0
      game-module/game-sdk/src/main/java/com/zanxiang/sdk/common/constant/WXPayConstants.java
  4. 4 3
      game-module/game-sdk/src/main/java/com/zanxiang/sdk/common/miPay/SnsSigCheck.java
  5. 4 4
      game-module/game-sdk/src/main/java/com/zanxiang/sdk/common/util/DeviceCheckUtil.java
  6. 107 40
      game-module/game-sdk/src/main/java/com/zanxiang/sdk/common/util/WxPayUtil.java
  7. 92 48
      game-module/game-sdk/src/main/java/com/zanxiang/sdk/common/util/XMLUtil.java
  8. 23 6
      game-module/game-sdk/src/main/java/com/zanxiang/sdk/controller/OrderController.java
  9. 4 0
      game-module/game-sdk/src/main/java/com/zanxiang/sdk/domain/bo/ProductPayParamBO.java
  10. 2 1
      game-module/game-sdk/src/main/java/com/zanxiang/sdk/domain/params/PreOrderParam.java
  11. 5 4
      game-module/game-sdk/src/main/java/com/zanxiang/sdk/domain/params/ProductPayParam.java
  12. 22 0
      game-module/game-sdk/src/main/java/com/zanxiang/sdk/domain/vo/MiniAppConfigVO.java
  13. 1 1
      game-module/game-sdk/src/main/java/com/zanxiang/sdk/domain/vo/OrderCheckInfoVO.java
  14. 1 1
      game-module/game-sdk/src/main/java/com/zanxiang/sdk/domain/vo/PreOrderMiPayConfigVO.java
  15. 6 4
      game-module/game-sdk/src/main/java/com/zanxiang/sdk/domain/vo/PreOrderVO.java
  16. 2 1
      game-module/game-sdk/src/main/java/com/zanxiang/sdk/service/Impl/MiPayServiceImpl.java
  17. 83 5
      game-module/game-sdk/src/main/java/com/zanxiang/sdk/service/Impl/pay/WxPayServiceImpl.java

+ 29 - 6
game-module/game-common/src/main/java/com/zanxiang/common/enums/PayDeviceEnum.java

@@ -14,10 +14,31 @@ import java.util.stream.Collectors;
  */
 @Getter
 public enum PayDeviceEnum {
-    PC(1, "pc", "Pc端支付"),
-    H5(2, "h5", "Wap端支付"),
-    APP(3, "app", "App支付"),
-    MP(4, "mp", "小程序");
+
+    /**
+     * Pc端支付
+     */
+    PC_PAY(1, "pc", "Pc端支付"),
+
+    /**
+     * h5支付
+     */
+    H5_PAY(2, "h5", "Wap端h5支付"),
+
+    /**
+     * App支付
+     */
+    APP_PAY(3, "app", "App支付"),
+
+    /**
+     * 小程序支付
+     */
+    MINI_APP_PAY(4, "mp", "小程序支付"),
+
+    /**
+     * 小程序支付
+     */
+    MI_PAY(5, "miPay", "米大师支付");
 
     private Integer code;
 
@@ -34,8 +55,9 @@ public enum PayDeviceEnum {
     public static String getCodeByNum(Integer code) {
         PayDeviceEnum[] values = PayDeviceEnum.values();
         for (int i = 0; i < values.length; i++) {
-            if (values[i].getCode().equals(code))
+            if (values[i].getCode().equals(code)) {
                 return values[i].getName();
+            }
         }
         return "";
     }
@@ -43,8 +65,9 @@ public enum PayDeviceEnum {
     public static String getDescByNum(Integer code) {
         PayDeviceEnum[] values = PayDeviceEnum.values();
         for (int i = 0; i < values.length; i++) {
-            if (values[i].getCode().equals(code))
+            if (values[i].getCode().equals(code)) {
                 return values[i].getDesc();
+            }
         }
         return "";
     }

+ 1 - 0
game-module/game-manage/src/main/java/com/zanxiang/manage/service/Impl/PayMerchantServiceImpl.java

@@ -51,6 +51,7 @@ public class PayMerchantServiceImpl extends ServiceImpl<PayMerchantMapper, PayMe
         return BeanUtils.copy(data, PayMerchantDTO.class);
     }
 
+    @Override
     public Boolean saveOrUpdate(PayMerchantBO bo) {
         PayMerchant data = BeanUtils.copy(bo, PayMerchant.class);
         //删除时候增加删除时间

+ 59 - 0
game-module/game-sdk/src/main/java/com/zanxiang/sdk/common/constant/WXPayConstants.java

@@ -0,0 +1,59 @@
+package com.zanxiang.sdk.common.constant;
+
+import org.apache.http.client.HttpClient;
+
+/**
+ * 常量
+ */
+public class WXPayConstants {
+
+    public enum SignType {
+        MD5, HMACSHA256
+    }
+
+    public static final String DOMAIN_API = "api.mch.weixin.qq.com";
+    public static final String DOMAIN_API2 = "api2.mch.weixin.qq.com";
+    public static final String DOMAIN_APIHK = "apihk.mch.weixin.qq.com";
+    public static final String DOMAIN_APIUS = "apius.mch.weixin.qq.com";
+
+
+    public static final String FAIL     = "FAIL";
+    public static final String SUCCESS  = "SUCCESS";
+    public static final String HMACSHA256 = "HMAC-SHA256";
+    public static final String MD5 = "MD5";
+
+    public static final String FIELD_SIGN = "sign";
+    public static final String FIELD_SIGN_TYPE = "sign_type";
+
+    public static final String WXPAYSDK_VERSION = "WXPaySDK/3.0.9";
+    public static final String USER_AGENT = WXPAYSDK_VERSION +
+            " (" + System.getProperty("os.arch") + " " + System.getProperty("os.name") + " " + System.getProperty("os.version") +
+            ") Java/" + System.getProperty("java.version") + " HttpClient/" + HttpClient.class.getPackage().getImplementationVersion();
+
+    public static final String MICROPAY_URL_SUFFIX     = "/pay/micropay";
+    public static final String UNIFIEDORDER_URL_SUFFIX = "/pay/unifiedorder";
+    public static final String ORDERQUERY_URL_SUFFIX   = "/pay/orderquery";
+    public static final String REVERSE_URL_SUFFIX      = "/secapi/pay/reverse";
+    public static final String CLOSEORDER_URL_SUFFIX   = "/pay/closeorder";
+    public static final String REFUND_URL_SUFFIX       = "/secapi/pay/refund";
+    public static final String REFUNDQUERY_URL_SUFFIX  = "/pay/refundquery";
+    public static final String DOWNLOADBILL_URL_SUFFIX = "/pay/downloadbill";
+    public static final String REPORT_URL_SUFFIX       = "/payitil/report";
+    public static final String SHORTURL_URL_SUFFIX     = "/tools/shorturl";
+    public static final String AUTHCODETOOPENID_URL_SUFFIX = "/tools/authcodetoopenid";
+
+    // sandbox
+    public static final String SANDBOX_MICROPAY_URL_SUFFIX     = "/sandboxnew/pay/micropay";
+    public static final String SANDBOX_UNIFIEDORDER_URL_SUFFIX = "/sandboxnew/pay/unifiedorder";
+    public static final String SANDBOX_ORDERQUERY_URL_SUFFIX   = "/sandboxnew/pay/orderquery";
+    public static final String SANDBOX_REVERSE_URL_SUFFIX      = "/sandboxnew/secapi/pay/reverse";
+    public static final String SANDBOX_CLOSEORDER_URL_SUFFIX   = "/sandboxnew/pay/closeorder";
+    public static final String SANDBOX_REFUND_URL_SUFFIX       = "/sandboxnew/secapi/pay/refund";
+    public static final String SANDBOX_REFUNDQUERY_URL_SUFFIX  = "/sandboxnew/pay/refundquery";
+    public static final String SANDBOX_DOWNLOADBILL_URL_SUFFIX = "/sandboxnew/pay/downloadbill";
+    public static final String SANDBOX_REPORT_URL_SUFFIX       = "/sandboxnew/payitil/report";
+    public static final String SANDBOX_SHORTURL_URL_SUFFIX     = "/sandboxnew/tools/shorturl";
+    public static final String SANDBOX_AUTHCODETOOPENID_URL_SUFFIX = "/sandboxnew/tools/authcodetoopenid";
+
+}
+

+ 4 - 3
game-module/game-sdk/src/main/java/com/zanxiang/sdk/common/miPay/SnsSigCheck.java

@@ -38,14 +38,14 @@ public class SnsSigCheck {
         }
     }
 
-    /* 生成签名
+    /** 生成签名
      *
      * @param method HTTP请求方法 "get" / "post"
      * @param url_path CGI名字, eg: /v3/user/get_info
      * @param params URL请求参数
      * @param secret 密钥
      * @return 签名值
-     * @throws OpensnsException 不支持指定编码以及不支持指定的加密方法时抛出异常。
+     * @throws RuntimeException 不支持指定编码以及不支持指定的加密方法时抛出异常。
      */
     public static String makeSig(String method, String url_path, HashMap<String, String> params, String secret) throws RuntimeException {
         String sig = null;
@@ -67,12 +67,13 @@ public class SnsSigCheck {
         return sig;
     }
 
-    /* 生成签名所需源串
+    /** 生成签名所需源串
      *
      * @param method HTTP请求方法 "get" / "post"
      * @param url_path CGI名字, eg: /v3/user/get_info
      * @param params URL请求参数
      * @return 签名所需源串
+     * @throws RuntimeException 不支持指定编码以及不支持指定的加密方法时抛出异常。
      */
     public static String makeSource(String method, String url_path, HashMap<String, String> params) throws RuntimeException {
         Object[] keys = params.keySet().toArray();

+ 4 - 4
game-module/game-sdk/src/main/java/com/zanxiang/sdk/common/util/DeviceCheckUtil.java

@@ -70,17 +70,17 @@ public class DeviceCheckUtil {
      */
     public static Integer getType(String userAgent) {
         //todo app判断场景待加入
-        //todo 微信小程序场景待校验
         boolean isMobile = isMobile(userAgent);
         if (!isMobile) {
-            return PayDeviceEnum.PC.getCode();
+            return PayDeviceEnum.PC_PAY.getCode();
         }
+        //todo 微信小程序场景待校验
         //验证是否为微信小程序环境
         Matcher matcherWxMini = wxMiniPat.matcher(userAgent);
         if (matcherWxMini.find()) {
-            return PayDeviceEnum.MP.getCode();
+            return PayDeviceEnum.MINI_APP_PAY.getCode();
         }
-        return PayDeviceEnum.H5.getCode();
+        return PayDeviceEnum.H5_PAY.getCode();
     }
 
     /**

+ 107 - 40
game-module/game-sdk/src/main/java/com/zanxiang/sdk/common/util/WxPayUtil.java

@@ -1,16 +1,19 @@
 package com.zanxiang.sdk.common.util;
 
-import com.zanxiang.common.constant.Constants;
+import com.zanxiang.sdk.common.constant.WXPayConstants;
+import com.zanxiang.sdk.common.constant.WXPayConstants.SignType;
 import com.zanxiang.sdk.domain.bo.WxPayConfigBO;
 
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
 import java.io.BufferedReader;
 import java.io.InputStreamReader;
 import java.net.URL;
 import java.net.URLConnection;
 import java.nio.charset.StandardCharsets;
-import java.util.Map;
-import java.util.SortedMap;
-import java.util.TreeMap;
+import java.security.MessageDigest;
+import java.security.SecureRandom;
+import java.util.*;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -43,42 +46,6 @@ public class WxPayUtil {
         packageParams.put("nonce_str", nonce_str);
     }
 
-    /**
-     * 该接口主要用于扫码原生支付模式一中的二维码链接转成短链接(weixin://wxpay/s/XXXXXX)
-     * 减小二维码数据量,提升扫描速度和精确度
-     *
-     * @param urlCode
-     * @return
-     */
-    public String shortUrl(String urlCode) {
-        try {
-            String key = wxPay.getApiKey();
-            SortedMap<Object, Object> packageParams = new TreeMap<>();
-            commonParams(packageParams);
-            packageParams.put("long_url", urlCode);
-            String sign = PayCommonUtil.createSign("UTF-8", packageParams, key);
-            packageParams.put("sign", sign);
-            String requestXml = PayCommonUtil.getRequestXml(packageParams);
-            String resXml = HttpUtil.postData(WxPayUrl.SHORT_URL, requestXml);
-            Map map = XMLUtil.doXmlParse(resXml);
-            String returnCode = (String) map.get("return_code");
-            if (Constants.SUCCESS.equalsIgnoreCase(returnCode)) {
-                String resultCode = (String) map.get("return_code");
-                if (Constants.SUCCESS.equalsIgnoreCase(resultCode)) {
-                    urlCode = (String) map.get("short_url");
-                    return urlCode;
-                } else {
-                    return Constants.FAIL;
-                }
-            } else {
-                return Constants.FAIL;
-            }
-        } catch (Exception e) {
-            e.printStackTrace();
-            return Constants.FAIL;
-        }
-    }
-
     /**
      * 转换deepLink
      *
@@ -117,4 +84,104 @@ public class WxPayUtil {
         }
         return s;
     }
+
+    //------------------- 腾讯的工具类 -------------------
+
+    private static final String SYMBOLS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
+
+    private static final Random RANDOM = new SecureRandom();
+
+    /**
+     * 生成签名
+     *
+     * @param data 待签名数据
+     * @param key  API密钥
+     * @return 签名
+     */
+    public static String generateSignature(final Map<String, String> data, String key) throws Exception {
+        return generateSignature(data, key, SignType.MD5);
+    }
+
+    /**
+     * 生成签名. 注意,若含有sign_type字段,必须和signType参数保持一致。
+     *
+     * @param data     待签名数据
+     * @param key      API密钥
+     * @param signType 签名方式
+     * @return 签名
+     */
+    public static String generateSignature(final Map<String, String> data, String key, SignType signType) throws Exception {
+        Set<String> keySet = data.keySet();
+        String[] keyArray = keySet.toArray(new String[keySet.size()]);
+        Arrays.sort(keyArray);
+        StringBuilder sb = new StringBuilder();
+        for (String k : keyArray) {
+            if (k.equals(WXPayConstants.FIELD_SIGN)) {
+                continue;
+            }
+            // 参数值为空,则不参与签名
+            if (data.get(k).trim().length() > 0) {
+                sb.append(k).append("=").append(data.get(k).trim()).append("&");
+            }
+        }
+        sb.append("key=").append(key);
+        if (SignType.MD5.equals(signType)) {
+            return MD5(sb.toString()).toUpperCase();
+        } else if (SignType.HMACSHA256.equals(signType)) {
+            return HMACSHA256(sb.toString(), key);
+        } else {
+            throw new Exception(String.format("Invalid sign_type: %s", signType));
+        }
+    }
+
+
+    /**
+     * 获取随机字符串 Nonce Str
+     *
+     * @return String 随机字符串
+     */
+    public static String generateNonceStr() {
+        char[] nonceChars = new char[32];
+        for (int index = 0; index < nonceChars.length; ++index) {
+            nonceChars[index] = SYMBOLS.charAt(RANDOM.nextInt(SYMBOLS.length()));
+        }
+        return new String(nonceChars);
+    }
+
+
+    /**
+     * 生成 MD5
+     *
+     * @param data 待处理数据
+     * @return MD5结果
+     */
+    public static String MD5(String data) throws Exception {
+        java.security.MessageDigest md = MessageDigest.getInstance("MD5");
+        byte[] array = md.digest(data.getBytes("UTF-8"));
+        StringBuilder sb = new StringBuilder();
+        for (byte item : array) {
+            sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
+        }
+        return sb.toString().toUpperCase();
+    }
+
+    /**
+     * 生成 HMACSHA256
+     *
+     * @param data 待处理数据
+     * @param key  密钥
+     * @return 加密结果
+     * @throws Exception
+     */
+    public static String HMACSHA256(String data, String key) throws Exception {
+        Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
+        SecretKeySpec secret_key = new SecretKeySpec(key.getBytes("UTF-8"), "HmacSHA256");
+        sha256_HMAC.init(secret_key);
+        byte[] array = sha256_HMAC.doFinal(data.getBytes("UTF-8"));
+        StringBuilder sb = new StringBuilder();
+        for (byte item : array) {
+            sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
+        }
+        return sb.toString().toUpperCase();
+    }
 }

+ 92 - 48
game-module/game-sdk/src/main/java/com/zanxiang/sdk/common/util/XMLUtil.java

@@ -1,5 +1,6 @@
 package com.zanxiang.sdk.common.util;
 
+import com.zanxiang.common.exception.BaseException;
 import com.zanxiang.common.utils.StringUtils;
 import org.jdom.Document;
 import org.jdom.Element;
@@ -8,22 +9,112 @@ import org.jdom.input.SAXBuilder;
 import org.w3c.dom.Node;
 import org.w3c.dom.NodeList;
 
+import javax.xml.XMLConstants;
 import javax.xml.parsers.DocumentBuilder;
 import javax.xml.parsers.DocumentBuilderFactory;
 import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.StringWriter;
+import java.nio.charset.StandardCharsets;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 
 /**
- * XML解析
+ * @author : lingfeng
+ * @time : 2022-09-16
+ * @description : xml 转换工具类
  */
 public class XMLUtil {
 
+    public static org.w3c.dom.Document newDocument() throws ParserConfigurationException {
+        return newDocumentBuilder().newDocument();
+    }
+
+    private static DocumentBuilder newDocumentBuilder() throws ParserConfigurationException {
+        DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
+        documentBuilderFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
+        documentBuilderFactory.setFeature("http://xml.org/sax/features/external-general-entities", false);
+        documentBuilderFactory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
+        documentBuilderFactory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
+        documentBuilderFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
+        documentBuilderFactory.setXIncludeAware(false);
+        documentBuilderFactory.setExpandEntityReferences(false);
+
+        return documentBuilderFactory.newDocumentBuilder();
+    }
+
+    public static Map<String, String> xmlToMap(String strXml) {
+        try {
+            Map<String, String> data = new HashMap<>();
+            DocumentBuilder documentBuilder = XMLUtil.newDocumentBuilder();
+            InputStream stream = new ByteArrayInputStream(strXml.getBytes(StandardCharsets.UTF_8));
+            org.w3c.dom.Document doc = documentBuilder.parse(stream);
+            doc.getDocumentElement().normalize();
+            NodeList nodeList = doc.getDocumentElement().getChildNodes();
+            for (int idx = 0; idx < nodeList.getLength(); ++idx) {
+                Node node = nodeList.item(idx);
+                if (node.getNodeType() == Node.ELEMENT_NODE) {
+                    org.w3c.dom.Element element = (org.w3c.dom.Element) node;
+                    data.put(element.getNodeName(), element.getTextContent());
+                }
+            }
+            try {
+                stream.close();
+            } catch (Exception ex) {
+                throw new BaseException("流关闭异常");
+            }
+            return data;
+        } catch (Exception ex) {
+            throw new BaseException("xml转map异常");
+        }
+
+    }
+
+    public static String mapToXml(Map<String, String> data) {
+        try {
+            org.w3c.dom.Document document = XMLUtil.newDocument();
+            org.w3c.dom.Element root = document.createElement("xml");
+            document.appendChild(root);
+            for (String key : data.keySet()) {
+                String value = data.get(key);
+                if (value == null) {
+                    value = "";
+                }
+                value = value.trim();
+                org.w3c.dom.Element filed = document.createElement(key);
+                filed.appendChild(document.createTextNode(value));
+                root.appendChild(filed);
+            }
+            TransformerFactory tf = TransformerFactory.newInstance();
+            Transformer transformer = tf.newTransformer();
+            DOMSource source = new DOMSource(document);
+            transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
+            transformer.setOutputProperty(OutputKeys.INDENT, "yes");
+            StringWriter writer = new StringWriter();
+            StreamResult result = new StreamResult(writer);
+            transformer.transform(source, result);
+            String output = writer.getBuffer().toString();
+            try {
+                writer.close();
+            } catch (Exception ex) {
+                throw new BaseException("流关闭异常");
+            }
+            return output;
+        } catch (Exception e) {
+            throw new BaseException("map转xml异常");
+        }
+    }
+
+
     /**
      * 解析xml,返回第一级元素键值对。如果第一级元素有子节点,则此节点的值是子节点的xml数据。
      *
@@ -100,51 +191,4 @@ public class XMLUtil {
         xmlStr = xmlStr.replace("DOCTYPE", "").replace("SYSTEM", "").replace("ENTITY", "").replace("PUBLIC", "");
         return xmlStr;
     }
-
-    /**
-     * 微信给出的 XXE漏洞方案
-     * https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=23_5
-     *
-     * @param strXML
-     * @return
-     * @throws Exception
-     */
-    @SuppressWarnings({"rawtypes", "unchecked"})
-    public static Map doXMLParse2(String strXML) throws Exception {
-        Map<String, String> m = new HashMap<>();
-        DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
-        String FEATURE = null;
-        try {
-            FEATURE = "http://apache.org/xml/features/disallow-doctype-decl";
-            documentBuilderFactory.setFeature(FEATURE, true);
-
-            FEATURE = "http://xml.org/sax/features/external-general-entities";
-            documentBuilderFactory.setFeature(FEATURE, false);
-
-            FEATURE = "http://xml.org/sax/features/external-parameter-entities";
-            documentBuilderFactory.setFeature(FEATURE, false);
-
-            FEATURE = "http://apache.org/xml/features/nonvalidating/load-external-dtd";
-            documentBuilderFactory.setFeature(FEATURE, false);
-
-            documentBuilderFactory.setXIncludeAware(false);
-            documentBuilderFactory.setExpandEntityReferences(false);
-        } catch (ParserConfigurationException e) {
-            e.printStackTrace();
-        }
-        DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
-        InputStream stream = new ByteArrayInputStream(strXML.getBytes("UTF-8"));
-        org.w3c.dom.Document doc = documentBuilder.parse(stream);
-        doc.getDocumentElement().normalize();
-        NodeList nodeList = doc.getDocumentElement().getChildNodes();
-        for (int idx = 0; idx < nodeList.getLength(); ++idx) {
-            Node node = nodeList.item(idx);
-            if (node.getNodeType() == Node.ELEMENT_NODE) {
-                org.w3c.dom.Element element = (org.w3c.dom.Element) node;
-                m.put(element.getNodeName(), element.getTextContent());
-            }
-        }
-        stream.close();
-        return m;
-    }
 }

+ 23 - 6
game-module/game-sdk/src/main/java/com/zanxiang/sdk/controller/OrderController.java

@@ -2,9 +2,11 @@ package com.zanxiang.sdk.controller;
 
 import com.zanxiang.common.domain.ResultMap;
 import com.zanxiang.common.enums.PayDeviceEnum;
-import com.zanxiang.common.exception.CustomException;
+import com.zanxiang.common.enums.PayWayEnum;
+import com.zanxiang.common.utils.JsonUtil;
 import com.zanxiang.common.utils.StringUtils;
 import com.zanxiang.common.utils.bean.BeanUtils;
+import com.zanxiang.mybatis.entity.GamePayWay;
 import com.zanxiang.sdk.common.annotation.ValidLogin;
 import com.zanxiang.sdk.common.miPay.RequestParam;
 import com.zanxiang.sdk.domain.bo.PlatformOrderBO;
@@ -13,9 +15,11 @@ import com.zanxiang.sdk.domain.params.MPayBalanceParam;
 import com.zanxiang.sdk.domain.params.OrderCheckInfoParam;
 import com.zanxiang.sdk.domain.params.PreOrderParam;
 import com.zanxiang.sdk.domain.params.UserData;
+import com.zanxiang.sdk.domain.vo.MiniAppConfigVO;
 import com.zanxiang.sdk.domain.vo.OrderCheckInfoVO;
-import com.zanxiang.sdk.domain.vo.PreOrderMIPayConfigVO;
+import com.zanxiang.sdk.domain.vo.PreOrderMiPayConfigVO;
 import com.zanxiang.sdk.domain.vo.PreOrderVO;
+import com.zanxiang.sdk.service.GamePayWayService;
 import com.zanxiang.sdk.service.GameStrategyService;
 import com.zanxiang.sdk.service.MiPayService;
 import com.zanxiang.sdk.service.PlatformOrderService;
@@ -25,7 +29,12 @@ import io.swagger.annotations.ApiResponse;
 import io.swagger.annotations.ApiResponses;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.validation.annotation.Validated;
-import org.springframework.web.bind.annotation.*;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.Objects;
 
 /**
  * @author xufeng
@@ -44,6 +53,9 @@ public class OrderController {
     @Autowired
     private MiPayService miPayService;
 
+    @Autowired
+    private GamePayWayService gamePayWayService;
+
     @ApiOperation(value = "订单生成")
     @PostMapping(value = "/preOrder")
     @ApiResponses(value = {@ApiResponse(code = 200, message = "成功", response = PreOrderVO.class)})
@@ -66,10 +78,15 @@ public class OrderController {
             RequestParam commonParam = miPayService.getCommonParam(Long.valueOf(order.getGameId()), String.valueOf(user.getUserId()));
             //密钥不返回出去
             commonParam.setApp_key("");
-            data.setPreOrderMIPayConfigVO(BeanUtils.copy(commonParam, PreOrderMIPayConfigVO.class));
+            data.setPreOrderMiPayConfigVO(BeanUtils.copy(commonParam, PreOrderMiPayConfigVO.class));
+        }
+        //小程序支付, 返回小程序信息
+        if (Objects.equals(order.getPayDevice(), PayDeviceEnum.MINI_APP_PAY.getCode())) {
+            GamePayWay gamePayWay = gamePayWayService.getPayWayToOrderPay(order.getGameId(), PayWayEnum.WXPAY.getNum());
+            MiniAppConfigVO miniAppConfigVO = JsonUtil.toObj(gamePayWay.getPayConfig(), MiniAppConfigVO.class);
+            data.setMiniAppConfigVO(miniAppConfigVO);
         }
         orderBo.setIsSwitch(paySwitch);
-
         return ResultMap.ok(data);
     }
 
@@ -83,7 +100,7 @@ public class OrderController {
         OrderCheckInfoVO result = new OrderCheckInfoVO();
         result.setPath(path);
         PlatformOrderDTO orderInfo = orderService.info(param.getOrderId(), param.getUserId());
-        if (PayDeviceEnum.MP.getCode().equals(user.getDeviceType())) {
+        if (PayDeviceEnum.MINI_APP_PAY.getCode().equals(user.getDeviceType())) {
             RequestParam commonParam = miPayService.getCommonParam(orderInfo.getGameId(), param.getUserId());
             if (StringUtils.isEmpty(commonParam.getAppid())) {
                 return ResultMap.error("获取小程序appId");

+ 4 - 0
game-module/game-sdk/src/main/java/com/zanxiang/sdk/domain/bo/ProductPayParamBO.java

@@ -66,6 +66,10 @@ public class ProductPayParamBO {
      */
     private Integer payWay;
 
+    /**
+     * 微信小程序商城支付code
+     */
+    private String code;
 
     //================以下非传项=================
 

+ 2 - 1
game-module/game-sdk/src/main/java/com/zanxiang/sdk/domain/params/PreOrderParam.java

@@ -44,8 +44,9 @@ public class PreOrderParam extends CommonParam {
     @ApiModelProperty("CP附加参数")
     private String ext;
 
-
     @ApiModelProperty("渠道")
     private String channel;
 
+    @ApiModelProperty("支付方式 (1:PC, 2: h5支付, 3: App支付, 4: 小程序支付(微信小游戏), 5: 米大师支付(微信小游戏))")
+    private Integer payDevice;
 }

+ 5 - 4
game-module/game-sdk/src/main/java/com/zanxiang/sdk/domain/params/ProductPayParam.java

@@ -22,6 +22,7 @@ import java.math.BigDecimal;
 @NoArgsConstructor
 @AllArgsConstructor
 public class ProductPayParam extends CommonParam implements Serializable {
+
     private static final long serialVersionUID = 1L;
 
     @ApiModelProperty("订单id")
@@ -36,10 +37,10 @@ public class ProductPayParam extends CommonParam implements Serializable {
     @NotNull
     private Integer payWay;
 
+    @ApiModelProperty("小程序支付code")
+    private String code;
 
-//    @ApiModelProperty("支付方式 (1:PC,平板 2:Wap 3:App )")
-////    @NotNull
-//    private Integer payDevice;
-
+    @ApiModelProperty("支付方式 (1:PC, 2: h5支付, 3: App支付, 4: 小程序支付(微信小游戏), 5: 米大师支付(微信小游戏))")
+    private Integer payDevice;
 
 }

+ 22 - 0
game-module/game-sdk/src/main/java/com/zanxiang/sdk/domain/vo/MiniAppConfigVO.java

@@ -0,0 +1,22 @@
+package com.zanxiang.sdk.domain.vo;
+
+import lombok.Data;
+
+/**
+ * @author : lingfeng
+ * @time : 2022-09-16
+ * @description : 小程序参数
+ */
+@Data
+public class MiniAppConfigVO {
+
+    /**
+     * 小程appId
+     */
+    private String appId;
+
+    /**
+     * 小程序路径
+     */
+    private String path;
+}

+ 1 - 1
game-module/game-sdk/src/main/java/com/zanxiang/sdk/domain/vo/OrderCheckInfoVO.java

@@ -16,6 +16,6 @@ public class OrderCheckInfoVO {
     private String path;
 
     @ApiModelProperty(notes = "米大师支付配置")
-    private PreOrderMIPayConfigVO preOrderMIPayConfigVO;
+    private PreOrderMiPayConfigVO preOrderMIPayConfigVO;
 
 }

+ 1 - 1
game-module/game-sdk/src/main/java/com/zanxiang/sdk/domain/vo/PreOrderMIPayConfigVO.java → game-module/game-sdk/src/main/java/com/zanxiang/sdk/domain/vo/PreOrderMiPayConfigVO.java

@@ -9,7 +9,7 @@ import lombok.NoArgsConstructor;
  * 预下单返回值
  */
 @Data
-public class PreOrderMIPayConfigVO extends RequestParam {
+public class PreOrderMiPayConfigVO extends RequestParam {
 
 
 }

+ 6 - 4
game-module/game-sdk/src/main/java/com/zanxiang/sdk/domain/vo/PreOrderVO.java

@@ -3,14 +3,12 @@ package com.zanxiang.sdk.domain.vo;
 import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
 
-import java.math.BigDecimal;
-import java.util.Date;
-
 /**
  * 预下单返回值
  */
 @Data
 public class PreOrderVO {
+
     @ApiModelProperty(notes = "订单id")
     private String orderId;
 
@@ -18,5 +16,9 @@ public class PreOrderVO {
     private Boolean paySwitch;
 
     @ApiModelProperty(notes = "米大师配置")
-    private PreOrderMIPayConfigVO preOrderMIPayConfigVO;
+    private PreOrderMiPayConfigVO preOrderMiPayConfigVO;
+
+    @ApiModelProperty(notes = "小程序支付配置")
+    private MiniAppConfigVO miniAppConfigVO;
+
 }

+ 2 - 1
game-module/game-sdk/src/main/java/com/zanxiang/sdk/service/Impl/MiPayServiceImpl.java

@@ -198,7 +198,8 @@ public class MiPayServiceImpl implements MiPayService {
         RequestParam requestParam = new RequestParam();
         requestParam.setOpenid(openId);
         requestParam.setAppid(payApplicationInfo.getAppId());
-        requestParam.setOffer_id(miPayConfig.getAppId());//实际上是米大师应用id
+        //实际上是米大师应用id
+        requestParam.setOffer_id(miPayConfig.getAppId());
         requestParam.setApp_key(miPayConfig.getAppKey());
         //沙箱时使用沙箱key
         if (requestParam.getIs_sand() == 1) {

+ 83 - 5
game-module/game-sdk/src/main/java/com/zanxiang/sdk/service/Impl/pay/WxPayServiceImpl.java

@@ -6,6 +6,7 @@ import com.zanxiang.common.domain.ResultMap;
 import com.zanxiang.common.enums.ResEnum;
 import com.zanxiang.common.utils.JsonUtil;
 import com.zanxiang.common.utils.RandomStringUtil;
+import com.zanxiang.common.utils.URIUtil;
 import com.zanxiang.sdk.common.util.*;
 import com.zanxiang.sdk.domain.bo.ProductPayAttachParamBO;
 import com.zanxiang.sdk.domain.bo.ProductPayParamBO;
@@ -13,15 +14,18 @@ import com.zanxiang.sdk.domain.bo.WxPayConfigBO;
 import com.zanxiang.sdk.service.OrderPayService;
 import lombok.extern.slf4j.Slf4j;
 import org.jdom.JDOMException;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Component;
 import org.springframework.stereotype.Service;
+import org.springframework.web.client.RestTemplate;
 import weixin.popular.api.SnsAPI;
 
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import java.io.*;
 import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
 import java.util.Map;
 import java.util.SortedMap;
 import java.util.TreeMap;
@@ -45,6 +49,11 @@ public class WxPayServiceImpl extends PayService implements OrderPayService {
      */
     private static final String WX_PAY_MWEB = "MWEB";
 
+    /**
+     * JSAPI支付
+     */
+    private static final String WX_PAY_JSAPI = "JSAPI";
+
     /**
      * 服务器域名
      */
@@ -70,6 +79,10 @@ public class WxPayServiceImpl extends PayService implements OrderPayService {
 
     private String outTradeNo;
 
+    @Autowired
+    private RestTemplate restTemplate;
+
+
     /**
      * 支付调起
      *
@@ -92,10 +105,9 @@ public class WxPayServiceImpl extends PayService implements OrderPayService {
                 //手机端H5
                 return this.mobile(product, WX_PAY_MWEB);
 //            case 3:
-//                return this.mobile(product, "MWEB");
             case 4:
-                //微信小程序
-                return this.mp(product);
+                //微信商城小程序支付
+                return this.miniAppPay(product);
             default:
                 throw new RuntimeException("未知支付方式");
         }
@@ -122,7 +134,7 @@ public class WxPayServiceImpl extends PayService implements OrderPayService {
         inputStream.close();
         log.info("微信支付回调 body:{}", sb);
         // 解析xml成map
-        Map m = XMLUtil.doXmlParse(sb.toString());
+        Map m = XMLUtil.xmlToMap(sb.toString());
         // 过滤空 设置 TreeMap
         SortedMap<Object, Object> packageParams = new TreeMap<>();
         for (Object parameter : m.keySet()) {
@@ -196,6 +208,72 @@ public class WxPayServiceImpl extends PayService implements OrderPayService {
         return ResultMap.ok(result);
     }
 
+    private ResultMap miniAppPay(ProductPayParamBO product) {
+        try {
+            Map<Object, Object> miniMap = new HashMap<>(5);
+            // 请求微信服务器,使用code获取openid和session_key
+            Map<String, String> paramMap = new HashMap<>(4);
+            paramMap.put("appid", config.getAppId());
+            paramMap.put("secret", config.getAppSecret());
+            paramMap.put("js_code", product.getCode());
+            paramMap.put("grant_type", "authorization_code");
+            // 发送请求
+            String url = URIUtil.fillUrlParams("https://api.weixin.qq.com/sns/jscode2session", paramMap, false);
+            String sr = restTemplate.getForObject(url, String.class);
+            // 解析相应内容(转换成json对象)
+            Map<String, String> userMap = JsonUtil.toMap(sr, Map.class, String.class);
+            // 小程序调取微信支付需要的随机字符串
+            String nonceStr = WxPayUtil.generateNonceStr();
+            //参数
+            Map<String, String> paramData = new HashMap<>(13);
+            paramData.put("appid", config.getAppId());
+            paramData.put("mch_id", config.getMchId());
+            paramData.put("nonce_str", WxPayUtil.generateNonceStr());
+            paramData.put("openid", userMap == null ? null : userMap.get("openid"));
+            paramData.put("body", body);
+            paramData.put("out_trade_no", outTradeNo);
+            paramData.put("fee_type", "CNY");
+            paramData.put("total_fee", totalFee);
+            paramData.put("spbill_create_ip", product.getSpbillCreateIp());
+            paramData.put("notify_url", notifyUrl);
+            paramData.put("trade_type", WX_PAY_JSAPI);
+            paramData.put("sign_type", config.getSignType());
+            paramData.put("sign", WxPayUtil.generateSignature(paramData, config.getApiKey()));
+            // 下单, 获取结果
+            String result = restTemplate.postForObject(WxPayUrl.UNIFIED_ORDER_URL, XMLUtil.mapToXml(paramData), String.class);
+            // 时间戳
+            long timeStamp = System.currentTimeMillis() / 1000;
+            // 这边要将返回的时间戳转化成字符串,不然小程序端调用wx.requestPayment方法会报签名错误
+            String createTime = String.valueOf(timeStamp);
+            Map<String, String> successMap = XMLUtil.xmlToMap(result);
+            // 返回状态码
+            String returnCode = successMap.get("return_code");
+            // 结果状态码
+            String resultCode = successMap.get("result_code");
+            String prepayId = successMap.get("prepay_id");
+            // 获取统一下单回调结果;判断并二次签名
+            if (Constants.SUCCESS.equalsIgnoreCase(returnCode) && returnCode.equals(resultCode)) {
+                HashMap<String, String> map = new HashMap<>();
+                map.put("appId", config.getAppId());
+                map.put("timeStamp", createTime);
+                map.put("nonceStr", nonceStr);
+                map.put("package", "prepay_id=" + prepayId);
+                map.put("signType", config.getSignType());
+                // 再次签名sign,这个签名用于小程序端调用支付方法
+                String sign = WxPayUtil.generateSignature(map, config.getApiKey());
+                miniMap.put("paySign", sign);
+            }
+            miniMap.put("signType", config.getSignType());
+            miniMap.put("timeStamp", createTime);
+            miniMap.put("nonceStr", nonceStr);
+            miniMap.put("package", XMLUtil.xmlToMap(result));
+            return ResultMap.ok(miniMap);
+        } catch (Exception e) {
+            log.error("微信小程序支付通信异常, 订单号:{}, e : {}", product.getOutTradeNo(), e.getMessage());
+            return ResultMap.error();
+        }
+    }
+
     /**
      * H5手机支付(jspapi)
      *
@@ -228,7 +306,7 @@ public class WxPayServiceImpl extends PayService implements OrderPayService {
             packageParams.put("sign", sign);
             String requestXml = PayCommonUtil.getRequestXml(packageParams);
             String resXml = HttpUtil.postData(WxPayUrl.UNIFIED_ORDER_URL, requestXml);
-            Map map = XMLUtil.doXmlParse(resXml);
+            Map map = XMLUtil.xmlToMap(resXml);
             if (map == null) {
                 log.error("微信支付通信异常, 订单号:{}", product.getOutTradeNo());
                 return ResultMap.error();