微信支付、回调、订单查询;

微信用户登录、用户信息查询、修改用户信息、同意用户协议;
文件OSS上传、删除接口;
This commit is contained in:
songmingsong
2024-12-05 17:33:25 +08:00
parent 4822174c5e
commit ffc9fcb95c
39 changed files with 2074 additions and 133 deletions

View File

@ -0,0 +1,29 @@
package com.ycwl.basic.service;
import com.ycwl.basic.utils.OssUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.util.Objects;
/**
* file请求服务
*
* @author songmingsong
*/
@Service
public class FileService {
@Autowired
private OssUtil ossUtil;
public String uploadFile(MultipartFile file) throws IOException {
return ossUtil.uploadFile(file.getInputStream(), Objects.requireNonNull(file.getOriginalFilename()));
}
public Boolean delete(String fileName) {
return ossUtil.deleteFile(fileName);
}
}

View File

@ -0,0 +1,121 @@
package com.ycwl.basic.service;
import com.ycwl.basic.utils.HttpServiceUtil;
import com.ycwl.basic.utils.SslUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpEntity;
import org.apache.http.NameValuePair;
import org.apache.http.StatusLine;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* Http请求服务
*
* @author songmingsong
*/
@Service
@Slf4j
public class HttpService {
/**
* @param requestUrl
* 请求地址
* @param params
* 参数
* @param encoding
* 编码
* @return String
* @throws Exception
* 抛出的异常新
*/
public String doHttpsPost(String requestUrl, Map<String, String> params, String encoding) throws Exception {
String result = HttpServiceUtil.REQUEST_NO_RESULT;
// build client 对象
CloseableHttpClient httpClient = null;
try {
httpClient = SslUtil.sslHttpClientBuild();
requestUrl = requestUrl.replaceAll("\\s*", "");
HttpPost post = new HttpPost(requestUrl);
RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(5000)
.setConnectionRequestTimeout(1000).setSocketTimeout(5000).build();
post.setConfig(requestConfig);
// 请求首部--可选的User-Agent对于一些服务器必选不加可能不会返回正确结果
post.setHeader("User-Agent",
"Mozilla/5.0 (Linux; U; Android 5.0.2; zh-cn; PLK-UL00 Build/HONORPLK-UL00) AppleWebKit/533.1 (KHTML, like Gecko)Version/4.0 MQQBrowser/5.4 TBS/025483 Mobile Safari/533.1 MicroMessenger/6.3.8.56_re6b2553.680 NetType/");
List<NameValuePair> parasList = new ArrayList<NameValuePair>();
if (null != params && params.size() > 0) {
for (String key : params.keySet()) {
NameValuePair param = new BasicNameValuePair(key, params.get(key));
parasList.add(param);
}
}
// Exec Request
HttpEntity paramsEntity = new UrlEncodedFormEntity(parasList, encoding);
post.setEntity(paramsEntity);
CloseableHttpResponse resp = httpClient.execute(post);
// 获得起始行
StatusLine status = resp.getStatusLine();
// 获取实体
if (HttpServiceUtil.success(status)) {
// 获取实体
HttpEntity entity = resp.getEntity();
// 编码格式
result = EntityUtils.toString(entity, encoding);
// 释放实体 response.close();//关闭响应
EntityUtils.consume(entity);
}
} catch (Exception e) {
log.error("doHttpsPost", e);
throw e;
} finally {
if (httpClient != null) {
httpClient.close();
}
}
return result;
}
public String doGet(String requestUrl, String encoding) throws Exception {
String result = HttpServiceUtil.REQUEST_NO_RESULT;
// build client 对象
CloseableHttpClient httpClient = null;
try {
httpClient = SslUtil.sslHttpClientBuild();
requestUrl = requestUrl.replaceAll("\\s*", "");
HttpGet get = new HttpGet(requestUrl);
// 请求首部--可选的User-Agent对于一些服务器必选不加可能不会返回正确结果
get.setHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64;rv:39.0) Gecko/20100101 Firefox/39.0");
// Exec Request
CloseableHttpResponse resp = httpClient.execute(get);
// 获得起始行
StatusLine status = resp.getStatusLine();
if (HttpServiceUtil.success(status)) {
// 获取实体
HttpEntity entity = resp.getEntity();
// 编码格式
result = EntityUtils.toString(entity, encoding);
// 释放实体 response.close();//关闭响应
EntityUtils.consume(entity);
}
} finally {
if (httpClient != null) {
httpClient.close();
}
}
return result;
}
}

