【U】新增支付拆安好相关代码

This commit is contained in:
殷林
2020-12-28 15:44:56 +08:00
parent 0ad509cfe2
commit 7acb364e95
40 changed files with 1535 additions and 11 deletions

View File

@@ -58,5 +58,8 @@ spring boot starter提供springcloud统一数据解析和异常传递。
### easy-spring-boot-wr-separation
该模块提供简单的读写分离
### easy-payment 聚合支付支付插件化代码
该模块提供通用的支付插件化协议分发
## 升级日志
升级日志 [https://github.com/yin5980280/easy/blob/develop/doc/update-log.md]

View File

@@ -173,6 +173,12 @@
<version>${revision}</version>
</dependency>
<dependency>
<groupId>cn.org.easysite</groupId>
<artifactId>easy-payment</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>cn.org.easysite</groupId>
<artifactId>easy-maven-plugins</artifactId>

22
easy-payment/pom.xml Normal file
View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>easy-parent</artifactId>
<groupId>cn.org.easysite</groupId>
<version>${revision}</version>
<relativePath>../easy-parent</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>easy-payment</artifactId>
<dependencies>
<dependency>
<groupId>cn.org.easysite</groupId>
<artifactId>easy-framework</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,46 @@
/*
* @Copyright : Copyright (c) 2020
* @Company : EasySite Technology 阿富汗 Co. Ltd.
*/
package cn.org.easysite.payment.plugin.exception;
import cn.org.easysite.framework.exception.ErrorCode;
/**
* @author : 潘多拉
* @version : 1.0
* @date : 2020/12/28 3:38 下午
* @link : cn.org.easysite.payment.plugin.exception.PaymentErrorCode
*/
public enum PaymentErrorCode implements ErrorCode {
/**
* 支付网关未找到插件实现类
*/
PAYMENT_PAY_CHANNEL_PLUGIN_NOT_FOUND_ERROR("10005", "未找到该渠道的插件实现类"),
/**
* 未在当前插件下找到该支付方式的实现
*/
PAYMENT_PAY_CHANNEL_PLUGIN_PAY_TYPE_NOT_FOUND_ERROR("10006", "未在当前插件下找到该支付方式的实现"),
;
private String errorCode;
private String message;
PaymentErrorCode(String errorCode, String message) {
this.errorCode = errorCode;
this.message = message;
}
@Override
public String getErrorCode() {
return this.errorCode;
}
@Override
public String getMessage() {
return this.message;
}
}

View File

@@ -0,0 +1,29 @@
/*
* @Copyright : Copyright (c) 2020
* @Company : EasySite Technology 阿富汗 Co. Ltd.
*/
package cn.org.easysite.payment.plugin.exception;
import cn.org.easysite.framework.exception.BaseException;
import cn.org.easysite.framework.exception.ErrorCode;
/**
* @author : 潘多拉
* @version : 1.0
* @date : 2020/12/27 12:58 下午
* @link : cn.org.easysite.payment.plugin.exception.PaymentException
*/
public class PaymentException extends BaseException {
public PaymentException(ErrorCode errorCode) {
super(errorCode);
}
public PaymentException(String code, Object[] args) {
super("payment", code, args);
}
public PaymentException(String defaultMessage) {
super(defaultMessage);
}
}

View File

@@ -0,0 +1,90 @@
/*
* @Copyright : Copyright (c) 2020
* @Company : EasySite Technology 阿富汗 Co. Ltd.
*/
package cn.org.easysite.payment.plugin.factory;
import org.apache.commons.lang3.ClassUtils;
import org.apache.commons.lang3.StringUtils;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import cn.org.easysite.payment.plugin.exception.PaymentException;
import cn.org.easysite.payment.plugin.spi.annotation.Channel;
import cn.org.easysite.payment.plugin.spi.annotation.Plugin;
import cn.org.easysite.payment.plugin.spi.basic.PaymentPlugin;
import lombok.extern.slf4j.Slf4j;
/**
* @author : 潘多拉
* @version : 1.0
* @date : 2020/12/27 2:08 下午
* @link : cn.org.easysite.payment.plugin.factory.PaymentFactory
*/
@Slf4j
public class PaymentFactory {
private PaymentFactory() {}
private static final Map<Class<? extends PaymentPlugin>, Map<String, PaymentPlugin>> CONTAINER = new HashMap<>();
public static synchronized void register(PaymentPlugin paymentPlugin) {
if (paymentPlugin == null) {
throw new PaymentException("插件BEAN为空无法注册");
}
Class<? extends PaymentPlugin> type = getPluginType(paymentPlugin);
String channelCode = getChannelCode(paymentPlugin);
Map<String, PaymentPlugin> plugins = CONTAINER.computeIfAbsent(type, k -> new HashMap<>());
if (plugins.get(channelCode) != null) {
throw new IllegalArgumentException("该渠道CODE[" + channelCode + "]实现已被[" + plugins.get(channelCode).getClass().getName() + "]注册,请检查并修改@Channel值");
}
log.info("正在注册[{}]类型的插件,渠道渠道编码为[{}]", type.getName(), channelCode);
plugins.put(channelCode, paymentPlugin);
}
private static Class<? extends PaymentPlugin> getPluginType(PaymentPlugin paymentPlugin) {
List<Class<?>> classes = ClassUtils.getAllInterfaces(paymentPlugin.getClass());
if (classes == null || classes.isEmpty()) {
throw new IllegalArgumentException("该渠道插件[" + paymentPlugin.getClass().getName() + "]实现有误");
}
for (Class<?> clazz : classes) {
if (clazz.isAnnotationPresent(Plugin.class)) {
Plugin plugin = clazz.getDeclaredAnnotation(Plugin.class);
log.info("正在注册类型为[{}]的插件", plugin.value());
return (Class<? extends PaymentPlugin>) clazz;
}
}
throw new IllegalArgumentException("该渠道插件[" + paymentPlugin.getClass().getName() + "]的聚合插件接口未标示@Plugin");
}
private static String getChannelCode(PaymentPlugin paymentPlugin) {
Channel channel = paymentPlugin.getClass().getDeclaredAnnotation(Channel.class);
if (channel == null || StringUtils.isEmpty(channel.value())) {
log.warn("支付渠道实现配置错误,必须标注@PayChannel 并设置value, 未配置无法分发");
}
if (channel == null || StringUtils.isBlank(channel.value())) {
throw new IllegalArgumentException("该渠道插件[" + paymentPlugin.getClass().getName() + "]未配置渠道值");
}
return channel.value();
}
public static PaymentPlugin getPaymentPlugin(Class<? extends PaymentPlugin> clazz, String channelCode) {
if (clazz == null) {
throw new PaymentException("插件类型不能为空");
}
if (channelCode == null) {
throw new PaymentException("渠道编码不能为空");
}
Map<String, PaymentPlugin> plugins = CONTAINER.get(clazz);
if (plugins == null) {
throw new PaymentException("该类型插件[" + clazz.getName() + "]尚未支持");
}
PaymentPlugin paymentPlugin = plugins.get(channelCode);
if (paymentPlugin == null) {
throw new PaymentException("该类型插件[" + clazz.getName() + "]下该渠道[" + channelCode + "]尚未支持");
}
return paymentPlugin;
}
}

View File

@@ -0,0 +1,28 @@
package cn.org.easysite.payment.plugin.spi.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author : yinlin
* @version : 1.0
* @date : 2019/7/23 7:00 PM
* @Description : 支付渠道注解
* @Copyright : Copyright (c) 2018
* @Company : IOT Technology Chengdu Co. Ltd.
* @link : cn.org.easysite.payment.plugin.spi.annotation.PayChannel
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Channel {
/**
* 支付渠道CODE 支付渠道在系统启动时需要获取CODE
* @return
*/
String value() default "";
}

View File

@@ -0,0 +1,28 @@
package cn.org.easysite.payment.plugin.spi.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author : yinlin
* @version : 1.0
* @date : 2019/7/23 7:00 PM
* @Description : 支付方式注解
* @Copyright : Copyright (c) 2018
* @Company : IOT Technology Chengdu Co. Ltd.
* @link : cn.org.easysite.payment.plugin.spi.annotation.PayType
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface PayType {
/**
* 支付方式code
* @return
*/
String value() default "";
}

View File

@@ -0,0 +1,28 @@
package cn.org.easysite.payment.plugin.spi.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author : yinlin
* @version : 1.0
* @date : 2019/7/23 7:00 PM
* @Description : 支付渠道注解
* @Copyright : Copyright (c) 2018
* @Company : IOT Technology Chengdu Co. Ltd.
* @link : cn.org.easysite.payment.plugin.spi.annotation.PayChannel
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Plugin {
/**
* 支付渠道在系统启动时需要获取CODE
* @return
*/
String value() default "";
}

View File

@@ -0,0 +1,18 @@
package cn.org.easysite.payment.plugin.spi.basic;
import cn.org.easysite.payment.plugin.spi.model.base.parameter.PayeePayParameter;
import cn.org.easysite.payment.plugin.spi.model.base.result.PaymentBaseResult;
/**
* 回调通知基础插件类
* @author yinlin
*/
public interface NotifyPlugin<T extends PayeePayParameter, E extends PaymentBaseResult> extends PaymentPlugin {
/**
* 回调抽象接口
* @param notify 回调参数实体
* @return 返回回调解析结果
*/
E notify(T notify);
}

View File

@@ -0,0 +1,24 @@
package cn.org.easysite.payment.plugin.spi.basic;
/**
* 支付插件基础类
* @author yinlin
*/
public interface PaymentPlugin {
/**
* 插件初始化
*/
void init();
/**
* 当前插件是否可用,留给接口或者插件实现类自己实现
* @return true 可用 false 不可用
*/
boolean usable();
/**
* 插件销毁
*/
void destroy();
}

View File

@@ -0,0 +1,18 @@
package cn.org.easysite.payment.plugin.spi.basic;
import cn.org.easysite.payment.plugin.spi.model.base.parameter.PayeePayParameter;
import cn.org.easysite.payment.plugin.spi.model.base.result.PaymentBaseResult;
/**
* 支付查询基础类
* @author yinlin
*/
public interface QueryPlugin<T extends PayeePayParameter, E extends PaymentBaseResult> extends PaymentPlugin {
/**
* 查询第三方订单状态
* @param parameter 支付参数
* @return 返回三方订单详情
*/
E query(T parameter);
}

View File

@@ -0,0 +1,43 @@
/*
* @Copyright : Copyright (c) 2020
* @Company : EasySite Technology 阿富汗 Co. Ltd.
*/
package cn.org.easysite.payment.plugin.spi.model.base.parameter;
import java.util.Map;
import cn.org.easysite.commons.base.BaseObject;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author : 潘多拉
* @version : 1.0
* @date : 2020/12/27 12:02 下午
* @link : cn.org.easysite.payment.plugin.spi.model.base.parameter.BasePayParameter
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class BasePayParameter extends BaseObject {
/**
* 支付渠道编码
*/
private String payChannel;
/**
* 支付机构编码
*/
private String payOrg;
/**
* 支付类型编码
*/
private String payType;
/**
* 支付参数扩展参数
*/
private Map<String, Object> basePayExtraParams;
}

View File

@@ -0,0 +1,33 @@
package cn.org.easysite.payment.plugin.spi.model.base.parameter;
import java.util.Map;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author : 盘达天神
* @version : 1.0
* @date : 2020/2/14 9:22 上午
* @Description :
* @Copyright : Copyright (c) 2019
* @Company : EasySite Technology 阿富汗 Co. Ltd.
* @link : cn.org.easysite.payment.plugin.spi.model.PaymentPayeeParameter
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class PayeePayParameter extends BasePayParameter {
/**
* 支付渠道商户支付参数JSON字符串,需要使用方自己解析
*/
private Map<String, String> payeeParams;
/**
* 支付参数扩展参数
*/
private Map<String, Object> payeeExtraParams;
}

View File

@@ -0,0 +1,34 @@
package cn.org.easysite.payment.plugin.spi.model.base.result;
import cn.org.easysite.commons.base.BaseObject;
import lombok.Data;
/**
* @author : 盘达天神
* @version : 1.0
* @date : 2020/2/14 10:19 上午
* @Description :
* @Copyright : Copyright (c) 2019
* @Company : EasySite Technology 阿富汗 Co. Ltd.
* @link : cn.org.easysite.payment.plugin.spi.model.base.result.PaymentBaseResult
*/
@Data
public class PaymentBaseResult extends BaseObject {
/**
* 状态描述啥的
*/
private String message;
/**
* 请求是否成功
*/
private Boolean success;
protected String code;
/**
* 回调通知返回CODE
*/
private String notifyReturnCode = "SUCCESS";
}

View File

@@ -0,0 +1,43 @@
/*
* @Copyright : Copyright (c) 2020
* @Company : EasySite Technology 阿富汗 Co. Ltd.
*/
package cn.org.easysite.payment.plugin.spi.model.pay.parameter;
import java.util.Map;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author : 潘多拉
* @version : 1.0
* @date : 2020/12/27 10:53 上午
* @link : cn.org.easysite.payment.plugin.spi.model.pay.parameter.PaymentPayNotifyParameter
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class NotifyPayParameter extends PayParameter {
/**
* 通知回调HttpServletRequest.getHeaderMap 所有回调参数map
*/
private Map<String, String> notifyRequestHeaderMap;
/**
* 通知回调HttpServletRequest.getRequestParam 所有回调参数map
*/
private Map<String, String> notifyRequestMap;
/**
* 通知回调请求body的字符串有的回调可能是流也需要转成String
*/
private String notifyRequestBody;
/**
* 退款扩展参数
*/
private Map<String, Object> refundExtraParams;
}

View File

@@ -0,0 +1,84 @@
/*
* @Copyright : Copyright (c) 2020
* @Company : EasySite Technology 阿富汗 Co. Ltd.
*/
package cn.org.easysite.payment.plugin.spi.model.pay.parameter;
import java.math.BigDecimal;
import java.util.Map;
import cn.org.easysite.payment.plugin.spi.model.base.parameter.PayeePayParameter;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author : 潘多拉
* @version : 1.0
* @date : 2020/12/27 10:51 上午
* @link : cn.org.easysite.payment.plugin.spi.model.pay.parameter.PaymentPayParameter
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class PayParameter extends PayeePayParameter {
/**
* 系统支付单号
*/
private String payOrderNo;
/**
* 第三方交易编号(查询时传递)
*/
private String orgPayTransactionNo;
/**
* 渠道方支付单号
*/
private String channelPayOrderNo;
/**
* 商品名称
**/
private String productName;
/**
* 商品描述
**/
private String productDesc;
/**
* 订单描述
*/
private String orderDesc;
/**
* 客户被扫时展示的付款码转换的支付编号
*/
private String authCode;
/**
* 支付金额
*/
private BigDecimal payAmount;
/**
* 客户端Ip地址
*/
private String ipAddress;
/**
* 支付用户编号 支付宝buyerId 微信对应openid 出款方payer
*/
private String payerOpenid;
/**
* 币种默认CNY
*/
private String currency = "CNY";
/**
* 支付扩展参数
*/
private Map<String, Object> payExtraParams;
}

View File

@@ -0,0 +1,51 @@
package cn.org.easysite.payment.plugin.spi.model.pay.result;
import java.util.Map;
import cn.org.easysite.payment.plugin.spi.model.base.result.PaymentBaseResult;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
/**
* @author : yinlin
* @version : 1.0
* @date : 2019/7/23 4:36 PM
* @Description : 预下单/一码付返回
* @Copyright : Copyright (c) 2018
* @Company : IOT Technology Chengdu Co. Ltd.
* @link : cn.org.easysite.payment.plugin.spi.model.pay.result.PaymentPrepayResultPayment
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@ToString
public class PayPreCreateResult extends PaymentBaseResult {
private Boolean form;
private Boolean qrcode;
/**
* 支付渠道方订单号
*/
private String channelPayOrderNo;
/**
* 参数
*/
private Map<String, String> prepayParam;
/**
* 原生二维码CODE字符串
*/
private String qrcodeUrl;
/**
* FORM
*/
private String formBody;
}

