Merge branch 'feature/mall_product' of https://gitee.com/zhijiantianya/ruoyi-vue-pro

This commit is contained in:
YunaiV
2023-08-31 20:38:59 +08:00
114 changed files with 1497 additions and 580 deletions

View File

@ -1,5 +1,7 @@
package cn.iocoder.yudao.framework.pay.core.client;
import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum;
/**
* 支付客户端的工厂接口
*
@ -25,4 +27,12 @@ public interface PayClientFactory {
<Config extends PayClientConfig> void createOrUpdatePayClient(Long channelId, String channelCode,
Config config);
/**
* 注册支付客户端 Class用于模块中实现的 PayClient
*
* @param channel 支付渠道的编码的枚举
* @param payClientClass 支付客户端 class
*/
void registerPayClientClass(PayChannelEnum channel, Class<?> payClientClass);
}

View File

@ -0,0 +1,31 @@
package cn.iocoder.yudao.framework.pay.core.client.impl;
import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig;
import lombok.Data;
import javax.validation.Validator;
/**
* 无需任何配置 PayClientConfig 实现类
*
* @author jason
*/
@Data
public class NonePayClientConfig implements PayClientConfig {
/**
* 配置名称
* <p>
* 如果不加任何属性JsonUtils.parseObject2 解析会报错,所以暂时加个名称
*/
private String name;
public NonePayClientConfig(){
this.name = "none-config";
}
@Override
public void validate(Validator validator) {
// 无任何配置不需要校验
}
}

View File