View File

@ -0,0 +1,141 @@
package com.ycwl.basic.service.impl.mobile;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ycwl.basic.config.WechatConfig;
import com.ycwl.basic.constant.BaseContextHandler;
import com.ycwl.basic.constant.NumberConstant;
import com.ycwl.basic.constant.WeiXinConstant;
import com.ycwl.basic.enums.AgreementEnum;
import com.ycwl.basic.enums.BizCodeEnum;
import com.ycwl.basic.enums.WechatErrorCodeEnum;
import com.ycwl.basic.exception.AppException;
import com.ycwl.basic.mapper.pc.MemberMapper;
import com.ycwl.basic.model.jwt.JwtInfo;
import com.ycwl.basic.model.mobile.DTO.WeChatUserInfoDTO;
import com.ycwl.basic.model.mobile.DTO.WeChatUserInfoUpdateDTO;
import com.ycwl.basic.model.pc.member.entity.MemberEntity;
import com.ycwl.basic.model.pc.member.req.MemberReqQuery;
import com.ycwl.basic.model.pc.member.resp.MemberRespVO;
import com.ycwl.basic.service.HttpService;
import com.ycwl.basic.service.mobile.AppMemberService;
import com.ycwl.basic.utils.ApiResponse;
import com.ycwl.basic.utils.BeanCopierUtils;
import com.ycwl.basic.utils.JwtTokenUtil;
import com.ycwl.basic.utils.SnowFlakeUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @Authorsongmingsong
*/
@Slf4j
@Service
public class AppMemberServiceImpl implements AppMemberService {
@Autowired
private WechatConfig config;
@Autowired
private HttpService httpService;
@Autowired
private MemberMapper memberMapper;
@Autowired
private JwtTokenUtil jwtTokenUtil;
@Override
public Map<String, Object> getOpenId(String code) {
Map<String, String> paramMap = new HashMap<>(NumberConstant.FOUR);
paramMap.put("appid", config.getMiniProgramAppId());
paramMap.put("secret", config.getMiniProgramSecret());
paramMap.put("js_code", code);
paramMap.put("grant_type", config.getGrandType());
try {
String response = httpService.doHttpsPost(WeiXinConstant.GET_OPEN_ID, paramMap, "UTF-8");
if (StringUtils.isBlank(response)) {
return null;
}
ObjectMapper mapper = new ObjectMapper();
return mapper.readValue(response, Map.class);
} catch (Exception e) {
log.error("getOpenId", e);
throw new AppException(BizCodeEnum.SERVER_INTERNAL_ERROR);
}
}
@Override
public ApiResponse login(String code, WeChatUserInfoDTO userInfoDTO) throws Exception {
Map<String, Object> weixinResponse = this.getOpenId(code);
if (CollectionUtils.isEmpty(weixinResponse)) {
throw new AppException(BizCodeEnum.SERVER_INTERNAL_ERROR);
}
Object errcode = weixinResponse.get("errcode");
if (errcode != null) {
WechatErrorCodeEnum errorCode = WechatErrorCodeEnum.getErrorCode(errcode.toString());
throw new AppException(BizCodeEnum.PARAM_ERROR.getCode(), errorCode.getDetail());
}
// 获取openId
Object openId = weixinResponse.get("openid");
if (openId == null) {
log.warn("warning={}", "未获取到当前用户openId");
throw new AppException(BizCodeEnum.SERVER_UNKONWN_ERROR, "未获取到当前用户openId");
}
MemberRespVO memberRespVO = new MemberRespVO();
JwtInfo jwtInfo = new JwtInfo();
// 根据返回的openId判断用户是否是新用户是的话将用户信息存到数据库
MemberReqQuery memberReqQuery = new MemberReqQuery();
memberReqQuery.setOpenId(openId.toString());
List<MemberRespVO> list = memberMapper.list(memberReqQuery);
if (list.isEmpty()) {
MemberEntity memberEntity = new MemberEntity();
memberEntity.setId(SnowFlakeUtil.getLongId());
BeanCopierUtils.copyProperties(userInfoDTO, memberEntity);
memberMapper.add(memberEntity);
BeanCopierUtils.copyProperties(memberEntity, memberRespVO);
} else {
BeanCopierUtils.copyProperties(list.get(NumberConstant.ZERO), memberRespVO);
}
jwtInfo.setUserId(memberRespVO.getId().toString());
jwtInfo.setName(memberRespVO.getNickname());
jwtInfo.setPhone(memberRespVO.getPhone());
String jwt = jwtTokenUtil.generateToken(jwtInfo);
Map<String, Object> resMap = new HashMap<>();
resMap.put("token", jwt);
resMap.put("user", memberRespVO);
return ApiResponse.success(resMap);
}
@Override
public ApiResponse<MemberRespVO> getUserInfo() {
MemberRespVO respVO = memberMapper.getById(Long.parseLong(BaseContextHandler.getUserId()));
return ApiResponse.success(respVO);
}
@Override
public ApiResponse<?> update(WeChatUserInfoUpdateDTO userInfoUpdateDTO) {
MemberEntity memberEntity = new MemberEntity();
memberEntity.setId(Long.parseLong(BaseContextHandler.getUserId()));
BeanCopierUtils.copyProperties(userInfoUpdateDTO, memberEntity);
return ApiResponse.success(memberMapper.update(memberEntity));
}
@Override
public ApiResponse<?> agreement() {
MemberEntity memberEntity = new MemberEntity();
memberEntity.setId(Long.parseLong(BaseContextHandler.getUserId()));
memberEntity.setAgreement(AgreementEnum.AGREE.getType());
return ApiResponse.success(memberMapper.update(memberEntity));
}
}