View File

@@ -0,0 +1,109 @@
package cn.org.easysite.payment.plugin.spi.model.pay.result;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
import java.util.Map;
import cn.org.easysite.payment.plugin.spi.model.base.result.PaymentBaseResult;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author : 盘达天神
* @version : 1.0
* @date : 2020/2/14 10:19 上午
* @Description :
* @Copyright : Copyright (c) 2019
* @Company : EasySite Technology 阿富汗 Co. Ltd.
* @link : cn.org.easysite.payment.plugin.spi.model.pay.result.PaymentPayResult
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class PayResult extends PaymentBaseResult {
/**
* 支付状态1未支付3支付成功9支付失败
*/
private Integer payStatus;
/**
* 支付单号
*/
private String payOrderNo;
/**
* 支付时间
*/
private Date payTime;
/**
* 支付平台交易号(支付宝/微信返回)
*/
private String orgTransactionNo;
/**
* 支付渠道支付单号
*/
private String channelPayOrderNo;
/**
* 支付金额 单位(元)
*/
private BigDecimal payAmount;
/**
* 实际支付金额 单位(元)
*/
private BigDecimal actual;
/**
* 支付平台减免金额 单位(元)
*/
private BigDecimal coupon;
/**
* 商户实际收款金额 单位(元)
*/
private BigDecimal receipt;
/**
* 手续费 单位(元)
*/
private BigDecimal serviceFee;
/**
* 收款账户(暂时没用)
*/
private String payeeAccount;
/**
* 第三方平台卖家用户id
*/
private String payeeOpenid;
/**
* 第三方平台用户id支付宝16位微信128位
*/
private String payerOpenid;
/**
* 支付账号
*/
private String payerAccount;
/**
* 第三方优惠信息列表
*/
private List<PayThirdCouponResult> coupons;
/**
* 第三方货币单位
*/
private String thirdCurrency;
private Map<String, Object> payResultExtraParams;
}

