|
@@ -6,43 +6,52 @@ import com.zanxiang.common.enums.OrderStateEnum;
|
|
|
import com.zanxiang.common.exception.BaseException;
|
|
|
import com.zanxiang.common.exception.CustomException;
|
|
|
import com.zanxiang.common.utils.StringUtils;
|
|
|
+import com.zanxiang.common.utils.URIUtil;
|
|
|
import com.zanxiang.module.util.JsonUtil;
|
|
|
import com.zanxiang.module.util.pojo.ResultVO;
|
|
|
import com.zanxiang.mybatis.entity.Order;
|
|
|
-import com.zanxiang.sdk.constant.MiPayConstants;
|
|
|
-import com.zanxiang.sdk.constant.RedisKeyConstant;
|
|
|
import com.zanxiang.sdk.domain.bo.ProductPayParamBO;
|
|
|
import com.zanxiang.sdk.domain.dto.*;
|
|
|
import com.zanxiang.sdk.service.IGameAppletService;
|
|
|
import com.zanxiang.sdk.service.IOrderService;
|
|
|
import com.zanxiang.sdk.service.IUserService;
|
|
|
-import com.zanxiang.sdk.util.RedisUtil;
|
|
|
-import com.zanxiang.sdk.util.WxPayUtil;
|
|
|
+import com.zanxiang.sdk.service.api.WxApiService;
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
-import org.apache.logging.log4j.util.Strings;
|
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
|
import org.springframework.beans.factory.annotation.Value;
|
|
|
import org.springframework.stereotype.Service;
|
|
|
import org.springframework.web.client.RestTemplate;
|
|
|
-import org.springframework.web.util.UriComponentsBuilder;
|
|
|
|
|
|
+import javax.crypto.Mac;
|
|
|
+import javax.crypto.spec.SecretKeySpec;
|
|
|
import javax.servlet.http.HttpServletRequest;
|
|
|
import javax.servlet.http.HttpServletResponse;
|
|
|
-import java.net.URI;
|
|
|
+import java.math.BigDecimal;
|
|
|
+import java.nio.charset.StandardCharsets;
|
|
|
import java.util.HashMap;
|
|
|
import java.util.Map;
|
|
|
import java.util.Objects;
|
|
|
|
|
|
/**
|
|
|
- * 米大师服务实现
|
|
|
- *
|
|
|
- * @author xufeng
|
|
|
- * @date 2022/7/14 16:33
|
|
|
+ * @author : lingfeng
|
|
|
+ * @time : 2023-05-26
|
|
|
+ * @description : 米大师支付2.0
|
|
|
*/
|
|
|
@Slf4j
|
|
|
@Service
|
|
|
public class MiPayService extends PayBaseService {
|
|
|
|
|
|
+ private static final String SIGN_HMACSHA256 = "HMACSHA256";
|
|
|
+
|
|
|
+ private static final String MI_PAY_HOST = "https://api.weixin.qq.com";
|
|
|
+
|
|
|
+ private static final String BALANCE_URL = "/wxa/game/getbalance";
|
|
|
+
|
|
|
+ private static final String PAY_URL = "/wxa/game/pay";
|
|
|
+
|
|
|
+ @Value("${payConfig.miPay.isSand}")
|
|
|
+ private Integer isSand;
|
|
|
+
|
|
|
@Autowired
|
|
|
private IOrderService orderService;
|
|
|
|
|
@@ -53,16 +62,46 @@ public class MiPayService extends PayBaseService {
|
|
|
private RestTemplate restTemplate;
|
|
|
|
|
|
@Autowired
|
|
|
- private IUserService userService;
|
|
|
+ private WxApiService wxApiService;
|
|
|
|
|
|
@Autowired
|
|
|
- private RedisUtil<String> redisUtil;
|
|
|
-
|
|
|
- @Value("${payConfig.miPay.isSand}")
|
|
|
- private Integer isSand;
|
|
|
+ private IUserService userService;
|
|
|
|
|
|
+ /**
|
|
|
+ * 支付配置
|
|
|
+ */
|
|
|
private MiPayConfigDTO config;
|
|
|
|
|
|
+ public Map<String, Object> paySynNotify(String orderId) {
|
|
|
+ //查询订单
|
|
|
+ PlatformOrderDTO platformOrderDTO = orderService.getByOrderId(orderId);
|
|
|
+ if (platformOrderDTO == null) {
|
|
|
+ log.error("参数错误, 订单信息不存在! orderId : {}", orderId);
|
|
|
+ throw new BaseException("参数错误, 订单信息不存在");
|
|
|
+ }
|
|
|
+ this.configInit(platformOrderDTO.getGameId());
|
|
|
+ //获取用户openId
|
|
|
+ UserDTO userDTO = userService.getUserByUserId(platformOrderDTO.getUserId());
|
|
|
+ String openId = userDTO.getOpenId();
|
|
|
+ String sessionKey = userDTO.getSessionKey();
|
|
|
+ //获取米大师钱包余额
|
|
|
+ Long balance = this.midasGetBalance(openId, sessionKey);
|
|
|
+ //余额不足, 无法扣除
|
|
|
+ if (balance < platformOrderDTO.getAmount().longValue()) {
|
|
|
+ log.error("米大师游戏币不足, 无法扣除! balance : {}, orderId : {}", balance, orderId);
|
|
|
+ throw new BaseException("米大师游戏币不足, 无法扣除!");
|
|
|
+ }
|
|
|
+ //余额充足, 直接扣除
|
|
|
+ String billNo = this.midasPay(openId, sessionKey, platformOrderDTO.getAmount(), platformOrderDTO.getOrderId());
|
|
|
+ //支付成功
|
|
|
+ this.paySuccess(platformOrderDTO.getOrderId(), platformOrderDTO.getAmount().toString(), config.getAppId());
|
|
|
+ //返回参数
|
|
|
+ Map<String, Object> payParamMap = new HashMap<>(2);
|
|
|
+ payParamMap.put("status", "0");
|
|
|
+ payParamMap.put("billNo", billNo);
|
|
|
+ return payParamMap;
|
|
|
+ }
|
|
|
+
|
|
|
@Override
|
|
|
public Map<String, Object> create(ProductPayParamBO product, GamePayWayDTO gamePayWayDTO) {
|
|
|
//查询订单
|
|
@@ -77,13 +116,14 @@ public class MiPayService extends PayBaseService {
|
|
|
//获取用户openId
|
|
|
UserDTO userDTO = userService.getUserByUserId(product.getUserId());
|
|
|
String openId = userDTO.getOpenId();
|
|
|
+ String sessionKey = userDTO.getSessionKey();
|
|
|
//获取米大师钱包余额
|
|
|
- Long balance = this.midasGetBalance(openId);
|
|
|
+ Long balance = this.midasGetBalance(openId, sessionKey);
|
|
|
//返回参数
|
|
|
Map<String, Object> payParamMap = new HashMap<>(9);
|
|
|
//余额充足, 直接扣除
|
|
|
if (balance >= orderInfo.getAmount().longValue()) {
|
|
|
- String billNo = this.midasPay(openId, orderInfo);
|
|
|
+ String billNo = this.midasPay(openId, sessionKey, orderInfo.getAmount(), orderInfo.getOrderId());
|
|
|
payParamMap.put("status", "0");
|
|
|
payParamMap.put("billNo", billNo);
|
|
|
//支付成功
|
|
@@ -108,98 +148,80 @@ public class MiPayService extends PayBaseService {
|
|
|
return payParamMap;
|
|
|
}
|
|
|
|
|
|
- private Long midasGetBalance(String openId) {
|
|
|
- Map<String, String> paramMap = new HashMap<>();
|
|
|
- paramMap.put("openid", openId);
|
|
|
- paramMap.put("appid", config.getAppId());
|
|
|
- paramMap.put("offer_id", config.getOfferId());
|
|
|
- paramMap.put("ts", String.valueOf(System.currentTimeMillis() / 1000));
|
|
|
- paramMap.put("zone_id", "1");
|
|
|
- paramMap.put("pf", "android");
|
|
|
- paramMap.put("sig", this.sign(MiPayConstants.BALANCE_URL, paramMap));
|
|
|
- Map<String, String> result = this.miPayApi(MiPayConstants.BALANCE_URL, paramMap);
|
|
|
- return Long.valueOf(result.get("balance"));
|
|
|
- }
|
|
|
-
|
|
|
- private String midasPay(String openId, PlatformOrderDTO orderInfo) {
|
|
|
- Map<String, String> paramMap = new HashMap<>();
|
|
|
- paramMap.put("appid", config.getAppId());
|
|
|
- paramMap.put("openid", openId);
|
|
|
- paramMap.put("offer_id", config.getOfferId());
|
|
|
- paramMap.put("ts", String.valueOf(System.currentTimeMillis() / 1000));
|
|
|
- paramMap.put("pf", "android");
|
|
|
- paramMap.put("zone_id", "1");
|
|
|
- paramMap.put("amt", orderInfo.getAmount().toString());
|
|
|
- paramMap.put("bill_no", orderInfo.getOrderId());
|
|
|
- paramMap.put("sig", this.sign(MiPayConstants.PAY_URL, paramMap));
|
|
|
- Map<String, String> result = this.miPayApi(MiPayConstants.PAY_URL, paramMap);
|
|
|
- return result.get("bill_no");
|
|
|
+ private Long midasGetBalance(String openId, String sessionKey) {
|
|
|
+ //接口参数
|
|
|
+ Map<String, Object> postBodyMap = new HashMap<>(5);
|
|
|
+ postBodyMap.put("offer_id", config.getOfferId());
|
|
|
+ postBodyMap.put("openid", openId);
|
|
|
+ postBodyMap.put("ts", System.currentTimeMillis() / 1000);
|
|
|
+ postBodyMap.put("zone_id", "1");
|
|
|
+ postBodyMap.put("env", this.isSand);
|
|
|
+ String postBody = JsonUtil.toString(postBodyMap);
|
|
|
+ //接口token
|
|
|
+ String accessToken = wxApiService.getAccessToken(config.getAppId(), config.getSecret());
|
|
|
+ //登录签名
|
|
|
+ String signature = calcSignature(postBody, sessionKey);
|
|
|
+ //支付签名
|
|
|
+ String paySig = calcPaySig(MiPayService.BALANCE_URL, postBody, config.getAppKeyByIsSand(this.isSand));
|
|
|
+ //请求头参数
|
|
|
+ Map<String, String> headParamMap = new HashMap<>(3);
|
|
|
+ headParamMap.put("access_token", accessToken);
|
|
|
+ headParamMap.put("signature", signature);
|
|
|
+ headParamMap.put("pay_sig", paySig);
|
|
|
+ //调接口返回
|
|
|
+ String url = URIUtil.fillUrlParams(MiPayService.MI_PAY_HOST + MiPayService.BALANCE_URL, headParamMap, Boolean.FALSE);
|
|
|
+ Map<String, String> resultMap = this.miPayApi(url, postBody);
|
|
|
+ return Long.valueOf(resultMap.get("balance"));
|
|
|
}
|
|
|
|
|
|
- private String getAccessToken(String appId, String appKey) {
|
|
|
- String redisKey = RedisKeyConstant.MI_PAY_TOKEN + appId;
|
|
|
- String cache = redisUtil.getCache(redisKey);
|
|
|
- if (StringUtils.isNotEmpty(cache)) {
|
|
|
- return cache;
|
|
|
- }
|
|
|
- //请求url
|
|
|
- URI uri = UriComponentsBuilder.fromHttpUrl(MiPayConstants.MI_PAY_HOST + MiPayConstants.TOKEN_URL)
|
|
|
- .queryParam("appid", appId)
|
|
|
- .queryParam("secret", appKey)
|
|
|
- .queryParam("grant_type", "client_credential")
|
|
|
- .build().toUri();
|
|
|
- String result;
|
|
|
- try {
|
|
|
- result = restTemplate.getForObject(uri, String.class);
|
|
|
- } catch (Exception e) {
|
|
|
- log.error("获取米大师token异常, appId : {}, appKey : {}, e : {}", appId, appKey, e.getMessage());
|
|
|
- throw new BaseException("获取米大师token异常");
|
|
|
- }
|
|
|
- if (Strings.isEmpty(result)) {
|
|
|
- log.error("获取米大师token失败, 返回结果为空, appId : {}, appKey : {}", appId, appKey);
|
|
|
- throw new BaseException("获取米大师token失败, 返回结果为空");
|
|
|
- }
|
|
|
- Map<String, String> data = JsonUtil.toMap(result, Map.class, String.class, String.class);
|
|
|
- String accessToken = data.get("access_token");
|
|
|
- String expiresIn = data.get("expires_in");
|
|
|
- if (Strings.isBlank(accessToken) || Strings.isBlank(expiresIn)) {
|
|
|
- log.error("获取米大师token失败, 返回结果参数不全, appId : {}, appKey : {}, data {}", appId, appKey, data);
|
|
|
- throw new BaseException("获取米大师token失败, 返回结果参数不全");
|
|
|
- }
|
|
|
- redisUtil.setCache(redisKey, accessToken, Long.valueOf(data.get("expires_in")));
|
|
|
- return accessToken;
|
|
|
+ private String midasPay(String openId, String sessionKey, BigDecimal amount, String orderId) {
|
|
|
+ //接口参数
|
|
|
+ Map<String, Object> postBodyMap = new HashMap<>(7);
|
|
|
+ postBodyMap.put("openid", openId);
|
|
|
+ postBodyMap.put("offer_id", config.getOfferId());
|
|
|
+ postBodyMap.put("ts", System.currentTimeMillis() / 1000);
|
|
|
+ postBodyMap.put("zone_id", "1");
|
|
|
+ postBodyMap.put("env", this.isSand);
|
|
|
+ postBodyMap.put("amount", amount);
|
|
|
+ postBodyMap.put("bill_no", orderId);
|
|
|
+ String postBody = JsonUtil.toString(postBodyMap);
|
|
|
+ //接口token
|
|
|
+ String accessToken = wxApiService.getAccessToken(config.getAppId(), config.getSecret());
|
|
|
+ //登录签名
|
|
|
+ String signature = this.calcSignature(postBody, sessionKey);
|
|
|
+ //支付签名
|
|
|
+ String paySig = this.calcPaySig(MiPayService.PAY_URL, postBody, config.getAppKeyByIsSand(this.isSand));
|
|
|
+ //请求头参数
|
|
|
+ Map<String, String> headParamMap = new HashMap<>(3);
|
|
|
+ headParamMap.put("access_token", accessToken);
|
|
|
+ headParamMap.put("signature", signature);
|
|
|
+ headParamMap.put("pay_sig", paySig);
|
|
|
+ //调接口返回
|
|
|
+ String url = URIUtil.fillUrlParams(MiPayService.MI_PAY_HOST + MiPayService.PAY_URL, headParamMap, Boolean.FALSE);
|
|
|
+ Map<String, String> resultMap = this.miPayApi(url, postBody);
|
|
|
+ return resultMap.get("bill_no");
|
|
|
}
|
|
|
|
|
|
- private Map<String, String> miPayApi(String url, Map<String, String> paramMap) {
|
|
|
- //使用沙箱时,沙箱地址替换
|
|
|
- if (this.isSand == 1) {
|
|
|
- url = url.replace("/cgi-bin/midas/", "/cgi-bin/midas/sandbox/");
|
|
|
- }
|
|
|
- String appKey = this.isSand == 1 ? config.getAppKeyDev() : config.getAppKey();
|
|
|
- //接口凭证
|
|
|
- String accessToken = getAccessToken(config.getAppId(), appKey);
|
|
|
- //接口url
|
|
|
- URI uri = UriComponentsBuilder.fromHttpUrl(MiPayConstants.MI_PAY_HOST + url)
|
|
|
- .queryParam("access_token", accessToken).build().toUri();
|
|
|
+ private Map<String, String> miPayApi(String url, String postBody) {
|
|
|
String result;
|
|
|
try {
|
|
|
- result = restTemplate.postForObject(uri, paramMap, String.class);
|
|
|
+ result = restTemplate.postForObject(url, postBody, String.class);
|
|
|
} catch (Exception e) {
|
|
|
- log.error("米大师接口调用失败, url : {}, paramMap : {}, e : {}", url, JsonUtil.toString(paramMap), e.getMessage());
|
|
|
- throw new BaseException("米大师接口调用失败");
|
|
|
+ log.error("米大师接口调用异常, url : {}, postBody : {}, e : {}", url, postBody, e.getMessage());
|
|
|
+ throw new BaseException("米大师接口调用异常");
|
|
|
}
|
|
|
if (StringUtils.isEmpty(result)) {
|
|
|
- log.error("米大师接口调用失败, 返回结果为空, url : {}, paramMap : {}", url, JsonUtil.toString(paramMap));
|
|
|
+ log.error("米大师接口调用失败, 返回结果为空, url : {}, postBody : {}", url, postBody);
|
|
|
throw new BaseException("米大师接口调用失败, 返回结果为空");
|
|
|
}
|
|
|
- Map<String, String> data = JsonUtil.toMap(result, Map.class, String.class, String.class);
|
|
|
+ Map<String, String> resultMap = JsonUtil.toMap(result, Map.class, String.class, String.class);
|
|
|
//返回结果成功
|
|
|
- if (Objects.equals("0", data.get("errcode"))) {
|
|
|
- return data;
|
|
|
+ if (Objects.equals("0", resultMap.get("errcode"))) {
|
|
|
+ return resultMap;
|
|
|
}
|
|
|
//返回结果失败
|
|
|
- log.error("米大师接口调用返回结果失败, url : {}, paramMap : {}, errcode : {}, errmsg : {}", url, paramMap,
|
|
|
- data.get("errcode"), data.get("errmsg"));
|
|
|
+ log.error("米大师接口调用返回结果失败, url : {}, postBody : {}, errcode : {}, errmsg : {}", url, postBody,
|
|
|
+ resultMap.get("errcode"), resultMap.get("errmsg"));
|
|
|
throw new BaseException("米大师接口调用返回结果失败");
|
|
|
}
|
|
|
|
|
@@ -216,14 +238,34 @@ public class MiPayService extends PayBaseService {
|
|
|
.build();
|
|
|
}
|
|
|
|
|
|
- private String sign(String url, Map<String, String> paramMap) {
|
|
|
- //沙箱环境地址替换
|
|
|
- if (this.isSand == 1) {
|
|
|
- url = url.replace("/cgi-bin/midas/", "/cgi-bin/midas/sandbox/");
|
|
|
+ private String calcPaySig(String uri, String postBody, String appKey) {
|
|
|
+ String needSignMsg = uri + "&" + postBody;
|
|
|
+ return this.calcSignature(needSignMsg, appKey);
|
|
|
+ }
|
|
|
+
|
|
|
+ private String calcSignature(String postBody, String sessionKey) {
|
|
|
+ try {
|
|
|
+ Mac hmacSha256 = Mac.getInstance(MiPayService.SIGN_HMACSHA256);
|
|
|
+ SecretKeySpec secretKey = new SecretKeySpec(sessionKey.getBytes(StandardCharsets.UTF_8), MiPayService.SIGN_HMACSHA256);
|
|
|
+ hmacSha256.init(secretKey);
|
|
|
+ byte[] signatureBytes = hmacSha256.doFinal(postBody.getBytes(StandardCharsets.UTF_8));
|
|
|
+ return bytesToHex(signatureBytes);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("米大师签名计算异常, postBody : {}, sessionKey : {}", postBody, sessionKey);
|
|
|
+ throw new BaseException("米大师签名计算异常");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private String bytesToHex(byte[] bytes) {
|
|
|
+ StringBuilder hexString = new StringBuilder();
|
|
|
+ for (byte b : bytes) {
|
|
|
+ String hex = Integer.toHexString(0xff & b);
|
|
|
+ if (hex.length() == 1) {
|
|
|
+ hexString.append('0');
|
|
|
+ }
|
|
|
+ hexString.append(hex);
|
|
|
}
|
|
|
- String appKey = this.isSand == 1 ? config.getAppKeyDev() : config.getAppKey();
|
|
|
- //签名
|
|
|
- return WxPayUtil.miPaySin("POST", url, paramMap, appKey);
|
|
|
+ return hexString.toString();
|
|
|
}
|
|
|
|
|
|
@Override
|