View File

@ -0,0 +1,201 @@
package com.ycwl.basic.service.impl.mobile;
import com.alibaba.fastjson.JSONObject;
import com.wechat.pay.java.core.Config;
import com.wechat.pay.java.core.RSAAutoCertificateConfig;
import com.wechat.pay.java.core.exception.ServiceException;
import com.wechat.pay.java.core.notification.NotificationConfig;
import com.wechat.pay.java.core.notification.NotificationParser;
import com.wechat.pay.java.core.notification.RequestParam;
import com.wechat.pay.java.service.partnerpayments.jsapi.model.Transaction;
import com.wechat.pay.java.service.payments.jsapi.JsapiService;
import com.wechat.pay.java.service.payments.jsapi.model.*;
import com.wechat.pay.java.service.payments.model.TransactionAmount;
import com.ycwl.basic.config.WechatConfig;
import com.ycwl.basic.constant.WeiXinConstant;
import com.ycwl.basic.enums.BizCodeEnum;
import com.ycwl.basic.enums.OrderStateEnum;
import com.ycwl.basic.exception.AppException;
import com.ycwl.basic.mapper.pc.OrderMapper;
import com.ycwl.basic.model.wxPay.WXPayOrderReqVO;
import com.ycwl.basic.model.wxPay.WxPayRespVO;
import com.ycwl.basic.model.wxPay.WxchatCallbackSuccessData;
import com.ycwl.basic.service.mobile.WxPayService;
import com.ycwl.basic.service.pc.OrderService;
import com.ycwl.basic.utils.WXPayUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.math.BigDecimal;
import java.util.Objects;
import java.util.UUID;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static com.wechat.pay.java.core.http.Constant.*;
/**
* @Author: songmingsong
* @CreateTime: 2024-12-05
* @Description: 微信支付实现
* @Version: 1.0
*/
@Slf4j
@Service
public class WxPayServiceImpl implements WxPayService {
@Autowired
private WechatConfig wechatConfig;
@Autowired
private OrderMapper orderMapper;
@Autowired
private OrderService orderService;
@Override
public WxPayRespVO createOrder(WXPayOrderReqVO req) {
try {
// 使用自动更新平台证书的RSA配置
// 一个商户号只能初始化一个配置,否则会因为重复的下载任务报错
Config config =
new RSAAutoCertificateConfig.Builder()
.merchantId(wechatConfig.getMchId())
.privateKeyFromPath(wechatConfig.getKeyPath())
.merchantSerialNumber(wechatConfig.getMchSerialNo())
.apiV3Key(wechatConfig.getApiV3())
.build();
// 构建service
JsapiService service = new JsapiService.Builder().config(config).build();
// request.setXxx(val)设置所需参数具体参数可见Request定义
PrepayRequest request = new PrepayRequest();
Amount amount = new Amount();
amount.setTotal(req.getTotalPrice());
request.setAmount(amount);
request.setAppid(wechatConfig.getAppId());
request.setMchid(wechatConfig.getMchId());
request.setDescription(req.getGoodsName());
request.setNotifyUrl(wechatConfig.getNotifyUrl());
request.setOutTradeNo(req.getOrderSn().toString());
Payer payer = new Payer();
payer.setOpenid(req.getOpenId());
request.setPayer(payer);
// 调用下单方法,得到应答
PrepayResponse response = service.prepay(request);
WxPayRespVO vo = new WxPayRespVO();
Long timeStamp = System.currentTimeMillis() / 1000;
vo.setTimeStamp(timeStamp);
String substring = UUID.randomUUID().toString().replaceAll("-", "").substring(0, 32);
vo.setNonceStr(substring);
String signatureStr = Stream.of(wechatConfig.getAppId(), String.valueOf(timeStamp), substring, "prepay_id=" + response.getPrepayId())
.collect(Collectors.joining("\n", "", "\n"));
String sign = WXPayUtil.getSign(signatureStr, wechatConfig.getKeyPath());
vo.setPaySign(sign);
vo.setPrepayId("prepay_id=" + response.getPrepayId());
return vo;
} catch (ServiceException e) {
JSONObject parse = JSONObject.parseObject(e.getResponseBody());
throw new AppException(BizCodeEnum.ADVANCE_PAYMENT_FAILED, parse.getString("message"));
} catch (Exception e) {
throw new AppException(BizCodeEnum.ADVANCE_PAYMENT_FAILED, e.toString());
}
}
@Override
public void payNotify(HttpServletRequest request) {
try {
// 读取请求体的信息
ServletInputStream inputStream = request.getInputStream();
StringBuffer stringBuffer = new StringBuffer();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
String s;
// 读取回调请求体
while ((s = bufferedReader.readLine()) != null) {
stringBuffer.append(s);
}
String s1 = stringBuffer.toString();
String timestamp = request.getHeader(WECHAT_PAY_TIMESTAMP);
String nonce = request.getHeader(WECHAT_PAY_NONCE);
String signType = request.getHeader(WeiXinConstant.WECHATPAY_SIGNATURE_TYPE);
String serialNo = request.getHeader(WECHAT_PAY_SERIAL);
String signature = request.getHeader(WECHAT_PAY_SIGNATURE);
NotificationConfig config = new RSAAutoCertificateConfig.Builder()
.merchantId(wechatConfig.getMchId())
.privateKeyFromPath(wechatConfig.getKeyPath())
.merchantSerialNumber(wechatConfig.getMchSerialNo())
.apiV3Key(wechatConfig.getApiV3())
.build();
// 初始化 NotificationParser
NotificationParser parser = new NotificationParser(config);
RequestParam requestParam = new RequestParam.Builder()
.serialNumber(serialNo)
.nonce(nonce)
.signature(signature)
.timestamp(timestamp)
// 若未设置signType默认值为 WECHATPAY2-SHA256-RSA2048
.signType(signType)
.body(s1)
.build();
Transaction parse = parser.parse(requestParam, Transaction.class);
log.info("[微信支付]parse = {}", parse);
// 更新订单信息
new Thread(() -> {
Transaction.TradeStateEnum tradeState = parse.getTradeState();
OrderStateEnum OrderStateEnum = null;
if (Transaction.TradeStateEnum.SUCCESS == tradeState) {
OrderStateEnum = OrderStateEnum.PAID;
}
if (Objects.nonNull(OrderStateEnum)) {
orderService.updateOrderState(Long.parseLong(parse.getOutTradeNo()), OrderStateEnum, null);
}
}).start();
} catch (Exception e) {
throw new AppException(BizCodeEnum.ADVANCE_PAYMENT_CALLBACK_FAILED, e.toString());
}
}
@Override
public WxchatCallbackSuccessData queryPay(Long orderId) {
WxchatCallbackSuccessData wxchatCallbackSuccessData = new WxchatCallbackSuccessData();
QueryOrderByOutTradeNoRequest queryRequest = new QueryOrderByOutTradeNoRequest();
queryRequest.setMchid(wechatConfig.getMchId());
queryRequest.setOutTradeNo(orderId.toString());
// 一个商户号只能初始化一个配置,否则会因为重复的下载任务报错
Config config =
new RSAAutoCertificateConfig.Builder()
.merchantId(wechatConfig.getMchId())
.privateKeyFromPath(wechatConfig.getKeyPath())
.merchantSerialNumber(wechatConfig.getMchSerialNo())
.apiV3Key(wechatConfig.getApiV3())
.build();
// 构建service
JsapiService service = new JsapiService.Builder().config(config).build();
try {
com.wechat.pay.java.service.payments.model.Transaction transaction = service.queryOrderByOutTradeNo(queryRequest);
String successTime = transaction.getSuccessTime();
wxchatCallbackSuccessData.setOrderId(orderId.toString());
wxchatCallbackSuccessData.setSuccessTime(successTime);
wxchatCallbackSuccessData.setTradetype(WeiXinConstant.getDescriptionType(transaction.getTradeType()));
wxchatCallbackSuccessData.setTradestate(WeiXinConstant.getDescriptionState(transaction.getTradeState()));
TransactionAmount amount = transaction.getAmount();
Integer total = amount.getTotal();
wxchatCallbackSuccessData.setTotalMoney(new BigDecimal(total).movePointLeft(2));
} catch (ServiceException e) {
// API返回失败, 例如ORDER_NOT_EXISTS
log.error("code={}, message={}", e.getErrorCode(), e.getErrorMessage());
log.error("reponse body={}", e.getResponseBody());
}
return wxchatCallbackSuccessData;
}
}