View File

@@ -0,0 +1,40 @@
package cn.org.easysite.payment.plugin.spi.model.pay.result;
import cn.org.easysite.payment.plugin.spi.model.base.result.PaymentBaseResult;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author : yinlin
* @version : 1.0
* @date : 2019/7/23 4:41 PM
* @Description : 用户被扫/查询/notify返回支付结果
* @Copyright : Copyright (c) 2018
* @Company : IOT Technology Chengdu Co. Ltd.
* @link : cn.org.easysite.payment.plugin.spi.model.pay.result.PaymentThirdCouponResultPayment
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class PayThirdCouponResult extends PaymentBaseResult {
/**
* 优惠券编码
*/
private String code;
/**
* 优惠券名称
*/
private String name;
/**
* 优惠券类型
*/
private String type;
/**
* 金额 单位(分)
*/
private Long amount;
}

View File

@@ -0,0 +1,36 @@
package cn.org.easysite.payment.plugin.spi.model.refund.parameter;
import java.util.Map;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author : 盘达天神
* @version : 1.0
* @date : 2020/2/14 9:32 上午
* @Description :
* @Copyright : Copyright (c) 2019
* @Company : EasySite Technology 阿富汗 Co. Ltd.
* @link : cn.org.easysite.payment.plugin.spi.model.refund.parameter.RefundNotifyPayParameter
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class RefundNotifyPayParameter extends RefundPayParameter {
/**
* 通知回调HttpServletRequest.getRequestParam 所有回调参数map json
*/
private Map<String, String> notifyRequestMap;
/**
* request headers
*/
private Map<String, String> requestHeaderMap;
/**
* 通知回调请求body的字符串有的回调可能是流也需要转成String
*/
private String notifyRequestBody;
}

