|
@@ -1,20 +1,35 @@
|
|
package com.zanxiang.sdk.service.pay;
|
|
package com.zanxiang.sdk.service.pay;
|
|
|
|
|
|
|
|
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
|
|
|
+import com.zanxiang.common.enums.HttpStatusEnum;
|
|
|
|
+import com.zanxiang.common.enums.OrderStateEnum;
|
|
import com.zanxiang.common.exception.BaseException;
|
|
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.common.utils.URIUtil;
|
|
import com.zanxiang.module.util.JsonUtil;
|
|
import com.zanxiang.module.util.JsonUtil;
|
|
|
|
+import com.zanxiang.module.util.pojo.ResultVO;
|
|
|
|
+import com.zanxiang.mybatis.entity.Order;
|
|
|
|
+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.service.api.WxApiService;
|
|
import lombok.extern.slf4j.Slf4j;
|
|
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.stereotype.Service;
|
|
import org.springframework.web.client.RestTemplate;
|
|
import org.springframework.web.client.RestTemplate;
|
|
|
|
|
|
import javax.crypto.Mac;
|
|
import javax.crypto.Mac;
|
|
import javax.crypto.spec.SecretKeySpec;
|
|
import javax.crypto.spec.SecretKeySpec;
|
|
|
|
+import javax.servlet.http.HttpServletRequest;
|
|
|
|
+import javax.servlet.http.HttpServletResponse;
|
|
import java.nio.charset.StandardCharsets;
|
|
import java.nio.charset.StandardCharsets;
|
|
-import java.security.InvalidKeyException;
|
|
|
|
-import java.security.NoSuchAlgorithmException;
|
|
|
|
import java.util.HashMap;
|
|
import java.util.HashMap;
|
|
import java.util.Map;
|
|
import java.util.Map;
|
|
|
|
+import java.util.Objects;
|
|
|
|
|
|
/**
|
|
/**
|
|
* @author : lingfeng
|
|
* @author : lingfeng
|
|
@@ -23,76 +38,191 @@ import java.util.Map;
|
|
*/
|
|
*/
|
|
@Slf4j
|
|
@Slf4j
|
|
@Service
|
|
@Service
|
|
-public class MiPay2Service {
|
|
|
|
-
|
|
|
|
- public void test() {
|
|
|
|
- String appId = "wx79b493916563c377";
|
|
|
|
- String secret = "e5b1825a288ea5b54b724c64cc57e5a4";
|
|
|
|
- String miPayAppId = "1450050301";
|
|
|
|
- String miPaySecret = "ZAksvG2Y2v7pVvRmbahZh4HR4aaCdpnE";
|
|
|
|
- String openId = "oA-YO5YY2VVhwuSGst1gHfDBGo6s";
|
|
|
|
- String sessionKey = "mnO0qqRHI6JOmAr0P5WbNA==";
|
|
|
|
-
|
|
|
|
- Map<String, Object> postBodyMap = new HashMap<>();
|
|
|
|
- postBodyMap.put("offer_id", miPayAppId);
|
|
|
|
- postBodyMap.put("openid", openId);
|
|
|
|
- postBodyMap.put("ts", System.currentTimeMillis() / 1000);
|
|
|
|
- postBodyMap.put("zone_id", "1");
|
|
|
|
- postBodyMap.put("env", 0);
|
|
|
|
-
|
|
|
|
- String postBody = JsonUtil.toString(postBodyMap);
|
|
|
|
|
|
+public class MiPay2Service extends PayBaseService {
|
|
|
|
|
|
- try {
|
|
|
|
-
|
|
|
|
- //接口token
|
|
|
|
- String accessToken = getAccessToken(appId, secret);
|
|
|
|
-
|
|
|
|
- //登录签名
|
|
|
|
- String signature = calcSignature(postBody, sessionKey);
|
|
|
|
|
|
+ private static final String SIGN_HMACSHA256 = "HMACSHA256";
|
|
|
|
|
|
- //支付签名
|
|
|
|
- String paySig = calcPaySig("/wxa/game/getbalance", postBody, miPaySecret);
|
|
|
|
|
|
+ private static final String MI_PAY_HOST = "https://api.weixin.qq.com";
|
|
|
|
|
|
|
|
+ private static final String BALANCE_URL = "/wxa/game/getbalance";
|
|
|
|
|
|
- Map<String, String> headParamMap = new HashMap<>();
|
|
|
|
- headParamMap.put("access_token", accessToken);
|
|
|
|
- headParamMap.put("signature", signature);
|
|
|
|
- headParamMap.put("pay_sig", paySig);
|
|
|
|
|
|
+ private static final String PAY_URL = "/wxa/game/pay";
|
|
|
|
|
|
|
|
+ @Value("${payConfig.miPay.isSand}")
|
|
|
|
+ private Integer isSand;
|
|
|
|
|
|
- //调用接口获取余额
|
|
|
|
- RestTemplate restTemplate = new RestTemplate();
|
|
|
|
|
|
+ @Autowired
|
|
|
|
+ private IOrderService orderService;
|
|
|
|
|
|
- String url = URIUtil.fillUrlParams("https://api.weixin.qq.com/wxa/game/getbalance", headParamMap, Boolean.FALSE);
|
|
|
|
|
|
+ @Autowired
|
|
|
|
+ private IGameAppletService gameAppletService;
|
|
|
|
|
|
|
|
+ @Autowired
|
|
|
|
+ private RestTemplate restTemplate;
|
|
|
|
|
|
- System.out.println("url : ------>" + url);
|
|
|
|
|
|
+ @Autowired
|
|
|
|
+ private WxApiService wxApiService;
|
|
|
|
|
|
|
|
+ @Autowired
|
|
|
|
+ private IUserService userService;
|
|
|
|
|
|
- String result = restTemplate.postForObject(url, postBody, String.class);
|
|
|
|
|
|
+ /**
|
|
|
|
+ * 支付配置
|
|
|
|
+ */
|
|
|
|
+ private MiPayConfigDTO config;
|
|
|
|
+
|
|
|
|
+ @Override
|
|
|
|
+ public Map<String, Object> create(ProductPayParamBO product, GamePayWayDTO gamePayWayDTO) {
|
|
|
|
+ //查询订单
|
|
|
|
+ PlatformOrderDTO orderInfo = orderService.getByOrderId(product.getOutTradeNo());
|
|
|
|
+ if (Objects.isNull(orderInfo)) {
|
|
|
|
+ throw new CustomException(HttpStatusEnum.ORDER_NO_FIND);
|
|
|
|
+ }
|
|
|
|
+ if (orderInfo.getGameId() == null || orderInfo.getGameId() == 0) {
|
|
|
|
+ throw new CustomException(HttpStatusEnum.ORDER_GAME_ID_IS_NULL);
|
|
|
|
+ }
|
|
|
|
+ this.configInit(product.getGameId());
|
|
|
|
+ //获取用户openId
|
|
|
|
+ UserDTO userDTO = userService.getUserByUserId(product.getUserId());
|
|
|
|
+ String openId = userDTO.getOpenId();
|
|
|
|
+ String sessionKey = userDTO.getSessionKey();
|
|
|
|
+ //获取米大师钱包余额
|
|
|
|
+ Long balance = this.midasGetBalance(openId, sessionKey);
|
|
|
|
+ //返回参数
|
|
|
|
+ Map<String, Object> payParamMap = new HashMap<>(9);
|
|
|
|
+ //余额充足, 直接扣除
|
|
|
|
+ if (balance >= orderInfo.getAmount().longValue()) {
|
|
|
|
+ String billNo = this.midasPay(openId, sessionKey, orderInfo);
|
|
|
|
+ payParamMap.put("status", "0");
|
|
|
|
+ payParamMap.put("billNo", billNo);
|
|
|
|
+ //支付成功
|
|
|
|
+ this.paySuccess(product.getOutTradeNo(), orderInfo.getAmount().toString(), config.getAppId());
|
|
|
|
+ } else {
|
|
|
|
+ //余额不足, 返回订单id, 前端调充值接口, 充值完后再继续支付
|
|
|
|
+ payParamMap.put("currencyType", "CNY");
|
|
|
|
+ payParamMap.put("mode", "game");
|
|
|
|
+ payParamMap.put("zoneId", "1");
|
|
|
|
+ payParamMap.put("offerId", config.getAppId());
|
|
|
|
+ payParamMap.put("env", String.valueOf(this.isSand));
|
|
|
|
+ payParamMap.put("buyQuantity", orderInfo.getAmount().toString());
|
|
|
|
+ payParamMap.put("status", "1");
|
|
|
|
+ payParamMap.put("balance", String.valueOf(balance));
|
|
|
|
+ payParamMap.put("orderId", product.getOutTradeNo());
|
|
|
|
+ //更新订单商户信息
|
|
|
|
+ orderService.update(new LambdaUpdateWrapper<Order>()
|
|
|
|
+ .set(Order::getMerchantNo, config.getAppId())
|
|
|
|
+ .set(Order::getStatus, OrderStateEnum.WAIT_PAY.getCode())
|
|
|
|
+ .eq(Order::getOrderId, product.getOutTradeNo()));
|
|
|
|
+ }
|
|
|
|
+ return payParamMap;
|
|
|
|
+ }
|
|
|
|
|
|
- System.out.println("result -----> " + result);
|
|
|
|
|
|
+ private Long midasGetBalance(String openId, String sessionKey) {
|
|
|
|
+ //接口参数
|
|
|
|
+ Map<String, Object> postBodyMap = new HashMap<>(5);
|
|
|
|
+ postBodyMap.put("offer_id", config.getAppId());
|
|
|
|
+ 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(MiPay2Service.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(MiPay2Service.MI_PAY_HOST + MiPay2Service.BALANCE_URL, headParamMap, Boolean.FALSE);
|
|
|
|
+ Map<String, String> resultMap = this.miPayApi(url, postBody);
|
|
|
|
+ return Long.valueOf(resultMap.get("balance"));
|
|
|
|
+ }
|
|
|
|
|
|
- } catch (Exception ignored) {
|
|
|
|
|
|
+ private String midasPay(String openId, String sessionKey, PlatformOrderDTO orderInfo) {
|
|
|
|
+ //接口参数
|
|
|
|
+ Map<String, Object> postBodyMap = new HashMap<>(7);
|
|
|
|
+ postBodyMap.put("openid", openId);
|
|
|
|
+ postBodyMap.put("offer_id", config.getAppId());
|
|
|
|
+ postBodyMap.put("ts", System.currentTimeMillis() / 1000);
|
|
|
|
+ postBodyMap.put("zone_id", "1");
|
|
|
|
+ postBodyMap.put("env", this.isSand);
|
|
|
|
+ postBodyMap.put("amount", orderInfo.getAmount());
|
|
|
|
+ postBodyMap.put("bill_no", orderInfo.getOrderId());
|
|
|
|
+ String postBody = JsonUtil.toString(postBodyMap);
|
|
|
|
+ //接口token
|
|
|
|
+ String accessToken = wxApiService.getAccessToken(config.getAppId(), config.getSecret());
|
|
|
|
+ //登录签名
|
|
|
|
+ String signature = this.calcSignature(postBody, sessionKey);
|
|
|
|
+ //支付签名
|
|
|
|
+ String paySig = this.calcPaySig(MiPay2Service.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(MiPay2Service.MI_PAY_HOST + MiPay2Service.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, String postBody) {
|
|
|
|
+ String result;
|
|
|
|
+ try {
|
|
|
|
+ result = restTemplate.postForObject(url, postBody, String.class);
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
+ log.error("米大师接口调用异常, url : {}, postBody : {}, e : {}", url, postBody, e.getMessage());
|
|
|
|
+ throw new BaseException("米大师接口调用异常");
|
|
|
|
+ }
|
|
|
|
+ if (StringUtils.isEmpty(result)) {
|
|
|
|
+ log.error("米大师接口调用失败, 返回结果为空, url : {}, postBody : {}", url, postBody);
|
|
|
|
+ throw new BaseException("米大师接口调用失败, 返回结果为空");
|
|
|
|
+ }
|
|
|
|
+ Map<String, String> resultMap = JsonUtil.toMap(result, Map.class, String.class, String.class);
|
|
|
|
+ //返回结果成功
|
|
|
|
+ if (Objects.equals("0", resultMap.get("errcode"))) {
|
|
|
|
+ return resultMap;
|
|
}
|
|
}
|
|
|
|
+ //返回结果失败
|
|
|
|
+ log.error("米大师接口调用返回结果失败, url : {}, postBody : {}, errcode : {}, errmsg : {}", url, postBody,
|
|
|
|
+ resultMap.get("errcode"), resultMap.get("errmsg"));
|
|
|
|
+ throw new BaseException("米大师接口调用返回结果失败");
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private void configInit(Long gameId) {
|
|
|
|
+ //查询游戏小程序信息
|
|
|
|
+ GameAppletDTO gameAppletDTO = gameAppletService.getByGameId(gameId);
|
|
|
|
+ //支付配置赋值
|
|
|
|
+ this.config = MiPayConfigDTO.builder()
|
|
|
|
+ .appId(gameAppletDTO.getAppId())
|
|
|
|
+ .secret(gameAppletDTO.getAppSecret())
|
|
|
|
+ .offerId(gameAppletDTO.getMiPayAppId())
|
|
|
|
+ .appKey(gameAppletDTO.getMiPayAppKey())
|
|
|
|
+ .appKeyDev(gameAppletDTO.getMiPayAppKeyDev())
|
|
|
|
+ .build();
|
|
}
|
|
}
|
|
|
|
|
|
- private String calcPaySig(String uri, String postBody, String appKey) throws NoSuchAlgorithmException, InvalidKeyException {
|
|
|
|
|
|
+ private String calcPaySig(String uri, String postBody, String appKey) {
|
|
String needSignMsg = uri + "&" + postBody;
|
|
String needSignMsg = uri + "&" + postBody;
|
|
- Mac hmacSha256 = Mac.getInstance("HmacSHA256");
|
|
|
|
- SecretKeySpec secretKey = new SecretKeySpec(appKey.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
|
|
|
|
- hmacSha256.init(secretKey);
|
|
|
|
- byte[] signatureBytes = hmacSha256.doFinal(needSignMsg.getBytes(StandardCharsets.UTF_8));
|
|
|
|
- return bytesToHex(signatureBytes);
|
|
|
|
|
|
+ return this.calcSignature(needSignMsg, appKey);
|
|
}
|
|
}
|
|
|
|
|
|
- private String calcSignature(String postBody, String sessionKey) throws NoSuchAlgorithmException, InvalidKeyException {
|
|
|
|
- Mac hmacSha256 = Mac.getInstance("HmacSHA256");
|
|
|
|
- SecretKeySpec secretKey = new SecretKeySpec(sessionKey.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
|
|
|
|
- hmacSha256.init(secretKey);
|
|
|
|
- byte[] signatureBytes = hmacSha256.doFinal(postBody.getBytes(StandardCharsets.UTF_8));
|
|
|
|
- return bytesToHex(signatureBytes);
|
|
|
|
|
|
+ private String calcSignature(String postBody, String sessionKey) {
|
|
|
|
+ try {
|
|
|
|
+ Mac hmacSha256 = Mac.getInstance(MiPay2Service.SIGN_HMACSHA256);
|
|
|
|
+ SecretKeySpec secretKey = new SecretKeySpec(sessionKey.getBytes(StandardCharsets.UTF_8), MiPay2Service.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) {
|
|
private String bytesToHex(byte[] bytes) {
|
|
@@ -107,27 +237,18 @@ public class MiPay2Service {
|
|
return hexString.toString();
|
|
return hexString.toString();
|
|
}
|
|
}
|
|
|
|
|
|
- /**
|
|
|
|
- * 获取访问令牌
|
|
|
|
- *
|
|
|
|
- * @param appId 应用程序id
|
|
|
|
- * @param secret 秘密
|
|
|
|
- * @return {@link String}
|
|
|
|
- */
|
|
|
|
- private String getAccessToken(String appId, String secret) {
|
|
|
|
- Map<String, String> paramMap = new HashMap<>(4);
|
|
|
|
- paramMap.put("appid", appId);
|
|
|
|
- paramMap.put("secret", secret);
|
|
|
|
- paramMap.put("grant_type", "client_credential");
|
|
|
|
- // 发送请求
|
|
|
|
- String url = URIUtil.fillUrlParams("https://api.weixin.qq.com/cgi-bin/token", paramMap, Boolean.FALSE);
|
|
|
|
- RestTemplate restTemplate = new RestTemplate();
|
|
|
|
- String sr = restTemplate.getForObject(url, String.class);
|
|
|
|
- // 解析相应内容(转换成json对象)
|
|
|
|
- Map<String, String> userMap = JsonUtil.toMap(sr, Map.class, String.class);
|
|
|
|
- if (userMap == null || Strings.isBlank(userMap.get("access_token"))) {
|
|
|
|
- throw new BaseException("获取应用token失败");
|
|
|
|
- }
|
|
|
|
- return userMap.get("access_token");
|
|
|
|
|
|
+ @Override
|
|
|
|
+ public String notify(HttpServletRequest request, HttpServletResponse response) {
|
|
|
|
+ throw new BaseException("米大师支付不存在异步回调");
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @Override
|
|
|
|
+ public ResultVO synNotify(HttpServletRequest request) {
|
|
|
|
+ throw new BaseException("米大师支付不存在同步回调");
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @Override
|
|
|
|
+ public void closeOrder(PlatformOrderDTO platformOrderDTO) {
|
|
|
|
+ log.error("米大师支付不存在取消订单");
|
|
}
|
|
}
|
|
}
|
|
}
|