View File

@ -3,6 +3,8 @@ package com.ycwl.basic.service.impl.pc;
import cn.hutool.core.bean.BeanUtil;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.ycwl.basic.constant.NumberConstant;
import com.ycwl.basic.enums.OrderStateEnum;
import com.ycwl.basic.mapper.pc.OrderMapper;
import com.ycwl.basic.model.pc.order.entity.OrderItemEntity;
import com.ycwl.basic.model.pc.order.req.OrderAddOrUpdateReq;
@ -18,6 +20,7 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
@ -82,4 +85,28 @@ public class OrderServiceImpl implements OrderService {
}
return ApiResponse.success(update);
}
/**
* 更新订单支付状态
*
* @param orderSn 订单编号也就是订单的ID
*/
@Override
public void updateOrderState(Long orderSn, OrderStateEnum orderStateEnum, String refundReason) {
OrderAddOrUpdateReq orderAddOrUpdateReq = new OrderAddOrUpdateReq();
orderAddOrUpdateReq.setId(orderSn);
if (orderStateEnum.getType() == NumberConstant.ONE) {
orderAddOrUpdateReq.setRefundStatus(orderStateEnum.getState());
orderAddOrUpdateReq.setRefundAt(new Date());
orderAddOrUpdateReq.setRefundReason(refundReason);
} else if (orderStateEnum.getType() == NumberConstant.TWO) {
int state = orderStateEnum.getState();
orderAddOrUpdateReq.setPayAt(new Date());
orderAddOrUpdateReq.setStatus(orderStateEnum.getState());
if (state == OrderStateEnum.CANCELED.getState()) {
orderAddOrUpdateReq.setCancelAt(new Date());
}
}
orderMapper.update(orderAddOrUpdateReq);
}
}