View File

@@ -0,0 +1,57 @@
/*
* @Copyright : Copyright (c) 2020
* @Company : EasySite Technology 阿富汗 Co. Ltd.
*/
package cn.org.easysite.payment.plugin.spi.model.refund.parameter;
import java.math.BigDecimal;
import java.util.Map;
import cn.org.easysite.payment.plugin.spi.model.pay.parameter.PayParameter;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author : 潘多拉
* @version : 1.0
* @date : 2020/12/27 10:55 上午
* @link : cn.org.easysite.payment.plugin.spi.model.refund.parameter.PaymentPayRefundParameter
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class RefundPayParameter extends PayParameter {
/**
* 系统退款单号
*/
private String refundOrderNo;
/**
* 渠道退款单号
*/
private String channelRefundOrderNo;
/**
* 机构交易号
*/
private String orgRefundTransactionNo;
/**
* 退款总额
*/
private BigDecimal refundAmount;
/**
* 退款原因
*/
private String refundReason;
/**
* 退款扩展参数
*/
private Map<String, Object> refundExtraParams;
}

View File

@@ -0,0 +1,57 @@
package cn.org.easysite.payment.plugin.spi.model.refund.result;
import java.math.BigDecimal;
import java.util.Date;
import cn.org.easysite.payment.plugin.spi.model.base.result.PaymentBaseResult;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author : yinlin
* @version : 1.0
* @date : 2019/7/23 4:57 PM
* @Description : 退款返回结果实体
* @Copyright : Copyright (c) 2018
* @Company : IOT Technology Chengdu Co. Ltd.
* @link : cn.org.easysite.payment.plugin.spi.model.refund.result.PaymentRefundResult
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class PayRefundResult extends PaymentBaseResult {
/**
* 退款单号
*/
private String refundOrderNo;
/**
* 退款金额,单位分
*/
private BigDecimal refundAmount;
/**
* 商户退款金额,单位分
*/
private BigDecimal actualRefundAmount;
private BigDecimal couponRefundAmount;
/**
* 退款状态 1- 退款中 2-退款成功 3-退款失败
*/
private Integer refundStatus;
/**
* 第三方退款单号
*/
private String channelRefundOrderNo;
/**
* 退款时间
*/
private Date refundTime;
}

