java110 преди 5 години
родител
ревизия
8675ae5b5a

+ 167 - 0
java110-core/src/main/java/com/java110/core/factory/ChinaUmsFactory.java

@@ -0,0 +1,167 @@
+package com.java110.core.factory;
+
+import com.alibaba.fastjson.JSONObject;
+import com.java110.core.annotation.Java110Synchronized;
+import com.java110.utils.cache.JWTCache;
+import com.java110.utils.cache.MappingCache;
+import com.java110.utils.constant.WechatConstant;
+import com.java110.utils.factory.ApplicationContextFactory;
+import com.java110.utils.util.PayUtil;
+import com.java110.utils.util.StringUtil;
+import org.apache.commons.codec.binary.Base64;
+import org.apache.commons.codec.binary.Hex;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.client.RestTemplate;
+
+import javax.crypto.Cipher;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+import java.io.UnsupportedEncodingException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+/**
+ * @ClassName 银联工具类
+ * @Description TODO
+ * @Author wuxw
+ * @Date 2020/6/13 22:15
+ * @Version 1.0
+ * add by wuxw 2020/6/13
+ **/
+public class ChinaUmsFactory {
+
+    private static Logger logger = LoggerFactory.getLogger(ChinaUmsFactory.class);
+
+    private static final String password = "you are bad boy!";
+
+
+    private static final String CHINA_UMS = "CHINA_UMS";
+    public static final String CHINA_UMS_DOMAIN = "CHINA_UMS";
+    private static final String GET_ACCESS_TOKEN = "https://api-mop.chinaums.com/v1/token/access";
+
+
+    private static final String SPLIT_STUB = "-";
+
+    /**
+     * 获取accessToken
+     *
+     * @return
+     */
+    @Java110Synchronized(value = "appId")
+    public static String getAccessToken() {
+        String appId = MappingCache.getValue(ChinaUmsFactory.CHINA_UMS_DOMAIN, "appId");
+        String appSecure = MappingCache.getValue(ChinaUmsFactory.CHINA_UMS_DOMAIN, "appKey");
+        String accessToken = JWTCache.getValue(CHINA_UMS + appId);
+        if (StringUtil.isEmpty(accessToken)) {
+            return refreshAccessToken(appId, appSecure);
+        }
+        return accessToken;
+    }
+
+    /**
+     * 刷新 access_token
+     *
+     * @param appId     应用ID
+     * @param appSecure 应用秘钥
+     * @return
+     */
+    private static String refreshAccessToken(String appId, String appSecure) {
+        String url = MappingCache.getRemark(CHINA_UMS_DOMAIN, WechatConstant.GET_ACCESS_TOKEN_URL);
+        if (StringUtil.isEmpty(url)) {
+            url = GET_ACCESS_TOKEN;
+        }
+
+        JSONObject paramMap = new JSONObject();
+        paramMap.put("appId", appId);
+        paramMap.put("timestamp", PayUtil.getCurrentTimeStamp());
+        paramMap.put("nonce", PayUtil.makeUUID(32));
+        paramMap.put("signMethod", "SHA256");
+        paramMap.put("signature", getSignature(paramMap, appSecure));
+
+
+        RestTemplate outRestTemplate = ApplicationContextFactory.getBean("outRestTemplate", RestTemplate.class);
+        ResponseEntity<String> response = outRestTemplate.postForEntity(url, paramMap.toJSONString(), String.class);
+
+        logger.debug("获取access_token 入参:" + url + " 返回参数" + response);
+
+        JSONObject responseObj = JSONObject.parseObject(response.getBody());
+
+        if (!"0000".equals(responseObj.getString("errCode"))) {
+            throw new IllegalArgumentException("获取签名失败");
+        }
+
+        if (responseObj.containsKey("accessToken")) {
+            String accessToken = responseObj.getString("accessToken");
+            int expiresIn = responseObj.getInteger("expiresIn");
+            JWTCache.setValue(CHINA_UMS + appId, accessToken, expiresIn - 200);
+            return accessToken;
+        }
+        return "";
+    }
+
+    public static String getSignature(JSONObject paramMap, String appSecure) {
+        MessageDigest messageDigest;
+        String str = paramMap.getString("appId") + paramMap.getString("timestamp") + paramMap.getString("nonce") + appSecure;
+        String encdeStr = "";
+        try {
+            messageDigest = MessageDigest.getInstance("SHA-256");
+            byte[] hash = messageDigest.digest(str.getBytes("UTF-8"));
+            encdeStr = Hex.encodeHexString(hash);
+        } catch (NoSuchAlgorithmException e) {
+            e.printStackTrace();
+        } catch (UnsupportedEncodingException e) {
+            e.printStackTrace();
+        }
+        return encdeStr;
+    }
+
+    /**
+     * 获取微信页面ID
+     *
+     * @param appId
+     * @return
+     */
+    public static String getWId(String appId) {
+        return AuthenticationFactory.encrypt(password, appId);
+    }
+
+    /**
+     * 获取微信AppId
+     *
+     * @param wId
+     * @return
+     */
+    public static String getAppId(String wId) {
+        wId = wId.replace(" ", "+");
+        return AuthenticationFactory.decrypt(password, wId);
+    }
+
+
+    public static String getPhoneNumberBeanS5(String decryptData, String key, String iv) {
+        /*
+         *这里你没必要非按照我的方式写,下面打代码主要是在一个自己的类中 放上decrypts5这个解密工具,工具在下方有代码
+         */
+        String resultMessage = decryptS5(decryptData, "UTF-8", key, iv);
+        return resultMessage;
+    }
+
+    public static String decryptS5(String sSrc, String encodingFormat, String sKey, String ivParameter) {
+        try {
+            Base64 base64 = new Base64();
+            byte[] raw = base64.decode(sKey);
+            SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
+            IvParameterSpec iv = new IvParameterSpec(base64.decode(ivParameter));
+            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
+            cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);
+            byte[] myendicod = base64.decode(sSrc);
+            byte[] original = cipher.doFinal(myendicod);
+            String originalString = new String(original, encodingFormat);
+            return originalString;
+        } catch (Exception ex) {
+            return null;
+        }
+    }
+
+}

