Browse Source

feat:0 支付工程

master
yang_shj 4 years ago
parent
commit
1e62944b3f
  1. 25
      payment-base-app/pom.xml
  2. 29
      payment-base-app/src/main/java/com/base/springcloud/aspect/annotation/BusinessException.java
  3. 42
      payment-base-app/src/main/java/com/base/springcloud/aspect/annotation/Rlock.java
  4. 24
      payment-base-app/src/main/java/com/base/springcloud/aspect/aspect/BusinessExceptionHandler.java
  5. 118
      payment-base-app/src/main/java/com/base/springcloud/aspect/aspect/RlockAspect.java
  6. 8
      payment-base-app/src/main/java/com/base/springcloud/base/CommonResult.java
  7. 10
      payment-base-app/src/main/java/com/base/springcloud/constant/ErrorConstant.java
  8. 59
      payment-base-app/src/main/java/com/base/springcloud/constant/FastJson2JsonRedisSerializer.java
  9. 5
      payment-base-app/src/main/java/com/base/springcloud/constant/PaymentConstant.java
  10. 58
      payment-base-app/src/main/java/com/base/springcloud/constant/RedisConfig.java
  11. 2
      payment-base-app/src/main/java/com/base/springcloud/controller/MerchantController.java
  12. 33
      payment-base-app/src/main/java/com/base/springcloud/dto/MerchantConfigReqDTO.java
  13. 74
      payment-base-app/src/main/java/com/base/springcloud/entity/CallbackBodyVo.java
  14. 57
      payment-base-app/src/main/java/com/base/springcloud/entity/WxpayRefundVo.java
  15. 119
      payment-base-app/src/main/java/com/base/springcloud/entity/WxpayTradeVo.java
  16. 6
      payment-base-app/src/main/java/com/base/springcloud/handle/RBSQueueHandler.java
  17. 8
      payment-base-app/src/main/java/com/base/springcloud/service/imp/MerchantServiceImp.java
  18. 12
      payment-base-app/src/main/java/com/base/springcloud/service/imp/OrderServiceImp.java
  19. 19
      payment-base-app/src/main/java/com/base/springcloud/service/imp/PaymentServiceImp.java
  20. 21
      payment-base-app/src/main/java/com/base/springcloud/util/StringHelper.java
  21. 344
      payment-base-app/src/main/java/com/base/springcloud/util/WxpayUtils.java
  22. 19
      payment-base-app/src/main/resources/application.yml
  23. 6
      payment-base-app/src/main/resources/com/base/payment/mapper/MerchantConfigMapper.xml
  24. 7
      payment-base-app/src/main/resources/com/base/payment/mapper/OrderMapper.xml
  25. 34
      payment-base-app/target/classes/application.yml
  26. 6
      pom.xml

25
payment-base-app/pom.xml

@ -73,11 +73,18 @@
<version>2.9.2</version>
</dependency>
<!-- gson -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.2.4</version>
</dependency>
<!-- 添加微信支付jar包 -->
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-apache-httpclient</artifactId>
<version>0.4.2</version>
<version>0.2.2</version>
</dependency>
<!-- ftp client -->
@ -93,6 +100,22 @@
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<!-- redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.15.1</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.47</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>

29
payment-base-app/src/main/java/com/base/springcloud/aspect/annotation/BusinessException.java

@ -0,0 +1,29 @@
package com.base.springcloud.aspect.annotation;
import com.base.springcloud.constant.ErrorConstant;
import lombok.Getter;
@Getter
public class BusinessException extends RuntimeException{
private String code;
/**
* 使用已有的错误类型
* @param type 枚举类中的错误类型
*/
public BusinessException(ErrorConstant type){
super(type.getMessage());
this.code = type.getCode();
}
/**
* 自定义错误类型
* @param code 自定义的错误码
* @param msg 自定义的错误提示
*/
public BusinessException(String code, String msg){
super(msg);
this.code = code;
}
}

42
payment-base-app/src/main/java/com/base/springcloud/aspect/annotation/Rlock.java

@ -0,0 +1,42 @@
package com.base.springcloud.aspect.annotation;
import java.lang.annotation.*;
import java.util.concurrent.TimeUnit;
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface Rlock {
/**
* 分布式锁的key前缀
*/
String prefix() default "redisLockPrefix::";
/**
* 分布式锁的key动态参数
*/
String suffix() default "lockDynamicKey";
/**
* 等待时间 默认五秒
*
* @return
*/
long waitTime() default 5;
/**
* 锁释放时间 默认十秒
*
* @return
*/
long leaseTime() default 10;
/**
* 时间格式 默认
*
* @return
*/
TimeUnit timeUnit() default TimeUnit.SECONDS;
}

24
payment-base-app/src/main/java/com/base/springcloud/aspect/aspect/BusinessExceptionHandler.java