View File

@ -0,0 +1,52 @@
package com.ycwl.basic.service.mobile;
import com.ycwl.basic.model.mobile.DTO.WeChatUserInfoDTO;
import com.ycwl.basic.model.mobile.DTO.WeChatUserInfoUpdateDTO;
import com.ycwl.basic.model.pc.member.resp.MemberRespVO;
import com.ycwl.basic.utils.ApiResponse;
import java.util.Map;
/**
* @Authorsongmingsong
*/
public interface AppMemberService {
/**
* 获取用户的openId
*
* @return Map
*/
Map<String, Object> getOpenId(String code);
/**
* 登录
*
* @param code 前端授权码
* @param userInfoDTO 实体信息
* @return
*/
ApiResponse login(String code, WeChatUserInfoDTO userInfoDTO) throws Exception;
/**
* 获取用户信息
*
* @return
*/
ApiResponse<MemberRespVO> getUserInfo();
/**
* 修改信息
*
* @param userInfoUpdateDTO
* @return
*/
ApiResponse<?> update(WeChatUserInfoUpdateDTO userInfoUpdateDTO);
/**
* 同意用户协议
*
* @return
*/
ApiResponse<?> agreement();
}

View File

@ -0,0 +1,25 @@
package com.ycwl.basic.service.mobile;
import com.ycwl.basic.model.wxPay.WXPayOrderReqVO;
import com.ycwl.basic.model.wxPay.WxPayRespVO;
import com.ycwl.basic.model.wxPay.WxchatCallbackSuccessData;
import javax.servlet.http.HttpServletRequest;
public interface WxPayService {
/**
* 微信预支付
*/
WxPayRespVO createOrder(WXPayOrderReqVO req) throws Exception;
/**
* 微信支付回调
*/
void payNotify(HttpServletRequest request);
/**
* 微信支付结果查询
*/
WxchatCallbackSuccessData queryPay(Long orderId);
}

View File

@ -1,7 +1,7 @@
package com.ycwl.basic.service.pc;
import com.github.pagehelper.PageInfo;
import com.ycwl.basic.model.pc.order.entity.OrderEntity;
import com.ycwl.basic.enums.OrderStateEnum;
import com.ycwl.basic.model.pc.order.req.OrderAddOrUpdateReq;
import com.ycwl.basic.model.pc.order.req.OrderReqQuery;
import com.ycwl.basic.model.pc.order.resp.OrderRespVO;
@ -19,4 +19,7 @@ public interface OrderService {
ApiResponse<OrderRespVO> detail(Long orderId);
ApiResponse<Integer> add(OrderAddOrUpdateReq query);
ApiResponse<Integer> update(OrderAddOrUpdateReq query);
void updateOrderState(Long orderSn, OrderStateEnum orderStateEnum, String refundReason);
}