@ -1,19 +1,22 @@
package cn.iocoder.yudao.framework.pay.core.client.impl;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ReflectUtil;
import cn.iocoder.yudao.framework.pay.core.client.PayClient;
import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig;
import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory;
import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.*;
import cn.iocoder.yudao.framework.pay.core.client.impl.mock.MockPayClient;
import cn.iocoder.yudao.framework.pay.core.client.impl.mock.MockPayClientConfig;
import cn.iocoder.yudao.framework.pay.core.client.impl.weixin.*;
import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum;
import lombok.extern.slf4j.Slf4j;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import static cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum.*;
/**
* 支付客户端的工厂实现类
*
@ -24,10 +27,38 @@ public class PayClientFactoryImpl implements PayClientFactory {
/**
* 支付客户端 Map
*
* key渠道编号
*/
private final ConcurrentMap<Long, AbstractPayClient<?>> clients = new ConcurrentHashMap<>();
/**
* 支付客户端 Class Map
*/
private final Map<PayChannelEnum, Class<?>> clientClass = new ConcurrentHashMap<>();
public PayClientFactoryImpl() {
// 微信支付客户端
clientClass.put(WX_PUB, WxPubPayClient.class);
clientClass.put(WX_LITE, WxLitePayClient.class);
clientClass.put(WX_APP, WxAppPayClient.class);
clientClass.put(WX_BAR, WxBarPayClient.class);
clientClass.put(WX_NATIVE, WxNativePayClient.class);
// 支付包支付客户端
clientClass.put(ALIPAY_WAP, AlipayWapPayClient.class);
clientClass.put(ALIPAY_QR, AlipayQrPayClient.class);
clientClass.put(ALIPAY_APP, AlipayAppPayClient.class);
clientClass.put(ALIPAY_PC, AlipayPcPayClient.class);
clientClass.put(ALIPAY_BAR, AlipayBarPayClient.class);
// Mock 支付客户端
clientClass.put(MOCK, MockPayClient.class);
}
@Override
public void registerPayClientClass(PayChannelEnum channel, Class<?> payClientClass) {
clientClass.put(channel, payClientClass);
}
@Override
public PayClient getPayClient(Long channelId) {
AbstractPayClient<?> client = clients.get(channelId);
@ -52,30 +83,13 @@ public class PayClientFactoryImpl implements PayClientFactory {
}
@SuppressWarnings("unchecked")
private <Config extends PayClientConfig> AbstractPayClient<Config> createPayClient(
Long channelId, String channelCode, Config config) {
private <Config extends PayClientConfig> AbstractPayClient<Config> createPayClient(Long channelId, String channelCode,
Config config) {
PayChannelEnum channelEnum = PayChannelEnum.getByCode(channelCode);
Assert.notNull(channelEnum, String.format("支付渠道(%s) 为空", channelEnum));
// 创建客户端
switch (channelEnum) {
// 微信支付
case WX_PUB: return (AbstractPayClient<Config>) new WxPubPayClient(channelId, (WxPayClientConfig) config);
case WX_LITE: return (AbstractPayClient<Config>) new WxLitePayClient(channelId, (WxPayClientConfig) config);
case WX_APP: return (AbstractPayClient<Config>) new WxAppPayClient(channelId, (WxPayClientConfig) config);
case WX_BAR: return (AbstractPayClient<Config>) new WxBarPayClient(channelId, (WxPayClientConfig) config);
case WX_NATIVE: return (AbstractPayClient<Config>) new WxNativePayClient(channelId, (WxPayClientConfig) config);
// 支付宝支付
case ALIPAY_WAP: return (AbstractPayClient<Config>) new AlipayWapPayClient(channelId, (AlipayPayClientConfig) config);
case ALIPAY_QR: return (AbstractPayClient<Config>) new AlipayQrPayClient(channelId, (AlipayPayClientConfig) config);
case ALIPAY_APP: return (AbstractPayClient<Config>) new AlipayAppPayClient(channelId, (AlipayPayClientConfig) config);
case ALIPAY_PC: return (AbstractPayClient<Config>) new AlipayPcPayClient(channelId, (AlipayPayClientConfig) config);
case ALIPAY_BAR: return (AbstractPayClient<Config>) new AlipayBarPayClient(channelId, (AlipayPayClientConfig) config);
// 其它支付
case MOCK: return (AbstractPayClient<Config>) new MockPayClient(channelId, (MockPayClientConfig) config);
}
// 创建失败,错误日志 + 抛出异常
log.error("[createPayClient][配置({}) 找不到合适的客户端实现]", config);
throw new IllegalArgumentException(String.format("配置(%s) 找不到合适的客户端实现", config));
Assert.notNull(channelEnum, String.format("支付渠道(%s) 为空", channelCode));
Class<?> payClientClass = clientClass.get(channelEnum);
Assert.notNull(payClientClass, String.format("支付渠道(%s) Class 为空", channelCode));
return (AbstractPayClient<Config>) ReflectUtil.newInstance(payClientClass, channelId, config);
}
}

View File

@ -0,0 +1,63 @@
package cn.iocoder.yudao.framework.pay.core.client.impl.delegate;
import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig;
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundRespDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO;
import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient;
import java.util.Map;
// TODO @jason其它模块主要是无法 pay client 初始化存在问题,所以我感觉,是不是可以搞个 PayClientInitializer 接口。这样PayClientFactory 去 get 这个支付模式对应的 PayClientInitializer通过它来创建。具体注入的地方可以在 PayChannel init 方法那;
/**
* 代理支付 Client 的抽象类。
*
* 用于支付 Client 由其它模块实现,例如钱包支付
*
* @author jason
*/
public abstract class DelegatePayClient<Config extends PayClientConfig> extends AbstractPayClient<PayClientConfig> {
private final DelegatePayClient<Config> delegate;
public DelegatePayClient(Long channelId, String channelCode, PayClientConfig config) {
super(channelId, channelCode, config);
delegate = this;
}
@Override
protected void doInit() {
delegate.doInit();
}
@Override
protected PayOrderRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
return delegate.doUnifiedOrder(reqDTO);
}
@Override
protected PayOrderRespDTO doGetOrder(String outTradeNo) {
return delegate.doGetOrder(outTradeNo);
}
@Override
protected PayRefundRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) {
return delegate.doUnifiedRefund(reqDTO);
}
@Override
protected PayRefundRespDTO doGetRefund(String outTradeNo, String outRefundNo) {
return delegate.doGetRefund(outTradeNo, outRefundNo);
}
@Override
protected PayRefundRespDTO doParseRefundNotify(Map<String,String> params, String body) {
return delegate.doParseRefundNotify(params, body);
}
@Override
protected PayOrderRespDTO doParseOrderNotify(Map<String,String> params, String body) {
return delegate.doParseOrderNotify(params, body);
}
}