+ 36 - 0
service-front/src/main/java/com/java110/front/controller/app/PaymentController.java

@@ -15,6 +15,7 @@
  */
 package com.java110.front.controller.app;
 
+import com.alibaba.fastjson.JSONObject;
 import com.java110.core.base.controller.BaseController;
 import com.java110.core.context.IPageData;
 import com.java110.core.context.PageData;
@@ -129,6 +130,7 @@ public class PaymentController extends BaseController {
                 appId);
         return toPayInGoOutSMOImpl.toPay(newPd);
     }
+
     /**
      * <p>统一下单入口</p>
      *
@@ -149,6 +151,7 @@ public class PaymentController extends BaseController {
                 appId);
         return toPayBackCitySMOImpl.toPay(newPd);
     }
+
     /**
      * <p>统一下单入口</p>
      *
@@ -182,10 +185,26 @@ public class PaymentController extends BaseController {
         logger.debug("微信支付回调报文" + postInfo);
 
         return toNotifySMOImpl.toNotify(postInfo, request);
+    }
 
+    /**
+     * <p>支付回调Api</p>
+     *
+     * @param request
+     * @throws Exception
+     */
+    @RequestMapping(path = "/notifyChinaUms", method = RequestMethod.POST)
+    public ResponseEntity<String> notifyChinaUms(HttpServletRequest request) {
+        JSONObject paramIn = new JSONObject();
+        for(String key :request.getParameterMap().keySet()){
+            paramIn.put(key,request.getParameter(key));
+        }
+        logger.debug("微信支付回调报文" + paramIn.toJSONString());
 
+        return toNotifySMOImpl.toNotify(paramIn.toJSONString(), request);
     }
 