View File

@@ -0,0 +1,26 @@
package cn.org.easysite.payment.plugin.spi.pay;
import cn.org.easysite.payment.plugin.spi.annotation.Plugin;
import cn.org.easysite.payment.plugin.spi.basic.NotifyPlugin;
import cn.org.easysite.payment.plugin.spi.basic.QueryPlugin;
import cn.org.easysite.payment.plugin.spi.model.pay.parameter.NotifyPayParameter;
import cn.org.easysite.payment.plugin.spi.model.pay.parameter.PayParameter;
import cn.org.easysite.payment.plugin.spi.model.pay.result.PayPreCreateResult;
import cn.org.easysite.payment.plugin.spi.model.pay.result.PayResult;
/**
* @author : yinlin
* @version : 1.0
* @date : 2019/7/23 3:38 PM
* @Description :
* @Copyright : Copyright (c) 2018
* @Company : IOT Technology Chengdu Co. Ltd.
* @link : cn.org.easysite.payment.plugin.spi.pay.PayAggregationPlugin
*/
@Plugin("支付")
public interface PayAggregationPlugin extends
PreCreatePlugin<PayParameter, PayPreCreateResult>,
PayPlugin<PayParameter, PayResult>,
QueryPlugin<PayParameter, PayResult>,
NotifyPlugin<NotifyPayParameter, PayResult> {
}

View File

@@ -0,0 +1,26 @@
package cn.org.easysite.payment.plugin.spi.pay;
import cn.org.easysite.payment.plugin.spi.basic.PaymentPlugin;
import cn.org.easysite.payment.plugin.spi.model.base.parameter.PayeePayParameter;
import cn.org.easysite.payment.plugin.spi.model.base.result.PaymentBaseResult;
/**
* @author : 盘达天神
* @version : 1.0
* @date : 2020/2/14 11:17 上午
* @Description :
* @Copyright : Copyright (c) 2019
* @Company : EasySite Technology 阿富汗 Co. Ltd.
* @link : cn.org.easysite.payment.plugin.spi.payPayPlugin
*/
public interface PayPlugin<T extends PayeePayParameter, E extends PaymentBaseResult> extends PaymentPlugin {
/**
* 支付被扫/刷卡等方法调用
*
* @param parameter
* @return
*/
E pay(T parameter);
}

View File

@@ -0,0 +1,24 @@
package cn.org.easysite.payment.plugin.spi.pay;
import cn.org.easysite.payment.plugin.spi.basic.PaymentPlugin;
import cn.org.easysite.payment.plugin.spi.model.base.parameter.PayeePayParameter;
import cn.org.easysite.payment.plugin.spi.model.base.result.PaymentBaseResult;
/**
* @author : 盘达天神
* @version : 1.0
* @date : 2020/2/14 10:35 上午
* @Description :
* @Copyright : Copyright (c) 2019
* @Company : EasySite Technology 阿富汗 Co. Ltd.
* @link : cn.org.easysite.payment.plugin.spi.pay.PreCreatePlugin
*/
public interface PreCreatePlugin<T extends PayeePayParameter, E extends PaymentBaseResult> extends PaymentPlugin {
/**
* 下单模式
* @param parameter
* @return
*/
E preCreate(T parameter);
}

View File