View File

@ -1,6 +1,7 @@
package cn.iocoder.yudao.framework.pay.core.enums.channel;
import cn.hutool.core.util.ArrayUtil;
import cn.iocoder.yudao.framework.pay.core.client.impl.NonePayClientConfig;
import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig;
import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayPayClientConfig;
import cn.iocoder.yudao.framework.pay.core.client.impl.mock.MockPayClientConfig;
@ -29,7 +30,9 @@ public enum PayChannelEnum {
ALIPAY_QR("alipay_qr", "支付宝扫码支付", AlipayPayClientConfig.class),
ALIPAY_BAR("alipay_bar", "支付宝条码支付", AlipayPayClientConfig.class),
MOCK("mock", "模拟支付", MockPayClientConfig.class);
MOCK("mock", "模拟支付", MockPayClientConfig.class),
WALLET("wallet", "钱包支付", NonePayClientConfig.class);
/**
* 编码

View File

@ -168,7 +168,7 @@ public abstract class AbstractAlipayClientTest extends BaseMockitoUnitTest {
@Test
@DisplayName("支付宝 Client 统一退款:抛出业务异常")
public void testUnifiedRefund_throwServiceException() throws AlipayApiException {
// mock
// mock 方法
when(defaultAlipayClient.execute(argThat((ArgumentMatcher<AlipayTradeRefundRequest>) request -> true)))
.thenThrow(ServiceExceptionUtil.exception(GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR));
// 准备请求参数
@ -182,7 +182,7 @@ public abstract class AbstractAlipayClientTest extends BaseMockitoUnitTest {
@Test
@DisplayName("支付宝 Client 统一退款:抛出系统异常")
public void testUnifiedRefund_throwPayException() throws AlipayApiException {
// mock
// mock 方法
when(defaultAlipayClient.execute(argThat((ArgumentMatcher<AlipayTradeRefundRequest>) request -> true)))
.thenThrow(new RuntimeException("系统异常"));
// 准备请求参数

View File

@ -70,7 +70,7 @@ public class AlipayPcPayClientTest extends AbstractAlipayClientTest {
@Test
@DisplayName("支付宝 PC 网站支付Form Display Mode 下单成功")
public void testUnifiedOrder_formSuccess() throws AlipayApiException {
// mock
// mock 方法
String notifyUrl = randomURL();
AlipayTradePagePayResponse response = randomPojo(AlipayTradePagePayResponse.class, o -> o.setSubCode(""));
when(defaultAlipayClient.pageExecute(argThat((ArgumentMatcher<AlipayTradePagePayRequest>) request -> true),
@ -99,7 +99,7 @@ public class AlipayPcPayClientTest extends AbstractAlipayClientTest {
@Test
@DisplayName("支付宝 PC 网站支付:渠道返回失败")
public void testUnifiedOrder_channelFailed() throws AlipayApiException {
// mock
// mock 方法
String subCode = randomString();
String subMsg = randomString();
AlipayTradePagePayResponse response = randomPojo(AlipayTradePagePayResponse.class, o -> {

View File

@ -39,9 +39,9 @@ public class AlipayQrPayClientTest extends AbstractAlipayClientTest {
}
@Test
@DisplayName("支付宝扫描支付下单成功")
public void test_unified_order_success() throws AlipayApiException {
// 准备返回对象
@DisplayName("支付宝扫描支付下单成功")
public void testUnifiedOrder_success() throws AlipayApiException {
// mock 方法
String notifyUrl = randomURL();
String qrCode = randomString();
Integer price = randomInteger();
@ -49,7 +49,6 @@ public class AlipayQrPayClientTest extends AbstractAlipayClientTest {
o.setQrCode(qrCode);
o.setSubCode("");
});
// mock
when(defaultAlipayClient.execute(argThat((ArgumentMatcher<AlipayTradePrecreateRequest>) request -> {
assertEquals(notifyUrl, request.getNotifyUrl());
return true;
@ -58,18 +57,25 @@ public class AlipayQrPayClientTest extends AbstractAlipayClientTest {
String outTradeNo = randomString();
PayOrderUnifiedReqDTO reqDTO = buildOrderUnifiedReqDTO(notifyUrl, outTradeNo, price);
// 调用
PayOrderRespDTO resp = client.unifiedOrder(reqDTO);
// 断言
assertEquals(WAITING.getStatus(), resp.getStatus());
assertEquals(PayOrderDisplayModeEnum.QR_CODE.getMode(), resp.getDisplayMode());
assertEquals(outTradeNo, resp.getOutTradeNo());
assertEquals(qrCode, resp.getDisplayContent());
assertNull(resp.getChannelOrderNo());
assertNull(resp.getChannelUserId());
assertNull(resp.getSuccessTime());
assertEquals(PayOrderDisplayModeEnum.QR_CODE.getMode(), resp.getDisplayMode());
assertEquals(response.getQrCode(), resp.getDisplayContent());
assertSame(response, resp.getRawData());
assertNull(resp.getChannelErrorCode());
assertNull(resp.getChannelErrorMsg());
}
@Test
@DisplayName("支付宝扫描支付,渠道返回失败")
public void test_unified_order_channel_failed() throws AlipayApiException {
@DisplayName("支付宝扫描支付渠道返回失败")
public void testUnifiedOrder_channelFailed() throws AlipayApiException {
// mock 方法
String notifyUrl = randomURL();
String subCode = randomString();
String subMsg = randomString();
@ -87,47 +93,55 @@ public class AlipayQrPayClientTest extends AbstractAlipayClientTest {
String outTradeNo = randomString();
PayOrderUnifiedReqDTO reqDTO = buildOrderUnifiedReqDTO(notifyUrl, outTradeNo, price);
// 调用
PayOrderRespDTO resp = client.unifiedOrder(reqDTO);
// 断言
assertEquals(CLOSED.getStatus(), resp.getStatus());
assertEquals(outTradeNo, resp.getOutTradeNo());
assertNull(resp.getChannelOrderNo());
assertNull(resp.getChannelUserId());
assertNull(resp.getSuccessTime());
assertNull(resp.getDisplayMode());
assertNull(resp.getDisplayContent());
assertSame(response, resp.getRawData());
assertEquals(subCode, resp.getChannelErrorCode());
assertEquals(subMsg, resp.getChannelErrorMsg());
assertSame(response, resp.getRawData());
}
@Test
@DisplayName("支付宝扫描支付, 抛出系统异常")
public void test_unified_order_throw_pay_exception() throws AlipayApiException {
// 准备请求参数
public void testUnifiedOrder_throwPayException() throws AlipayApiException {
// mock 方法
String outTradeNo = randomString();
String notifyUrl = randomURL();
Integer price = randomInteger();
// mock
when(defaultAlipayClient.execute(argThat((ArgumentMatcher<AlipayTradePrecreateRequest>) request -> {
assertEquals(notifyUrl, request.getNotifyUrl());
return true;
}))).thenThrow(new RuntimeException("系统异常"));
// 准备请求参数
PayOrderUnifiedReqDTO reqDTO = buildOrderUnifiedReqDTO(notifyUrl, outTradeNo,price);
// 断言
// 调用,并断言
assertThrows(PayException.class, () -> client.unifiedOrder(reqDTO));
}
@Test
@DisplayName("支付宝 Client 统一下单,抛出业务异常")
public void test_unified_order_throw_service_exception() throws AlipayApiException {
// 准备请求参数
@DisplayName("支付宝 Client 统一下单抛出业务异常")
public void testUnifiedOrder_throwServiceException() throws AlipayApiException {
// mock 方法
String outTradeNo = randomString();
String notifyUrl = randomURL();
Integer price = randomInteger();
// mock
when(defaultAlipayClient.execute(argThat((ArgumentMatcher<AlipayTradePrecreateRequest>) request -> {
assertEquals(notifyUrl, request.getNotifyUrl());
return true;
}))).thenThrow(ServiceExceptionUtil.exception(GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR));
// 准备请求参数
PayOrderUnifiedReqDTO reqDTO = buildOrderUnifiedReqDTO(notifyUrl, outTradeNo, price);
// 断言
// 调用,并断言
assertThrows(ServiceException.class, () -> client.unifiedOrder(reqDTO));
}
}

View File

@ -42,9 +42,9 @@ public class AlipayWapPayClientTest extends AbstractAlipayClientTest {
}
@Test
@DisplayName("支付宝 H5 支付下单成功")
public void test_unified_order_success() throws AlipayApiException {
// 准备响应对象
@DisplayName("支付宝 H5 支付下单成功")
public void testUnifiedOrder_success() throws AlipayApiException {
// mock 方法
String h5Body = randomString();
Integer price = randomInteger();
AlipayTradeWapPayResponse response = randomPojo(AlipayTradeWapPayResponse.class, o -> {
@ -52,7 +52,6 @@ public class AlipayWapPayClientTest extends AbstractAlipayClientTest {
o.setBody(h5Body);
});
String notifyUrl = randomURL();
// mock
when(defaultAlipayClient.pageExecute(argThat((ArgumentMatcher<AlipayTradeWapPayRequest>) request -> {
assertInstanceOf(AlipayTradeWapPayModel.class, request.getBizModel());
AlipayTradeWapPayModel bizModel = (AlipayTradeWapPayModel) request.getBizModel();
@ -60,37 +59,53 @@ public class AlipayWapPayClientTest extends AbstractAlipayClientTest {
assertEquals(notifyUrl, request.getNotifyUrl());
return true;
}), eq(Method.GET.name()))).thenReturn(response);
// 准备请求参数
String outTradeNo = randomString();
PayOrderUnifiedReqDTO reqDTO = buildOrderUnifiedReqDTO(notifyUrl, outTradeNo, price);
// 调用
PayOrderRespDTO resp = client.unifiedOrder(reqDTO);
// 断言
assertEquals(WAITING.getStatus(), resp.getStatus());
assertEquals(PayOrderDisplayModeEnum.URL.getMode(), resp.getDisplayMode());
assertEquals(outTradeNo, resp.getOutTradeNo());
assertEquals(h5Body, resp.getDisplayContent());
assertNull(resp.getChannelOrderNo());
assertNull(resp.getChannelUserId());
assertNull(resp.getSuccessTime());
assertEquals(PayOrderDisplayModeEnum.URL.getMode(), resp.getDisplayMode());
assertEquals(response.getBody(), resp.getDisplayContent());
assertSame(response, resp.getRawData());
assertNull(resp.getChannelErrorCode());
assertNull(resp.getChannelErrorMsg());
}
@Test
@DisplayName("支付宝 H5 支付,渠道返回失败")
@DisplayName("支付宝 H5 支付渠道返回失败")
public void test_unified_order_channel_failed() throws AlipayApiException {
// 准备响应对象
// mock 方法
String subCode = randomString();
String subMsg = randomString();
AlipayTradeWapPayResponse response = randomPojo(AlipayTradeWapPayResponse.class, o -> {
o.setSubCode(subCode);
o.setSubMsg(subMsg);
});
// mock
when(defaultAlipayClient.pageExecute(argThat((ArgumentMatcher<AlipayTradeWapPayRequest>) request -> true),
eq(Method.GET.name()))).thenReturn(response);
PayOrderUnifiedReqDTO reqDTO = buildOrderUnifiedReqDTO(randomURL(), randomString(), randomInteger());
String outTradeNo = randomString();
PayOrderUnifiedReqDTO reqDTO = buildOrderUnifiedReqDTO(randomURL(), outTradeNo, randomInteger());
// 调用
PayOrderRespDTO resp = client.unifiedOrder(reqDTO);
// 断言
assertEquals(CLOSED.getStatus(), resp.getStatus());
assertEquals(outTradeNo, resp.getOutTradeNo());
assertNull(resp.getChannelOrderNo());
assertNull(resp.getChannelUserId());
assertNull(resp.getSuccessTime());
assertNull(resp.getDisplayMode());
assertNull(resp.getDisplayContent());
assertSame(response, resp.getRawData());
assertEquals(subCode, resp.getChannelErrorCode());
assertEquals(subMsg, resp.getChannelErrorMsg());
assertSame(response, resp.getRawData());
}
}