Merge remote-tracking branch 'origin/master'

This commit is contained in:
zjf committed 2025-05-06 16:09:31 +08:00
commit 5b4b73e79d
27 files changed
+705 -86

No files matched your search

@@ -12,9 +12,9 @@ import java.util.Arrays;
@AllArgsConstructor
@Getter
public enum ThirdPayApiBizTypeEnum {
PAY("10", "支付"),
REFUND("20", "退款"),
WALLET_INFO("30", "钱包详情"),
;
private final String type;
@@ -18,7 +18,9 @@ public enum ThirdPayVoStatusEnum {
PARAMS_ERROR(500, "参数错误"),
USER_EXIST(501, "用户不存在"),
ORDER_EXIST(502, "订单不存在"),
MCH_EXIST(503, "第三方商户未配置"),
BALANCE_NOT_ENOUGH(505, "用户余额不足"),
UNKNOWN_ERROR(599, "未知错误,请联系管理员"),
;
private final int type;
@@ -3,6 +3,8 @@ package com.bsd.pjcloud.thirdparty.dto.pay;
import com.bsd.pjcloud.thirdparty.constant.pay.ThirdPaySoureBizTypeEnum;
import lombok.Data;
import java.util.List;
@Data
public class PayDto {
@@ -12,18 +14,43 @@ public class PayDto {
private Long userId;
/**
* 第三方唯一id
* 第三方订单唯一id
*/
private String thirdOrderId;
/**
* 第三方统一支付类型
*/
private ThirdPaySoureBizTypeEnum bizType;
/**
* 扣款金额(分)
*/
private Integer money;
private Integer price;
/**
* 商品列表
*/
private List<Product> products;
@Data
public static class Product {
/**
* 商品名
*/
private String name;
/**
* 商品图片(网络可访问)
*/
private String pic;
/**
* 商品价格(分)
*/
private Integer price;
/**
* 商品数量
*/
private Integer count;
}
}
@@ -15,4 +15,14 @@ public class RefundDto {
*/
private String platformOrderId;
/**
* 第三方退款单号
*/
private String thirdRefundId;
/**
* 退款金额(单位分)
*/
private Integer price;
}
@@ -18,8 +18,8 @@ public class PayVo {
private String thirdOrderId;
/**
* 平台支付时间
* 平台支付时间(yyyy-MM-dd HH:mm:ss格式)
*/
private LocalDateTime payTime;
private String payTime;
}
@@ -5,6 +5,11 @@ import lombok.Data;
@Data
public class PayWalletInfoVo {
/**
* 用户id
*/
private String userId;
/**
* sm4加密后的手机号
*/
@@ -2,7 +2,6 @@ package com.bsd.pjcloud.thirdparty.vo.pay;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class RefundVo {
@@ -22,9 +21,19 @@ public class RefundVo {
*/
private String thirdOrderId;
/**
* 第三方唯一退款单号
*/
private String thirdRefundId;
/**
* 退款金额(单位分)
*/
private Integer price;
/**
* 退款时间
*/
private LocalDateTime refundTime;
private String refundTime;
}
@@ -7,7 +7,9 @@ import com.bsd.pjcloud.thirdparty.dto.pay.PayGetWalletInfoDto;
import com.bsd.pjcloud.thirdparty.service.ThirdPayApiService;
import com.bsd.pjcloud.thirdparty.service.ThirdPayLogService;
import com.bsd.pjcloud.thirdparty.vo.pay.PayBaseVo;
import com.bsd.pjcloud.thirdparty.vo.pay.PayVo;
import com.bsd.pjcloud.thirdparty.vo.pay.PayWalletInfoVo;
import com.bsd.pjcloud.thirdparty.vo.pay.RefundVo;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
@@ -39,8 +41,21 @@ public class ThirdPayApiController {
return thirdPayApiService.getWalletInfo(body, headers);
}
@Inner(value = false)
@Operation(summary = "通过userId扣款", description = "通过userId扣款")
@PostMapping("/wallet-pay")
public PayBaseVo<PayVo> walletPay(HttpEntity<String> httpEntity, @RequestHeader Map<String, String> headers) {
String body = httpEntity.getBody();
return thirdPayApiService.walletPay(body, headers);
}
@Inner(value = false)
@Operation(summary = "退款", description = "退款")
@PostMapping("/wallet-refund")
public PayBaseVo<RefundVo> walletRefund(HttpEntity<String> httpEntity, @RequestHeader Map<String, String> headers) {
String body = httpEntity.getBody();
return thirdPayApiService.walletRefund(body, headers);
}
}
@@ -11,14 +11,14 @@ import java.util.Map;
public interface ThirdPayApiService {
/**
* 扣款
* 通过userId扣款
*/
PayVo pay(PayDto dto);
PayBaseVo<PayVo> walletPay(String json, Map<String, String> headers);
/**
* 退款
*/
RefundVo refund(RefundDto dto);
PayBaseVo<RefundVo> walletRefund(String json, Map<String, String> headers);
/**
* 根据加密的手机号查询用户钱包信息
@@ -1,6 +1,7 @@
package com.bsd.pjcloud.thirdparty.service.impl;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.bsd.pjcloud.thirdparty.constant.ThirdKeyTypeEnum;
@@ -19,13 +20,14 @@ import java.util.Map;
/**
* 第三方密钥管理
*/
@Service
@Validated
@Slf4j
@RequiredArgsConstructor
public class ThirdKeyManageServiceImpl extends ServiceImpl<ThirdKeyManageMapper, ThirdKeyManageEntity> implements ThirdKeyManageService {
private static final String TAG = "第三方密钥管理";
/**
* 新增
*/
@@ -61,38 +63,43 @@ public class ThirdKeyManageServiceImpl extends ServiceImpl<ThirdKeyManageMapper,
*/
@Override
public ThirdKeyManageEntity checkSignAndReturnConfig(String json, Map<String, String> headers) {
Map<String, String> transitionHeader = new LinkedHashMap<>();
for (Map.Entry<String, String> entry : headers.entrySet()) {
transitionHeader.put(entry.getKey().toUpperCase(), entry.getValue());
}
// 验签
String appKey = transitionHeader.get("APP-KEY");
String bizType = transitionHeader.get("BIZ-TYPE");
String timestamp = transitionHeader.get("TIMESTAMP");
String sign = transitionHeader.get("SIGN");
if (!ObjectUtil.isAllNotEmpty(appKey, bizType, timestamp, sign)) {
try {
Map<String, String> transitionHeader = new LinkedHashMap<>();
for (Map.Entry<String, String> entry : headers.entrySet()) {
transitionHeader.put(entry.getKey().toUpperCase(), entry.getValue());
}
// 验签
String appKey = transitionHeader.get("APP-KEY");
String bizType = transitionHeader.get("BIZ-TYPE");
String timestamp = transitionHeader.get("TIMESTAMP");
String sign = transitionHeader.get("SIGN");
if (!ObjectUtil.isAllNotEmpty(appKey, bizType, timestamp, sign)) {
return null;
}
// 判断bizType
ThirdKeyTypeEnum keyType = ThirdKeyTypeEnum.valueOfByType(bizType);
if (ObjectUtil.isEmpty(keyType)) {
return null;
}
// 查询配置
ThirdKeyManageEntity keyManage = get(keyType);
if (ObjectUtil.isEmpty(keyManage)) {
return null;
}
if (ObjectUtil.notEqual(appKey, keyManage.getAppKey())) {
return null;
}
// 计算签名
String signStr = String.format("%s%s%s%s%s", keyManage.getAppKey(), keyManage.getAppSecret(), keyManage.getBizType(), timestamp, json);
String successSignMd5 = DigestUtils.md5Hex(signStr);
if (ObjectUtil.notEqual(successSignMd5.toLowerCase(), sign.toLowerCase())) {
return null;
}
return keyManage;
} catch (Exception e) {
log.error("|{}|checkSign意外的错误,加密参数|{}|", TAG, JSONUtil.toJsonStr(json), e);
return null;
}
// 判断bizType
ThirdKeyTypeEnum keyType = ThirdKeyTypeEnum.valueOfByType(bizType);
if (ObjectUtil.isEmpty(keyType)) {
return null;
}
// 查询配置
ThirdKeyManageEntity keyManage = get(keyType);
if (ObjectUtil.isEmpty(keyManage)) {
return null;
}
if (ObjectUtil.notEqual(appKey, keyManage.getAppKey())) {
return null;
}
// 计算签名
String signStr = String.format("%s%s%s%s%s", keyManage.getAppKey(), keyManage.getAppSecret(), keyManage.getBizType(), timestamp, json);
String successSignMd5 = DigestUtils.md5Hex(signStr);
if (ObjectUtil.notEqual(successSignMd5.toLowerCase(), sign.toLowerCase())) {
return null;
}
return keyManage;
}
/**
@@ -2,8 +2,10 @@ package com.bsd.pjcloud.thirdparty.service.impl.pay;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONUtil;
import com.bsd.pjcloud.common.core.util.R;
import com.bsd.pjcloud.hard.api.feign.RemoteHardOrderService;
import com.bsd.pjcloud.hard.api.feign.RemoteHardThirdPayService;
import com.bsd.pjcloud.thirdparty.constant.pay.ThirdPayApiBizTypeEnum;
import com.bsd.pjcloud.thirdparty.constant.pay.ThirdPaySoureBizTypeEnum;
import com.bsd.pjcloud.thirdparty.constant.pay.ThirdPayVoStatusEnum;
import com.bsd.pjcloud.thirdparty.dto.pay.PayDto;
import com.bsd.pjcloud.thirdparty.dto.pay.PayGetWalletInfoDto;
@@ -23,7 +25,6 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import java.util.Arrays;
import java.util.Map;
/**
@@ -40,48 +41,106 @@ public class ThirdPayApiServiceImpl implements ThirdPayApiService {
private final ThirdKeyManageService keyManageService;
private final ThirdPayLogService payLogService;
private final RemoteWalletService remoteWalletService;
private final RemoteWalletService walletService;
private final RemoteHardOrderService hardOrderService;
private final RemoteHardThirdPayService hardThirdPayService;
/**
* 扣款
* 通过userId扣款
*/
@Override
public PayVo pay(PayDto dto) {
public PayBaseVo<PayVo> walletPay(String json, Map<String, String> headers) {
long startTime = System.currentTimeMillis();
ThirdPayLogEntity logObj = ThirdPayLogEntity.builder().apiUrl("/pay").bizType(ThirdPayApiBizTypeEnum.PAY.getType()).req(JSONUtil.toJsonStr(dto)).build();
PayVo vo = new PayVo();
try {
} catch (Exception e) {
String error = String.format("|%s||pay|意外的错误error|%s|stack|%s|", TAG, e.getMessage(), Arrays.toString(e.getStackTrace()));
log.error(error);
logObj.setError(error);
} finally {
ThirdPayLogEntity logObj = ThirdPayLogEntity.builder().apiUrl("/third/third-pay-api/wallet-pay").bizType(ThirdPayApiBizTypeEnum.PAY.getType()).paySourceType(ThirdPaySoureBizTypeEnum.LB.getType()).req(json).build();
// 开始处理
PayBaseVo baseVo = new PayBaseVo<PayVo>();
ThirdKeyManageEntity keySetting = keyManageService.checkSignAndReturnConfig(json, headers);
if (ObjectUtil.isEmpty(keySetting)) {
baseVo.set(ThirdPayVoStatusEnum.SIGN_ERROR);
// 日志
logObj.setRes(JSONUtil.toJsonStr(baseVo));
logObj.setTimeConsuming(Long.toString(System.currentTimeMillis() - startTime));
payLogService.save(logObj);
return baseVo;
}
PayDto dto = JSONUtil.toBean(json, PayDto.class);
if (ObjectUtil.isEmpty(dto)) {
baseVo.set(ThirdPayVoStatusEnum.PARAMS_ERROR);
// 日志
logObj.setRes(JSONUtil.toJsonStr(baseVo));
logObj.setTimeConsuming(Long.toString(System.currentTimeMillis() - startTime));
payLogService.save(logObj);
return baseVo;
}
// 开始支付
try {
PayBaseVo<PayVo> payVoPayBaseVo = hardThirdPayService.walletPay(dto, WalletTypeEnum.PAY);
if (ObjectUtil.isEmpty(payVoPayBaseVo)) {
throw new RuntimeException("支付失败");
}
// 日志
logObj.setRes(JSONUtil.toJsonStr(payVoPayBaseVo));
logObj.setTimeConsuming(Long.toString(System.currentTimeMillis() - startTime));
payLogService.save(logObj);
return payVoPayBaseVo;
} catch (Exception e) {
log.error("|{}|通过userId扣款错误,json|{}|headers|{}|", TAG, json, headers, e);
baseVo.set(ThirdPayVoStatusEnum.UNKNOWN_ERROR);
// 日志
logObj.setRes(JSONUtil.toJsonStr(baseVo));
logObj.setTimeConsuming(Long.toString(System.currentTimeMillis() - startTime));
payLogService.save(logObj);
return baseVo;
}
return vo;
}
/**
* 退款
*/
@Override
public RefundVo refund(RefundDto dto) {
public PayBaseVo<RefundVo> walletRefund(String json, Map<String, String> headers) {
long startTime = System.currentTimeMillis();
ThirdPayLogEntity logObj = ThirdPayLogEntity.builder().apiUrl("/refund").bizType(ThirdPayApiBizTypeEnum.REFUND.getType()).req(JSONUtil.toJsonStr(dto)).build();
RefundVo vo = new RefundVo();
try {
} catch (Exception e) {
String error = String.format("|%s||refund|意外的错误error|%s|stack|%s|", TAG, e.getMessage(), Arrays.toString(e.getStackTrace()));
log.error(error);
logObj.setError(error);
} finally {
ThirdPayLogEntity logObj = ThirdPayLogEntity.builder().apiUrl("/third/third-pay-api/wallet-refund").bizType(ThirdPayApiBizTypeEnum.REFUND.getType()).paySourceType(ThirdPaySoureBizTypeEnum.LB.getType()).req(json).build();
// 开始处理
PayBaseVo baseVo = new PayBaseVo<RefundVo>();
ThirdKeyManageEntity keySetting = keyManageService.checkSignAndReturnConfig(json, headers);
if (ObjectUtil.isEmpty(keySetting)) {
baseVo.set(ThirdPayVoStatusEnum.SIGN_ERROR);
// 日志
logObj.setRes(JSONUtil.toJsonStr(baseVo));
logObj.setTimeConsuming(Long.toString(System.currentTimeMillis() - startTime));
payLogService.save(logObj);
return baseVo;
}
RefundDto dto = JSONUtil.toBean(json, RefundDto.class);
if (ObjectUtil.isEmpty(dto)) {
baseVo.set(ThirdPayVoStatusEnum.PARAMS_ERROR);
// 日志
logObj.setRes(JSONUtil.toJsonStr(baseVo));
logObj.setTimeConsuming(Long.toString(System.currentTimeMillis() - startTime));
payLogService.save(logObj);
return baseVo;
}
// 开始退款
try {
PayBaseVo<RefundVo> payVoPayBaseVo = hardThirdPayService.walletRefund(dto);
if (ObjectUtil.isEmpty(payVoPayBaseVo)) {
throw new RuntimeException("退款失败");
}
// 日志
logObj.setRes(JSONUtil.toJsonStr(payVoPayBaseVo));
logObj.setTimeConsuming(Long.toString(System.currentTimeMillis() - startTime));
payLogService.save(logObj);
return payVoPayBaseVo;
} catch (Exception e) {
log.error("|{}|退款,json|{}|headers|{}|", TAG, json, headers, e);
baseVo.set(ThirdPayVoStatusEnum.UNKNOWN_ERROR);
// 日志
logObj.setRes(JSONUtil.toJsonStr(baseVo));
logObj.setTimeConsuming(Long.toString(System.currentTimeMillis() - startTime));
payLogService.save(logObj);
return baseVo;
}
return vo;
}
/**
@@ -89,27 +148,47 @@ public class ThirdPayApiServiceImpl implements ThirdPayApiService {
*/
@Override
public PayBaseVo<PayWalletInfoVo> getWalletInfo(String json, Map<String, String> headers) {
// 日志
long startTime = System.currentTimeMillis();
ThirdPayLogEntity logObj = ThirdPayLogEntity.builder().apiUrl("/third/third-pay-api/user-wallet").bizType(ThirdPayApiBizTypeEnum.WALLET_INFO.getType()).req(json).paySourceType(ThirdPaySoureBizTypeEnum.LB.getType()).build();
// 开始处理
PayBaseVo baseVo = new PayBaseVo<PayWalletInfoVo>();
ThirdKeyManageEntity keySetting = keyManageService.checkSignAndReturnConfig(json, headers);
if (ObjectUtil.isEmpty(keySetting)) {
baseVo.set(ThirdPayVoStatusEnum.SIGN_ERROR);
// 日志
logObj.setRes(JSONUtil.toJsonStr(baseVo));
logObj.setTimeConsuming(Long.toString(System.currentTimeMillis() - startTime));
payLogService.save(logObj);
return baseVo;
}
PayGetWalletInfoDto dto = JSONUtil.toBean(json, PayGetWalletInfoDto.class);
if (ObjectUtil.isEmpty(dto)) {
baseVo.set(ThirdPayVoStatusEnum.PARAMS_ERROR);
// 日志
logObj.setRes(JSONUtil.toJsonStr(baseVo));
logObj.setTimeConsuming(Long.toString(System.currentTimeMillis() - startTime));
payLogService.save(logObj);
return baseVo;
}
// 手机号解密
String phone = LbRetailSignUtils.sm4Decrypt(dto.getSignPhone(), keySetting.getFieldKey());
if (ObjectUtil.isEmpty(phone)) {
baseVo.set(ThirdPayVoStatusEnum.PARAMS_ERROR);
baseVo.setMessage("手机号解迷失败");
baseVo.setMessage("手机号解析失败");
// 日志
logObj.setRes(JSONUtil.toJsonStr(baseVo));
logObj.setTimeConsuming(Long.toString(System.currentTimeMillis() - startTime));
payLogService.save(logObj);
return baseVo;
}
WalletManageVo userWallet = remoteWalletService.getWalletByUserPhone(phone, WalletTypeEnum.EAT).getData();
WalletManageVo userWallet = walletService.getWalletByUserPhone(phone, WalletTypeEnum.EAT).getData();
if (ObjectUtil.isEmpty(userWallet)) {
baseVo.set(ThirdPayVoStatusEnum.USER_EXIST);
// 日志
logObj.setRes(JSONUtil.toJsonStr(baseVo));
logObj.setTimeConsuming(Long.toString(System.currentTimeMillis() - startTime));
payLogService.save(logObj);
return baseVo;
}
PayGetWalletInfoVo result = new PayGetWalletInfoVo();
@@ -119,6 +198,10 @@ public class ThirdPayApiServiceImpl implements ThirdPayApiService {
// 返回成功的参数
baseVo.set(ThirdPayVoStatusEnum.SUCCESS);
baseVo.setData(result);
// 日志
logObj.setRes(JSONUtil.toJsonStr(baseVo));
logObj.setTimeConsuming(Long.toString(System.currentTimeMillis() - startTime));
payLogService.save(logObj);
return baseVo;
}
@@ -0,0 +1,69 @@
package com.bsd.pjcloud.admin;
import cn.hutool.http.HttpRequest;
import cn.hutool.json.JSONUtil;
import com.bsd.pjcloud.thirdparty.ThirdPartyApp;
import com.bsd.pjcloud.thirdparty.constant.ThirdKeyTypeEnum;
import com.bsd.pjcloud.thirdparty.entity.ThirdKeyManageEntity;
import com.bsd.pjcloud.thirdparty.service.ThirdKeyManageService;
import com.bsd.pjcloud.thirdparty.utils.lb.tool.LbRetailSignUtils;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import java.time.Instant;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Slf4j
@SpringBootTest(classes = ThirdPartyApp.class)
public class PayTest {
private final String baseUrl = "127.0.0.1:9999";
private final String walletInfoUrl = "/third/third-pay-api/user-wallet";
private final String walletPayUrl = "/third/third-pay-api/wallet-pay";
private final String walletRefundUrl = "/third/third-pay-api/wallet-refund";
@Resource
private ThirdKeyManageService keyManageService;
@Test
public void walletInfo() throws Exception {
ThirdKeyManageEntity config = getLbConfig();
Map<String, Object> paramsMap = new HashMap<>();
paramsMap.put("signPhone", LbRetailSignUtils.sm4Encrypt("17683843144", config.getFieldKey()));
String body = JSONUtil.toJsonStr(paramsMap);
// 发送请求
String res = HttpRequest.post(baseUrl + walletInfoUrl).contentType("application/json").header(apiCountersign(body)).body(body).execute().body();
log.info(res);
}
private ThirdKeyManageEntity getLbConfig() {
return keyManageService.get(ThirdKeyTypeEnum.LB);
}
private Map<String, List<String>> apiCountersign(String body) {
ThirdKeyManageEntity config = getLbConfig();
String timestamp = String.valueOf(Instant.now().getEpochSecond());
Map<String, List<String>> headers = new HashMap<>();
headers.put("timestamp", List.of(timestamp));
headers.put("app-key", List.of(config.getAppKey()));
headers.put("biz-type", List.of(config.getBizType()));
headers.put("sign", List.of());
String signStr = String.format("%s%s%s%s%s", config.getAppKey(), config.getAppSecret(), config.getBizType(), timestamp, body);
String successSignMd5 = DigestUtils.md5Hex(signStr);
headers.put("sign", List.of(successSignMd5));
return headers;
}
}