@ -0,0 +1,24 @@
package com.base.springcloud.aspect.aspect;
import com.base.springcloud.aspect.annotation.BusinessException;
import com.base.springcloud.base.CommonResult;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
@ControllerAdvice
public class BusinessExceptionHandler {
/**
* @ExceptionHandler相当于controller的@RequestMapping
* 如果抛出的的是BusinessException则调用该方法
* @param se 业务异常
* @return
*/
@ExceptionHandler(BusinessException.class)
@ResponseBody
public CommonResult handle(BusinessException se){
return CommonResult.error(se.getCode(),se.getMessage());
}
}

118
payment-base-app/src/main/java/com/base/springcloud/aspect/aspect/RlockAspect.java

@ -0,0 +1,118 @@
package com.base.springcloud.aspect.aspect;
import com.base.springcloud.aspect.annotation.Rlock;
import com.base.springcloud.handle.RBSQueueHandler;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
@Aspect
@Component
@Slf4j
public class RlockAspect {
Logger logger = LoggerFactory.getLogger(RBSQueueHandler.class);
@Autowired
private RedissonClient redissonClient;
/**
* 用于SpEL表达式解析.
*/
private SpelExpressionParser parser = new SpelExpressionParser();
/**
* 用于获取方法参数定义名字.
*/
private DefaultParameterNameDiscoverer nameDiscoverer = new DefaultParameterNameDiscoverer();
@Pointcut("@annotation(com.base.springcloud.aspect.annotation.Rlock)")
public void RlockAspect() {
}
@Around("RlockAspect()")
public Object arround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
Object object = null;
RLock lock = null;
try {
// 获取注解信息
Rlock rlockInfo = getRlockInfo(proceedingJoinPoint);
String key = getLocalKey(proceedingJoinPoint, rlockInfo);
// 根据名字获取锁实例
lock = redissonClient.getLock(key);
if (lock != null) {
final boolean status = lock.tryLock(rlockInfo.waitTime(), rlockInfo.leaseTime(), rlockInfo.timeUnit());
if (status) {
logger.info("锁获取成功,加锁成功 : {}........",key);
object = proceedingJoinPoint.proceed();
} else {
logger.info("锁被占用,加锁失败 : {} .........",key);
throw new RuntimeException("加锁失败");
}
}
} finally {
// 方法执行完成释放锁
if (lock != null && lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
return object;
}
public Rlock getRlockInfo(ProceedingJoinPoint proceedingJoinPoint) {
MethodSignature methodSignature = (MethodSignature) proceedingJoinPoint.getSignature();
return methodSignature.getMethod().getAnnotation(Rlock.class);
}
/**
* @param proceedingJoinPoint
* @return
*/
public String getLocalKey(ProceedingJoinPoint proceedingJoinPoint, Rlock rlockInfo) {
StringBuilder localKey = new StringBuilder();
//获取注解参数值
String prefix = rlockInfo.prefix();
String spELString = rlockInfo.suffix();
String key = generateKeyBySpEL(spELString,proceedingJoinPoint);
localKey.append(prefix).append(key);
logger.info("加锁 key :{}",localKey.toString());
return localKey.toString();
}
public String generateKeyBySpEL(String spELString, ProceedingJoinPoint joinPoint) {
// 通过joinPoint获取被注解方法
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Method method = methodSignature.getMethod();
// 使用spring的DefaultParameterNameDiscoverer获取方法形参名数组
String[] paramNames = nameDiscoverer.getParameterNames(method);
// 解析过后的Spring表达式对象
Expression expression = parser.parseExpression(spELString);
// spring的表达式上下文对象
EvaluationContext context = new StandardEvaluationContext();
// 通过joinPoint获取被注解方法的形参
Object[] args = joinPoint.getArgs();
// 给上下文赋值
for(int i = 0 ; i < args.length ; i++) {
context.setVariable(paramNames[i], args[i]);
}
// 表达式从上下文中计算出实际参数值
return expression.getValue(context).toString();
}
}

8
payment-base-app/src/main/java/com/base/springcloud/base/CommonResult.java

@ -23,4 +23,12 @@ public class CommonResult<T> {
public CommonResult(String code, String message) {
this(code,message,null);
}
public static CommonResult error(String code, String msg){
CommonResult res = new CommonResult();
res.setCode(code);
res.setMessage(msg);
return res;
}
}

10
payment-base-app/src/main/java/com/base/springcloud/constant/ErrorConstant.java

@ -8,7 +8,7 @@ public enum ErrorConstant {
private String code;
private String message;
private ErrorConstant(String code, String message) {
ErrorConstant(String code, String message) {
this.setCode(code);
this.setMessage(message);
}
@ -28,12 +28,4 @@ public enum ErrorConstant {
public void setMessage(String message) {
this.message = message;
}
@Override
public String toString() {
return "ErrorConstant{" +
"code='" + code + '\'' +
", message='" + message + '\'' +
'}';
}
}

59
payment-base-app/src/main/java/com/base/springcloud/constant/FastJson2JsonRedisSerializer.java

@ -0,0 +1,59 @@
package com.base.springcloud.constant;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.TypeFactory;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;
import org.springframework.util.Assert;
import java.nio.charset.Charset;
/**
* FastJson2JsonRedisSerializer
* Redis使用FastJson序列化
*/
public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T> {
private ObjectMapper objectMapper = new ObjectMapper();
public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
private Class<T> clazz;
static {
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
}
public FastJson2JsonRedisSerializer(Class<T> clazz) {
super();
this.clazz = clazz;
}
public byte[] serialize(T t) throws SerializationException {
if (t == null) {
return new byte[0];
}
return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
}
public T deserialize(byte[] bytes) throws SerializationException {
if (bytes == null || bytes.length <= 0) {
return null;
}
String str = new String(bytes, DEFAULT_CHARSET);
return JSON.parseObject(str, clazz);
}
public void setObjectMapper(ObjectMapper objectMapper) {
Assert.notNull(objectMapper, "'objectMapper' must not be null");
this.objectMapper = objectMapper;
}
protected JavaType getJavaType(Class<?> clazz) {
return TypeFactory.defaultInstance().constructType(clazz);
}
}

5
payment-base-app/src/main/java/com/base/springcloud/constant/PaymentConstant.java

@ -21,5 +21,10 @@ public interface PaymentConstant {
String CP = "CP"; // 云闪付
String QRCCP = "QRCCP";
String NATIVE = "Native"; // Native支付
String H5 = "H5"; // H5支付
String JSAPI = "Jsapi"; // JSAPI支付
String ORDER_QUEUE = "order_queue"; // 订单队列
String ORDER_LOCK_PREFIX = "ORDER_KEY:"; // 订单锁前缀
}

58
payment-base-app/src/main/java/com/base/springcloud/constant/RedisConfig.java

@ -0,0 +1,58 @@
package com.base.springcloud.constant;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.redisson.Redisson;
import org.redisson.config.Config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private String port;
@Value("${spring.redis.password}")
private String password;
@Bean
@SuppressWarnings(value = {"unchecked", "rawtypes"})
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
serializer.setObjectMapper(mapper);
template.setValueSerializer(serializer);
// 使用StringRedisSerializer来序列化和反序列化redis的key值
template.setKeySerializer(new StringRedisSerializer());
template.afterPropertiesSet();
return template;
}
@Bean
public Redisson redisson() {
Config config = new Config();
config.useSingleServer().setAddress("redis://" + host + ":" + port);
config.useSingleServer().setPassword(password);
return (Redisson) Redisson.create(config);
}
}

2
payment-base-app/src/main/java/com/base/springcloud/controller/MerchantController.java

@ -29,7 +29,7 @@ public class MerchantController {
@PostMapping("/merchantConfig")
public CommonResult paymentConfigure(@RequestBody MerchantConfigReqDTO merchantConfigDTO) {
MerchantConfigBO merchantConfigBO = new MerchantConfigBO();
BeanUtils.copyProperties(merchantConfigBO, merchantConfigDTO);
BeanUtils.copyProperties(merchantConfigDTO, merchantConfigBO);
merchantService.paymentConfigure(merchantConfigBO);
return new CommonResult(ErrorConstant.NULL_OBJ.getCode(), ErrorConstant.NULL_OBJ.getMessage(), null);
}

33
payment-base-app/src/main/java/com/base/springcloud/dto/MerchantConfigReqDTO.java

@ -7,29 +7,28 @@ import java.io.File;
@Data
public class MerchantConfigReqDTO {
/**
* 微信支付证书文件
*/
private File certFile;
/**
* 公众号
*/
private String appId;
/**
* 商户私钥
*/
private String appKey;
private String certPath;
/**
* 证书序列号
*/
private String serialNo;
/**
* 商户号
*/
private String merchantId;
private String merchantName;
private String cityStoreName;
private String storeBrandName;
private String signType;
private String privateKey;
private String deviceInfo;
private String spbillCreateIp;
private String notifyUrl;
}

74
payment-base-app/src/main/java/com/base/springcloud/entity/CallbackBodyVo.java

@ -0,0 +1,74 @@
package com.base.springcloud.entity;
import lombok.Data;
import java.io.Serializable;
/**
* 支付退款数据回调通用
*
* @author shuaifengjie.com
*/
@Data
public class CallbackBodyVo implements Serializable {
/**
*
*/
private static final long serialVersionUID = -3279905156049535047L;
/**
* 通知Id
*/
private String id;
/**
* 通知创建时间
*/
private String create_time;
/**
* 通知类型
* 支付成功TRANSACTION.SUCCESS
* 退款成功REFUND.SUCCESS
*/
private String resource_type;
/**
* 通知数据类型
*/
private String event_type;
/**
* 回调摘要
*/
private String summary;
/**
* 通知数据
*/
private Resource resource;
/**
* 通知数据
*/
@Data
public static class Resource {
/**
* 对开启结果数据进行加密的加密算法目前只支持AEAD_AES_256_GCM
*/
private String algorithm;
/**
* Base64编码后的开启/停用结果数据密文
*/
private String ciphertext;
/**
* 附加数据
*/
private String associated_data;
/**
* 加密使用的随机串
*/
private String nonce;
/**
* 原始回调类型
*/
private String original_type;
}
}

57
payment-base-app/src/main/java/com/base/springcloud/entity/WxpayRefundVo.java

@ -0,0 +1,57 @@
package com.base.springcloud.entity;
import lombok.Data;
import java.io.Serializable;
/**
* 微信退款预下单-主体信息
* @author shuaifengjie.com
* v3
*/
@Data
public class WxpayRefundVo implements Serializable {
private static final long serialVersionUID = 8189139771687842957L;
// 原支付交易对应的微信订单号
// 示例值:1217752501201407033233368018
private String transaction_id;
// 若商户传入,会在下发给用户的退款消息中体现退款原因
// 示例值:商品已售完
private String reason;
// 商户系统内部的退款单号,商户系统内部唯一,只能是数字、大小写字母_-|*@ ,同一退款单号多次请求只退一笔。
// 示例值:1217752501201407033233368018
private String out_refund_no;
// 异步接收微信支付退款结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数。 如果参数中传了notify_url,则商户平台上配置的回调地址将不会生效,优先回调当前传的这个地址。
// 示例值:https://weixin.qq.com
private String notify_url;
// 订单金额信息
private WxRefundAmountVo amount;
public WxpayRefundVo() {
super();
}
@Data
public static class WxRefundAmountVo {
// 退款金额,币种的最小单位,只能为整数,不能超过原订单支付金额。
// 示例值:888
private long refund;
// 原支付交易的订单总金额,币种的最小单位,只能为整数。
// 示例值:888
private long total;
// CNY:人民币,境内商户号仅支持人民币。
// 示例值:CNY
private String currency = "CNY";
public WxRefundAmountVo() {
super();
}
public WxRefundAmountVo(long refund, long total) {
super();
this.refund = refund;
this.total = total;
}
}
}

119
payment-base-app/src/main/java/com/base/springcloud/entity/WxpayTradeVo.java

@ -0,0 +1,119 @@
package com.base.springcloud.entity;
import lombok.Data;
import java.io.Serializable;
/**
* 微信支付预下单-主体信息
* @author shuaifengjie.com
* v3
*/
@Data
public class WxpayTradeVo implements Serializable {
/**
*
*/
private static final long serialVersionUID = 8189139771687842957L;
// 由微信生成的应用ID,全局唯一。请求基础下单接口时请注意APPID的应用属性,应为公众号的APPID
// 示例值:wxd678efh567hg6787
private String appid;
// 直连商户的商户号,由微信支付生成并下发。
// 示例值:1230000109
private String mchid;
// 商品描述
// 示例值:Image形象店-深圳腾大-QQ公仔
private String description;
// 商户系统内部订单号,只能是数字、大小写字母_-*且在同一个商户号下唯一
// 示例值:1217752501201407033233368018
private String out_trade_no;
// 通知URL必须为直接可访问的URL,不允许携带查询串。
// 示例值:https://www.weixin.qq.com/wxpay/pay.php
private String notify_url;
// 订单金额信息
private WxTradeAmountVo amount;
// 支付者信息 - JSAPI,必传
private WxPayerVo payer;
// 支付场景描述 - H5,必传
private WxSceneInfoVo scene_info;
public WxpayTradeVo() {
super();
}
public WxpayTradeVo(String appid, String mchid, String description, String out_trade_no, String notify_url) {
super();
this.appid = appid;
this.mchid = mchid;
this.description = description;
this.out_trade_no = out_trade_no;
this.notify_url = notify_url;
}
@Data
public static class WxTradeAmountVo {
// 订单总金额,单位为分。
// 示例值:100
private long total;
// CNY:人民币,境内商户号仅支持人民币。
// 示例值:CNY
private String currency = "CNY";
public WxTradeAmountVo() {
super();
}
public WxTradeAmountVo(long total) {
super();
this.total = total;
}
}
@Data
public static class WxPayerVo {
// 用户在直连商户appid下的唯一标识。 下单前需获取到用户的Openid,Openid获取详见
// 示例值:oUpF8uMuAJO_M2pxb1Q9zNjWeS6o
private String openid;
public WxPayerVo() {
super();
}
public WxPayerVo(String openid) {
super();
this.openid = openid;
}
}
@Data
public static class WxSceneInfoVo {
// 用户的客户端IP,支持IPv4和IPv6两种格式的IP地址。
// 示例值:14.23.150.211
private String payer_client_ip;
// H5场景信息
private WxH5InfoVo h5_info;
public WxSceneInfoVo() {
super();
}
public WxSceneInfoVo(String payer_client_ip) {
super();
this.payer_client_ip = payer_client_ip;
}
}
@Data
public static class WxH5InfoVo {
// 场景类型
// 示例值:iOS, Android, Wap
private String type = "Wap";
}
}

6
payment-base-app/src/main/java/com/base/springcloud/handle/RBSQueueHandler.java

@ -1,7 +1,11 @@
package com.base.springcloud.handle;
import com.base.springcloud.aspect.annotation.Rlock;
import com.base.springcloud.entity.Order;
import com.base.springcloud.schedule.OrderTask;
import com.base.springcloud.service.PaymentService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@ -9,6 +13,8 @@ import org.springframework.stereotype.Component;
@Component
public class RBSQueueHandler {
Logger logger = LoggerFactory.getLogger(RBSQueueHandler.class);
@Autowired
private PaymentService paymentService;

8
payment-base-app/src/main/java/com/base/springcloud/service/imp/MerchantServiceImp.java

@ -16,6 +16,7 @@ import java.io.FileNotFoundException;
import java.io.InputStream;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;
@Service
@ -34,11 +35,12 @@ public class MerchantServiceImp implements MerchantService {
@Override
public void paymentConfigure(MerchantConfigBO merchantConfigBO) {
// 证书上传服务器
String certPath = uplodWecPayCert(merchantConfigBO.getMerchantId(),merchantConfigBO.getCertfile());
//String certPath = uplodWecPayCert(merchantConfigBO.getMerchantId(),merchantConfigBO.getCertfile());
// 商户支付信息保存
merchantConfigBO.setCertPath(certPath);
//merchantConfigBO.setCertPath(certPath);
MerchantConfig merchantConfig = new MerchantConfig();
BeanUtils.copyProperties(merchantConfig,merchantConfigBO);
merchantConfig.setId(UUID.randomUUID().toString().replace("-", ""));
BeanUtils.copyProperties(merchantConfigBO,merchantConfig);
merchantConfigMapper.insert(merchantConfig);
}

12
payment-base-app/src/main/java/com/base/springcloud/service/imp/OrderServiceImp.java

@ -12,10 +12,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.*;
import java.util.stream.Collectors;
@Service
@ -51,11 +48,12 @@ public class OrderServiceImp implements OrderService {
if(!Optional.ofNullable(orderList).isPresent()){
return null;
}
List<OrderBO> orderBOList = orderList.stream().map(o -> {
return orderList.stream().map(o -> {
OrderBO orderBO = new OrderBO();
BeanUtils.copyProperties(orderBO,o);
Order order = new Order();
BeanUtils.copyProperties(o,order);
orderBO.setOrder(order);
return orderBO;
}).collect(Collectors.toList());
return orderBOList;
}
}

19
payment-base-app/src/main/java/com/base/springcloud/service/imp/PaymentServiceImp.java

@ -1,5 +1,6 @@
package com.base.springcloud.service.imp;
import com.base.springcloud.aspect.annotation.Rlock;
import com.base.springcloud.bo.OrderBO;
import com.base.springcloud.constant.PaymentConstant;
import com.base.springcloud.entity.Order;
@ -7,11 +8,12 @@ import com.base.springcloud.service.BillService;
import com.base.springcloud.service.OrderService;
import com.base.springcloud.service.PaymentService;
import com.base.springcloud.service.WecPaymentService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.xml.crypto.Data;
import java.util.Date;
import java.util.List;
import java.util.Optional;
@ -19,6 +21,8 @@ import java.util.Optional;
@Service
public class PaymentServiceImp implements PaymentService {
Logger logger = LoggerFactory.getLogger(PaymentServiceImp.class);
@Autowired
private OrderService orderService;
@ -52,12 +56,12 @@ public class PaymentServiceImp implements PaymentService {
public void syncOrderInfo(Date date) {
// 获取订单列表
List<OrderBO> orderList = orderService.queryOrder(date);
if(Optional.ofNullable(orderList).isPresent()) return;
if(!Optional.ofNullable(orderList).isPresent()) return;
// 遍历查询第三方订单信息
for (OrderBO orderBO : orderList){
// 异步处理数据
amqpTemplate.convertSendAndReceive(PaymentConstant.ORDER_QUEUE,orderBO);
amqpTemplate.convertSendAndReceive(PaymentConstant.ORDER_QUEUE,orderBO.getOrder());
}
}
@ -66,8 +70,15 @@ public class PaymentServiceImp implements PaymentService {
* @param order
*/
@Override
@Rlock(prefix = PaymentConstant.ORDER_LOCK_PREFIX, suffix = "#order.orderNo",waitTime = 0,leaseTime = 60)
public void queryOrder(Order order) {
logger.info("线程 : {} 开始睡觉....",Thread.currentThread().getName());
try {
Thread.sleep(10000);
}catch (Exception e){
logger.info(e.getMessage());
}
logger.info("线程 : {} 结束睡觉....",Thread.currentThread().getName());
}
// 参数检查

21
payment-base-app/src/main/java/com/base/springcloud/util/StringHelper.java

@ -0,0 +1,21 @@
package com.base.springcloud.util;
import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException;
import com.google.gson.reflect.TypeToken;
import java.util.Map;
public class StringHelper {
public static Map<String, String> json2map(String str_json) {
Map<String, String> res = null;
try {
Gson gson = new Gson();
res = gson.fromJson(str_json, new TypeToken<Map<String, String>>() {
}.getType());
} catch (JsonSyntaxException e) {
}
return res;
}
}

344
payment-base-app/src/main/java/com/base/springcloud/util/WxpayUtils.java

@ -0,0 +1,344 @@
package com.base.springcloud.util;
import com.alibaba.fastjson.JSON;
import com.base.springcloud.constant.PaymentConstant;
import com.base.springcloud.entity.CallbackBodyVo;
import com.base.springcloud.entity.WxpayRefundVo;
import com.base.springcloud.entity.WxpayTradeVo;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.springframework.util.Base64Utils;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.cert.X509Certificate;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.*;
import java.util.stream.Collectors;
/**
* 微信支付帮助类
*
* @author shuaifengjie.com
*/
@Slf4j
public class WxpayUtils {
/**
* 发起支付
*
* @param tradeVo
* @param scene
* @param privateKey
* @param certificate
* @param serialNo
* @return
*/
public static ObjectNode getWxpay(WxpayTradeVo tradeVo, String scene, String privateKey, String certificate, String serialNo) {
// ...
String responseBody = response(JSON.toJSONString(tradeVo), getUrl(scene), tradeVo.getMchid(), privateKey, certificate, serialNo);
log.info("======>>> wxpay response body: ", responseBody);
// parse
@SuppressWarnings("unchecked")
Map<String, String> resultMap = StringHelper.json2map(responseBody);
// ...
ObjectMapper objectMapper = new ObjectMapper();
ObjectNode objectNode = objectMapper.createObjectNode();
// ...
if (PaymentConstant.NATIVE.equals(scene)) {// pc - 扫码支付(Native)
objectNode.put("code_url", resultMap.get("code_url"));
} else if (PaymentConstant.H5.equals(scene)) {// 手机网页(H5)
objectNode.put("h5_url", resultMap.get("h5_url"));
} else if (PaymentConstant.JSAPI.equals(scene)) {// 微信公众号(H5)
String prepayId = resultMap.get("prepay_id");
objectNode.put("prepay_id", prepayId);
// ...
objectNode.put("appId", tradeVo.getAppid());
String timestamp = getTimestamp();
objectNode.put("timeStamp", timestamp);
String nonceStr = CreateNoncestr();
objectNode.put("nonceStr", nonceStr);
String packageStr = "prepay_id=" + prepayId;
objectNode.put("package", packageStr);
// 签名
String paySign = doRequestSign(privateKey, tradeVo.getAppid(), timestamp, nonceStr, packageStr);
objectNode.put("paySign", paySign);
objectNode.put("signType", "RSA");
}
return objectNode;
}
/**
* 发起退款
*
* @param tradeVo
* @param mchid
* @param privateKey
* @param certificate
* @param serialNo
* @return
*/
public static void refund(WxpayRefundVo tradeVo, String mchid, String privateKey, String certificate, String serialNo) {
// ...
String responseBody = response(JSON.toJSONString(tradeVo), REFUND_URL, mchid, privateKey, certificate, serialNo);
log.info("======>>> wxrefund response body: ", responseBody);
}
/**
* 公共请求
*
* @param data
* @param url
* @param mchid
* @param privateKey
* @param certificate
* @param serialNo
* @return
*/
public static String response(String data, String url, String mchid, String privateKey, String certificate, String serialNo) {
log.info("====>>> wxpay response, mchid: {}, serialNo: {}, url: {}, privateKey: {}, certificate: {}, data: {}",
mchid, serialNo, url, privateKey, certificate, data);
// ...
HttpPost httpPost = new HttpPost(url);
StringEntity reqEntity = new StringEntity(data, ContentType.create("application/json", "utf-8"));
// ...
httpPost.setEntity(reqEntity);
httpPost.addHeader("Accept", "application/json");
// ...
CloseableHttpResponse response = null;
// 返回
String body = null;
try {
// 商户私钥
PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(new ByteArrayInputStream(privateKey.getBytes("utf-8")));
// ...
response = getClient(mchid, serialNo, merchantPrivateKey, certificate).execute(httpPost);
log.info("====>>> wxpay response, result: {}", JSON.toJSONString(response));
// response = httpClient.execute(httpPost);
// assertTrue(response.getStatusLine().getStatusCode() != 401);
int statusCode = response.getStatusLine().getStatusCode();
HttpEntity entity = response.getEntity();
body = EntityUtils.toString(entity);
log.info("====>>> wxpay response, success: {}, body: {}", statusCode, body);
if (statusCode != 200) {
@SuppressWarnings("unchecked")
Map<String, String> errorMsg = StringHelper.json2map(body);
}
// 执行释放
EntityUtils.consume(entity);
} catch (IOException e) {
log.error("======>>> wxpay response, client exception: {}", e);
} catch (Exception e) {
log.error("======>>> wxpay response, exception: {}", e);
} finally {
try {
response.close();
} catch (IOException e) {
log.error("======>>> wxpay response, ioe exception: {}", e);
}
}
return body;
}
/**
* 创建Client
*
* @param mchid
* @param serialNo
* @param privateKey
* @param certificate
* @return
* @throws UnsupportedEncodingException
*/
public static CloseableHttpClient getClient(String mchid, String serialNo, PrivateKey privateKey, String certificate) throws UnsupportedEncodingException {
// 使用自动更新的签名验证器,不需要传入证书
// AutoUpdateCertificatesVerifier verifier = new AutoUpdateCertificatesVerifier(
// new WechatPay2Credentials(mchId, new PrivateKeySigner(mchSerialNo, merchantPrivateKey)), apiV3Key.getBytes("utf-8"));
// builder
WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create().withMerchant(mchid, serialNo, privateKey);
// .withValidator(new WechatPay2Validator(verifier));
List<X509Certificate> certs = new ArrayList<>();
certs.add(PemUtil.loadCertificate(new ByteArrayInputStream(certificate.getBytes("utf-8"))));
builder.withWechatpay(certs);
return builder.build();
}
private static final String JSAPI_URL = "https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi";
private static final String H5_URL = "https://api.mch.weixin.qq.com/v3/pay/transactions/h5";
private static final String NATIVE_URL = "https://api.mch.weixin.qq.com/v3/pay/transactions/native";
private static final String REFUND_URL = "https://api.mch.weixin.qq.com/v3/refund/domestic/refunds";
// 获取支付订单类型
private static String getUrl(String scene) {
String url = "";
switch (scene) {
case PaymentConstant.NATIVE:
url = NATIVE_URL;
break;
case PaymentConstant.H5:
url = H5_URL;
break;
case PaymentConstant.JSAPI:
url = JSAPI_URL;
break;
default:
}
return url;
}
/**
* 字符串
*
* @return
*/
public static String CreateNoncestr() {
String chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
String res = "";
for (int i = 0; i < 16; i++) {
Random rd = new Random();
res += chars.charAt(rd.nextInt(chars.length() - 1));
}
return res;
}
/**
* 得到时间戳
*
* @return
*/
public static String getTimestamp() {
long epochSecond = LocalDateTime.now().toEpochSecond(ZoneOffset.of("+8"));
return String.valueOf(epochSecond);
}
/**
* 加密算法提供方 - BouncyCastle
*/
private static final String BC_PROVIDER = "BC";
/**
* @param privateKey
* @param orderedComponents
* @return
*/
public static String doRequestSign(String privateKey, String... orderedComponents) {
try {
// 商户私钥
PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(new ByteArrayInputStream(privateKey.getBytes("utf-8")));
Signature signer = Signature.getInstance("SHA256withRSA", BC_PROVIDER);
signer.initSign(merchantPrivateKey);
final String signatureStr = createSign(true, orderedComponents);
signer.update(signatureStr.getBytes(StandardCharsets.UTF_8));
return Base64Utils.encodeToString(signer.sign());
} catch (InvalidKeyException e) {
log.error("InvalidKeyException: {}", e);
} catch (SignatureException e) {
log.error("SignatureException: {}", e);
} catch (NoSuchProviderException e) {
log.error("NoSuchProviderException: {}", e);
} catch (NoSuchAlgorithmException e) {
log.error("NoSuchAlgorithmException: {}", e);
} catch (UnsupportedEncodingException e) {
log.error("UnsupportedEncodingException: {}", e);
}
return null;
}
/**
* 请求时设置签名 组件
*
* @param components the components
* @return string string
*/
private static String createSign(boolean newLine, String... components) {
String suffix = newLine ? "\n" : "";
return Arrays.stream(components).collect(Collectors.joining("\n", "", suffix));
}
/**
* 使用微信平台证书对微信回调数据验签做应答签名比较
*
* @param certificate
* @param wechatpaySignature
* @param wechatpayTimestamp
* @param wechatpayNonce
* @param body
* @return
*/
public static boolean responseSignVerify(String certificate, String wechatpaySignature, String wechatpayTimestamp, String wechatpayNonce, String body) {
try {
final String signatureStr = createSign(true, wechatpayTimestamp, wechatpayNonce, body);
Signature signer = Signature.getInstance("SHA256withRSA");
signer.initVerify(PemUtil.loadCertificate(new ByteArrayInputStream(certificate.getBytes("utf-8"))));
signer.update(signatureStr.getBytes(StandardCharsets.UTF_8));
// ...
return signer.verify(Base64Utils.decodeFromString(wechatpaySignature));
} catch (UnsupportedEncodingException e) {
log.error("UnsupportedEncodingException: {}", e);
} catch (SignatureException e) {
log.error("SignatureException: {}", e);
} catch (NoSuchAlgorithmException e) {
log.error("NoSuchAlgorithmException: {}", e);
} catch (InvalidKeyException e) {
log.error("InvalidKeyException: {}", e);
}
return false;
}
/**
* 微信V3密钥解密响应体
*
* @param apiv3Key
* @param bodyVo
* @return
*/
public static String decryptResponseBody(String apiv3Key, CallbackBodyVo bodyVo) {
try {
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
SecretKeySpec key = new SecretKeySpec(apiv3Key.getBytes(StandardCharsets.UTF_8), "AES");
GCMParameterSpec spec = new GCMParameterSpec(128, bodyVo.getResource().getNonce().getBytes(StandardCharsets.UTF_8));
cipher.init(Cipher.DECRYPT_MODE, key, spec);
cipher.updateAAD(bodyVo.getResource().getAssociated_data().getBytes(StandardCharsets.UTF_8));
byte[] bytes = cipher.doFinal(Base64Utils.decodeFromString(bodyVo.getResource().getCiphertext()));
return new String(bytes, StandardCharsets.UTF_8);
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
log.error("NoSuchAlgorithmException: {}", e);
} catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
log.error("InvalidKeyException: {}", e);
} catch (IllegalBlockSizeException e) {
log.error("IllegalBlockSizeException: {}", e);
} catch (BadPaddingException e) {
log.error("BadPaddingException: {}", e);
}
return null;
}
}

19
payment-base-app/src/main/resources/application.yml

@ -31,6 +31,25 @@ spring:
port: 5672
username: rbs-payment
password: pay123123
# redis 配置
redis:
# 地址
host: 127.0.0.1
# 端口,默认为6379
port: 6379
# 密码
password: 123456
# 连接超时时间
timeout: 10s
lettuce:
pool:
# 连接池中的最小空闲连接
min-idle: 0
# 连接池中的最大空闲连接
# 连接池的最大数据库连接数
max-active: 8
# #连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
#mybatis:配置
mybatis:
type-aliases-package: com.base.springcloud.dao #实体类包路径

6
payment-base-app/src/main/resources/com/base/payment/mapper/MerchantConfigMapper.xml

@ -58,6 +58,9 @@
<if test="certPath != null">
cert_path,
</if>
<if test="serialNo != null">
serial_no
</if>
<if test="merchantId != null">
merchant_id,
</if>
@ -102,6 +105,9 @@
<if test="certPath != null">
#{certPath,jdbcType=VARCHAR},
</if>
<if test="serialNo != null">
#{serialNo,jdbcType=VARCHAR},
</if>
<if test="merchantId != null">
#{merchantId,jdbcType=VARCHAR},
</if>

7
payment-base-app/src/main/resources/com/base/payment/mapper/OrderMapper.xml

@ -4,6 +4,8 @@
<resultMap id="BaseResultMap" type="com.base.springcloud.entity.Order">
<id column="id" jdbcType="VARCHAR" property="id" />
<result column="order_no" jdbcType="VARCHAR" property="orderNo" />
<result column="order_state" jdbcType="VARCHAR" property="orderState" />
<result column="mch_id" jdbcType="VARCHAR" property="mchId" />
<result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
<result column="expire_time" jdbcType="TIMESTAMP" property="expireTime" />
<result column="request_no" jdbcType="VARCHAR" property="requestNo" />
@ -14,7 +16,7 @@
<result column="reserved" jdbcType="VARCHAR" property="reserved" />
</resultMap>
<sql id="Base_Column_List">
id, order_no, create_time, expire_time, request_no, order_amount, good_name, good_id,
id, order_no,order_state,mch_id,create_time, expire_time, request_no, order_amount, good_name, good_id,
order_type, reserved
</sql>
<select id="selectByPrimaryKey" parameterType="java.lang.String" resultMap="BaseResultMap">
@ -161,7 +163,8 @@
<include refid="Base_Column_List" />
from pay_order
<where>
<![CDATA[ create_time >= #{startTime,jdbcType=DATE} ]]>
order_state = 'W'
and DATE_FORMAT(create_time,'%Y-%m-%d') = DATE_FORMAT(#{date},'%Y-%m-%d')
</where>
order by time_stamp desc limit 10
</select>

34
payment-base-app/target/classes/application.yml

@ -25,7 +25,39 @@ spring:
url: jdbc:mysql://106.14.81.251:3306/rbs_erp_test?characterEncoding=UTF-8&useUnicode=true&useSSL=false&tinyInt1isBit=false
username: root
password: hnrr%%360
#rabbitmq 配置
rabbitmq:
host: localhost
port: 5672
username: rbs-payment
password: pay123123
# redis 配置
redis:
# 地址
host: 127.0.0.1
# 端口,默认为6379
port: 6379
# 密码
password: 123456
# 连接超时时间
timeout: 10s
lettuce:
pool:
# 连接池中的最小空闲连接
min-idle: 0
# 连接池中的最大空闲连接
# 连接池的最大数据库连接数
max-active: 8
# #连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
#mybatis:配置
mybatis:
type-aliases-package: com.base.springcloud.dao #实体类包路径
mapper-locations: classpath*:com/base/payment/mapper/*.xml #mapper.xml路径
mapper-locations: classpath*:com/base/payment/mapper/*.xml #mapper.xml路径
ftp:
ftp_ip: 192.168.71.132
ftp_prot : 21
ftp_username : wangying
ftp_password : 123456
ftp_basepath : /home/wangying

6
pom.xml

@ -87,12 +87,6 @@
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.38</version>
</dependency>
<!-- mybatis-generator的依赖 自动生成javabean和sql -->
<dependency>
<groupId>org.mybatis.generator</groupId>

Loading…
Cancel
Save