@@ -0,0 +1,145 @@
package cn.org.easysite.payment.plugin.spi.pay.impl;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.annotation.AnnotationUtils;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import cn.org.easysite.framework.exception.BaseException;
import cn.org.easysite.payment.plugin.exception.PaymentException;
import cn.org.easysite.payment.plugin.spi.annotation.Channel;
import cn.org.easysite.payment.plugin.spi.annotation.PayType;
import cn.org.easysite.payment.plugin.spi.model.pay.parameter.PayParameter;
import cn.org.easysite.payment.plugin.spi.model.pay.result.PayPreCreateResult;
import cn.org.easysite.payment.plugin.spi.model.pay.result.PayResult;
import cn.org.easysite.payment.plugin.spi.pay.PayAggregationPlugin;
import lombok.extern.slf4j.Slf4j;
import static cn.org.easysite.payment.plugin.exception.PaymentErrorCode.PAYMENT_PAY_CHANNEL_PLUGIN_PAY_TYPE_NOT_FOUND_ERROR;
/**
* @author : yinlin
* @version : 1.0
* @date : 2019/7/23 3:35 PM
* @Description : 支付正向流程抽象
* @Copyright : Copyright (c) 2018
* @Company : IOT Technology ChengDu Co. Ltd.
* @link : cn.org.easysite.payment.plugin.spi.pay.impl.AbstractPaymentPayPlugin
*/
@Slf4j
public abstract class AbstractPayAggregationPlugin implements PayAggregationPlugin {
private String PAY_NOTIFY_URL_PATTERN = "pay/notify/{channelCode}/{payOrderNo}";
/**
* 项目启动支付渠道插件bean启动时注册到factory中
*/
@PostConstruct
@Override
public void init() {
String code = this.getChannelCode();
//注册实现
if (code != null && usable()) {
// PaymentPayPluginFactory.register(code, this);
log.info(getChannelCode());
}
}
@Override
public PayPreCreateResult preCreate(PayParameter parameter) {
if (!this.usable()) {
//抛出异常
}
Method method = this.findMethodByPayType(parameter.getPayType());
try {
return (PayPreCreateResult) method.invoke(this, parameter);
} catch (IllegalAccessException e) {
log.error("AbstractPayAggregationPlugin#preCreate(PayParameter) 该支付方式当前插件不支持=[{}]", parameter.getPayType());
throw new PaymentException(PAYMENT_PAY_CHANNEL_PLUGIN_PAY_TYPE_NOT_FOUND_ERROR);
} catch (InvocationTargetException e) {
if (e.getTargetException() instanceof BaseException) {
throw ((BaseException) e.getTargetException());
}
log.error("AbstractPayAggregationPlugin#preCreate(PayParameter) 执行出错,未捕获的异常[{}]", e.getTargetException().getMessage());
throw new PaymentException(PAYMENT_PAY_CHANNEL_PLUGIN_PAY_TYPE_NOT_FOUND_ERROR);
}
}
@Override
public PayResult pay(PayParameter parameter) {
if (!this.usable()) {
//抛出异常
}
Method method = this.findMethodByPayType(parameter.getPayType());
try {
return (PayResult) method.invoke(this, parameter);
} catch (IllegalAccessException e) {
log.error("AbstractPayAggregationPlugin#pay(PayParameter) 该支付方式当前插件不支持=[{}]", parameter.getPayType());
throw new PaymentException(PAYMENT_PAY_CHANNEL_PLUGIN_PAY_TYPE_NOT_FOUND_ERROR);
} catch (InvocationTargetException e) {
if (e.getTargetException() instanceof BaseException) {
throw ((BaseException) e.getTargetException());
}
log.error("AbstractPayAggregationPlugin#pay(PayParameter) 执行出错,未捕获的异常{}", e.getTargetException().getMessage());
throw new PaymentException(PAYMENT_PAY_CHANNEL_PLUGIN_PAY_TYPE_NOT_FOUND_ERROR);
}
}
/**
* 反射查找有注解 @PayType("PAY_MODE_SHOWCODE") 的方法 由于annotationUtils已经给我们做了缓存该方法不做缓存处理
* @param payType
* @return
*/
private Method findMethodByPayType(String payType) {
Method[] methods = this.getClass().getDeclaredMethods();
for (Method method : methods) {
PayType type = AnnotationUtils.findAnnotation(method, PayType.class);
if (type != null && type.value().equalsIgnoreCase(payType)) {
return method;
}
}
log.error("AbstractPayAggregationPlugin#findMethodByPayType(String) 该支付方式[{}]当前插件不支持", payType);
throw new PaymentException(PAYMENT_PAY_CHANNEL_PLUGIN_PAY_TYPE_NOT_FOUND_ERROR);
}
/**
* 获取支付渠道编码
* @return 返回注册在支付渠道实现类上的channelCode也可以自定义
*/
protected String getChannelCode() {
Channel channel = this.getClass().getDeclaredAnnotation(Channel.class);
if (channel == null || StringUtils.isEmpty(channel.value())) {
log.warn("支付渠道实现配置错误,必须标注@Channel 并设置value, 未配置无法分发");
}
return channel == null ? null : channel.value();
}
/**
* 表示该插件是否可用
* @return
*/
@Override
public boolean usable() {
return true;
}
@PreDestroy
@Override
public void destroy() {
log.warn("当前支付渠道[{}]支付插件被卸载", getChannelCode());
}
/**
* 获取异步通知地址
* @param payParameter 支付参数
* @return 返回组装好的支付回调地址
*/
protected String getNotifyUrl(PayParameter payParameter) {
return PAY_NOTIFY_URL_PATTERN.replace("{channelCode}", payParameter.getPayChannel()).replace("{payOrderNo}", payParameter.getPayOrderNo());
}
}

View File

@@ -0,0 +1,24 @@
package cn.org.easysite.payment.plugin.spi.refund;
import cn.org.easysite.payment.plugin.spi.annotation.Plugin;
import cn.org.easysite.payment.plugin.spi.basic.NotifyPlugin;
import cn.org.easysite.payment.plugin.spi.basic.QueryPlugin;
import cn.org.easysite.payment.plugin.spi.model.refund.parameter.RefundNotifyPayParameter;
import cn.org.easysite.payment.plugin.spi.model.refund.parameter.RefundPayParameter;
import cn.org.easysite.payment.plugin.spi.model.refund.result.PayRefundResult;
/**
* @author : yinlin
* @version : 1.0
* @date : 2019/7/23 3:39 PM
* @Description : 支付退款插件集成接口
* @Copyright : Copyright (c) 2018
* @Company : IOT Technology Chengdu Co. Ltd.
* @link : cn.org.easysite.payment.plugin.spi.refund.PayRefundAggregationPlugin
*/
@Plugin("退款")
public interface PayRefundAggregationPlugin extends
RefundPlugin<RefundPayParameter, PayRefundResult>,
QueryPlugin<RefundPayParameter, PayRefundResult>,
NotifyPlugin<RefundNotifyPayParameter, PayRefundResult> {
}

