diff --git a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AbstractAlipayPayClient.java b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AbstractAlipayPayClient.java index 5e52e8e8e..b1edf87ac 100644 --- a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AbstractAlipayPayClient.java +++ b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AbstractAlipayPayClient.java @@ -27,6 +27,7 @@ import com.alipay.api.request.AlipayTradeRefundRequest; import com.alipay.api.response.AlipayTradeFastpayRefundQueryResponse; import com.alipay.api.response.AlipayTradeQueryResponse; import com.alipay.api.response.AlipayTradeRefundResponse; +import lombok.Getter; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; @@ -47,6 +48,7 @@ import static cn.hutool.core.date.DatePattern.NORM_DATETIME_FORMATTER; @Slf4j public abstract class AbstractAlipayPayClient extends AbstractPayClient { + @Getter // 仅用于单测场景 protected DefaultAlipayClient client; public AbstractAlipayPayClient(Long channelId, String channelCode, AlipayPayClientConfig config) { diff --git a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayBarPayClient.java b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayBarPayClient.java index 5254bc8c9..94644430f 100644 --- a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayBarPayClient.java +++ b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayBarPayClient.java @@ -13,6 +13,8 @@ import com.alipay.api.request.AlipayTradePayRequest; import com.alipay.api.response.AlipayTradePayResponse; import lombok.extern.slf4j.Slf4j; +import java.time.LocalDateTime; + import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.BAD_REQUEST; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception0; @@ -63,8 +65,10 @@ public class AlipayBarPayClient extends AbstractAlipayPayClient { return buildClosedPayOrderRespDTO(reqDTO, response); } if ("10000".equals(response.getCode())) { // 免密支付 - return PayOrderRespDTO.successOf(response.getTradeNo(), response.getBuyerUserId(), LocalDateTimeUtil.of(response.getGmtPayment()), - response.getOutTradeNo(), response); + LocalDateTime successTime = LocalDateTimeUtil.of(response.getGmtPayment()); + return PayOrderRespDTO.successOf(response.getTradeNo(), response.getBuyerUserId(), successTime, + response.getOutTradeNo(), response) + .setDisplayMode(displayMode).setDisplayContent(""); } // 大额支付,需要用户输入密码,所以返回 waiting。此时,前端一般会进行轮询 return PayOrderRespDTO.waitingOf(displayMode, "", diff --git a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/test/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AbstractAlipayClientTest.java b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/test/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AbstractAlipayClientTest.java index 664a3ce52..57d629021 100644 --- a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/test/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AbstractAlipayClientTest.java +++ b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/test/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AbstractAlipayClientTest.java @@ -2,7 +2,6 @@ package cn.iocoder.yudao.framework.pay.core.client.impl.alipay; import cn.hutool.core.date.LocalDateTimeUtil; import cn.hutool.core.util.RandomUtil; -import cn.hutool.core.util.ReflectUtil; import cn.iocoder.yudao.framework.common.exception.ServiceException; import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants; import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil; @@ -18,6 +17,7 @@ import com.alipay.api.DefaultSigner; import com.alipay.api.domain.AlipayTradeRefundModel; import com.alipay.api.request.AlipayTradeRefundRequest; import com.alipay.api.response.AlipayTradeRefundResponse; +import lombok.Setter; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -34,25 +34,26 @@ import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.Mockito.when; /** + * 支付宝 Client 的测试基类 + * * @author jason */ public abstract class AbstractAlipayClientTest extends BaseMockitoUnitTest { - private final String privateKey = randomString(); - - protected AlipayPayClientConfig config = randomPojo(AlipayPayClientConfig.class, t -> { - t.setServerUrl(randomURL()); - t.setPrivateKey(privateKey); - t.setMode(MODE_PUBLIC_KEY); - t.setSignType(AlipayPayClientConfig.SIGN_TYPE_DEFAULT); - t.setAppCertContent(""); - t.setAlipayPublicCertContent(""); - t.setRootCertContent(""); + protected AlipayPayClientConfig config = randomPojo(AlipayPayClientConfig.class, o -> { + o.setServerUrl(randomURL()); + o.setPrivateKey(randomString()); + o.setMode(MODE_PUBLIC_KEY); + o.setSignType(AlipayPayClientConfig.SIGN_TYPE_DEFAULT); + o.setAppCertContent(""); + o.setAlipayPublicCertContent(""); + o.setRootCertContent(""); }); @Mock protected DefaultAlipayClient defaultAlipayClient; + @Setter private AbstractAlipayPayClient client; /** @@ -61,24 +62,22 @@ public abstract class AbstractAlipayClientTest extends BaseMockitoUnitTest { @BeforeEach public abstract void setUp(); - public void setClient(AbstractAlipayPayClient client) { - this.client = client; - } - @Test @DisplayName("支付宝 Client 初始化") - public void test_do_init() { + public void testDoInit() { + // 调用 client.doInit(); - DefaultAlipayClient realClient = (DefaultAlipayClient) ReflectUtil.getFieldValue(client, "client"); + // 断言 + DefaultAlipayClient realClient = client.getClient(); assertNotSame(defaultAlipayClient, realClient); assertInstanceOf(DefaultSigner.class, realClient.getSigner()); - assertEquals(privateKey, ((DefaultSigner) realClient.getSigner()).getPrivateKey()); + assertEquals(config.getPrivateKey(), ((DefaultSigner) realClient.getSigner()).getPrivateKey()); } @Test - @DisplayName("支付宝 Client 统一退款成功") - public void test_unified_refund_success() throws AlipayApiException { - // 准备返回对象 + @DisplayName("支付宝 Client 统一退款:成功") + public void testUnifiedRefund_success() throws AlipayApiException { + // mock 方法 String notifyUrl = randomURL(); Date refundTime = randomDate(); String outRefundNo = randomString(); @@ -88,7 +87,6 @@ public abstract class AbstractAlipayClientTest extends BaseMockitoUnitTest { o.setSubCode(""); o.setGmtRefundPay(refundTime); }); - // mock when(defaultAlipayClient.execute(argThat((ArgumentMatcher) request -> { assertInstanceOf(AlipayTradeRefundModel.class, request.getBizModel()); AlipayTradeRefundModel bizModel = (AlipayTradeRefundModel) request.getBizModel(); @@ -104,19 +102,23 @@ public abstract class AbstractAlipayClientTest extends BaseMockitoUnitTest { o.setNotifyUrl(notifyUrl); o.setRefundPrice(refundAmount); }); + + // 调用 PayRefundRespDTO resp = client.unifiedRefund(refundReqDTO); // 断言 assertEquals(PayRefundStatusRespEnum.SUCCESS.getStatus(), resp.getStatus()); + assertEquals(outRefundNo, resp.getOutRefundNo()); assertNull(resp.getChannelRefundNo()); assertEquals(LocalDateTimeUtil.of(refundTime), resp.getSuccessTime()); - assertEquals(outRefundNo, resp.getOutRefundNo()); assertSame(response, resp.getRawData()); + assertNull(resp.getChannelErrorCode()); + assertNull(resp.getChannelErrorMsg()); } @Test - @DisplayName("支付宝 Client 统一退款,渠道返回失败") + @DisplayName("支付宝 Client 统一退款:渠道返回失败") public void test_unified_refund_channel_failed() throws AlipayApiException { - // 准备返回对象 + // mock 方法 String notifyUrl = randomURL(); String subCode = randomString(); String subMsg = randomString(); @@ -124,7 +126,6 @@ public abstract class AbstractAlipayClientTest extends BaseMockitoUnitTest { o.setSubCode(subCode); o.setSubMsg(subMsg); }); - // mock when(defaultAlipayClient.execute(argThat((ArgumentMatcher) request -> { assertInstanceOf(AlipayTradeRefundModel.class, request.getBizModel()); return true; @@ -137,59 +138,64 @@ public abstract class AbstractAlipayClientTest extends BaseMockitoUnitTest { o.setOutTradeNo(outTradeNo); o.setNotifyUrl(notifyUrl); }); + + // 调用 PayRefundRespDTO resp = client.unifiedRefund(refundReqDTO); // 断言 assertEquals(PayRefundStatusRespEnum.FAILURE.getStatus(), resp.getStatus()); + assertEquals(outRefundNo, resp.getOutRefundNo()); assertNull(resp.getChannelRefundNo()); + assertNull(resp.getSuccessTime()); + assertSame(response, resp.getRawData()); assertEquals(subCode, resp.getChannelErrorCode()); assertEquals(subMsg, resp.getChannelErrorMsg()); - assertNull(resp.getSuccessTime()); - assertEquals(outRefundNo, resp.getOutRefundNo()); - assertSame(response, resp.getRawData()); } @Test - @DisplayName("支付宝 Client 统一退款,参数校验不通过") - public void test_unified_refund_param_validate() { + @DisplayName("支付宝 Client 统一退款:参数校验不通过") + public void testUnifiedRefund_paramInvalidate() { // 准备请求参数 String notifyUrl = randomURL(); PayRefundUnifiedReqDTO refundReqDTO = randomPojo(PayRefundUnifiedReqDTO.class, o -> { o.setOutTradeNo(""); o.setNotifyUrl(notifyUrl); }); - // 断言 + + // 调用,并断言 assertThrows(ConstraintViolationException.class, () -> client.unifiedRefund(refundReqDTO)); } @Test - @DisplayName("支付宝 Client 统一退款,抛出业务异常") - public void test_unified_refund_throw_service_exception() throws AlipayApiException { + @DisplayName("支付宝 Client 统一退款:抛出业务异常") + public void testUnifiedRefund_throwServiceException() throws AlipayApiException { // mock when(defaultAlipayClient.execute(argThat((ArgumentMatcher) request -> true))) .thenThrow(ServiceExceptionUtil.exception(GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR)); // 准备请求参数 String notifyUrl = randomURL(); PayRefundUnifiedReqDTO refundReqDTO = randomPojo(PayRefundUnifiedReqDTO.class, o -> o.setNotifyUrl(notifyUrl)); - // 断言 + + // 调用,并断言 assertThrows(ServiceException.class, () -> client.unifiedRefund(refundReqDTO)); } @Test - @DisplayName("支付宝 Client 统一退款,抛出系统异常") - public void test_unified_refund_throw_pay_exception() throws AlipayApiException { + @DisplayName("支付宝 Client 统一退款:抛出系统异常") + public void testUnifiedRefund_throwPayException() throws AlipayApiException { // mock when(defaultAlipayClient.execute(argThat((ArgumentMatcher) request -> true))) .thenThrow(new RuntimeException("系统异常")); // 准备请求参数 String notifyUrl = randomURL(); PayRefundUnifiedReqDTO refundReqDTO = randomPojo(PayRefundUnifiedReqDTO.class, o -> o.setNotifyUrl(notifyUrl)); - // 断言 + + // 调用,并断言 assertThrows(PayException.class, () -> client.unifiedRefund(refundReqDTO)); } @Test - @DisplayName("支付宝 Client 统一下单, 参数校验不通过") - public void test_unified_order_param_validate() { + @DisplayName("支付宝 Client 统一下单:参数校验不通过") + public void testUnifiedOrder_paramInvalidate() { // 准备请求参数 String outTradeNo = randomString(); String notifyUrl = randomURL(); @@ -197,7 +203,8 @@ public abstract class AbstractAlipayClientTest extends BaseMockitoUnitTest { o.setOutTradeNo(outTradeNo); o.setNotifyUrl(notifyUrl); }); - // 断言 + + // 调用,并断言 assertThrows(ConstraintViolationException.class, () -> client.unifiedOrder(reqDTO)); } @@ -210,4 +217,5 @@ public abstract class AbstractAlipayClientTest extends BaseMockitoUnitTest { o.setBody(RandomUtil.randomString(32)); }); } + } diff --git a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/test/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayBarPayClientTest.java b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/test/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayBarPayClientTest.java index b6f24e136..47f10081c 100644 --- a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/test/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayBarPayClientTest.java +++ b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/test/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayBarPayClientTest.java @@ -28,7 +28,7 @@ import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.Mockito.when; /** - * {@link AlipayBarPayClient} 单元测试 + * {@link AlipayBarPayClient} 单元测试 * * @author jason */ @@ -44,16 +44,14 @@ public class AlipayBarPayClientTest extends AbstractAlipayClientTest { } @Test - @DisplayName("支付宝条码支付,非免密码支付下单成功") - public void test_unified_order_success() throws AlipayApiException { + @DisplayName("支付宝条码支付:非免密码支付下单成功") + public void testUnifiedOrder_success() throws AlipayApiException { + // mock 方法 String outTradeNo = randomString(); String notifyUrl = randomURL(); Integer price = randomInteger(); String authCode = randomString(); - // 准备返回对象 AlipayTradePayResponse response = randomPojo(AlipayTradePayResponse.class, o -> o.setSubCode("")); - - // mock when(defaultAlipayClient.execute(argThat((ArgumentMatcher) request -> { assertInstanceOf(AlipayTradePayModel.class, request.getBizModel()); assertEquals(notifyUrl, request.getNotifyUrl()); @@ -65,28 +63,33 @@ public class AlipayBarPayClientTest extends AbstractAlipayClientTest { }))).thenReturn(response); // 准备请求参数 PayOrderUnifiedReqDTO reqDTO = buildOrderUnifiedReqDTO(notifyUrl, outTradeNo, price); - // 设置条码 Map extraParam = new HashMap<>(); extraParam.put("auth_code", authCode); reqDTO.setChannelExtras(extraParam); - // 下单请求 + + // 调用方法 PayOrderRespDTO resp = client.unifiedOrder(reqDTO); // 断言 assertEquals(WAITING.getStatus(), resp.getStatus()); - assertEquals(PayOrderDisplayModeEnum.BAR_CODE.getMode(), resp.getDisplayMode()); assertEquals(outTradeNo, resp.getOutTradeNo()); + assertNull(resp.getChannelOrderNo()); + assertNull(resp.getChannelUserId()); + assertNull(resp.getSuccessTime()); + assertEquals(PayOrderDisplayModeEnum.BAR_CODE.getMode(), resp.getDisplayMode()); assertEquals("", resp.getDisplayContent()); assertSame(response, resp.getRawData()); + assertNull(resp.getChannelErrorCode()); + assertNull(resp.getChannelErrorMsg()); } @Test - @DisplayName("支付宝条码支付,免密码支付下单成功") - public void test_unified_order_code_10000_success() throws AlipayApiException { + @DisplayName("支付宝条码支付:免密码支付下单成功") + public void testUnifiedOrder_code10000Success() throws AlipayApiException { + // mock 方法 String outTradeNo = randomString(); String channelNo = randomString(); String channelUserId = randomString(); Date payTime = randomDate(); - // 准备返回对象 AlipayTradePayResponse response = randomPojo(AlipayTradePayResponse.class, o -> { o.setSubCode(""); o.setCode("10000"); @@ -95,16 +98,15 @@ public class AlipayBarPayClientTest extends AbstractAlipayClientTest { o.setBuyerUserId(channelUserId); o.setGmtPayment(payTime); }); - // mock when(defaultAlipayClient.execute(argThat((ArgumentMatcher) request -> true))) .thenReturn(response); // 准备请求参数 String authCode = randomString(); PayOrderUnifiedReqDTO reqDTO = buildOrderUnifiedReqDTO(randomURL(), outTradeNo, randomInteger()); - // 设置条码 Map extraParam = new HashMap<>(); extraParam.put("auth_code", authCode); reqDTO.setChannelExtras(extraParam); + // 下单请求 PayOrderRespDTO resp = client.unifiedOrder(reqDTO); // 断言 @@ -113,45 +115,56 @@ public class AlipayBarPayClientTest extends AbstractAlipayClientTest { assertEquals(channelNo, resp.getChannelOrderNo()); assertEquals(channelUserId, resp.getChannelUserId()); assertEquals(LocalDateTimeUtil.of(payTime), resp.getSuccessTime()); + assertEquals(PayOrderDisplayModeEnum.BAR_CODE.getMode(), resp.getDisplayMode()); + assertEquals("", resp.getDisplayContent()); assertSame(response, resp.getRawData()); + assertNull(resp.getChannelErrorCode()); + assertNull(resp.getChannelErrorMsg()); } @Test - @DisplayName("支付宝条码支付,没有传条码") - public void test_unified_order_empty_auth_code() { + @DisplayName("支付宝条码支付:没有传条码") + public void testUnifiedOrder_emptyAuthCode() { + // 准备参数 PayOrderUnifiedReqDTO reqDTO = buildOrderUnifiedReqDTO(randomURL(), randomString(), randomInteger()); - // 断言 + + // 调用,并断言 assertThrows(ServiceException.class, () -> client.unifiedOrder(reqDTO)); } @Test - @DisplayName("支付宝条码支付,渠道返回失败") + @DisplayName("支付宝条码支付:渠道返回失败") public void test_unified_order_channel_failed() throws AlipayApiException { - // 准备响应对象 + // mock 方法 String subCode = randomString(); String subMsg = randomString(); AlipayTradePayResponse response = randomPojo(AlipayTradePayResponse.class, o -> { o.setSubCode(subCode); o.setSubMsg(subMsg); }); - // mock when(defaultAlipayClient.execute(argThat((ArgumentMatcher) request -> true))) .thenReturn(response); // 准备请求参数 String authCode = randomString(); String outTradeNo = randomString(); PayOrderUnifiedReqDTO reqDTO = buildOrderUnifiedReqDTO(randomURL(), outTradeNo, randomInteger()); - // 设置条码 Map extraParam = new HashMap<>(); extraParam.put("auth_code", authCode); reqDTO.setChannelExtras(extraParam); - // 下单请求 + + // 调用方法 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()); - } + }