+
     /**
      * <p>出租统一下单入口</p>
      *
@@ -206,6 +225,23 @@ public class PaymentController extends BaseController {
         return rentingToPaySMOImpl.toPay(newPd);
     }
 
+    /**
+     * <p>欠费银联回调</p>
+     *
+     * @param request
+     * @throws Exception
+     */
+    @RequestMapping(path = "/oweFeeNotifyChinaUms", method = RequestMethod.POST)
+    public ResponseEntity<String> oweFeeNotifyChinaUms( HttpServletRequest request) {
+        JSONObject paramIn = new JSONObject();
+        for(String key :request.getParameterMap().keySet()){
+            paramIn.put(key,request.getParameter(key));
+        }
+        logger.debug("微信支付回调报文" + paramIn.toJSONString());
+
+        return oweFeeToNotifySMOImpl.toNotify(paramIn.toJSONString(), request);
+    }
+
     /**
      * <p>出租统一下单入口</p>
      *

+ 241 - 0
service-front/src/main/java/com/java110/front/smo/payment/adapt/chinaums/ChinaUmsOweFeeToNotifyAdapt.java

@@ -0,0 +1,241 @@
+/*
+ * Copyright 2017-2020 吴学文 and java110 team.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.java110.front.smo.payment.adapt.chinaums;
+
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import com.java110.core.factory.WechatFactory;
+import com.java110.dto.fee.FeeDto;
+import com.java110.dto.smallWeChat.SmallWeChatDto;
+import com.java110.front.properties.WechatAuthProperties;
+import com.java110.front.smo.payment.adapt.IOweFeeToNotifyAdapt;
+import com.java110.utils.cache.CommonCache;
+import com.java110.utils.constant.CommonConstant;
+import com.java110.utils.constant.ServiceConstant;
+import com.java110.utils.util.BeanConvertUtil;
+import com.java110.utils.util.DateUtil;
+import com.java110.utils.util.PayUtil;
+import com.java110.utils.util.StringUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.*;
+import org.springframework.stereotype.Component;
+import org.springframework.web.client.HttpStatusCodeException;
+import org.springframework.web.client.RestTemplate;
+
+import java.util.Date;
+import java.util.SortedMap;
+import java.util.TreeMap;
+import java.util.UUID;
+
+/**
+ * 富友 支付 通知实现
+ * 说明:信息通过 http 或 https 形式 post 请求递交给前置系统,编码必须为 UTF-8
+ * Json 格式参数名:如下表
+ * 参数值:如下表
+ * 测试地址:商户提供
+ * 生产地址:待定
+ * <p>
+ * 如图中第 6 步中异步回调,下单(主扫)交易的结果是以异步的形式进行回调的。富友在接受到支付宝等支付通道的回调结果以
+ * 后再回调商户。商户接收回调成功处理成功后返回字符串”1” , 后富友停止回调给商户。最多回调 5 次,每次间隔 30S。
+ * (重要~重要~重要:不保证通知最终一定能成功,在订单状态不明或者没有收到微信,支付结果通知的情况下,
+ * 建议商户主动调用【2.3 订单查询】确认订单状态)
+ * 只有主扫、公众号/服务窗支付会通过此接口发异步通知,条码支付没有异步通知。
+ *
+ * @desc add by 吴学文 15:33
+ */
+
+@Component(value = "chinaUmsOweFeeToNotifyAdapt")
+public class ChinaUmsOweFeeToNotifyAdapt implements IOweFeeToNotifyAdapt {
+
+    private static final Logger logger = LoggerFactory.getLogger(ChinaUmsOweFeeToNotifyAdapt.class);
+
+    private static final String APP_ID = "992020011134400001";
+
+    @Autowired
+    private RestTemplate restTemplate;
+
+    @Autowired
+    private WechatAuthProperties wechatAuthProperties;
+
+    /**
+     * 预下单
+     *
+     * @param param
+     * @return
+     * @throws Exception
+     */
+    public String confirmPayFee(String param, String wId) {
+        JSONObject resJson = new JSONObject();
+        resJson.put("errCode", "INTERNAL_ERROR");
+        resJson.put("errMsg", "失败");
+        try {
+            JSONObject map = JSONObject.parseObject(param);
+            logger.info("【银联支付回调】 回调数据: \n" + map);
+            //更新数据
+            int result = confirmPayFee(map, wId);
+            if (result > 0) {
+                //支付成功
+                resJson.put("errCode", "SUCCESS");
+                resJson.put("errMsg", "成功");
+            }
+        } catch (Exception e) {
+            logger.error("通知失败", e);
+            resJson.put("result_msg", "鉴权失败");
+        }
+
+        return resJson.toJSONString();
+    }
+
+
+    public int confirmPayFee(JSONObject map, String wId) {
+        wId = wId.replace(" ", "+");
+        ResponseEntity<String> responseEntity = null;
+
+        String appId = WechatFactory.getAppId(wId);
+        SmallWeChatDto smallWeChatDto = getSmallWechat(appId);
+
+        if (smallWeChatDto == null) { //从配置文件中获取 小程序配置信息
+            smallWeChatDto = new SmallWeChatDto();
+            smallWeChatDto.setAppId(wechatAuthProperties.getAppId());
+            smallWeChatDto.setAppSecret(wechatAuthProperties.getSecret());
+            smallWeChatDto.setMchId(wechatAuthProperties.getMchId());
+            smallWeChatDto.setPayPassword(wechatAuthProperties.getKey());
+        }
+        SortedMap<String, String> paramMap = new TreeMap<String, String>();
+        for (String key : map.keySet()) {
+            if ("wId".equals(key)) {
+                continue;
+            }
+            paramMap.put(key, map.get(key).toString());
+        }
+        String sign = PayUtil.createSign(paramMap, smallWeChatDto.getPayPassword());
+        if (!sign.equals(map.get("sign"))) {
+            throw new IllegalArgumentException("鉴权失败");
+        }
+        JSONObject billPayment = JSONObject.parseObject(map.getString("billPayment"));
+        String orderId = billPayment.get("merOrderId").toString();
+        String order = CommonCache.getAndRemoveValue(FeeDto.REDIS_PAY_OWE_FEE + orderId);
+
+        if (StringUtil.isEmpty(order)) {
+            return 1;// 说明已经处理过了 再不处理
+        }
+
+        //查询用户ID
+        JSONObject paramIn = JSONObject.parseObject(order);
+        paramIn.put("oId", orderId);
+        freshFees(paramIn);
+        String url = ServiceConstant.SERVICE_API_URL + "/api/fee.payOweFee";
+        responseEntity = this.callCenterService(restTemplate, "-1", paramIn.toJSONString(), url, HttpMethod.POST);
+
+        if (responseEntity.getStatusCode() != HttpStatus.OK) {
+            return 0;
+        }
+        return 1;
+    }
+
+
+    private void freshFees(JSONObject paramIn) {
+        if (!paramIn.containsKey("fees")) {
+            return;
+        }
+
+        JSONArray fees = paramIn.getJSONArray("fees");
+        JSONObject fee = null;
+        for (int feeIndex = 0; feeIndex < fees.size(); feeIndex++) {
+            fee = fees.getJSONObject(feeIndex);
+            if (fee.containsKey("deadlineTime")) {
+                fee.put("startTime", fee.getString("endTime"));
+                fee.put("endTime", fee.getString("deadlineTime"));
+                fee.put("receivedAmount", fee.getString("feePrice"));
+                fee.put("state", "");
+            }
+        }
+    }
+
+
+    /**
+     * 调用中心服务
+     *
+     * @return
+     */
+    protected ResponseEntity<String> callCenterService(RestTemplate restTemplate, String userId, String param, String url, HttpMethod httpMethod) {
+
+        ResponseEntity<String> responseEntity = null;
+        HttpHeaders header = new HttpHeaders();
+        header.add(CommonConstant.HTTP_APP_ID.toLowerCase(), APP_ID);
+        header.add(CommonConstant.HTTP_USER_ID.toLowerCase(), userId);
+        header.add(CommonConstant.HTTP_TRANSACTION_ID.toLowerCase(), UUID.randomUUID().toString());
+        header.add(CommonConstant.HTTP_REQ_TIME.toLowerCase(), DateUtil.getDefaultFormateTimeString(new Date()));
+        header.add(CommonConstant.HTTP_SIGN.toLowerCase(), "");
+        header.add("Content-Type", "application/json");
+        HttpEntity<String> httpEntity = new HttpEntity<String>(param, header);
+        //logger.debug("请求中心服务信息,{}", httpEntity);
+        try {
+            responseEntity = restTemplate.exchange(url, httpMethod, httpEntity, String.class);
+        } catch (HttpStatusCodeException e) { //这里spring 框架 在4XX 或 5XX 时抛出 HttpServerErrorException 异常,需要重新封装一下
+            responseEntity = new ResponseEntity<String>(e.getResponseBodyAsString(), e.getStatusCode());
+        } catch (Exception e) {
+            responseEntity = new ResponseEntity<String>(e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
+        } finally {
+            logger.debug("请求地址为,{} 请求中心服务信息,{},中心服务返回信息,{}", url, httpEntity, responseEntity);
+        }
+        return responseEntity;
+    }
+
+
+    private SmallWeChatDto getSmallWechat(String appId) {
+
+        ResponseEntity responseEntity = null;
+
+        responseEntity = this.callCenterService(restTemplate, "-1", "",
+                ServiceConstant.SERVICE_API_URL + "/api/smallWeChat.listSmallWeChats?appId="
+                        + appId + "&page=1&row=1", HttpMethod.GET);
+
+        if (responseEntity.getStatusCode() != HttpStatus.OK) {
+            return null;
+        }
+        JSONObject smallWechatObj = JSONObject.parseObject(responseEntity.getBody().toString());
+        JSONArray smallWeChats = smallWechatObj.getJSONArray("smallWeChats");
+
+        if (smallWeChats == null || smallWeChats.size() < 1) {
+            return null;
+        }
+
+        return BeanConvertUtil.covertBean(smallWeChats.get(0), SmallWeChatDto.class);
+    }
+
+    /**
+     * 富友 生成sign 方法
+     *
+     * @param paramMap
+     * @param payPassword
+     * @return
+     */
+    private String createSign(JSONObject paramMap, String payPassword) {
+        String str = paramMap.getString("mchnt_cd") + "|"
+                + paramMap.getString("mchnt_order_no") + "|"
+                + paramMap.getString("settle_order_amt") + "|"
+                + paramMap.getString("order_amt") + "|"
+                + paramMap.getString("txn_fin_ts") + "|"
+                + paramMap.getString("reserved_fy_settle_dt") + "|"
+                + paramMap.getString("random_str") + "|"
+                + payPassword;
+        return PayUtil.md5(str);
+    }
+
+}

+ 200 - 0
service-front/src/main/java/com/java110/front/smo/payment/adapt/chinaums/ChinaUmsPayAdapt.java

@@ -0,0 +1,200 @@
+/*
+ * Copyright 2017-2020 吴学文 and java110 team.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.java110.front.smo.payment.adapt.chinaums;
+
+import com.alibaba.fastjson.JSONObject;
+import com.java110.core.factory.ChinaUmsFactory;
+import com.java110.core.factory.WechatFactory;
+import com.java110.dto.smallWeChat.SmallWeChatDto;
+import com.java110.front.properties.WechatAuthProperties;
+import com.java110.front.smo.payment.adapt.IPayAdapt;
+import com.java110.utils.cache.MappingCache;
+import com.java110.utils.constant.WechatConstant;
+import com.java110.utils.util.DateUtil;
+import com.java110.utils.util.PayUtil;
+import com.java110.utils.util.StringUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.*;
+import org.springframework.stereotype.Component;
+import org.springframework.web.client.RestTemplate;
+
+import java.util.Map;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+/**
+ * 本接口为商户的订单H5页面向银商网络支付前置系统发起的支付跳转
+ * 商户需遵循商户订单号生成规范,即以银商分配的4位来源编号作为账单号的前4位,且在商户系统中此账单号保证唯一。总长
+ * 度需大于6位,小于28位。银商的推荐规则为(无特殊情况下,建议遵守此规则):
+ * {来源编号(4位)}{时间(yyyyMMddmmHHssSSS)(17位)}{7位随机数}
+ *
+ * @desc add by 吴学文 15:33
+ */
+
+@Component(value = "chinaUmsPayAdapt")
+public class ChinaUmsPayAdapt implements IPayAdapt {
+    private static final Logger logger = LoggerFactory.getLogger(ChinaUmsPayAdapt.class);
+
+    //微信支付
+    public static final String PAY_UNIFIED_ORDER_URL = "https://api-mop.chinaums.com/v1/netpay/wx/unified-order";
+
+
+    private static final String VERSION = "1.0";
+
+    @Autowired
+    private WechatAuthProperties wechatAuthProperties;
+
+    /**
+     * 预下单
+     *
+     * @param orderNum
+     * @param money
+     * @param openId
+     * @return
+     * @throws Exception
+     */
+    public Map<String, String> java110Payment(RestTemplate outRestTemplate,
+                                              String feeName, String tradeType,
+                                              String orderNum, double money,
+                                              String openId, SmallWeChatDto smallWeChatDto) throws Exception {
+        return java110Payment(outRestTemplate, feeName, tradeType, orderNum, money, openId, smallWeChatDto, "");
+    }
+
+    /**
+     * 预下单
+     *
+     * @param orderNum
+     * @param money
+     * @param openId
+     * @return
+     * @throws Exception
+     */
+    public Map<String, String> java110Payment(RestTemplate outRestTemplate,
+                                              String feeName, String tradeType,
+                                              String orderNum, double money,
+                                              String openId, SmallWeChatDto smallWeChatDto, String notifyUrl) throws Exception {
+        logger.info("【小程序支付】 统一下单开始, 订单编号=" + orderNum);
+        SortedMap<String, String> resultMap = new TreeMap<String, String>();
+        //生成支付金额,开发环境处理支付金额数到0.01、0.02、0.03元
+        double payAmount = PayUtil.getPayAmountByEnv(MappingCache.getValue("HC_ENV"), money);
+        //添加或更新支付记录(参数跟进自己业务需求添加)
+
+        JSONObject resMap = null;
+
+        if (StringUtil.isEmpty(notifyUrl)) {
+            resMap = this.java110UnifieldOrder(outRestTemplate, feeName, orderNum, tradeType, payAmount, openId, smallWeChatDto);
+        } else {
+            resMap = this.java110UnifieldOrder(outRestTemplate, feeName, orderNum, tradeType, payAmount, openId, smallWeChatDto, notifyUrl);
+        }
+
+        if ("SUCCESS".equals(resMap.getString("errCode"))) {
+            if (WechatAuthProperties.TRADE_TYPE_JSAPI.equals(tradeType)) {
+                resultMap.putAll(JSONObject.toJavaObject(JSONObject.parseObject(resMap.getString("jsPayRequest")), Map.class));
+                resultMap.put("sign",resultMap.get("paySign"));
+            } else if (WechatAuthProperties.TRADE_TYPE_APP.equals(tradeType)) {
+                resultMap.put("appId", smallWeChatDto.getAppId());
+                resultMap.put("timeStamp", PayUtil.getCurrentTimeStamp());
+                resultMap.put("nonceStr", PayUtil.makeUUID(32));
+                resultMap.put("partnerid", smallWeChatDto.getMchId());
+                resultMap.put("prepayid", resMap.getString("session_id"));
+                //resultMap.put("signType", "MD5");
+                resultMap.put("sign", PayUtil.createSign(resultMap, smallWeChatDto.getPayPassword()));
+            } else if (WechatAuthProperties.TRADE_TYPE_NATIVE.equals(tradeType)) {
+                resultMap.put("prepayId", resMap.getString("session_id"));
+                resultMap.put("codeUrl", resMap.getString("qr_code"));
+            }
+            resultMap.put("code", "0");
+            resultMap.put("msg", "下单成功");
+            logger.info("【小程序支付】统一下单成功,返回参数:" + resultMap);
+        } else {
+            resultMap.put("code", resMap.getString("errCode"));
+            resultMap.put("msg", resMap.getString("errMsg"));
+            logger.info("【小程序支付】统一下单失败,失败原因:" + resMap.get("errMsg"));
+        }
+        return resultMap;
+    }
+
+    /**
+     * 小程序支付统一下单
+     */
+    private JSONObject java110UnifieldOrder(RestTemplate outRestTemplate, String feeName, String orderNum,
+                                            String tradeType, double payAmount, String openid,
+                                            SmallWeChatDto smallWeChatDto) throws Exception {
+        return java110UnifieldOrder(outRestTemplate, feeName, orderNum, tradeType, payAmount, openid, smallWeChatDto, wechatAuthProperties.getWxNotifyUrl());
+    }
+
+    /**
+     * 小程序支付统一下单
+     */
+    private JSONObject java110UnifieldOrder(RestTemplate outRestTemplate, String feeName, String orderNum,
+                                            String tradeType, double payAmount, String openid,
+                                            SmallWeChatDto smallWeChatDto, String notifyUrl) throws Exception {
+
+        String systemName = MappingCache.getValue(WechatConstant.WECHAT_DOMAIN, WechatConstant.PAY_GOOD_NAME);
+
+        JSONObject paramMap = new JSONObject();
+        paramMap.put("requestTimestamp", DateUtil.getNow(DateUtil.DATE_FORMATE_STRING_A));
+        paramMap.put("mid", smallWeChatDto.getMchId()); // 富友分配给二级商户的商户号
+        paramMap.put("tid", "88880001"); //终端号
+        paramMap.put("instMid", "YUEDANDEFAULT");
+        paramMap.put("merOrderId", orderNum);
+        paramMap.put("totalAmount", PayUtil.moneyToIntegerStr(payAmount));
+        paramMap.put("notifyUrl", notifyUrl + "?wId=" + WechatFactory.getWId(smallWeChatDto.getAppId()));
+        paramMap.put("tradeType", tradeType);
+        paramMap.put("subopenId", openid);
+        paramMap.put("subAppId", smallWeChatDto.getAppId());
+
+        logger.debug("调用支付统一下单接口" + paramMap.toJSONString());
+        HttpHeaders headers = new HttpHeaders();
+        headers.add("Content-Type", "application/json");
+        headers.add("Authorization", ChinaUmsFactory.getAccessToken());
+        HttpEntity httpEntity = new HttpEntity(paramMap.toJSONString(), headers);
+        ResponseEntity<String> responseEntity = outRestTemplate.exchange(
+                wechatAuthProperties.getWxPayUnifiedOrder(), HttpMethod.POST, httpEntity, String.class);
+
+        logger.debug("统一下单返回" + responseEntity);
+
+        if (responseEntity.getStatusCode() != HttpStatus.OK) {
+            throw new IllegalArgumentException("支付失败" + responseEntity.getBody());
+        }
+        return JSONObject.parseObject(responseEntity.getBody());
+    }
+
+    /**
+     * 富友 生成sign 方法
+     *
+     * @param paramMap
+     * @param payPassword
+     * @return
+     */
+    private String createSign(JSONObject paramMap, String payPassword) {
+        String str = paramMap.getString("mchnt_cd") + "|"
+                + paramMap.getString("trade_type") + "|"
+                + paramMap.getString("order_amt") + "|"
+                + paramMap.getString("mchnt_order_no") + "|"
+                + paramMap.getString("txn_begin_ts") + "|"
+                + paramMap.getString("goods_des") + "|"
+                + paramMap.getString("term_id") + "|"
+                + paramMap.getString("term_ip") + "|"
+                + paramMap.getString("notify_url") + "|"
+                + paramMap.getString("random_str") + "|"
+                + paramMap.getString("version") + "|"
+                + payPassword;
+        return PayUtil.md5(str);
+    }
+}

+ 214 - 0
service-front/src/main/java/com/java110/front/smo/payment/adapt/chinaums/ChinaUmsPayNotifyAdapt.java

@@ -0,0 +1,214 @@
+/*
+ * Copyright 2017-2020 吴学文 and java110 team.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.java110.front.smo.payment.adapt.chinaums;
+
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import com.java110.core.factory.WechatFactory;
+import com.java110.dto.smallWeChat.SmallWeChatDto;
+import com.java110.front.properties.WechatAuthProperties;
+import com.java110.front.smo.payment.adapt.IPayNotifyAdapt;
+import com.java110.utils.constant.CommonConstant;
+import com.java110.utils.constant.ServiceConstant;
+import com.java110.utils.util.BeanConvertUtil;
+import com.java110.utils.util.DateUtil;
+import com.java110.utils.util.PayUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.*;
+import org.springframework.stereotype.Component;
+import org.springframework.web.client.HttpStatusCodeException;
+import org.springframework.web.client.RestTemplate;
+
+import java.util.Date;
+import java.util.SortedMap;
+import java.util.TreeMap;
+import java.util.UUID;
+
+/**
+ * 富友 支付 通知实现
+ * 说明:信息通过 http 或 https 形式 post 请求递交给前置系统,编码必须为 UTF-8
+ * Json 格式参数名:如下表
+ * 参数值:如下表
+ * 测试地址:商户提供
+ * 生产地址:待定
+ * <p>
+ * 如图中第 6 步中异步回调,下单(主扫)交易的结果是以异步的形式进行回调的。富友在接受到支付宝等支付通道的回调结果以
+ * 后再回调商户。商户接收回调成功处理成功后返回字符串”1” , 后富友停止回调给商户。最多回调 5 次,每次间隔 30S。
+ * (重要~重要~重要:不保证通知最终一定能成功,在订单状态不明或者没有收到微信,支付结果通知的情况下,
+ * 建议商户主动调用【2.3 订单查询】确认订单状态)
+ * 只有主扫、公众号/服务窗支付会通过此接口发异步通知,条码支付没有异步通知。
+ *
+ * @desc add by 吴学文 15:33
+ */
+
+@Component(value = "chinaUmsPayNotifyAdapt")
+public class ChinaUmsPayNotifyAdapt implements IPayNotifyAdapt {
+
+    private static final Logger logger = LoggerFactory.getLogger(ChinaUmsPayNotifyAdapt.class);
+
+    private static final String APP_ID = "992020011134400001";
+
+    @Autowired
+    private RestTemplate restTemplate;
+
+    @Autowired
+    private WechatAuthProperties wechatAuthProperties;
+
+    /**
+     * 预下单
+     *
+     * @param param
+     * @return
+     * @throws Exception
+     */
+    public String confirmPayFee(String param, String wId) {
+        JSONObject resJson = new JSONObject();
+        resJson.put("errCode", "INTERNAL_ERROR");
+        resJson.put("errMsg", "失败");
+        try {
+            JSONObject map = JSONObject.parseObject(param);
+            logger.info("【银联支付回调】 回调数据: \n" + map);
+            //更新数据
+            int result = confirmPayFee(map, wId);
+            if (result > 0) {
+                //支付成功
+                resJson.put("errCode", "SUCCESS");
+                resJson.put("errMsg", "成功");
+            }
+        } catch (Exception e) {
+            logger.error("通知失败", e);
+            resJson.put("result_msg", "鉴权失败");
+        }
+
+        return resJson.toJSONString();
+    }
+
+
+    public int confirmPayFee(JSONObject map, String wId) {
+        wId = wId.replace(" ", "+");
+
+        ResponseEntity<String> responseEntity = null;
+
+        String appId = WechatFactory.getAppId(wId);
+        SmallWeChatDto smallWeChatDto = getSmallWechat(appId);
+
+        if (smallWeChatDto == null) { //从配置文件中获取 小程序配置信息
+            smallWeChatDto = new SmallWeChatDto();
+            smallWeChatDto.setAppId(wechatAuthProperties.getAppId());
+            smallWeChatDto.setAppSecret(wechatAuthProperties.getSecret());
+            smallWeChatDto.setMchId(wechatAuthProperties.getMchId());
+            smallWeChatDto.setPayPassword(wechatAuthProperties.getKey());
+        }
+        SortedMap<String, String> paramMap = new TreeMap<String, String>();
+        for (String key : map.keySet()) {
+            if ("wId".equals(key)) {
+                continue;
+            }
+            paramMap.put(key, map.get(key).toString());
+        }
+        String sign = PayUtil.createSign(paramMap, smallWeChatDto.getPayPassword());
+
+        if (!sign.equals(map.get("sign"))) {
+            throw new IllegalArgumentException("鉴权失败");
+        }
+        JSONObject billPayment = JSONObject.parseObject(map.getString("billPayment"));
+        String outTradeNo = billPayment.get("merOrderId").toString();
+
+        //查询用户ID
+        JSONObject paramIn = new JSONObject();
+        paramIn.put("oId", outTradeNo.substring(4));
+        String url = ServiceConstant.SERVICE_API_URL + "/api/fee.payFeeConfirm";
+        responseEntity = this.callCenterService(restTemplate, "-1", paramIn.toJSONString(), url, HttpMethod.POST);
+
+        if (responseEntity.getStatusCode() != HttpStatus.OK) {
+            return 0;
+        }
+        return 1;
+    }
+
+
+    /**
+     * 调用中心服务
+     *
+     * @return
+     */
+    protected ResponseEntity<String> callCenterService(RestTemplate restTemplate, String userId, String param, String url, HttpMethod httpMethod) {
+
+        ResponseEntity<String> responseEntity = null;
+        HttpHeaders header = new HttpHeaders();
+        header.add(CommonConstant.HTTP_APP_ID.toLowerCase(), APP_ID);
+        header.add(CommonConstant.HTTP_USER_ID.toLowerCase(), userId);
+        header.add(CommonConstant.HTTP_TRANSACTION_ID.toLowerCase(), UUID.randomUUID().toString());
+        header.add(CommonConstant.HTTP_REQ_TIME.toLowerCase(), DateUtil.getDefaultFormateTimeString(new Date()));
+        header.add(CommonConstant.HTTP_SIGN.toLowerCase(), "");
+        HttpEntity<String> httpEntity = new HttpEntity<String>(param, header);
+        //logger.debug("请求中心服务信息,{}", httpEntity);
+        try {
+            responseEntity = restTemplate.exchange(url, httpMethod, httpEntity, String.class);
+        } catch (HttpStatusCodeException e) { //这里spring 框架 在4XX 或 5XX 时抛出 HttpServerErrorException 异常,需要重新封装一下
+            responseEntity = new ResponseEntity<String>(e.getResponseBodyAsString(), e.getStatusCode());
+        } catch (Exception e) {
+            responseEntity = new ResponseEntity<String>(e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
+        } finally {
+            logger.debug("请求地址为,{} 请求中心服务信息,{},中心服务返回信息,{}", url, httpEntity, responseEntity);
+        }
+        return responseEntity;
+    }
+
+
+    private SmallWeChatDto getSmallWechat(String appId) {
+
+        ResponseEntity responseEntity = null;
+
+        responseEntity = this.callCenterService(restTemplate, "-1", "",
+                ServiceConstant.SERVICE_API_URL + "/api/smallWeChat.listSmallWeChats?appId="
+                        + appId + "&page=1&row=1", HttpMethod.GET);
+
+        if (responseEntity.getStatusCode() != HttpStatus.OK) {
+            return null;
+        }
+        JSONObject smallWechatObj = JSONObject.parseObject(responseEntity.getBody().toString());
+        JSONArray smallWeChats = smallWechatObj.getJSONArray("smallWeChats");
+
+        if (smallWeChats == null || smallWeChats.size() < 1) {
+            return null;
+        }
+
+        return BeanConvertUtil.covertBean(smallWeChats.get(0), SmallWeChatDto.class);
+    }
+
+    /**
+     * 富友 生成sign 方法
+     *
+     * @param paramMap
+     * @param payPassword
+     * @return
+     */
+    private String createSign(JSONObject paramMap, String payPassword) {
+        String str = paramMap.getString("mchnt_cd") + "|"
+                + paramMap.getString("mchnt_order_no") + "|"
+                + paramMap.getString("settle_order_amt") + "|"
+                + paramMap.getString("order_amt") + "|"
+                + paramMap.getString("txn_fin_ts") + "|"
+                + paramMap.getString("reserved_fy_settle_dt") + "|"
+                + paramMap.getString("random_str") + "|"
+                + payPassword;
+        return PayUtil.md5(str);
+    }
+
+}

+ 184 - 0
service-front/src/main/java/com/java110/front/smo/payment/adapt/chinaums/ChinaUmsRentingToNotifyAdapt.java

@@ -0,0 +1,184 @@
+/*
+ * Copyright 2017-2020 吴学文 and java110 team.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.java110.front.smo.payment.adapt.chinaums;
+
+import com.alibaba.fastjson.JSONObject;
+import com.java110.dto.rentingPool.RentingPoolDto;
+import com.java110.dto.smallWeChat.SmallWeChatDto;
+import com.java110.front.properties.WechatAuthProperties;
+import com.java110.front.smo.payment.adapt.IPayNotifyAdapt;
+import com.java110.utils.cache.CommonCache;
+import com.java110.utils.constant.CommonConstant;
+import com.java110.utils.constant.ServiceConstant;
+import com.java110.utils.util.DateUtil;
+import com.java110.utils.util.PayUtil;
+import com.java110.utils.util.StringUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.*;
+import org.springframework.stereotype.Component;
+import org.springframework.web.client.HttpStatusCodeException;
+import org.springframework.web.client.RestTemplate;
+
+import java.util.Date;
+import java.util.UUID;
+
+/**
+ * 富友 支付 通知实现
+ * 说明:信息通过 http 或 https 形式 post 请求递交给前置系统,编码必须为 UTF-8
+ * Json 格式参数名:如下表
+ * 参数值:如下表
+ * 测试地址:商户提供
+ * 生产地址:待定
+ * <p>
+ * 如图中第 6 步中异步回调,下单(主扫)交易的结果是以异步的形式进行回调的。富友在接受到支付宝等支付通道的回调结果以
+ * 后再回调商户。商户接收回调成功处理成功后返回字符串”1” , 后富友停止回调给商户。最多回调 5 次,每次间隔 30S。
+ * (重要~重要~重要:不保证通知最终一定能成功,在订单状态不明或者没有收到微信,支付结果通知的情况下,
+ * 建议商户主动调用【2.3 订单查询】确认订单状态)
+ * 只有主扫、公众号/服务窗支付会通过此接口发异步通知,条码支付没有异步通知。
+ *
+ * @desc add by 吴学文 15:33
+ */
+
+@Component(value = "chinaUmsRentingToNotifyAdapt")
+public class ChinaUmsRentingToNotifyAdapt implements IPayNotifyAdapt {
+
+    private static final Logger logger = LoggerFactory.getLogger(ChinaUmsRentingToNotifyAdapt.class);
+
+    private static final String APP_ID = "992020011134400001";
+
+    @Autowired
+    private RestTemplate restTemplate;
+
+    @Autowired
+    private WechatAuthProperties wechatAuthProperties;
+
+    /**
+     * 预下单
+     *
+     * @param param
+     * @return
+     * @throws Exception
+     */
+    public String confirmPayFee(String param,String wId) {
+        JSONObject resJson = new JSONObject();
+        resJson.put("result_code", "010002");
+        resJson.put("result_msg", "失败");
+        try {
+            JSONObject map = JSONObject.parseObject(param);
+            logger.info("【富友支付回调】 回调数据: \n" + map);
+            String resultCode = map.getString("result_code");
+            if ("000000".equals(resultCode)) {
+                //更新数据
+                int result = confirmPayFee(map);
+                if (result > 0) {
+                    //支付成功
+                    resJson.put("result_code", "000000");
+                    resJson.put("result_msg", "成功");
+                }
+            }
+        } catch (Exception e) {
+            logger.error("通知失败", e);
+            resJson.put("result_msg", "鉴权失败");
+        }
+
+        return resJson.toJSONString();
+    }
+
+
+    public int confirmPayFee(JSONObject map) {
+
+        //String appId = WechatFactory.getAppId(wId);
+        SmallWeChatDto smallWeChatDto = new SmallWeChatDto();
+        smallWeChatDto.setAppId(wechatAuthProperties.getAppId());
+        smallWeChatDto.setAppSecret(wechatAuthProperties.getSecret());
+        smallWeChatDto.setMchId(wechatAuthProperties.getMchId());
+        smallWeChatDto.setPayPassword(wechatAuthProperties.getKey());
+        String sign = createSign(map, smallWeChatDto.getPayPassword());
+
+        if (!sign.equals(map.get("sign"))) {
+            throw new IllegalArgumentException("鉴权失败");
+        }
+
+        String orderId = map.get("out_trade_no").toString();
+        String order = CommonCache.getAndRemoveValue(RentingPoolDto.REDIS_PAY_RENTING + orderId);
+
+        if (StringUtil.isEmpty(order)) {
+            return 1;// 说明已经处理过了 再不处理
+        }
+
+        //查询用户ID
+        JSONObject paramIn = JSONObject.parseObject(order);
+        paramIn.put("oId", orderId);
+        String url = ServiceConstant.SERVICE_API_URL + "/api/fee.rentingPayFeeConfirm";
+        ResponseEntity responseEntity = this.callCenterService(restTemplate, "-1", paramIn.toJSONString(), url, HttpMethod.POST);
+
+        if (responseEntity.getStatusCode() != HttpStatus.OK) {
+            return 0;
+        }
+        return 1;
+    }
+
+
+    /**
+     * 调用中心服务
+     *
+     * @return
+     */
+    protected ResponseEntity<String> callCenterService(RestTemplate restTemplate, String userId, String param, String url, HttpMethod httpMethod) {
+
+        ResponseEntity<String> responseEntity = null;
+        HttpHeaders header = new HttpHeaders();
+        header.add(CommonConstant.HTTP_APP_ID.toLowerCase(), APP_ID);
+        header.add(CommonConstant.HTTP_USER_ID.toLowerCase(), userId);
+        header.add(CommonConstant.HTTP_TRANSACTION_ID.toLowerCase(), UUID.randomUUID().toString());
+        header.add(CommonConstant.HTTP_REQ_TIME.toLowerCase(), DateUtil.getDefaultFormateTimeString(new Date()));
+        header.add(CommonConstant.HTTP_SIGN.toLowerCase(), "");
+        HttpEntity<String> httpEntity = new HttpEntity<String>(param, header);
+        //logger.debug("请求中心服务信息,{}", httpEntity);
+        try {
+            responseEntity = restTemplate.exchange(url, httpMethod, httpEntity, String.class);
+        } catch (HttpStatusCodeException e) { //这里spring 框架 在4XX 或 5XX 时抛出 HttpServerErrorException 异常,需要重新封装一下
+            responseEntity = new ResponseEntity<String>(e.getResponseBodyAsString(), e.getStatusCode());
+        } catch (Exception e) {
+            responseEntity = new ResponseEntity<String>(e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
+        } finally {
+            logger.debug("请求地址为,{} 请求中心服务信息,{},中心服务返回信息,{}", url, httpEntity, responseEntity);
+        }
+        return responseEntity;
+    }
+
+    /**
+     * 富友 生成sign 方法
+     *
+     * @param paramMap
+     * @param payPassword
+     * @return
+     */
+    private String createSign(JSONObject paramMap, String payPassword) {
+        String str = paramMap.getString("mchnt_cd") + "|"
+                + paramMap.getString("mchnt_order_no") + "|"
+                + paramMap.getString("settle_order_amt") + "|"
+                + paramMap.getString("order_amt") + "|"
+                + paramMap.getString("txn_fin_ts") + "|"
+                + paramMap.getString("reserved_fy_settle_dt") + "|"
+                + paramMap.getString("random_str") + "|"
+                + payPassword;
+        return PayUtil.md5(str);
+    }
+
+}