View File

@@ -0,0 +1,19 @@
package cn.org.easysite.payment.plugin.spi.refund;
import cn.org.easysite.payment.plugin.spi.basic.PaymentPlugin;
import cn.org.easysite.payment.plugin.spi.model.base.parameter.PayeePayParameter;
import cn.org.easysite.payment.plugin.spi.model.base.result.PaymentBaseResult;
/**
* 退款基础插件
* @author yinlin
*/
public interface RefundPlugin<T extends PayeePayParameter, E extends PaymentBaseResult> extends PaymentPlugin {
/**
* 支付退款方法
* @param parameter 支付退款参数
* @return 支付退款返回值
*/
E refund(T parameter);
}

View File

@@ -0,0 +1,65 @@
/*
* @Copyright : Copyright (c) 2020
* @Company : EasySite Technology 阿富汗 Co. Ltd.
*/
package cn.org.easysite.payment.plugin.spi.refund.impl;
import org.apache.commons.lang3.StringUtils;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import cn.org.easysite.payment.plugin.spi.annotation.Channel;
import cn.org.easysite.payment.plugin.spi.refund.PayRefundAggregationPlugin;
import lombok.extern.slf4j.Slf4j;
/**
* @author : 潘多拉
* @version : 1.0
* @date : 2020/12/27 4:17 下午
* @link : cn.org.easysite.payment.plugin.spi.refund.impl
* .AbstractPayRefundAggregationPlugin
*/
@Slf4j
public abstract class AbstractPayRefundAggregationPlugin implements PayRefundAggregationPlugin {
/**
* 项目启动支付渠道插件bean启动时注册到factory中
*/
@PostConstruct
@Override
public void init() {
}
@Override
public boolean usable() {
return true;
}
@PreDestroy
@Override
public void destroy() {
log.info("当前支付渠道[{}]退款插件被卸载", getChannelCode());
}
/**
* 获取支付渠道编码
* @return 返回注册在支付渠道实现类上的channelCode也可以自定义
*/
protected String getChannelCode() {
Channel channel = this.getClass().getDeclaredAnnotation(Channel.class);
if (channel == null || StringUtils.isEmpty(channel.value())) {
log.warn("支付渠道实现配置错误,必须标注@Channel 并设置value, 未配置无法分发");
}
return channel == null ? null : channel.value();
}
/**
* 组装退款url
* @param outTradeNo
* @return
*/
protected String getNotifyUrl(String outTradeNo) {
return "";
}
}

View File

@@ -0,0 +1,7 @@
/**
* @author : 潘多拉
* @date : 2020/12/27 3:16 下午
* @version : 1.0
* @link : cn.org.easysite.payment.plugin.support.package-info
*/
package cn.org.easysite.payment.plugin.support;

View File

@@ -0,0 +1,33 @@
/*
* @Copyright : Copyright (c) 2020
* @Company : EasySite Technology 阿富汗 Co. Ltd.
*/
package cn.org.easysite.payment.plugin.support.spring.annotation;
import org.springframework.context.annotation.Import;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import cn.org.easysite.payment.plugin.support.spring.register.ChannelPluginsRegister;
import cn.org.easysite.payment.plugin.support.spring.register.ChannelPluginsScannerRegister;
/**
* @author : 潘多拉
* @version : 1.0
* @date : 2020/12/27 3:21 下午
* @link : cn.org.easysite.payment.plugin.support.spring.annotation.EnableChannelPlugins
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import({ChannelPluginsScannerRegister.class, ChannelPluginsRegister.class})
public @interface EnablePaymentPluginRegistries {
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
}

View File

@@ -0,0 +1,7 @@
/**
* @author : 潘多拉
* @date : 2020/12/27 3:20 下午
* @version : 1.0
* @link : cn.org.easysite.payment.plugin.support.spring.package-info
*/
package cn.org.easysite.payment.plugin.support.spring;

View File

@@ -0,0 +1,34 @@
/*
* @Copyright : Copyright (c) 2020
* @Company : EasySite Technology 阿富汗 Co. Ltd.
*/
package cn.org.easysite.payment.plugin.support.spring.register;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import java.util.Map;
import cn.org.easysite.payment.plugin.factory.PaymentFactory;
import cn.org.easysite.payment.plugin.spi.basic.PaymentPlugin;
import lombok.extern.slf4j.Slf4j;
/**
* @author : 潘多拉
* @version : 1.0
* @date : 2020/12/27 3:59 下午
* @link : cn.org.easysite.payment.plugin.support.spring.register.ChannelPluginsRegister
*/
@Slf4j
public class ChannelPluginsRegister implements ApplicationContextAware {
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
Map<String, PaymentPlugin> map = applicationContext.getBeansOfType(PaymentPlugin.class);
if (map.isEmpty()) {
log.error("PaymentPlugin Spring Bean not found");
return;
}
map.forEach((k, v) -> PaymentFactory.register(v));
}
}

View File

