小程序登录
官方文档链接: https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/user-login/code2Session.html
https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/login.html
小程序登录1.前端调用 wx.login() 获取 临时登录凭证code ,并回传到开发者服务器
小程序登录2.后端调用 auth.code2Session 接口,换取 用户唯一标识 OpenID
小程序登录3.前端获取手机号,后端进行手机号解密
https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/user-info/phone-number/getPhoneNumber.html
代码展示
controller
@RestController
@RequestMapping("/auth")
public class AuthController {
private Logger logger = LoggerFactory.getLogger(AuthController.class);
@Autowired
private AuthService authService;
/**
* 登陆逻辑:
* 1.前端传入code,后台去微信请求当前code的sessionStatus。
* 2.为该sessionStatus生成一个token。
* 3.将token为key, sessionStatus为value存储到redis。
* 4.将该token返回到客户端。
* @param code code
* @return 携带token的响应
*/
@GetMapping("/login")
@ApiOperation("微信小程序授权登录")
public BaseResult<AuthInfoVo> login(@NotNull @RequestParam String code) {
try {
return authService.login(code);
} catch (Exception e) {
logger.error("user execute login error code:{}", code, e);
return BaseResult.fail(EnumPgjgFbsErrorCode.ERR_SYSTEM_ERROR.getCode(), EnumPgjgFbsErrorCode.ERR_SYSTEM_ERROR.getMsg());
}
}
/**
* 解密逻辑:
* 1. 通过前端传入的token,去redis获取sessionStatus。
* 2. 通过sessionStatus中的信息和前端传入的加密数据,解密出手机号。
* 3. 将手机号返回给前端页面。
* @param request
* @param param param
* @return 解密后的手机号
*/
@ApiOperation("解密手机号")
@PostMapping("/decryptPhone")
public BaseResult<String> decrypt(HttpServletRequest request, @RequestBody JSONObject param) {
String token = request.getHeader("Token");
try {
return authService.decrypt(token, param);
} catch (Exception e) {
logger.error("execute decrypt phone token:{}, param:{}", token, param.toJSONString(), e);
return BaseResult.fail(EnumPgjgFbsErrorCode.ERR_SYSTEM_ERROR.getCode(), EnumPgjgFbsErrorCode.ERR_SYSTEM_ERROR.getMsg());
}
}
/**
* 查询用户信息
* @param param
* @return
*/
@ApiOperation("根据token查询加密的用户信息")
@PostMapping("/findWxByToken")
public BaseResult<String> findWxByToken(HttpServletRequest request) {
try {
BaseResult<String> result = authService.findWxByToken(request);
return result;
} catch (Exception e) {
logger.error("findWxByToken appear exception, param:", e);
return BaseResult.fail(EnumPgjgFbsErrorCode.ERR_SYSTEM_ERROR.getCode(), EnumPgjgFbsErrorCode.ERR_SYSTEM_ERROR.getMsg());
}
}
}
service
@Service
public class AuthServiceImpl implements AuthService {
private Logger logger = LoggerFactory.getLogger(AuthServiceImpl.class);
@Qualifier("stringRedisTemplate")
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Autowired
private UserWxRepository wxRepository;
@Autowired
RestTemplate restTemplate;
@Autowired
private IUserDefaultCanteenService iUserDefaultCanteenService;
@Value("${spring.cache.redis.token-key-prefix}")
private String redisTokenKeyPrefix;
@Value("${spring.cache.redis.code-key-prefix}")
private String redisCodeKeyPrefix;
@Value("${wechat-pay.appId}")
private String appid;
@Value("${wechat-pay.secret}")
private String secret;
@Autowired
private IrdsClient irdsClient;
@Autowired
private RedissonClient redisson;
//锁失效时间
private long lockLeaseTime = 10000L;
//加锁超时时间,根据业务情况进行设置,如果只需要一个线程执行,可设置值为很小,如果是想串行化,可根据并发情况设置合理值。
private long tryGetlockTime = 1L;
@Autowired
private UserCanteenService userCanteenService;
@Autowired
private CanteenService canteenService;
public static boolean test = false;
/**
* 小程序登录1.调用 wx.login() 获取 临时登录凭证code ,并回传到开发者服务器
* @param code 小程序页面调用wx.login获取到的code
* @return 附带token的返回体
*/
@Override
public BaseResult<AuthInfoVo> login(String code) {
logger.info("execute user login, code:{}", code);
RLock lock=null;
try {
String body = sessionStatus(code);
if (body != null && body.length() > 0) {
JSONObject bodyEntity = JSONObject.parseObject(body);
String sessionKey = bodyEntity.getString("session_key");
String openid = bodyEntity.getString("openid");
String unionId=bodyEntity.getString("unionid");
lock = redisson.getLock(openid);
//获取锁
boolean flag = lock.tryLock(tryGetlockTime, lockLeaseTime, TimeUnit.MILLISECONDS);
if(flag){
// 维护会话状态,返回唯一标识
String token = UUIDUtil.generateToken();
JSONObject userStatus = new JSONObject();
userStatus.put("openid", openid);
userStatus.put("sessionKey", sessionKey);
userStatus.put("unionid",unionId);
String tokenKey = redisTokenKeyPrefix + token;
// redis存储token,前端检查token失效后重新调用该接口生成token
redisTemplate.opsForValue().set(tokenKey, userStatus.toJSONString());
logger.info("小程序微信授权 wx login {}",userStatus.toJSONString());
// redisTemplate.opsForValue().setIfAbsent(tokenKey, userStatus.toJSONString(), 1, TimeUnit.DAYS);
//将微信用户入库
UserWxEntity wxEntity = wxRepository.findByOpenId(openid);
if(wxEntity==null){
wxEntity=new UserWxEntity();
wxEntity.setOpenId(openid);
wxEntity.setSessionKey(sessionKey);
wxEntity.setUnionId(unionId);
wxEntity.setToken(tokenKey);
wxEntity.setOrgin(0);
}else{
wxEntity.setSessionKey(sessionKey);
wxEntity.setUnionId(unionId);
wxEntity.setToken(tokenKey);
}
wxRepository.save(wxEntity);
AuthInfoVo vo = new AuthInfoVo();
vo.setToken(tokenKey);
return BaseResult.success(vo);
}else {
logger.info("login repeat ");
return BaseResult.fail(EnumPgjgFbsErrorCode.ERR_REPEAT_LOGIN_ERROR.getCode(), EnumPgjgFbsErrorCode.ERR_REPEAT_LOGIN_ERROR.getMsg());
}
} else {
logger.info("execute get user session status, body is null");
return BaseResult.fail(EnumPgjgFbsErrorCode.ERR_REMOTE_API_ERROR.getCode(), EnumPgjgFbsErrorCode.ERR_REMOTE_API_ERROR.getMsg());
}
} catch (Exception e) {
logger.error("handle user session status failure", e);
return BaseResult.fail(EnumPgjgFbsErrorCode.ERR_SYSTEM_ERROR.getCode(), EnumPgjgFbsErrorCode.ERR_SYSTEM_ERROR.getMsg());
}finally {
//释放锁
if(lock!=null){
if (lock.isLocked() && lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
}
/**
* 解密手机号
* @param token token
* @param param 原始参数
* @return 附带手机号的返回体
*/
@Override
public BaseResult<String> decrypt(String token, JSONObject param) {
try {
logger.info("will execute decrypt, token:{}, param:{}", token, param.toJSONString());
if (test) {
String phoneNumber = UUIDUtil.createPhoneNum();
return BaseResult.success(phoneNumber);
}
String userStatus = redisTemplate.opsForValue().get(token);
if (StringUtils.isBlank(userStatus)) {
logger.error("not find sessionStatus about with this token, token:{}", token);
return BaseResult.fail(EnumPgjgFbsErrorCode.ERR_EXPIRED_TOKEN_ERROR.getCode(), EnumPgjgFbsErrorCode.ERR_EXPIRED_TOKEN_ERROR.getMsg());
}
UserWxEntity wxEntity= wxRepository.findByToken(token);
if(wxEntity==null){
return BaseResult.fail(EnumPgjgFbsErrorCode.ERR_EXPIRED_TOKEN_ERROR.getCode(), EnumPgjgFbsErrorCode.ERR_EXPIRED_TOKEN_ERROR.getMsg());
}
// sessionKey
JSONObject userStatusResp = JSONObject.parseObject(userStatus);
String sessionKey = userStatusResp.getString("sessionKey");
// encryptedData iv
String encryptedData = param.getString("encryptedData");
String iv = param.getString("iv");
AESCBCUtil aescbcUtil = new AESCBCUtil(encryptedData, sessionKey, iv);
String result = aescbcUtil.decrypt();
JSONObject resultRes = JSONObject.parseObject(result);
logger.info("execute decrypt end, resultRes:{}", resultRes.toJSONString());
String phoneNumber = resultRes.get("phoneNumber").toString().replaceAll("\"", "");
UserWxEntity byPhone = wxRepository.findByPhone(phoneNumber);
if(byPhone!=null){
logger.info("will execute decrypt,repeatable phone:{}", JSON.toJSON(byPhone));
logger.info("will execute decrypt,delete repeatable phone:{}", JSON.toJSON(byPhone));
wxRepository.delete(byPhone);
}
wxEntity.setPhoneNo(phoneNumber);
wxRepository.save(wxEntity);
BaseResult<IrdsPersonInfoVo> irdsPersonInfoVoBaseResult = irdsClient.personInfo(phoneNumber);
JSONObject jsonObject=new JSONObject();
if(irdsPersonInfoVoBaseResult!=null&&irdsPersonInfoVoBaseResult.getData()!=null){
jsonObject.put("login",true);
}else {
jsonObject.put("login",false);
}
jsonObject.put("phoneNo",phoneNumber);
return BaseResult.success(jsonObject.toJSONString());
} catch (Exception e) {
logger.error("decrypt user phone data failure stack:{}, message:{}", e);
return BaseResult.fail(EnumPgjgFbsErrorCode.ERR_SYSTEM_ERROR.getCode(), EnumPgjgFbsErrorCode.ERR_SYSTEM_ERROR.getMsg());
}
}
@Override
public BaseResult<String> findWxByToken(HttpServletRequest request) throws Exception {
logger.info("---------------------------findWxByToken start -------------------------------");
String token = request.getHeader("token");
if (StringUtils.isBlank(token)) {
return BaseResult.fail(EnumPgjgFbsErrorCode.ERR_NOT_TOKEN_ERROR.getCode(), EnumPgjgFbsErrorCode.ERR_NOT_TOKEN_ERROR.getMsg());
}
UserWxEntity userWxEntity = wxRepository.findByToken(token);
if (userWxEntity == null || StringUtils.isBlank(userWxEntity.getOpenId()) || StringUtils.isBlank(userWxEntity.getPhoneNo())) {
return BaseResult.fail(EnumPgjgFbsErrorCode.ERR_EXPIRED_TOKEN_ERROR.getCode(), EnumPgjgFbsErrorCode.ERR_EXPIRED_TOKEN_ERROR.getMsg());
}
FindWxByTokenVo byToken = new FindWxByTokenVo();
BeanUtils.copyProperties(userWxEntity, byToken);
byToken.setIsAdmin(CommUtil.isNull(userCanteenService.queryByUserId(userWxEntity.getPhoneNo())) ? false : true);
BaseResult<IrdsPersonInfoVo> personResult = irdsClient.personInfo(userWxEntity.getPhoneNo());
// UserDefaultCanteenEntity canteenEntity = iUserDefaultCanteenService.getById(byToken.getPhoneNo());
JSONObject jsonObject = new JSONObject();
jsonObject.put("openId", AESSecretUtil.encryptToStr(byToken.getOpenId(), AESSecretUtil.DATA_KEY));
jsonObject.put("phone", byToken.getPhoneNo());
jsonObject.put("isAdmin", byToken.getIsAdmin());
jsonObject.put("orgin", byToken.getOrgin());
jsonObject.put("token", AESSecretUtil.encryptToStr(token, AESSecretUtil.DATA_KEY));
jsonObject.put("realName", userWxEntity.getRealName());
// jsonObject.put("canteen", JsonUtil.toJson(canteenEntity));
if (personResult != null && personResult.getData() != null &&
StringUtils.isNotBlank(personResult.getData().getPhoneNo())) {
List<CanteenUserVo> canteenUserVos = canteenService.listMerchantsByPersonId(userWxEntity.getPhoneNo());
if(canteenUserVos!=null&&canteenUserVos.size()>0){
jsonObject.put("loginAuth", 1);
userWxEntity.setLoginAuth(1);
wxRepository.save(userWxEntity);
}else {
jsonObject.put("loginAuth", 0);
userWxEntity.setLoginAuth(0);
wxRepository.save(userWxEntity);
}
} else {
jsonObject.put("loginAuth", 0);
userWxEntity.setLoginAuth(0);
wxRepository.save(userWxEntity);
}
logger.info("---------------------------findWxByToken end -------------------------------");
return BaseResult.success(jsonObject.toJSONString());
}
/**
* 小程序登录2.调用 auth.code2Session 接口,换取 用户唯一标识 OpenID
*/
private String sessionStatus(String code) {
RestTemplate restTemplate = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity httpEntity = new HttpEntity<>(null, headers);
String grantType = "authorization_code";
String loginApi = PayUrlConstant.weiXinJscode2sessionApi;
String domian = PayUrlConstant.weiXinDomain;
String loginUrl = domian + loginApi + "?appid=" + appid + "&secret=" + secret + "&js_code=" + code + "&grant_type=" + grantType;
logger.info("execute request from weixin url:{}", loginUrl);
String body = "";
// todo 添加失败重试
try {
ResponseEntity<String> respEntity = restTemplate.exchange(loginUrl, HttpMethod.GET, httpEntity, String.class);
logger.info("execute request from weixin respEntity:{}", JSONObject.toJSONString(respEntity));
HttpStatus statusCode = respEntity.getStatusCode();
if (HttpStatus.OK.value() == statusCode.value()) {
body = respEntity.getBody();
}
} catch (Exception e) {
logger.error("execute request from weixin error, url:{}", loginUrl, e);
}
return body;
}
}
工具类AESCBCUtil
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.AlgorithmParameters;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.Security;
import java.security.spec.InvalidParameterSpecException;
import java.util.Base64;
/**
* 写点注释吧
*
* @author gaozhilong
* @date 2021/1/25 15:47
* @since v1.0.0001
*/
public class AESCBCUtil {
private Logger logger = LoggerFactory.getLogger(AESCBCUtil.class);
/**
* 加密方式
*/
private static String keyAlgorithm = "AES";
/**
* 避免重复new生成多个BouncyCastleProvider对象,因为GC回收不了,会造成内存溢出
* 只在第一次调用decrypt()方法时才new 对象
*/
private static boolean initialized = false;
/**
* 用于Base64解密
*/
private Base64.Decoder decoder = Base64.getDecoder();
/**
* 待解密的数据
*/
private String encryptedData;
/**
* 会话密钥sessionKey
*/
private String sessionKey;
/**
* 加密算法的初始向量
*/
private String iv;
public AESCBCUtil(String encryptedData, String sessionKey, String iv) {
this.encryptedData = encryptedData;
this.sessionKey = sessionKey;
this.iv = iv;
}
/**
* BouncyCastle作为安全提供,防止我们加密解密时候因为jdk内置的不支持改模式运行报错。
*/
private static void initialize() {
if (initialized) {
return;
}
Security.addProvider(new BouncyCastleProvider());
initialized = true;
}
/**
* 生成iv
* @param iv iv
* @return
* @throws NoSuchAlgorithmException
* @throws InvalidParameterSpecException
*/
private static AlgorithmParameters generateIV(byte[] iv) throws NoSuchAlgorithmException, InvalidParameterSpecException {
AlgorithmParameters params = AlgorithmParameters.getInstance(keyAlgorithm);
params.init(new IvParameterSpec(iv));
return params;
}
public String decrypt() {
initialize();
try {
//数据填充方式
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding", "BC");
Key sKeySpec = new SecretKeySpec(decoder.decode(this.sessionKey), keyAlgorithm);
// 初始化
cipher.init(Cipher.DECRYPT_MODE, sKeySpec, generateIV(decoder.decode(this.iv)));
byte[] data = cipher.doFinal(decoder.decode(this.encryptedData));
return new String(data, StandardCharsets.UTF_8);
} catch (Exception e) {
logger.error("execute decrypt error", e);
return null;
}
}
}
微信配置信息
wechat-pay.appId=***
wechat-pay.secret=***
wechat-pay.mchId=***
wechat-pay.payNotifyUrl=https://***/wallet/account/notifyUrl
wechat-pay.refundNotifyUrl=
wechat-pay.serialNo=xxxxxxxxxxxx
wechat-pay.apiV3Key=xxxxxxxxxxxxxxxxxxx
wechat-pay.certificateKeyPath=xxxxxxxxxxxxxxxxxxx
ums-pay.appId=8a81c1bd7f93c875017faa165b6b00d7
ums-pay.appKey=c26600c9595742bc801462f20d9ecad8
ums-pay.mid=89810008651023B
ums-pay.tid=DM243149
ums-pay.notifyKey=CRaGxE6i27QKN3nyRt8irXdw8fMS7YASFzcCQ2kRp5KyZWX6
ums-pay.payNotifyUrl=https://pgjghy.bjpg.org/pgjgfbs/wallet/account/notifyUrl
ums-pay.sourceCode=13LT
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
/**
* @description:
* @author: wangxiaogang10
* @date: 2022/2/28 15:46
* @see
* @since 1.0
*/
@Data
@Configuration
@ConfigurationProperties(prefix = "ums-pay")
public class UmsConfig {
/**
* appid must
*/
@Value("${ums-pay.appId}")
private String appId;
/**
* 小程序唯一凭证密钥
*/
@Value("${ums-pay.appKey}")
private String appKey;
/**
* 小程序唯一凭证密钥
*/
@Value("${ums-pay.notifyKey}")
private String notifyKey;
/**
* 终端号 must
*/
@Value("${ums-pay.tid}")
private String tid;
/**
* 商户号 must
*/
@Value("${ums-pay.mid}")
private String mid;
/**
* 微信支付回到接口 must
*/
@Value("${ums-pay.payNotifyUrl}")
private String payNotifyUrl;
/**
* 微信支付回到接口 must
*/
@Value("${ums-pay.sourceCode}")
private String sourceCode;
}
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Data
@Configuration
@ConfigurationProperties(prefix = "wechat-pay")
public class WxConfig {
/**
* appid must
*/
@Value("${wechat-pay.appId}")
private String appId;
/**
* 小程序唯一凭证密钥
*/
@Value("${wechat-pay.secret}")
private String secret;
/**
* 商户号 must
*/
@Value("${wechat-pay.mchId}")
private String mchId;
/**
* 微信支付回到接口 must
*/
@Value("${wechat-pay.payNotifyUrl}")
private String payNotifyUrl;
/**
* 退款回调接口
*/
private String refundNotifyUrl;
/**
* 序列号
*/
private String serialNo;
/**
* API v3密钥 must
*/
private String apiV3Key;
/**
* 证书在服务器上的路径
*/
private String certificateKeyPath;
}
Token拦截器接口鉴权
import com.hikvision.pgjgfbs.interceptor.TokenInterceptor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.config.annotation.InterceptorRegistration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.ArrayList;
import java.util.List;
/**
* web配置,配置下过滤器的规则
*
*/
@Configuration
public class WebAppConfig implements WebMvcConfigurer {
private Logger logger = LoggerFactory.getLogger(WebAppConfig.class);
@Qualifier("stringRedisTemplate")
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Value("${component.enable.filter}")
private boolean enableFilter;
@Value("${spring.cache.redis.token-key-prefix}")
private String redisTokenKeyPrefix;
@Value("${spring.cache.redis.web-token-key-prefix}")
private String webTokenPrefix;
private static List<String> webExcludePath = new ArrayList<>();
private static List<String> defaultExcludePath = new ArrayList<String>();
private static List<String> defaulAddtPath = new ArrayList<String>();
static {
// login登陆接口
defaultExcludePath.add("/auth/login");
defaultExcludePath.add("/wallet/account/notifyUrl");
webExcludePath.add("/index");
webExcludePath.add("/wallet/hello");
webExcludePath.add("/**/**/*.html");
webExcludePath.add("/**/*.html");
defaulAddtPath.add("/wallet/**");
defaulAddtPath.add("/auth/decryptPhone");
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 添加小程序请求拦截器
if (enableFilter) {
TokenInterceptor tokenInterceptor = new TokenInterceptor(redisTemplate, redisTokenKeyPrefix);
InterceptorRegistration registration = registry.addInterceptor(tokenInterceptor);
registration.addPathPatterns(defaulAddtPath);
// 忽略默认的几个端口
String[] swaggerExcludes=new String[]{"/swagger-ui.html","/swagger-resources/**","/webjars/**"};
registration.excludePathPatterns(defaultExcludePath);
// 忽略web端的几个接口
registration.excludePathPatterns(webExcludePath);
registration.excludePathPatterns(swaggerExcludes);
}
}
private void addExcludePath(InterceptorRegistration registration, String pathMessage) {
try {
if (StringUtils.hasText(pathMessage)) {
String[] paths = pathMessage.split(",");
for (String path : paths) {
registration.excludePathPatterns(path);
}
}
} catch (Exception e) {
logger.error("add exclude error, pathMessage:{}", pathMessage, e);
}
}
}
import com.alibaba.fastjson.JSONObject;
import com.hikvision.pgjgfbs.common.BaseResult;
import com.hikvision.pgjgfbs.common.enums.EnumPgjgFbsErrorCode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
/**
*
* token拦截器
* @author gaozhilong
* @date 2021/1/25 17:46
* @since v1.0.0001
*/
public class TokenInterceptor implements HandlerInterceptor {
private static final Logger log = LoggerFactory.getLogger(TokenInterceptor.class);
RedisTemplate<String, String> redisTemplate;
String redisKeyTokenPrefix;
public TokenInterceptor(RedisTemplate<String, String> redisTemplate, String redisKeyTokenPrefix) {
this.redisTemplate = redisTemplate;
this.redisKeyTokenPrefix = redisKeyTokenPrefix;
}
/**
* 进入controller层之前拦截请求
* @author gaozhilong
* @date 2021/1/25 17:46
* @since v1.0.0001
* @param request request
* @param response response
* @param object object
* @return res
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object object) throws Exception {
// 验证token, token不存在或过期,需要用户重新登陆
String token = request.getHeader("token");
log.info("---------------------preHandle enter token interceptor---------------------------- token:{}",token);
if (!StringUtils.hasText(token)) {
response.setCharacterEncoding("UTF-8");
PrintWriter printWriter = response.getWriter();
BaseResult<Object> failRes = BaseResult.fail(EnumPgjgFbsErrorCode.ERR_NOT_TOKEN_ERROR.getCode(), EnumPgjgFbsErrorCode.ERR_NOT_TOKEN_ERROR.getMsg());
String message = JSONObject.toJSONString(failRes);
printWriter.write(message);
return false;
}
String userStatus = (String)redisTemplate.opsForValue().get(token);
if (!StringUtils.hasText(userStatus)) {
response.setCharacterEncoding("UTF-8");
PrintWriter printWriter = response.getWriter();
BaseResult<Object> failRes = BaseResult.fail(EnumPgjgFbsErrorCode.ERR_EXPIRED_TOKEN_ERROR.getCode(), EnumPgjgFbsErrorCode.ERR_EXPIRED_TOKEN_ERROR.getMsg());
String message = JSONObject.toJSONString(failRes);
printWriter.write(message);
return false;
}
return true;
}
@Override
public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
}
}
小程序银联支付
应用场景
小程序支付是指在商户既有的小程序内通过对接微信支付API,实现用户在小程序内完成交易的场景。客户由于微信支付抽成较高的关系,改对接银联支付,银联入驻微信支付的服务商,实现方式前端调起支付api不变,后端改为对接银联的支付api。
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.tomcat.util.codec.binary.Base64;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.*;
/**
* @description:
* @author: wangxiaogang10
* @date: 2022/2/28 10:15
* @see
* @since 1.0
*/
@Slf4j
@Service
public class UmsPayServiceImpl implements PayService {
@Autowired
private WxConfig wxConfig;
@Autowired
private UmsConfig umsConfig;
@Autowired
private WalletService walletService;
@Resource
private OrderRepository orderRepository;
@Resource
private UserWxRepository userWxRepository;
@Override
public Map<String, String> unifiedOrder(MiniRechargeParam param) throws Exception {
//1.通过手机号,查询人员信息 irds
String personInfoStr = walletService.personInfo(param.getPhoneNo());
ApiResponse<PersonInfo> personInfoApiResponse = JSON.parseObject(personInfoStr, new TypeReference<ApiResponse<PersonInfo>>() {});
if(personInfoApiResponse.getCode().equals(CommonConstant.ok)||personInfoApiResponse.getCode().equals(CommonConstant.ok_200)){
PersonInfo person = personInfoApiResponse.getData();
if(person!=null){
String outTradeNo= getOutTradeNo();
UmsUnifiedParam unifiedOrderParam = getUnifiedOrderParam(param,outTradeNo);
String result= HttpUmsUtils.sendPost(PayUrlConstant.ums_unified_order, JSON.toJSONString(unifiedOrderParam),
getOpenBodySig(JSON.toJSONString(unifiedOrderParam)));
JSONObject jsonObject = JSONObject.parseObject(result);
JSONObject miniPayRequestObj = jsonObject.getJSONObject("miniPayRequest");
String miniPayRequest=miniPayRequestObj.toJSONString();
if (StringUtils.isBlank(miniPayRequest)) {
throw new CustomException("miniPayRequest 为空");
}
Map<String, String> umsPayParam = new HashMap<>();
umsPayParam.put("miniPayRequest",miniPayRequest);
umsPayParam.put("merOrderId",outTradeNo);
OrderEntity orderEntity=new OrderEntity();
orderEntity.setMoney(param.getMoney());
orderEntity.setPrepayId(miniPayRequestObj.getString("prepayid"));
orderEntity.setPersonId(person.getPersonId());
orderEntity.setPhoneNo(param.getPhoneNo());
orderEntity.setStatus("0");
orderEntity.setOutTradeNo(outTradeNo);
orderRepository.save(orderEntity);
return umsPayParam;
}else {
throw new Exception("通过手机号,查询人员信息 irds person null");
}
}else {
throw new Exception("通过手机号,查询人员信息 irds api调用失败");
}
}
/**
* 支付回调
* @param request
* @return
* @throws Exception
*/
@Override
public String PayNotify(HttpServletRequest request, HttpServletResponse response) throws Exception {
/*接收参数*/
Map<String, String> params = getRequestParams(request);
log.info("payNotify:{}",params);
String sign = params.get("sign");
/*验签*/
//对通知内容生成sign
String strSign = makeSign(umsConfig.getNotifyKey(),params);
//System.out.println("strSign="+strSign);
//判断签名是否相等
if (sign.equals(strSign)) {
// 收到通知后记得返回SUCCESS
log.info("支付回调{}",JSON.toJSONString(params));
String status = params.get("status");
String merOrderId=params.get("merOrderId");
String totalAmount=params.get("totalAmount");
//根据支付状态做不同的业务处理
if ("TRADE_SUCCESS".equals(status)) {
//todo 支付成功业务逻辑,如将订单状态更新为已支付
OrderEntity orderEntity= orderRepository.findByOutTradeNo(merOrderId);
if(orderEntity==null){
throw new CustomException("没有此订单");
}
if("1".equals(orderEntity.getStatus())){
PrintWriter writer = response.getWriter();
writer.print("SUCCESS");
writer.flush();
JSONObject jsonObject=new JSONObject();
jsonObject.put("code","SUCCESS");
jsonObject.put("message","成功");
return jsonObject.toJSONString();
}
CemsRechargeParam param=new CemsRechargeParam();
param.setMoney(Integer.parseInt(totalAmount));
param.setPersonId(orderEntity.getPersonId());
param.setRechargeId(merOrderId);
String rechargeStr = walletService.recharge(param,JSON.toJSONString(params));
ApiResponse<RechargeInfo> apiResponse = JSON.parseObject(rechargeStr, new TypeReference<ApiResponse<RechargeInfo>>() {});
if(!apiResponse.getCode().equals(CommonConstant.ok)||apiResponse.getData()==null){
orderEntity.setStatus("3");
orderRepository.save(orderEntity);
log.info("cems recharge 调用失败 response{}",JSON.toJSONString(apiResponse));
throw new CustomException("cems recharge 调用失败", EnumPgjgFbsErrorCode.ERR_SYSTEM_CEMS_ERROR.getCode());
}
orderEntity.setStatus("1");
orderRepository.save(orderEntity);
PrintWriter writer = response.getWriter();
writer.print("SUCCESS");
writer.flush();
JSONObject jsonObject=new JSONObject();
jsonObject.put("code","SUCCESS");
jsonObject.put("message","成功");
return jsonObject.toJSONString();
} else {
//主动调用查询交易api
MiniQueryParam miniQueryParam = new MiniQueryParam();
miniQueryParam.setOutTradeNo(merOrderId);
BaseResult baseResult = queryByOutTradeNo(miniQueryParam);
if(CommonConstant.ok.equals(baseResult.getCode())){
PrintWriter writer = response.getWriter();
writer.print("SUCCESS");
writer.flush();
JSONObject jsonObject=new JSONObject();
jsonObject.put("code","SUCCESS");
jsonObject.put("message","成功");
return jsonObject.toJSONString();
}else {
PrintWriter writer = response.getWriter();
writer.print("FAILED");
writer.flush();
JSONObject jsonObject=new JSONObject();
jsonObject.put("code","FAILED");
jsonObject.put("message","失败");
return jsonObject.toJSONString();
}
}
}else {
log.info("payNotify验签失败{}",strSign);
PrintWriter writer = response.getWriter();
writer.print("FAILED");
writer.flush();
JSONObject jsonObject=new JSONObject();
jsonObject.put("code","FAILED");
jsonObject.put("message","验签失败");
return jsonObject.toJSONString();
}
}
@Override
public BaseResult queryByOutTradeNo(MiniQueryParam param) throws Exception {
JSONObject queryOrderParam=new JSONObject();
queryOrderParam.put("requestTimestamp",DateUtils.dateToStrFormat(new Date(),DateUtils.FORMAT_yyyyMMddHHmmss));
queryOrderParam.put("mid",umsConfig.getMid());
queryOrderParam.put("tid",umsConfig.getTid());
queryOrderParam.put("merOrderId",param.getOutTradeNo());
String result= HttpUmsUtils.sendPost(PayUrlConstant.ums_query_order, queryOrderParam.toJSONString(),
getOpenBodySig(queryOrderParam.toString()));
JSONObject reponse = JSONObject.parseObject(result);
String status = reponse.getString("status");
String merOrderId=reponse.getString("merOrderId");
String totalAmount=reponse.getString("totalAmount");
if("TRADE_SUCCESS".equals(status)){
OrderEntity orderEntity= orderRepository.findByOutTradeNo(merOrderId);
if(orderEntity==null){
throw new CustomException("没有此订单");
}
if(orderEntity.getStatus().equals("1")){
return BaseResult.success(result);
}
CemsRechargeParam cemsRechargeParam=new CemsRechargeParam();
cemsRechargeParam.setMoney(Integer.parseInt(totalAmount));
cemsRechargeParam.setPersonId(orderEntity.getPersonId());
cemsRechargeParam.setRechargeId(merOrderId);
String rechargeStr = walletService.rechargeQuery(cemsRechargeParam,result);
ApiResponse<RechargeInfo> rechargeInfoApiResponse = JSON.parseObject(rechargeStr, new TypeReference<ApiResponse<RechargeInfo>>() {});
if(!rechargeInfoApiResponse.getCode().equals(CommonConstant.ok)||rechargeInfoApiResponse.getData()==null){
orderEntity.setStatus("3");
orderRepository.save(orderEntity);
throw new CustomException("cems recharge 调用失败", EnumPgjgFbsErrorCode.ERR_SYSTEM_CEMS_ERROR.getCode());
}
orderEntity.setStatus("1");
orderRepository.save(orderEntity);
return BaseResult.success(result);
}else {
return BaseResult.fail(EnumPgjgFbsErrorCode.ERR_WX_PAY_FAIL.getCode(),result);
}
}
public BaseResult closeByOutTradeNo(MiniQueryParam param) throws Exception {
JSONObject closeOrderParam=new JSONObject();
closeOrderParam.put("requestTimestamp",DateUtils.dateToStrFormat(new Date(),DateUtils.FORMAT_yyyyMMddHHmmss));
closeOrderParam.put("mid",umsConfig.getMid());
closeOrderParam.put("tid",umsConfig.getTid());
closeOrderParam.put("instMid","MINIDEFAULT");
closeOrderParam.put("merOrderId",param.getOutTradeNo());
String result= HttpUmsUtils.sendPost(PayUrlConstant.ums_close_order, closeOrderParam.toJSONString(),
getOpenBodySig(closeOrderParam.toString()));
JSONObject reponse = JSONObject.parseObject(result);
String errCode = reponse.getString("errCode");
String merOrderId=reponse.getString("merOrderId");
if("SUCCESS".equals(errCode)){
OrderEntity orderEntity= orderRepository.findByOutTradeNo(merOrderId);
if(orderEntity==null){
throw new CustomException("没有此订单");
}
orderEntity.setStatus("2");
orderRepository.save(orderEntity);
return BaseResult.success(result);
}else {
return BaseResult.fail(EnumPgjgFbsErrorCode.ERR_WX_CLOSE_FAIL.getCode(),result);
}
}
@Override
public void Refund(Long userId, String orderNo) throws Exception {
}
@Override
public void RefundNotify(HttpServletRequest request) throws Exception {
}
private String getOutTradeNo() {
Random random=new Random();
StringBuffer buf=new StringBuffer();
for (int i=0;i<7 ;i++ ){
buf.append(random.nextInt(10));
}
return umsConfig.getSourceCode()+DateUtils.dateToStrFormat(new Date(),DateUtils.FORMAT_yyyyMMddmmHHssSSS+buf.toString());
}
/**
* 拼接下单参数
* @param param
* @param outTradeNo
* @return
*/
private UmsUnifiedParam getUnifiedOrderParam(MiniRechargeParam param, String outTradeNo) {
UserWxEntity wxEntity= userWxRepository.findByPhone(param.getPhoneNo());
if(wxEntity==null){
throw new CustomException("手机号不存在");
}
return UmsUnifiedParam.builder()
.merOrderId(outTradeNo)
.mid(umsConfig.getMid())
.tid(umsConfig.getTid())
.subAppId(wxConfig.getAppId())
.subOpenId(wxEntity.getOpenId())
.tradeType("MINI")
.totalAmount(param.getMoney()+"")
.notifyUrl(umsConfig.getPayNotifyUrl())
.showUrl("")
.returnUrl("")
.msgId(outTradeNo)
.requestTimestamp(DateUtils.dateToStrFormat(new Date(),DateUtils.FORMAT_yyyyMMddHHmmss))
.build();
}
public String getOpenBodySig(String body) throws IOException {
String timestamp=DateUtils.dateToStrFormat(new Date(),DateUtils.FORMAT_yyyyMMddHHmmss);
String nonce=UUIDUtil.generateCode();
InputStream is=new ByteArrayInputStream(body.getBytes("UTF-8"));
String sha256 = DigestUtils.sha256Hex(is);
log.info(sha256);
String append=umsConfig.getAppId()+timestamp+nonce+sha256;
byte[] localsignature = hmacSHA256(append.getBytes(), umsConfig.getAppKey().getBytes());
String signature = Base64.encodeBase64String(localsignature);
return "OPEN-BODY-SIG AppId="+"\""+umsConfig.getAppId()+"\""+", Timestamp="+"\""+timestamp+"\""+", Nonce="+"\""+nonce+"\""+
", Signature="+"\""+signature+"\"";
}
public static String makeSign(String md5Key, Map<String, String> params) throws UnsupportedEncodingException {
String preStr = buildSignString(params); // 把数组所有元素,按照“参数=参数值”的模式用“&”字符拼接成字符串
String text = preStr + md5Key;
return DigestUtils.sha256Hex(getContentBytes(text)).toUpperCase();
}
public byte[] hmacSHA256(byte[] data, byte[] key){
try {
Mac mac= Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(key,"HmacSHA256"));
return mac.doFinal(data);
}catch (Exception e){
log.error("getOpenBodySig error ",e);
return null;
}
}
/**
* 解析request中的数据
*
* @param request HttpServletRequest
* @return request中的数据
*/
private String getRequestBody(HttpServletRequest request) {
StringBuilder sb = new StringBuilder();
try (ServletInputStream stream = request.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(stream))) {
String line;
while ((line = reader.readLine()) != null) {
sb.append(line);
}
} catch (Exception e) {
log.error("failed e", e);
throw new CustomException("操作失败");
}
return sb.toString();
}
// 获取HttpServletRequest里面的参数
public static Map<String, String> getRequestParams(HttpServletRequest request) {
Map<String, String[]> params = request.getParameterMap();
Map<String, String> params2 = new HashMap<>();
for (String key : params.keySet()) {
String[] values = params.get(key);
if (values.length > 0) {
params2.put(key, request.getParameter(key));
}
}
return params2;
}
// 构建签名字符串
public static String buildSignString(Map<String, String> params) {
if (params == null || params.size() == 0) {
return "";
}
List<String> keys = new ArrayList<>(params.size());
for (String key : params.keySet()) {
if ("sign".equals(key))
continue;
if (StringUtils.isEmpty(params.get(key)))
continue;
keys.add(key);
}
Collections.sort(keys);
StringBuilder buf = new StringBuilder();
for (int i = 0; i < keys.size(); i++) {
String key = keys.get(i);
String value = params.get(key);
if (i == keys.size() - 1) {// 拼接时,不包括最后一个&字符
buf.append(key + "=" + value);
} else {
buf.append(key + "=" + value + "&");
}
}
return buf.toString();
}
// 根据编码类型获得签名内容byte[]
public static byte[] getContentBytes(String content) throws UnsupportedEncodingException {
return content.getBytes("UTF-8");
}
}
微信小程序支付 未验证
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
import com.wechat.pay.contrib.apache.httpclient.auth.AutoUpdateCertificatesVerifier;
import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Credentials;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Validator;
import com.wechat.pay.contrib.apache.httpclient.util.AesUtil;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.Signature;
import java.security.cert.X509Certificate;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.*;
/**
* 不需要签名和验签
*/
@Slf4j
@Service
public class WxPayServiceImpl implements PayService {
@Resource
private WxConfig wxConfig;
@Autowired
private WalletService walletService;
@Resource
private OrderRepository orderRepository;
@Resource
private UserWxRepository userWxRepository;
/**
* 统一下单
*
* @return 小程序调起微信支付控件的参数
* @throws Exception 异常
*/
@Override
public Map<String, String> unifiedOrder(MiniRechargeParam param) throws Exception {
//1.通过手机号,查询人员信息 irds
String personInfoStr = walletService.personInfo(param.getPhoneNo());
ApiResponse<PersonInfo> personInfoApiResponse = JSON.parseObject(personInfoStr, new TypeReference<ApiResponse<PersonInfo>>() {});
if(personInfoApiResponse.getCode().equals(CommonConstant.ok)||personInfoApiResponse.getCode().equals(CommonConstant.ok_200)){
PersonInfo person = personInfoApiResponse.getData();
if(person!=null){
String outTradeNo= SnowFlake.getNextId32();
UnifiedOrderParam unifiedOrderParam = getUnifiedOrderParam(param,outTradeNo);
HttpResponse response = post(PayUrlConstant.UNIFIED_ORDER, JSONObject.toJSONString(unifiedOrderParam));
String bodyAsString = EntityUtils.toString(response.getEntity());
JSONObject jsonObject = JSONObject.parseObject(bodyAsString);
String prepay_id = jsonObject.get("prepay_id").toString();
if (StringUtils.isBlank(prepay_id)) {
throw new CustomException("prepay_id 为空");
}
Map<String, String> wxPayParam = getWxPayParam(prepay_id,outTradeNo);
OrderEntity orderEntity=new OrderEntity();
orderEntity.setMoney(param.getMoney());
orderEntity.setPrepayId(prepay_id);
orderEntity.setPersonId(person.getPersonId());
orderEntity.setPhoneNo(param.getPhoneNo());
orderEntity.setStatus("0");
orderEntity.setOutTradeNo(wxPayParam.get("nonceStr"));
orderRepository.save(orderEntity);
return wxPayParam;
}else {
throw new Exception("通过手机号,查询人员信息 irds person null");
}
}else {
throw new Exception("通过手机号,查询人员信息 irds api调用失败");
}
}
/**
* 统一下单参数拼接
* @return 统一下单参数
*/
private UnifiedOrderParam getUnifiedOrderParam(MiniRechargeParam param, String outTradeNo) {
UserWxEntity wxEntity= userWxRepository.findByPhone(param.getPhoneNo());
if(wxEntity==null){
throw new CustomException("手机号不存在");
}
// 订单金额
UnifiedOrderAmount amount = UnifiedOrderAmount.builder().total(param.getMoney()).currency("CNY").build();
//支付者
UnifiedOrderPayer payer = UnifiedOrderPayer.builder().openid(wxEntity.getOpenId()).build();
return UnifiedOrderParam
.builder()
.appid(wxConfig.getAppId())
.mchid(wxConfig.getMchId())
.description("商品描述")
.out_trade_no(outTradeNo)
.notify_url(wxConfig.getPayNotifyUrl())
.amount(amount)
.payer(payer)
.build();
}
/**
* 拼接小程序调起微信支付控件的参数
*
* @param prePayId 预支付编码
* @return 参数
* @throws Exception 异常
*/
public Map<String, String> getWxPayParam(String prePayId,String nonceStr) throws Exception {
//String nonceStr = RandomStringUtils.randomAlphanumeric(32);
long timestamp = System.currentTimeMillis() / 1000;
//包装prepay_id 等 返给前端由前端拉起支付
HashMap<String, String> params = new HashMap<>();
params.put("appId", wxConfig.getAppId());
params.put("nonceStr", nonceStr);
params.put("package", "prepay_id=" + prePayId);
params.put("signType", "RSA");
params.put("timeStamp", String.valueOf(timestamp));
//重新签名
String signMessage = spliceNewline(wxConfig.getAppId(), timestamp, nonceStr, "prepay_id=" + prePayId);
String paySign = sign(signMessage.getBytes(StandardCharsets.UTF_8));
params.put("paySign", paySign);
return params;
}
/**
* 支付回调(回调接口和获取平台证书的接口都需要做解密)
*
* @param request HttpServletRequest
* @throws Exception 异常
*/
@Override
public String PayNotify(HttpServletRequest request,HttpServletResponse response) throws Exception {
PaySuccessNoticeVo paySuccessNoticeVo = buildCallbackResult(request, PaySuccessNoticeVo.class);
log.info("支付回调{}",JSONObject.toJSONString(paySuccessNoticeVo));
//根据支付状态做不同的业务处理
if (TradeStateEnum.SUCCESS.getCode().equals(paySuccessNoticeVo.getTrade_state())) {
//todo 支付成功业务逻辑,如将订单状态更新为已支付
String out_trade_no = paySuccessNoticeVo.getOut_trade_no();
OrderEntity orderEntity= orderRepository.findByOutTradeNo(out_trade_no);
if(orderEntity==null){
throw new CustomException("没有此订单");
}
if(orderEntity.getStatus().equals("1")){
JSONObject jsonObject=new JSONObject();
jsonObject.put("code","SUCCESS");
jsonObject.put("message","成功");
return jsonObject.toJSONString();
}
CemsRechargeParam param=new CemsRechargeParam();
param.setMoney(paySuccessNoticeVo.getAmount().getPayer_total());
param.setPersonId(orderEntity.getPersonId());
param.setRechargeId(out_trade_no);
String rechargeStr = walletService.recharge(param,"");
ApiResponse<RechargeInfo> apiResponse = JSON.parseObject(rechargeStr, new TypeReference<ApiResponse<RechargeInfo>>() {});
if(!apiResponse.getCode().equals(CommonConstant.ok)){
throw new CustomException("cems recharge 调用失败");
}
RechargeInfo rechargeInfo = apiResponse.getData();
if(rechargeInfo==null){
throw new CustomException("cems recharge 调用成功,返回数据为null");
}
orderEntity.setStatus("1");
orderRepository.save(orderEntity);
JSONObject jsonObject=new JSONObject();
jsonObject.put("code","SUCCESS");
jsonObject.put("message","成功");
return jsonObject.toJSONString();
} else {
JSONObject jsonObject=new JSONObject();
jsonObject.put("code","FAIL");
jsonObject.put("message","失败");
return jsonObject.toJSONString();
}
}
/**
* 申请退款
*
* @param userId 用户id
* @param orderNo 商户订单号
* @throws Exception 异常
*/
@Override
public void Refund(Long userId, String orderNo) throws Exception {
//查询订单信息
Optional<OrderInfo> optional = null;
if (!optional.isPresent()) {
throw new CustomException("无效订单");
}
if (optional.get().getOrderStatus() != 0) {//可退款状态
throw new CustomException("订单处于不可退款状态!!!,请联系客服.");
}
//执行退款
RefundOrderParam param = getRefundOrderParam();
HttpResponse response = post(PayUrlConstant.UNIFIED_ORDER, JSONObject.toJSONString(param));
String bodyAsString = EntityUtils.toString(response.getEntity());
RefundSuccessVo refundSuccessVo = JSON.parseObject(bodyAsString, RefundSuccessVo.class);
if ("SUCCESS".equals(refundSuccessVo.getStatus())) {
//todo 申请退款成功业务逻辑
}
}
/**
* 退款参数拼接
*
* @return 退款参数
*/
private RefundOrderParam getRefundOrderParam() {
// 退款金额
RefundOrderAmount amount = RefundOrderAmount
.builder()
//原订单金额 去订单表查询此次订单金额
.total(0)
//退款金额 实际退款金额 根据业务需求计算
.refund(0)
.currency("CNY")
.build();
return RefundOrderParam
.builder()
.transaction_id("微信支付订单号")
.out_trade_no("商户订单号")
.out_refund_no("商户退款单号")
.reason("用户退款")
//退款回调接口地址
.notify_url(wxConfig.getRefundNotifyUrl())
.funds_account("AVAILABLE")
.amount(amount)
.build();
}
/**
* 退款回调
*
* @param request HttpServletRequest
* @throws Exception 异常
*/
@Override
public void RefundNotify(HttpServletRequest request) throws Exception {
RefundSuccessNoticeVo refundSuccessNoticeVo = buildCallbackResult(request, RefundSuccessNoticeVo.class);
if ("SUCCESS".equals(refundSuccessNoticeVo.getRefund_status())) {
//todo 退款成功业务逻辑处理
}
}
@Override
public BaseResult queryByOutTradeNo(MiniQueryParam param) throws Exception {
String url = PayUrlConstant.QueryByOutTradeNo + param.getOutTradeNo() + "?mchid=" +wxConfig.getMchId() ;
HttpResponse response = get(url);
String bodyAsString = EntityUtils.toString( response.getEntity());
log.info("queryByOutTradeNo param{} return{}",JSONObject.toJSONString(param),bodyAsString);
//JSONObject jsonObject = JSONObject.parseObject(bodyAsString);
//String trade_state = jsonObject.get("trade_state").toString();
PaySuccessNoticeVo paySuccessNoticeVo = JSON.parseObject(bodyAsString, PaySuccessNoticeVo.class);
if(TradeStateEnum.SUCCESS.getCode().equals(paySuccessNoticeVo.getTrade_state())){
OrderEntity orderEntity= orderRepository.findByOutTradeNo(paySuccessNoticeVo.getOut_trade_no());
if(orderEntity==null){
throw new CustomException("没有此订单");
}
if(orderEntity.getStatus().equals("1")){
return BaseResult.success(bodyAsString);
}
CemsRechargeParam cemsRechargeParam=new CemsRechargeParam();
cemsRechargeParam.setMoney(paySuccessNoticeVo.getAmount().getPayer_total());
cemsRechargeParam.setPersonId(orderEntity.getPersonId());
cemsRechargeParam.setRechargeId(paySuccessNoticeVo.getOut_trade_no());
String rechargeStr = walletService.recharge(cemsRechargeParam,"");
ApiResponse<RechargeInfo> rechargeInfoApiResponse = JSON.parseObject(rechargeStr, new TypeReference<ApiResponse<RechargeInfo>>() {});
if(!rechargeInfoApiResponse.getCode().equals(CommonConstant.ok)){
throw new CustomException("cems recharge 调用失败");
}
RechargeInfo rechargeInfo = rechargeInfoApiResponse.getData();
if(rechargeInfo==null){
throw new CustomException("cems recharge 调用成功,返回数据为null");
}
orderEntity.setStatus("1");
orderRepository.save(orderEntity);
return BaseResult.success(bodyAsString);
}else {
return BaseResult.fail(EnumPgjgFbsErrorCode.ERR_WX_PAY_FAIL.getCode(),bodyAsString);
}
}
/**
* 获取平台证书列表
*
* @return 平台证书列表
* @throws Exception 异常
*/
public String getPlatformCertificateList() throws Exception {
HttpResponse httpResponse = get(PayUrlConstant.GET_CERTIFICATE_LIST);
JSONObject response = JSON.parseObject(EntityUtils.toString(httpResponse.getEntity()));
JSONArray resultArr = (Objects.isNull(response) || Objects.isNull(response.get("data"))) ? new JSONArray() : JSONObject.parseArray(response.get("data").toString());
JSONObject result = JSON.parseObject((Objects.isNull(resultArr) || Objects.isNull(resultArr.get(0))) ? StringUtils.EMPTY : resultArr.get(0).toString());
EncryptMessageVo resource = JSON.parseObject(JSON.toJSONString(result.get("encrypt_certificate")), EncryptMessageVo.class);
AesUtil aesUtil = new AesUtil(wxConfig.getApiV3Key().getBytes(StandardCharsets.UTF_8));
return aesUtil.decryptToString(resource.getAssociated_data().getBytes(StandardCharsets.UTF_8),
resource.getNonce().getBytes(StandardCharsets.UTF_8),
resource.getCiphertext());
}
/**
* 解析request中的数据
*
* @param request HttpServletRequest
* @return request中的数据
*/
private String getRequestBody(HttpServletRequest request) {
StringBuilder sb = new StringBuilder();
try (ServletInputStream stream = request.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(stream))) {
String line;
while ((line = reader.readLine()) != null) {
sb.append(line);
}
} catch (Exception e) {
log.error("failed e", e);
throw new CustomException("操作失败");
}
return sb.toString();
}
/**
* 处理回调接口返回结果
*
* @param request HttpServletRequest
* @param clazz 结果类型
* @return 回调接口返回结果
* @throws Exception 异常
*/
private <T> T buildCallbackResult(HttpServletRequest request, Class<T> clazz) throws Exception {
NoticeVo noticeVo = JSON.parseObject(getRequestBody(request), NoticeVo.class);
EncryptMessageVo resource = noticeVo.getResource();
AesUtil aesUtil = new AesUtil(wxConfig.getApiV3Key().getBytes(StandardCharsets.UTF_8));
String result = aesUtil.decryptToString(resource.getAssociated_data().getBytes(StandardCharsets.UTF_8),
resource.getNonce().getBytes(StandardCharsets.UTF_8),
resource.getCiphertext());
return JSON.parseObject(result, clazz);
}
/**
* 拼接换行符
*
* @param param 参数
* @return 拼接后的结果
*/
private String spliceNewline(Object... param) {
StringBuilder result = new StringBuilder();
for (Object str : param) {
result.append(str).append(StringUtils.LF);
}
return result.toString();
}
/**
* 生成签名
*
* @param message 签名信息
* @return 签名
* @throws Exception 异常
*/
private String sign(byte[] message) throws Exception {
Signature sign = Signature.getInstance("SHA256withRSA");
sign.initSign(getPrivateKey());
sign.update(message);
return Base64.getEncoder().encodeToString(sign.sign());
}
/**
* post请求
*
* @param url url地址
* @param jsonBody 参数体
* @return 返回结果
*/
private HttpResponse post(String url, String jsonBody) throws Exception {
URIBuilder uriBuilder = new URIBuilder(url);
HttpPost httpPost = new HttpPost(uriBuilder.build());
//header封装
httpPost.addHeader("Accept", "application/json");
httpPost.addHeader("Content-Type", "application/json");
StringEntity entity = new StringEntity(jsonBody, StandardCharsets.UTF_8);
httpPost.setEntity(entity);
//执行方法
// 创建httpClient
CloseableHttpClient httpClient = HttpClients.createDefault();
HttpResponse response = httpClient.execute(httpPost);
//状态判断
if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
throw new CustomException("操作失败");
}
return response;
}
/**
* get请求
*
* @param url 访问url
* @return 返回结果
* @throws Exception 异常
*/
private HttpResponse get(String url) throws Exception {
URIBuilder uriBuilder = new URIBuilder(url);
HttpGet httpGet = new HttpGet(uriBuilder.build());
httpGet.addHeader("Accept", "application/json");
CloseableHttpClient httpClient = HttpClients.createDefault();
HttpResponse response = httpClient.execute(httpGet);
if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
throw new CustomException("操作失败");
}
return response;
}
/***
* 构建httpclient
* @return HttpClient
* @throws Exception 异常
*/
private HttpClient buildHttpClient() throws Exception {
ClassPathResource resource = new ClassPathResource("证书所在路径");
X509Certificate weChatPayCertificate = PemUtil
.loadCertificate(resource.getInputStream());
return WechatPayHttpClientBuilder.create()
.withMerchant(wxConfig.getMchId(), wxConfig.getSerialNo(), getPrivateKey())
.withWechatpay(Collections.singletonList(weChatPayCertificate))//证书
.build();
}
/***
* 构建httpclient(自动更新证书)
* @return HttpClient
*/
private HttpClient buildHttpClientAutoUpdateCertificate() throws IOException {
//不需要传入微信支付证书了
AutoUpdateCertificatesVerifier verifier = new AutoUpdateCertificatesVerifier(
new WechatPay2Credentials(wxConfig.getMchId(), new PrivateKeySigner(wxConfig.getSerialNo(), getPrivateKey())),
wxConfig.getApiV3Key().getBytes(StandardCharsets.UTF_8));
return WechatPayHttpClientBuilder.create()
.withMerchant(wxConfig.getMchId(), wxConfig.getSerialNo(), getPrivateKey())
.withValidator(new WechatPay2Validator(verifier))
.build();
}
/**
* 获取私钥。
*
* @return 私钥对象
*/
public static PrivateKey getPrivateKey() throws IOException {
ClassPathResource resource = new ClassPathResource("private_key.pem");
String content = new String(Files.readAllBytes(Paths.get(resource.getPath())), StandardCharsets.UTF_8);
try {
String privateKey = content.replaceAll("\\s+", StringUtils.EMPTY);
KeyFactory kf = KeyFactory.getInstance("RSA");
return kf.generatePrivate(
new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKey)));
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("当前Java环境不支持RSA", e);
} catch (InvalidKeySpecException e) {
throw new RuntimeException("无效的密钥格式");
}
}
}