@@ -0,0 +1,148 @@
/*
* @Copyright : Copyright (c) 2020
* @Company : EasySite Technology 阿富汗 Co. Ltd.
*/
package cn.org.easysite.payment.plugin.support.spring.register;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
import java.lang.annotation.Annotation;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import cn.org.easysite.payment.plugin.spi.annotation.Channel;
import cn.org.easysite.payment.plugin.support.spring.annotation.EnablePaymentPluginRegistries;
import lombok.extern.slf4j.Slf4j;
/**
* @author : 潘多拉
* @version : 1.0
* @date : 2020/12/27 3:22 下午
* @link : cn.org.easysite.payment.plugin.support.spring.register.ChannelPluginsScannerRegister
*/
@Slf4j
public class ChannelPluginsScannerRegister implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, BeanClassLoaderAware, EnvironmentAware {
private ResourceLoader resourceLoader;
private ClassLoader classLoader;
private Environment environment;
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
}
@Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
Set<String> basePackages = getBasePackages(importingClassMetadata);
ClassPathScanningCandidateComponentProvider scanner = getScanner();
scanner.setResourceLoader(this.resourceLoader);
scanner.addIncludeFilter(new AnnotationTypeFilter(Channel.class));
for (String basePackage : basePackages) {
Set<BeanDefinition> candidateComponents = scanner.findCandidateComponents(basePackage);
for (BeanDefinition candidateComponent : candidateComponents) {
if (candidateComponent instanceof AnnotatedBeanDefinition) {
AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
this.registerPluginBean(registry, annotationMetadata);
}
}
}
}
/**
* 注册bean
* @param registry
* @param annotationMetadata
*/
private void registerPluginBean(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata) {
try {
String className = annotationMetadata.getClassName();
Class<?> beanClazz = Class.forName(className);
if (!beanClazz.isAnnotationPresent(Channel.class)) {
throw new RuntimeException("@Channel is required!");
}
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(beanClazz);
BeanDefinition definition = builder.getRawBeanDefinition();
registry.registerBeanDefinition(beanClazz.getSimpleName(), definition);
} catch (ClassNotFoundException e) {
log.error("Could not register target class: " + annotationMetadata.getClassName(), e);
}
}
protected Set<String> getBasePackages(AnnotationMetadata importingClassMetadata) {
Map<String, Object> attributes = importingClassMetadata.getAnnotationAttributes(EnablePaymentPluginRegistries.class.getCanonicalName());
Set<String> basePackages = new HashSet<>();
for (String pkg : (String[]) attributes.get("basePackages")) {
if (StringUtils.hasText(pkg)) {
basePackages.add(pkg);
}
}
for (Class<?> clazz : (Class[]) attributes.get("basePackageClasses")) {
basePackages.add(ClassUtils.getPackageName(clazz));
}
if (basePackages.isEmpty()) {
basePackages.add(ClassUtils.getPackageName(importingClassMetadata.getClassName()));
}
return basePackages;
}
protected ClassPathScanningCandidateComponentProvider getScanner() {
return new ClassPathScanningCandidateComponentProvider(false, this.environment) {
@Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
if (beanDefinition.getMetadata().isIndependent()) {
if (
beanDefinition.getMetadata().isInterface()
&& beanDefinition.getMetadata().getInterfaceNames().length == 1
&& Annotation.class.getName().equals(beanDefinition.getMetadata().getInterfaceNames()[0])) {
try {
Class<?> target = ClassUtils.forName(
beanDefinition.getMetadata().getClassName(),
ChannelPluginsScannerRegister.this.classLoader);
return !target.isAnnotation();
} catch (Exception ex) {
this.logger.error("Could not load target class: " + beanDefinition.getMetadata().getClassName(), ex);
}
}
return true;
}
return false;
}
};
}
}

View File

@@ -25,5 +25,12 @@
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<optional>true</optional>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@@ -1,11 +0,0 @@
package cn.org.easysite;
/**
* Unit test for simple App.
*/
public class AppTest
{
/**
* Rigorous Test :-)
*/
}

View File

@@ -0,0 +1,19 @@
/*
* @Copyright : Copyright (c) 2020
* @Company : EasySite Technology 阿富汗 Co. Ltd.
*/
package cn.org.easysite.spring.boot.distributed.lock.test;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
/**
* @author : 潘多拉
* @version : 1.0
* @date : 2020/10/20 10:22 上午
* @link : cn.org.easysite.spring.boot.distributed.lock.test.DistributedApplicationBootstrap
*/
@SpringBootApplication
@EnableAspectJAutoProxy
public class DistributedApplicationBootstrap {
}

View File

@@ -0,0 +1,23 @@
/*
* @Copyright : Copyright (c) 2020
* @Company : EasySite Technology 阿富汗 Co. Ltd.
*/
package cn.org.easysite.spring.boot.distributed.lock.test;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
/**
* @author : 潘多拉
* @version : 1.0
* @date : 2020/10/20 10:22 上午
* @link : cn.org.easysite.spring.boot.distributed.lock.test.DistributedApplicationBootstrap
*/
@SpringBootApplication
@EnableAspectJAutoProxy
public class DistributedApplicationBootstrap {
public static void main(String[] args) {
SpringApplication.run(DistributedApplicationBootstrap.class, args);
}
}

View File

@@ -22,6 +22,7 @@
<module>easy-spring-boot-wr-separation</module>
<module>easy-spring-cloud-feign</module>
<module>easy-maven-plugins</module>
<module>easy-payment</module>
</modules>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>