From d4ed2c49f18a4258d141fe289245d279b6c89b3e Mon Sep 17 00:00:00 2001 From: YunaiV Date: Fri, 16 May 2025 23:27:00 +0800 Subject: [PATCH] reactor:将 pay starter 融合到 pay-module 的 framework/pay 里 --- yudao-module-infra/yudao-module-infra-api/src/main/java/cn/iocoder/yudao/module/infra/enums/DictTypeConstants.java | 2 ++ yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/logger/vo/apiaccesslog/ApiAccessLogRespVO.java | 2 +- yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/logger/vo/apierrorlog/ApiErrorLogRespVO.java | 2 +- yudao-module-pay/pom.xml | 1 - yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/PayChannelEnum.java | 15 +++++++++++++++ yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/order/PayOrderStatusEnum.java | 10 ++++++++++ yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/transfer/PayTransferStatusEnum.java | 4 ++-- yudao-module-pay/yudao-module-pay-biz/pom.xml | 23 ++++++++++++++++++----- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/PayTransferApiImpl.java | 2 +- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/notify/PayNotifyController.java | 10 +++++----- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/PayOrderController.java | 4 ++-- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/order/AppPayOrderController.java | 4 ++-- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/order/PayOrderConvert.java | 4 ++-- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/channel/PayChannelDO.java | 55 ++++++++++++++++++++++++++++++++++++++++++++++++++++--- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/order/PayOrderDO.java | 2 +- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/order/PayOrderExtensionDO.java | 2 +- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/refund/PayRefundDO.java | 4 ++-- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/transfer/PayTransferDO.java | 6 +++--- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/config/PayConfiguration.java | 9 +++++++++ yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/WalletPayClient.java | 251 ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/PayClient.java | 118 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/PayClientConfig.java | 27 +++++++++++++++++++++++++++ yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/PayClientFactory.java | 28 ++++++++++++++++++++++++++++ yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/dto/order/PayOrderRespDTO.java | 141 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/dto/order/PayOrderUnifiedReqDTO.java | 92 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/dto/refund/PayRefundRespDTO.java | 115 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/dto/refund/PayRefundUnifiedReqDTO.java | 70 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/dto/transfer/PayTransferRespDTO.java | 116 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/dto/transfer/PayTransferUnifiedReqDTO.java | 73 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/exception/PayClientException.java | 17 +++++++++++++++++ yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/AbstractPayClient.java | 251 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/NonePayClientConfig.java | 31 +++++++++++++++++++++++++++++++ yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/PayClientFactoryImpl.java | 97 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AbstractAlipayPayClient.java | 379 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AlipayAppPayClient.java | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AlipayBarPayClient.java | 86 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AlipayPayClientConfig.java | 128 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AlipayPcPayClient.java | 70 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AlipayQrPayClient.java | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AlipayWapPayClient.java | 59 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/mock/MockPayClient.java | 84 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/wallet/WalletPayClient.java | 251 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/weixin/AbstractWxPayClient.java | 597 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/weixin/WxAppPayClient.java | 63 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/weixin/WxBarPayClient.java | 107 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/weixin/WxLitePayClient.java | 22 ++++++++++++++++++++++ yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/weixin/WxNativePayClient.java | 59 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/weixin/WxPayClientConfig.java | 107 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/weixin/WxPubPayClient.java | 81 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/weixin/WxWapPayClient.java | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/enums/PayOrderDisplayModeEnum.java | 29 +++++++++++++++++++++++++++++ yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/package-info.java | 6 ++++++ yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/channel/PayChannelService.java | 2 +- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/channel/PayChannelServiceImpl.java | 25 ++++++++++--------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/order/PayOrderService.java | 2 +- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/order/PayOrderServiceImpl.java | 28 ++++++++++++++-------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundService.java | 2 +- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundServiceImpl.java | 13 ++++++------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/transfer/PayTransferService.java | 2 +- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/transfer/PayTransferServiceImpl.java | 15 +++++++-------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletRechargeServiceImpl.java | 3 +-- yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/PayClientFactoryImplIntegrationTest.java | 134 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AbstractAlipayClientTest.java | 221 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AlipayBarPayClientTest.java | 170 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AlipayPcPayClientTest.java | 131 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AlipayQrPayClientTest.java | 147 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AlipayWapPayClientTest.java | 111 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/weixin/WxBarPayClientIntegrationTest.java | 123 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/weixin/WxNativePayClientIntegrationTest.java | 84 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/service/channel/PayChannelServiceTest.java | 10 +++++----- yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/service/order/PayOrderServiceTest.java | 31 +++++++++++++++---------------- yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundServiceTest.java | 33 ++++++++++++++++----------------- yudao-module-pay/yudao-spring-boot-starter-biz-pay/pom.xml | 77 ----------------------------------------------------------------------------- yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/config/YudaoPayAutoConfiguration.java | 22 ---------------------- yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/PayClient.java | 118 ---------------------------------------------------------------------------------------------------------------------- yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/PayClientConfig.java | 27 --------------------------- yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/PayClientFactory.java | 39 --------------------------------------- yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/order/PayOrderRespDTO.java | 141 --------------------------------------------------------------------------------------------------------------------------------------------- yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/order/PayOrderUnifiedReqDTO.java | 92 -------------------------------------------------------------------------------------------- yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/refund/PayRefundRespDTO.java | 115 ------------------------------------------------------------------------------------------------------------------- yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/refund/PayRefundUnifiedReqDTO.java | 70 ---------------------------------------------------------------------- yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/transfer/PayTransferRespDTO.java | 116 -------------------------------------------------------------------------------------------------------------------- yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/transfer/PayTransferUnifiedReqDTO.java | 73 ------------------------------------------------------------------------- yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/exception/PayException.java | 17 ----------------- yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/AbstractPayClient.java | 251 ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/NonePayClientConfig.java | 31 ------------------------------- yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/PayClientFactoryImpl.java | 97 ------------------------------------------------------------------------------------------------- yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AbstractAlipayPayClient.java | 379 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayAppPayClient.java | 61 ------------------------------------------------------------- yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayBarPayClient.java | 87 --------------------------------------------------------------------------------------- yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayPayClientConfig.java | 128 -------------------------------------------------------------------------------------------------------------------------------- yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayPcPayClient.java | 71 ----------------------------------------------------------------------- yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayQrPayClient.java | 66 ------------------------------------------------------------------ yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayWapPayClient.java | 59 ----------------------------------------------------------- yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/mock/MockPayClient.java | 84 ------------------------------------------------------------------------------------ yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/weixin/AbstractWxPayClient.java | 597 --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/weixin/WxAppPayClient.java | 63 --------------------------------------------------------------- yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/weixin/WxBarPayClient.java | 107 ----------------------------------------------------------------------------------------------------------- yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/weixin/WxLitePayClient.java | 22 ---------------------- yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/weixin/WxNativePayClient.java | 59 ----------------------------------------------------------- yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/weixin/WxPayClientConfig.java | 107 ----------------------------------------------------------------------------------------------------------- yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/weixin/WxPubPayClient.java | 75 --------------------------------------------------------------------------- yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/weixin/WxWapPayClient.java | 62 -------------------------------------------------------------- yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/enums/channel/PayChannelEnum.java | 69 --------------------------------------------------------------------- yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/enums/order/PayOrderDisplayModeEnum.java | 29 ----------------------------- yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/enums/order/PayOrderStatusRespEnum.java | 56 -------------------------------------------------------- yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/enums/refund/PayRefundStatusRespEnum.java | 32 -------------------------------- yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/enums/transfer/PayTransferStatusRespEnum.java | 37 ------------------------------------- yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports | 1 - yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/test/java/cn/iocoder/yudao/framework/pay/core/client/impl/PayClientFactoryImplIntegrationTest.java | 133 ------------------------------------------------------------------------------------------------------------------------------------- yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/test/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AbstractAlipayClientTest.java | 221 ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/test/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayBarPayClientTest.java | 170 -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/test/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayPcPayClientTest.java | 131 ----------------------------------------------------------------------------------------------------------------------------------- yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/test/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayQrPayClientTest.java | 147 --------------------------------------------------------------------------------------------------------------------------------------------------- yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/test/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayWapPayClientTest.java | 111 --------------------------------------------------------------------------------------------------------------- yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/test/java/cn/iocoder/yudao/framework/pay/core/client/impl/weixin/WxBarPayClientIntegrationTest.java | 123 --------------------------------------------------------------------------------------------------------------------------- yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/test/java/cn/iocoder/yudao/framework/pay/core/client/impl/weixin/WxNativePayClientIntegrationTest.java | 83 ----------------------------------------------------------------------------------- 117 files changed, 4820 insertions(+), 5027 deletions(-) delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/WalletPayClient.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/PayClient.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/PayClientConfig.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/PayClientFactory.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/dto/order/PayOrderRespDTO.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/dto/order/PayOrderUnifiedReqDTO.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/dto/refund/PayRefundRespDTO.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/dto/refund/PayRefundUnifiedReqDTO.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/dto/transfer/PayTransferRespDTO.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/dto/transfer/PayTransferUnifiedReqDTO.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/exception/PayClientException.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/AbstractPayClient.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/NonePayClientConfig.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/PayClientFactoryImpl.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AbstractAlipayPayClient.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AlipayAppPayClient.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AlipayBarPayClient.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AlipayPayClientConfig.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AlipayPcPayClient.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AlipayQrPayClient.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AlipayWapPayClient.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/mock/MockPayClient.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/wallet/WalletPayClient.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/weixin/AbstractWxPayClient.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/weixin/WxAppPayClient.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/weixin/WxBarPayClient.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/weixin/WxLitePayClient.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/weixin/WxNativePayClient.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/weixin/WxPayClientConfig.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/weixin/WxPubPayClient.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/weixin/WxWapPayClient.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/enums/PayOrderDisplayModeEnum.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/package-info.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/PayClientFactoryImplIntegrationTest.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AbstractAlipayClientTest.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AlipayBarPayClientTest.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AlipayPcPayClientTest.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AlipayQrPayClientTest.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AlipayWapPayClientTest.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/weixin/WxBarPayClientIntegrationTest.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/weixin/WxNativePayClientIntegrationTest.java delete mode 100644 yudao-module-pay/yudao-spring-boot-starter-biz-pay/pom.xml delete mode 100644 yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/config/YudaoPayAutoConfiguration.java delete mode 100644 yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/PayClient.java delete mode 100644 yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/PayClientConfig.java delete mode 100644 yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/PayClientFactory.java delete mode 100644 yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/order/PayOrderRespDTO.java delete mode 100644 yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/order/PayOrderUnifiedReqDTO.java delete mode 100644 yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/refund/PayRefundRespDTO.java delete mode 100644 yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/refund/PayRefundUnifiedReqDTO.java delete mode 100644 yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/transfer/PayTransferRespDTO.java delete mode 100644 yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/transfer/PayTransferUnifiedReqDTO.java delete mode 100644 yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/exception/PayException.java delete mode 100644 yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/AbstractPayClient.java delete mode 100644 yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/NonePayClientConfig.java delete mode 100644 yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/PayClientFactoryImpl.java delete mode 100644 yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AbstractAlipayPayClient.java delete mode 100644 yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayAppPayClient.java delete mode 100644 yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayBarPayClient.java delete mode 100644 yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayPayClientConfig.java delete mode 100644 yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayPcPayClient.java delete mode 100644 yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayQrPayClient.java delete mode 100644 yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayWapPayClient.java delete mode 100644 yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/mock/MockPayClient.java delete mode 100644 yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/weixin/AbstractWxPayClient.java delete mode 100644 yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/weixin/WxAppPayClient.java delete mode 100644 yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/weixin/WxBarPayClient.java delete mode 100644 yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/weixin/WxLitePayClient.java delete mode 100644 yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/weixin/WxNativePayClient.java delete mode 100644 yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/weixin/WxPayClientConfig.java delete mode 100644 yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/weixin/WxPubPayClient.java delete mode 100644 yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/weixin/WxWapPayClient.java delete mode 100644 yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/enums/channel/PayChannelEnum.java delete mode 100644 yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/enums/order/PayOrderDisplayModeEnum.java delete mode 100644 yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/enums/order/PayOrderStatusRespEnum.java delete mode 100644 yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/enums/refund/PayRefundStatusRespEnum.java delete mode 100644 yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/enums/transfer/PayTransferStatusRespEnum.java delete mode 100644 yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports delete mode 100644 yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/test/java/cn/iocoder/yudao/framework/pay/core/client/impl/PayClientFactoryImplIntegrationTest.java delete mode 100644 yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/test/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AbstractAlipayClientTest.java delete mode 100644 yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/test/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayBarPayClientTest.java delete mode 100644 yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/test/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayPcPayClientTest.java delete mode 100644 yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/test/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayQrPayClientTest.java delete mode 100644 yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/test/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayWapPayClientTest.java delete mode 100644 yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/test/java/cn/iocoder/yudao/framework/pay/core/client/impl/weixin/WxBarPayClientIntegrationTest.java delete mode 100644 yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/test/java/cn/iocoder/yudao/framework/pay/core/client/impl/weixin/WxNativePayClientIntegrationTest.java diff --git a/yudao-module-infra/yudao-module-infra-api/src/main/java/cn/iocoder/yudao/module/infra/enums/DictTypeConstants.java b/yudao-module-infra/yudao-module-infra-api/src/main/java/cn/iocoder/yudao/module/infra/enums/DictTypeConstants.java index 36ad63d..fcab9e6 100644 --- a/yudao-module-infra/yudao-module-infra-api/src/main/java/cn/iocoder/yudao/module/infra/enums/DictTypeConstants.java +++ b/yudao-module-infra/yudao-module-infra-api/src/main/java/cn/iocoder/yudao/module/infra/enums/DictTypeConstants.java @@ -7,6 +7,8 @@ package cn.iocoder.yudao.module.infra.enums; */ public interface DictTypeConstants { + String USER_TYPE = "user_type"; // 用户类型 + String JOB_STATUS = "infra_job_status"; // 定时任务状态的枚举 String JOB_LOG_STATUS = "infra_job_log_status"; // 定时任务日志状态的枚举 diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/logger/vo/apiaccesslog/ApiAccessLogRespVO.java b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/logger/vo/apiaccesslog/ApiAccessLogRespVO.java index e991450..d9e65c4 100644 --- a/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/logger/vo/apiaccesslog/ApiAccessLogRespVO.java +++ b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/logger/vo/apiaccesslog/ApiAccessLogRespVO.java @@ -2,7 +2,7 @@ package cn.iocoder.yudao.module.infra.controller.admin.logger.vo.apiaccesslog; import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; -import cn.iocoder.yudao.module.system.enums.DictTypeConstants; +import cn.iocoder.yudao.module.infra.enums.DictTypeConstants; import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; import com.alibaba.excel.annotation.ExcelProperty; import io.swagger.v3.oas.annotations.media.Schema; diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/logger/vo/apierrorlog/ApiErrorLogRespVO.java b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/logger/vo/apierrorlog/ApiErrorLogRespVO.java index 8029f15..a3f6f0e 100644 --- a/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/logger/vo/apierrorlog/ApiErrorLogRespVO.java +++ b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/logger/vo/apierrorlog/ApiErrorLogRespVO.java @@ -29,7 +29,7 @@ public class ApiErrorLogRespVO { @Schema(description = "用户类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") @ExcelProperty(value = "用户类型", converter = DictConvert.class) - @DictFormat(cn.iocoder.yudao.module.system.enums.DictTypeConstants.USER_TYPE) + @DictFormat(DictTypeConstants.USER_TYPE) private Integer userType; @Schema(description = "应用名", requiredMode = Schema.RequiredMode.REQUIRED, example = "dashboard") diff --git a/yudao-module-pay/pom.xml b/yudao-module-pay/pom.xml index 85bcb1e..c1581b6 100644 --- a/yudao-module-pay/pom.xml +++ b/yudao-module-pay/pom.xml @@ -13,7 +13,6 @@ yudao-module-pay-api yudao-module-pay-biz - yudao-spring-boot-starter-biz-pay ${project.artifactId} diff --git a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/PayChannelEnum.java b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/PayChannelEnum.java index 1c577e2..2733282 100644 --- a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/PayChannelEnum.java +++ b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/PayChannelEnum.java @@ -1,5 +1,7 @@ package cn.iocoder.yudao.module.pay.enums; +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.core.ArrayValuable; import lombok.AllArgsConstructor; import lombok.Getter; @@ -27,6 +29,7 @@ public enum PayChannelEnum implements ArrayValuable { ALIPAY_APP("alipay_app", "支付宝App 支付"), ALIPAY_QR("alipay_qr", "支付宝扫码支付"), ALIPAY_BAR("alipay_bar", "支付宝条码支付"), + MOCK("mock", "模拟支付"), WALLET("wallet", "钱包支付"); @@ -49,4 +52,16 @@ public enum PayChannelEnum implements ArrayValuable { return ARRAYS; } + public static PayChannelEnum getByCode(String code) { + return ArrayUtil.firstMatch(o -> o.getCode().equals(code), values()); + } + + public static boolean isAlipay(String channelCode) { + return StrUtil.startWith(channelCode, "alipay_"); + } + + public static boolean isWeixin(String channelCode) { + return StrUtil.startWith(channelCode, "wx_"); + } + } diff --git a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/order/PayOrderStatusEnum.java b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/order/PayOrderStatusEnum.java index d5c8b01..5b5be4b 100644 --- a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/order/PayOrderStatusEnum.java +++ b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/order/PayOrderStatusEnum.java @@ -51,6 +51,16 @@ public enum PayOrderStatusEnum implements ArrayValuable { } /** + * 判断是否已退款 + * + * @param status 状态 + * @return 是否已退款 + */ + public static boolean isRefund(Integer status) { + return Objects.equals(status, REFUND.getStatus()); + } + + /** * 判断是否支付成功或者已退款 * * @param status 状态 diff --git a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/transfer/PayTransferStatusEnum.java b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/transfer/PayTransferStatusEnum.java index cfb6898..7a345f7 100644 --- a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/transfer/PayTransferStatusEnum.java +++ b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/transfer/PayTransferStatusEnum.java @@ -40,7 +40,7 @@ public enum PayTransferStatusEnum { return Objects.equals(status, WAITING.getStatus()); } - public static boolean isProgressing(Integer status) { + public static boolean isProcessing(Integer status) { return Objects.equals(status, PROCESSING.getStatus()); } @@ -51,7 +51,7 @@ public enum PayTransferStatusEnum { * @return 是否 */ public static boolean isWaitingOrProcessing(Integer status) { - return isWaiting(status) || isProgressing(status); + return isWaiting(status) || isProcessing(status); } /** diff --git a/yudao-module-pay/yudao-module-pay-biz/pom.xml b/yudao-module-pay/yudao-module-pay-biz/pom.xml index f2fe3ee..6089d53 100644 --- a/yudao-module-pay/yudao-module-pay-biz/pom.xml +++ b/yudao-module-pay/yudao-module-pay-biz/pom.xml @@ -32,11 +32,6 @@ cn.iocoder.boot - yudao-spring-boot-starter-biz-pay - ${revision} - - - cn.iocoder.boot yudao-spring-boot-starter-biz-tenant @@ -75,6 +70,24 @@ cn.iocoder.boot yudao-spring-boot-starter-excel + + + + com.alipay.sdk + alipay-sdk-java + 4.35.79.ALL + + + org.bouncycastle + bcprov-jdk15on + + + + + com.github.binarywang + weixin-java-pay + + diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/PayTransferApiImpl.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/PayTransferApiImpl.java index 8763468..6c6ea06 100644 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/PayTransferApiImpl.java +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/PayTransferApiImpl.java @@ -1,7 +1,7 @@ package cn.iocoder.yudao.module.pay.api.transfer; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; -import cn.iocoder.yudao.framework.pay.core.client.impl.weixin.WxPayClientConfig; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.impl.weixin.WxPayClientConfig; import cn.iocoder.yudao.module.pay.api.transfer.dto.PayTransferCreateReqDTO; import cn.iocoder.yudao.module.pay.api.transfer.dto.PayTransferCreateRespDTO; import cn.iocoder.yudao.module.pay.api.transfer.dto.PayTransferRespDTO; diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/notify/PayNotifyController.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/notify/PayNotifyController.java index 0482101..bcf97ea 100644 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/notify/PayNotifyController.java +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/notify/PayNotifyController.java @@ -4,10 +4,10 @@ import cn.hutool.core.collection.CollUtil; import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; -import cn.iocoder.yudao.framework.pay.core.client.PayClient; -import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO; -import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundRespDTO; -import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferRespDTO; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.PayClient; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.order.PayOrderRespDTO; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.refund.PayRefundRespDTO; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.transfer.PayTransferRespDTO; import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore; import cn.iocoder.yudao.module.pay.controller.admin.notify.vo.PayNotifyTaskDetailRespVO; import cn.iocoder.yudao.module.pay.controller.admin.notify.vo.PayNotifyTaskPageReqVO; @@ -156,7 +156,7 @@ public class PayNotifyController { } // 拼接返回 Map apps = appService.getAppMap(convertList(pageResult.getList(), PayNotifyTaskDO::getAppId)); - + // 转换对象 return success(BeanUtils.toBean(pageResult, PayNotifyTaskRespVO.class, order -> { PayAppDO app = apps.get(order.getAppId()); diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/PayOrderController.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/PayOrderController.java index 7a6f893..97bd29f 100755 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/PayOrderController.java +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/PayOrderController.java @@ -6,15 +6,15 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; -import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum; import cn.iocoder.yudao.module.pay.controller.admin.order.vo.*; import cn.iocoder.yudao.module.pay.convert.order.PayOrderConvert; import cn.iocoder.yudao.module.pay.dal.dataobject.app.PayAppDO; import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO; import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderExtensionDO; import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletDO; +import cn.iocoder.yudao.module.pay.enums.PayChannelEnum; import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum; -import cn.iocoder.yudao.module.pay.framework.pay.core.WalletPayClient; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.impl.wallet.WalletPayClient; import cn.iocoder.yudao.module.pay.service.app.PayAppService; import cn.iocoder.yudao.module.pay.service.order.PayOrderService; import cn.iocoder.yudao.module.pay.service.wallet.PayWalletService; diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/order/AppPayOrderController.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/order/AppPayOrderController.java index 1af25d3..88eeb09 100644 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/order/AppPayOrderController.java +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/order/AppPayOrderController.java @@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.pay.controller.app.order; import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; -import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum; import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderRespVO; import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderSubmitRespVO; import cn.iocoder.yudao.module.pay.controller.app.order.vo.AppPayOrderSubmitReqVO; @@ -10,8 +9,9 @@ import cn.iocoder.yudao.module.pay.controller.app.order.vo.AppPayOrderSubmitResp import cn.iocoder.yudao.module.pay.convert.order.PayOrderConvert; import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO; import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletDO; +import cn.iocoder.yudao.module.pay.enums.PayChannelEnum; import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum; -import cn.iocoder.yudao.module.pay.framework.pay.core.WalletPayClient; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.impl.wallet.WalletPayClient; import cn.iocoder.yudao.module.pay.service.order.PayOrderService; import cn.iocoder.yudao.module.pay.service.wallet.PayWalletService; import com.google.common.collect.Maps; diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/order/PayOrderConvert.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/order/PayOrderConvert.java index 6820060..8321de3 100755 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/order/PayOrderConvert.java +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/order/PayOrderConvert.java @@ -3,7 +3,7 @@ package cn.iocoder.yudao.module.pay.convert.order; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.framework.common.util.collection.MapUtils; -import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO; import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderCreateReqDTO; import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderRespDTO; import cn.iocoder.yudao.module.pay.controller.admin.order.vo.*; @@ -67,7 +67,7 @@ public interface PayOrderConvert { PayOrderUnifiedReqDTO convert2(PayOrderSubmitReqVO reqVO, String userIp); @Mapping(source = "order.status", target = "status") - PayOrderSubmitRespVO convert(PayOrderDO order, cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO respDTO); + PayOrderSubmitRespVO convert(PayOrderDO order, cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.order.PayOrderRespDTO respDTO); AppPayOrderSubmitRespVO convert3(PayOrderSubmitRespVO bean); diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/channel/PayChannelDO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/channel/PayChannelDO.java index 72387eb..0039786 100644 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/channel/PayChannelDO.java +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/channel/PayChannelDO.java @@ -1,16 +1,26 @@ package cn.iocoder.yudao.module.pay.dal.dataobject.channel; +import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; -import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig; -import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum; +import cn.iocoder.yudao.framework.common.util.json.JsonUtils; +import cn.iocoder.yudao.module.pay.enums.PayChannelEnum; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.PayClientConfig; import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO; import cn.iocoder.yudao.module.pay.dal.dataobject.app.PayAppDO; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.impl.NonePayClientConfig; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.impl.alipay.AlipayAppPayClient; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.impl.alipay.AlipayPayClientConfig; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.impl.weixin.WxPayClientConfig; import com.baomidou.mybatisplus.annotation.KeySequence; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.extension.handlers.AbstractJsonTypeHandler; import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; +import com.fasterxml.jackson.core.type.TypeReference; import lombok.*; +import java.lang.reflect.Field; + /** * 支付渠道 DO * 一个应用下,会有多种支付渠道,例如说微信支付、支付宝支付等等 @@ -63,7 +73,46 @@ public class PayChannelDO extends TenantBaseDO { /** * 支付渠道配置 */ - @TableField(typeHandler = JacksonTypeHandler.class) + @TableField(typeHandler = PayClientConfigTypeHandler.class) private PayClientConfig config; + public static class PayClientConfigTypeHandler extends AbstractJsonTypeHandler { + + public PayClientConfigTypeHandler(Class type) { + super(type); + } + + public PayClientConfigTypeHandler(Class type, Field field) { + super(type, field); + } + + @Override + public Object parse(String json) { + PayClientConfig config = JsonUtils.parseObjectQuietly(json, new TypeReference<>() {}); + if (config != null) { + return config; + } + + // 兼容老版本的包路径 + String className = JsonUtils.parseObject(json, "@class", String.class); + className = StrUtil.subAfter(className, ".", true); + switch (className) { + case "AlipayPayClientConfig": + return JsonUtils.parseObject2(json, AlipayPayClientConfig.class); + case "WxPayClientConfig": + return JsonUtils.parseObject2(json, WxPayClientConfig.class); + case "NonePayClientConfig": + return JsonUtils.parseObject2(json, NonePayClientConfig.class); + default: + throw new IllegalArgumentException("未知的 PayClientConfig 类型:" + json); + } + } + + @Override + public String toJson(Object obj) { + return JsonUtils.toJsonString(obj); + } + + } + } diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/order/PayOrderDO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/order/PayOrderDO.java index a51b875..ac0ef34 100644 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/order/PayOrderDO.java +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/order/PayOrderDO.java @@ -1,9 +1,9 @@ package cn.iocoder.yudao.module.pay.dal.dataobject.order; import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; -import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum; import cn.iocoder.yudao.module.pay.dal.dataobject.app.PayAppDO; import cn.iocoder.yudao.module.pay.dal.dataobject.channel.PayChannelDO; +import cn.iocoder.yudao.module.pay.enums.PayChannelEnum; import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum; import com.baomidou.mybatisplus.annotation.KeySequence; import com.baomidou.mybatisplus.annotation.TableName; diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/order/PayOrderExtensionDO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/order/PayOrderExtensionDO.java index 9466243..9d2779b 100644 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/order/PayOrderExtensionDO.java +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/order/PayOrderExtensionDO.java @@ -1,6 +1,6 @@ package cn.iocoder.yudao.module.pay.dal.dataobject.order; -import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.order.PayOrderRespDTO; import cn.iocoder.yudao.module.pay.dal.dataobject.channel.PayChannelDO; import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum; import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/refund/PayRefundDO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/refund/PayRefundDO.java index 475f62a..607483f 100644 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/refund/PayRefundDO.java +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/refund/PayRefundDO.java @@ -1,8 +1,8 @@ package cn.iocoder.yudao.module.pay.dal.dataobject.refund; import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; -import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundRespDTO; -import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum; +import cn.iocoder.yudao.module.pay.enums.PayChannelEnum; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.refund.PayRefundRespDTO; import cn.iocoder.yudao.module.pay.dal.dataobject.app.PayAppDO; import cn.iocoder.yudao.module.pay.dal.dataobject.channel.PayChannelDO; import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO; diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/transfer/PayTransferDO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/transfer/PayTransferDO.java index 69dc506..94f8521 100644 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/transfer/PayTransferDO.java +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/transfer/PayTransferDO.java @@ -1,10 +1,10 @@ package cn.iocoder.yudao.module.pay.dal.dataobject.transfer; import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; -import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum; -import cn.iocoder.yudao.framework.pay.core.enums.transfer.PayTransferStatusRespEnum; +import cn.iocoder.yudao.module.pay.enums.PayChannelEnum; import cn.iocoder.yudao.module.pay.dal.dataobject.app.PayAppDO; import cn.iocoder.yudao.module.pay.dal.dataobject.channel.PayChannelDO; +import cn.iocoder.yudao.module.pay.enums.transfer.PayTransferStatusEnum; import com.baomidou.mybatisplus.annotation.KeySequence; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; @@ -89,7 +89,7 @@ public class PayTransferDO extends BaseDO { /** * 转账状态 * - * 枚举 {@link PayTransferStatusRespEnum} + * 枚举 {@link PayTransferStatusEnum} */ private Integer status; diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/config/PayConfiguration.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/config/PayConfiguration.java index 376cd0a..47c9b9f 100644 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/config/PayConfiguration.java +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/config/PayConfiguration.java @@ -1,9 +1,18 @@ package cn.iocoder.yudao.module.pay.framework.pay.config; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.PayClientFactory; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.impl.PayClientFactoryImpl; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration(proxyBeanMethods = false) @EnableConfigurationProperties(PayProperties.class) public class PayConfiguration { + + @Bean + public PayClientFactory payClientFactory() { + return new PayClientFactoryImpl(); + } + } diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/WalletPayClient.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/WalletPayClient.java deleted file mode 100644 index 933c2a1..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/WalletPayClient.java +++ /dev/null @@ -1,251 +0,0 @@ -package cn.iocoder.yudao.module.pay.framework.pay.core; - -import cn.hutool.core.lang.Assert; -import cn.hutool.core.map.MapUtil; -import cn.hutool.extra.spring.SpringUtil; -import cn.iocoder.yudao.framework.common.exception.ServiceException; -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.dto.transfer.PayTransferRespDTO; -import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferUnifiedReqDTO; -import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient; -import cn.iocoder.yudao.framework.pay.core.client.impl.NonePayClientConfig; -import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum; -import cn.iocoder.yudao.framework.pay.core.enums.refund.PayRefundStatusRespEnum; -import cn.iocoder.yudao.framework.pay.core.enums.transfer.PayTransferStatusRespEnum; -import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderExtensionDO; -import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO; -import cn.iocoder.yudao.module.pay.dal.dataobject.transfer.PayTransferDO; -import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletTransactionDO; -import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum; -import cn.iocoder.yudao.module.pay.enums.wallet.PayWalletBizTypeEnum; -import cn.iocoder.yudao.module.pay.service.order.PayOrderService; -import cn.iocoder.yudao.module.pay.service.refund.PayRefundService; -import cn.iocoder.yudao.module.pay.service.transfer.PayTransferService; -import cn.iocoder.yudao.module.pay.service.wallet.PayWalletService; -import cn.iocoder.yudao.module.pay.service.wallet.PayWalletTransactionService; -import lombok.extern.slf4j.Slf4j; - -import java.util.Map; - -import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR; -import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.PAY_ORDER_EXTENSION_NOT_FOUND; -import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.REFUND_NOT_FOUND; - -/** - * 钱包支付的 PayClient 实现类 - * - * @author jason - */ -@Slf4j -public class WalletPayClient extends AbstractPayClient { - - public static final String WALLET_ID_KEY = "walletId"; - - private PayWalletService wallService; - private PayWalletTransactionService walletTransactionService; - - private PayOrderService orderService; - private PayRefundService refundService; - private PayTransferService transferService; - - public WalletPayClient(Long channelId, NonePayClientConfig config) { - super(channelId, PayChannelEnum.WALLET.getCode(), config); - } - - @Override - protected void doInit() { - if (wallService == null) { - wallService = SpringUtil.getBean(PayWalletService.class); - } - if (walletTransactionService == null) { - walletTransactionService = SpringUtil.getBean(PayWalletTransactionService.class); - } - } - - @Override - @SuppressWarnings("PatternVariableCanBeUsed") - protected PayOrderRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) { - try { - Long walletId = MapUtil.getLong(reqDTO.getChannelExtras(), WALLET_ID_KEY); - Assert.notNull(walletId, "钱包编号"); - PayWalletTransactionDO transaction = wallService.orderPay(walletId, - reqDTO.getOutTradeNo(), reqDTO.getPrice()); - return PayOrderRespDTO.successOf(transaction.getNo(), transaction.getCreator(), - transaction.getCreateTime(), - reqDTO.getOutTradeNo(), transaction); - } catch (Throwable ex) { - log.error("[doUnifiedOrder][reqDTO({}) 异常]", reqDTO, ex); - Integer errorCode = INTERNAL_SERVER_ERROR.getCode(); - String errorMsg = INTERNAL_SERVER_ERROR.getMsg(); - if (ex instanceof ServiceException) { - ServiceException serviceException = (ServiceException) ex; - errorCode = serviceException.getCode(); - errorMsg = serviceException.getMessage(); - } - return PayOrderRespDTO.closedOf(String.valueOf(errorCode), errorMsg, - reqDTO.getOutTradeNo(), ""); - } - } - - @Override - protected PayOrderRespDTO doParseOrderNotify(Map params, String body, Map headers) { - throw new UnsupportedOperationException("钱包支付无支付回调"); - } - - @Override - protected PayOrderRespDTO doGetOrder(String outTradeNo) { - if (orderService == null) { - orderService = SpringUtil.getBean(PayOrderService.class); - } - PayOrderExtensionDO orderExtension = orderService.getOrderExtensionByNo(outTradeNo); - // 支付交易拓展单不存在, 返回关闭状态 - if (orderExtension == null) { - return PayOrderRespDTO.closedOf(String.valueOf(PAY_ORDER_EXTENSION_NOT_FOUND.getCode()), - PAY_ORDER_EXTENSION_NOT_FOUND.getMsg(), outTradeNo, ""); - } - // 关闭状态 - if (PayOrderStatusEnum.isClosed(orderExtension.getStatus())) { - return PayOrderRespDTO.closedOf(orderExtension.getChannelErrorCode(), - orderExtension.getChannelErrorMsg(), outTradeNo, ""); - } - // 成功状态 - if (PayOrderStatusEnum.isSuccess(orderExtension.getStatus())) { - PayWalletTransactionDO walletTransaction = walletTransactionService.getWalletTransaction( - String.valueOf(orderExtension.getOrderId()), PayWalletBizTypeEnum.PAYMENT); - Assert.notNull(walletTransaction, "支付单 {} 钱包流水不能为空", outTradeNo); - return PayOrderRespDTO.successOf(walletTransaction.getNo(), walletTransaction.getCreator(), - walletTransaction.getCreateTime(), outTradeNo, walletTransaction); - } - // 其它状态为无效状态 - log.error("[doGetOrder] 支付单 {} 的状态不正确", outTradeNo); - throw new IllegalStateException(String.format("支付单[%s] 状态不正确", outTradeNo)); - } - - @Override - @SuppressWarnings("PatternVariableCanBeUsed") - protected PayRefundRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) { - try { - PayWalletTransactionDO payWalletTransaction = wallService.orderRefund(reqDTO.getOutRefundNo(), - reqDTO.getRefundPrice(), reqDTO.getReason()); - return PayRefundRespDTO.successOf(payWalletTransaction.getNo(), payWalletTransaction.getCreateTime(), - reqDTO.getOutRefundNo(), payWalletTransaction); - } catch (Throwable ex) { - log.error("[doUnifiedRefund][reqDOT({}) 异常]", reqDTO, ex); - Integer errorCode = INTERNAL_SERVER_ERROR.getCode(); - String errorMsg = INTERNAL_SERVER_ERROR.getMsg(); - if (ex instanceof ServiceException) { - ServiceException serviceException = (ServiceException) ex; - errorCode = serviceException.getCode(); - errorMsg = serviceException.getMessage(); - } - return PayRefundRespDTO.failureOf(String.valueOf(errorCode), errorMsg, - reqDTO.getOutRefundNo(), ""); - } - } - - @Override - protected PayRefundRespDTO doParseRefundNotify(Map params, String body, Map headers) { - throw new UnsupportedOperationException("钱包支付无退款回调"); - } - - @Override - protected PayRefundRespDTO doGetRefund(String outTradeNo, String outRefundNo) { - if (refundService == null) { - refundService = SpringUtil.getBean(PayRefundService.class); - } - PayRefundDO payRefund = refundService.getRefundByNo(outRefundNo); - // 支付退款单不存在, 返回退款失败状态 - if (payRefund == null) { - return PayRefundRespDTO.failureOf(String.valueOf(REFUND_NOT_FOUND), REFUND_NOT_FOUND.getMsg(), - outRefundNo, ""); - } - // 退款失败 - if (PayRefundStatusRespEnum.isFailure(payRefund.getStatus())) { - return PayRefundRespDTO.failureOf(payRefund.getChannelErrorCode(), payRefund.getChannelErrorMsg(), - outRefundNo, ""); - } - // 退款成功 - if (PayRefundStatusRespEnum.isSuccess(payRefund.getStatus())) { - PayWalletTransactionDO walletTransaction = walletTransactionService.getWalletTransaction( - String.valueOf(payRefund.getId()), PayWalletBizTypeEnum.PAYMENT_REFUND); - Assert.notNull(walletTransaction, "支付退款单 {} 钱包流水不能为空", outRefundNo); - return PayRefundRespDTO.successOf(walletTransaction.getNo(), walletTransaction.getCreateTime(), - outRefundNo, walletTransaction); - } - // 其它状态为无效状态 - log.error("[doGetRefund] 支付退款单 {} 的状态不正确", outRefundNo); - throw new IllegalStateException(String.format("支付退款单[%s] 状态不正确", outRefundNo)); - } - - @Override - @SuppressWarnings("PatternVariableCanBeUsed") - public PayTransferRespDTO doUnifiedTransfer(PayTransferUnifiedReqDTO reqDTO) { - try { - Long walletId = Long.parseLong(reqDTO.getUserAccount()); - PayWalletTransactionDO transaction = wallService.addWalletBalance(walletId, String.valueOf(reqDTO.getOutTransferNo()), - PayWalletBizTypeEnum.TRANSFER, reqDTO.getPrice()); - return PayTransferRespDTO.successOf(transaction.getNo(), transaction.getCreateTime(), - reqDTO.getOutTransferNo(), transaction); - } catch (Throwable ex) { - log.error("[doUnifiedTransfer][reqDTO({}) 异常]", reqDTO, ex); - Integer errorCode = INTERNAL_SERVER_ERROR.getCode(); - String errorMsg = INTERNAL_SERVER_ERROR.getMsg(); - if (ex instanceof ServiceException) { - ServiceException serviceException = (ServiceException) ex; - errorCode = serviceException.getCode(); - errorMsg = serviceException.getMessage(); - } - return PayTransferRespDTO.closedOf(String.valueOf(errorCode), errorMsg, - reqDTO.getOutTransferNo(), ""); - } - } - - @Override - protected PayTransferRespDTO doParseTransferNotify(Map params, String body, Map headers) { - throw new UnsupportedOperationException("钱包支付无转账回调"); - } - - @Override - protected PayTransferRespDTO doGetTransfer(String outTradeNo) { - if (transferService == null) { - transferService = SpringUtil.getBean(PayTransferService.class); - } - // 获取转账单 - PayTransferDO transfer = transferService.getTransferByNo(outTradeNo); - // 转账单不存在,返回关闭状态 - if (transfer == null) { - return PayTransferRespDTO.closedOf(String.valueOf(PAY_ORDER_EXTENSION_NOT_FOUND.getCode()), - PAY_ORDER_EXTENSION_NOT_FOUND.getMsg(), outTradeNo, ""); - } - // 关闭状态 - if (PayTransferStatusRespEnum.isClosed(transfer.getStatus())) { - return PayTransferRespDTO.closedOf(transfer.getChannelErrorCode(), - transfer.getChannelErrorMsg(), outTradeNo, ""); - } - // 成功状态 - if (PayTransferStatusRespEnum.isSuccess(transfer.getStatus())) { - PayWalletTransactionDO walletTransaction = walletTransactionService.getWalletTransaction( - String.valueOf(transfer.getId()), PayWalletBizTypeEnum.TRANSFER); - Assert.notNull(walletTransaction, "转账单 {} 钱包流水不能为空", outTradeNo); - return PayTransferRespDTO.successOf(walletTransaction.getNo(), walletTransaction.getCreateTime(), - outTradeNo, walletTransaction); - } - // 处理中状态 - if (PayTransferStatusRespEnum.isProcessing(transfer.getStatus())) { - return PayTransferRespDTO.processingOf(transfer.getChannelTransferNo(), - outTradeNo, transfer); - } - // 等待状态 - if (transfer.getStatus().equals(PayTransferStatusRespEnum.WAITING.getStatus())) { - return PayTransferRespDTO.waitingOf(transfer.getChannelTransferNo(), - outTradeNo, transfer); - } - // 其它状态为无效状态 - log.error("[doGetTransfer] 转账单 {} 的状态不正确", outTradeNo); - throw new IllegalStateException(String.format("转账单[%s] 状态不正确", outTradeNo)); - } - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/PayClient.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/PayClient.java new file mode 100644 index 0000000..4748c40 --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/PayClient.java @@ -0,0 +1,118 @@ +package cn.iocoder.yudao.module.pay.framework.pay.core.client; + +import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.order.PayOrderRespDTO; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.refund.PayRefundRespDTO; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.transfer.PayTransferRespDTO; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.transfer.PayTransferUnifiedReqDTO; + +import java.util.Map; + +/** + * 支付客户端,用于对接各支付渠道的 SDK,实现发起支付、退款等功能 + * + * @author 芋道源码 + */ +public interface PayClient { + + /** + * 获得渠道编号 + * + * @return 渠道编号 + */ + Long getId(); + + /** + * 获得渠道配置 + * + * @return 渠道配置 + */ + Config getConfig(); + + // ============ 支付相关 ========== + + /** + * 调用支付渠道,统一下单 + * + * @param reqDTO 下单信息 + * @return 支付订单信息 + */ + PayOrderRespDTO unifiedOrder(PayOrderUnifiedReqDTO reqDTO); + + /** + * 解析 order 回调数据 + * + * @param params HTTP 回调接口 content type 为 application/x-www-form-urlencoded 的所有参数 + * @param body HTTP 回调接口的 request body + * @param headers HTTP 回调接口的 request headers + * @return 支付订单信息 + */ + PayOrderRespDTO parseOrderNotify(Map params, String body, Map headers); + + /** + * 获得支付订单信息 + * + * @param outTradeNo 外部订单号 + * @return 支付订单信息 + */ + PayOrderRespDTO getOrder(String outTradeNo); + + // ============ 退款相关 ========== + + /** + * 调用支付渠道,进行退款 + * + * @param reqDTO 统一退款请求信息 + * @return 退款信息 + */ + PayRefundRespDTO unifiedRefund(PayRefundUnifiedReqDTO reqDTO); + + /** + * 解析 refund 回调数据 + * + * @param params HTTP 回调接口 content type 为 application/x-www-form-urlencoded 的所有参数 + * @param body HTTP 回调接口的 request body + * @param headers HTTP 回调接口的 request headers + * @return 支付订单信息 + */ + PayRefundRespDTO parseRefundNotify(Map params, String body, Map headers); + + /** + * 获得退款订单信息 + * + * @param outTradeNo 外部订单号 + * @param outRefundNo 外部退款号 + * @return 退款订单信息 + */ + PayRefundRespDTO getRefund(String outTradeNo, String outRefundNo); + + // ============ 转账相关 ========== + + /** + * 调用渠道,进行转账 + * + * @param reqDTO 统一转账请求信息 + * @return 转账信息 + */ + PayTransferRespDTO unifiedTransfer(PayTransferUnifiedReqDTO reqDTO); + + /** + * 获得转账订单信息 + * + * @param outTradeNo 外部订单号 + * @return 转账信息 + */ + PayTransferRespDTO getTransfer(String outTradeNo); + + /** + * 解析 transfer 回调数据 + * + * @param params HTTP 回调接口 content type 为 application/x-www-form-urlencoded 的所有参数 + * @param body HTTP 回调接口的 request body + * @param headers HTTP 回调接口的 request headers + * @return 转账信息 + */ + PayTransferRespDTO parseTransferNotify(Map params, String body, Map headers); + +} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/PayClientConfig.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/PayClientConfig.java new file mode 100644 index 0000000..d0c307d --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/PayClientConfig.java @@ -0,0 +1,27 @@ +package cn.iocoder.yudao.module.pay.framework.pay.core.client; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import jakarta.validation.Validator; + +/** + * 支付客户端的配置,本质是支付渠道的配置 + * 每个不同的渠道,需要不同的配置,通过子类来定义 + * + * @author 芋道源码 + */ +@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) +// @JsonTypeInfo 注解的作用,Jackson 多态 +// 1. 序列化到时数据库时,增加 @class 属性。 +// 2. 反序列化到内存对象时,通过 @class 属性,可以创建出正确的类型 +@JsonIgnoreProperties(ignoreUnknown = true) // 目的:忽略未知的属性,避免反序列化失败 +public interface PayClientConfig { + + /** + * 参数校验 + * + * @param validator 校验对象 + */ + void validate(Validator validator); + +} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/PayClientFactory.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/PayClientFactory.java new file mode 100644 index 0000000..35b7b95 --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/PayClientFactory.java @@ -0,0 +1,28 @@ +package cn.iocoder.yudao.module.pay.framework.pay.core.client; + +/** + * 支付客户端的工厂接口 + * + * @author 芋道源码 + */ +public interface PayClientFactory { + + /** + * 获得支付客户端 + * + * @param channelId 渠道编号 + * @return 支付客户端 + */ + PayClient getPayClient(Long channelId); + + /** + * 创建支付客户端 + * + * @param channelId 渠道编号 + * @param channelCode 渠道编码 + * @param config 支付配置 + * @return 支付客户端 + */ + PayClient createOrUpdatePayClient(Long channelId, String channelCode, + Config config); +} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/dto/order/PayOrderRespDTO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/dto/order/PayOrderRespDTO.java new file mode 100644 index 0000000..3d6ce86 --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/dto/order/PayOrderRespDTO.java @@ -0,0 +1,141 @@ +package cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.order; + +import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.exception.PayClientException; +import cn.iocoder.yudao.module.pay.framework.pay.core.enums.PayOrderDisplayModeEnum; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 渠道支付订单 Response DTO + * + * @author 芋道源码 + */ +@Data +public class PayOrderRespDTO { + + /** + * 支付状态 + * + * 枚举:{@link PayOrderStatusEnum} + */ + private Integer status; + + /** + * 外部订单号 + * + * 对应 PayOrderExtensionDO 的 no 字段 + */ + private String outTradeNo; + + /** + * 支付渠道编号 + */ + private String channelOrderNo; + /** + * 支付渠道用户编号 + */ + private String channelUserId; + + /** + * 支付成功时间 + */ + private LocalDateTime successTime; + + /** + * 原始的同步/异步通知结果 + */ + private Object rawData; + + // ========== 主动发起支付时,会返回的字段 ========== + + /** + * 展示模式 + * + * 枚举 {@link PayOrderDisplayModeEnum} 类 + */ + private String displayMode; + /** + * 展示内容 + */ + private String displayContent; + + /** + * 调用渠道的错误码 + * + * 注意:这里返回的是业务异常,而是不系统异常。 + * 如果是系统异常,则会抛出 {@link PayClientException} + */ + private String channelErrorCode; + /** + * 调用渠道报错时,错误信息 + */ + private String channelErrorMsg; + + public PayOrderRespDTO() { + } + + /** + * 创建【WAITING】状态的订单返回 + */ + public static PayOrderRespDTO waitingOf(String displayMode, String displayContent, + String outTradeNo, Object rawData) { + PayOrderRespDTO respDTO = new PayOrderRespDTO(); + respDTO.status = PayOrderStatusEnum.WAITING.getStatus(); + respDTO.displayMode = displayMode; + respDTO.displayContent = displayContent; + // 相对通用的字段 + respDTO.outTradeNo = outTradeNo; + respDTO.rawData = rawData; + return respDTO; + } + + /** + * 创建【SUCCESS】状态的订单返回 + */ + public static PayOrderRespDTO successOf(String channelOrderNo, String channelUserId, LocalDateTime successTime, + String outTradeNo, Object rawData) { + PayOrderRespDTO respDTO = new PayOrderRespDTO(); + respDTO.status = PayOrderStatusEnum.SUCCESS.getStatus(); + respDTO.channelOrderNo = channelOrderNo; + respDTO.channelUserId = channelUserId; + respDTO.successTime = successTime; + // 相对通用的字段 + respDTO.outTradeNo = outTradeNo; + respDTO.rawData = rawData; + return respDTO; + } + + /** + * 创建指定状态的订单返回,适合支付渠道回调时 + */ + public static PayOrderRespDTO of(Integer status, String channelOrderNo, String channelUserId, LocalDateTime successTime, + String outTradeNo, Object rawData) { + PayOrderRespDTO respDTO = new PayOrderRespDTO(); + respDTO.status = status; + respDTO.channelOrderNo = channelOrderNo; + respDTO.channelUserId = channelUserId; + respDTO.successTime = successTime; + // 相对通用的字段 + respDTO.outTradeNo = outTradeNo; + respDTO.rawData = rawData; + return respDTO; + } + + /** + * 创建【CLOSED】状态的订单返回,适合调用支付渠道失败时 + */ + public static PayOrderRespDTO closedOf(String channelErrorCode, String channelErrorMsg, + String outTradeNo, Object rawData) { + PayOrderRespDTO respDTO = new PayOrderRespDTO(); + respDTO.status = PayOrderStatusEnum.CLOSED.getStatus(); + respDTO.channelErrorCode = channelErrorCode; + respDTO.channelErrorMsg = channelErrorMsg; + // 相对通用的字段 + respDTO.outTradeNo = outTradeNo; + respDTO.rawData = rawData; + return respDTO; + } + +} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/dto/order/PayOrderUnifiedReqDTO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/dto/order/PayOrderUnifiedReqDTO.java new file mode 100644 index 0000000..ae9fc24 --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/dto/order/PayOrderUnifiedReqDTO.java @@ -0,0 +1,92 @@ +package cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.order; + +import cn.iocoder.yudao.module.pay.framework.pay.core.enums.PayOrderDisplayModeEnum; +import lombok.Data; +import org.hibernate.validator.constraints.Length; +import org.hibernate.validator.constraints.URL; + +import jakarta.validation.constraints.DecimalMin; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import java.time.LocalDateTime; +import java.util.Map; + +/** + * 统一下单 Request DTO + * + * @author 芋道源码 + */ +@Data +public class PayOrderUnifiedReqDTO { + + /** + * 用户 IP + */ + @NotEmpty(message = "用户 IP 不能为空") + private String userIp; + + // ========== 商户相关字段 ========== + + /** + * 外部订单号 + * + * 对应 PayOrderExtensionDO 的 no 字段 + */ + @NotEmpty(message = "外部订单编号不能为空") + private String outTradeNo; + /** + * 商品标题 + */ + @NotEmpty(message = "商品标题不能为空") + @Length(max = 32, message = "商品标题不能超过 32") + private String subject; + /** + * 商品描述信息 + */ + @Length(max = 128, message = "商品描述信息长度不能超过128") + private String body; + /** + * 支付结果的 notify 回调地址 + */ + @NotEmpty(message = "支付结果的回调地址不能为空") + @URL(message = "支付结果的 notify 回调地址必须是 URL 格式") + private String notifyUrl; + /** + * 支付结果的 return 回调地址 + */ + @URL(message = "支付结果的 return 回调地址必须是 URL 格式") + private String returnUrl; + + // ========== 订单相关字段 ========== + + /** + * 支付金额,单位:分 + */ + @NotNull(message = "支付金额不能为空") + @DecimalMin(value = "0", inclusive = false, message = "支付金额必须大于零") + private Integer price; + + /** + * 支付过期时间 + */ + @NotNull(message = "支付过期时间不能为空") + private LocalDateTime expireTime; + + // ========== 拓展参数 ========== + /** + * 支付渠道的额外参数 + * + * 例如说,微信公众号需要传递 openid 参数 + */ + private Map channelExtras; + + /** + * 展示模式 + * + * 如果不传递,则每个支付渠道使用默认的方式 + * + * 枚举 {@link PayOrderDisplayModeEnum} + */ + private String displayMode; + +} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/dto/refund/PayRefundRespDTO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/dto/refund/PayRefundRespDTO.java new file mode 100644 index 0000000..fbe945b --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/dto/refund/PayRefundRespDTO.java @@ -0,0 +1,115 @@ +package cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.refund; + +import cn.iocoder.yudao.module.pay.enums.refund.PayRefundStatusEnum; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.exception.PayClientException; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 渠道退款订单 Response DTO + * + * @author jason + */ +@Data +public class PayRefundRespDTO { + + /** + * 退款状态 + * + * 枚举 {@link PayRefundStatusEnum} + */ + private Integer status; + + /** + * 外部退款号 + * + * 对应 PayRefundDO 的 no 字段 + */ + private String outRefundNo; + + /** + * 渠道退款单号 + * + * 对应 PayRefundDO.channelRefundNo 字段 + */ + private String channelRefundNo; + + /** + * 退款成功时间 + */ + private LocalDateTime successTime; + + /** + * 原始的异步通知结果 + */ + private Object rawData; + + /** + * 调用渠道的错误码 + * + * 注意:这里返回的是业务异常,而是不系统异常。 + * 如果是系统异常,则会抛出 {@link PayClientException} + */ + private String channelErrorCode; + /** + * 调用渠道报错时,错误信息 + */ + private String channelErrorMsg; + + private PayRefundRespDTO() { + } + + /** + * 创建【WAITING】状态的退款返回 + */ + public static PayRefundRespDTO waitingOf(String channelRefundNo, + String outRefundNo, Object rawData) { + PayRefundRespDTO respDTO = new PayRefundRespDTO(); + respDTO.status = PayRefundStatusEnum.WAITING.getStatus(); + respDTO.channelRefundNo = channelRefundNo; + // 相对通用的字段 + respDTO.outRefundNo = outRefundNo; + respDTO.rawData = rawData; + return respDTO; + } + + /** + * 创建【SUCCESS】状态的退款返回 + */ + public static PayRefundRespDTO successOf(String channelRefundNo, LocalDateTime successTime, + String outRefundNo, Object rawData) { + PayRefundRespDTO respDTO = new PayRefundRespDTO(); + respDTO.status = PayRefundStatusEnum.SUCCESS.getStatus(); + respDTO.channelRefundNo = channelRefundNo; + respDTO.successTime = successTime; + // 相对通用的字段 + respDTO.outRefundNo = outRefundNo; + respDTO.rawData = rawData; + return respDTO; + } + + /** + * 创建【FAILURE】状态的退款返回 + */ + public static PayRefundRespDTO failureOf(String outRefundNo, Object rawData) { + return failureOf(null, null, + outRefundNo, rawData); + } + + /** + * 创建【FAILURE】状态的退款返回 + */ + public static PayRefundRespDTO failureOf(String channelErrorCode, String channelErrorMsg, + String outRefundNo, Object rawData) { + PayRefundRespDTO respDTO = new PayRefundRespDTO(); + respDTO.status = PayRefundStatusEnum.FAILURE.getStatus(); + respDTO.channelErrorCode = channelErrorCode; + respDTO.channelErrorMsg = channelErrorMsg; + // 相对通用的字段 + respDTO.outRefundNo = outRefundNo; + respDTO.rawData = rawData; + return respDTO; + } + +} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/dto/refund/PayRefundUnifiedReqDTO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/dto/refund/PayRefundUnifiedReqDTO.java new file mode 100644 index 0000000..08d826c --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/dto/refund/PayRefundUnifiedReqDTO.java @@ -0,0 +1,70 @@ +package cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.refund; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; +import org.hibernate.validator.constraints.URL; + +import jakarta.validation.constraints.DecimalMin; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; + +/** + * 统一 退款 Request DTO + * + * @author jason + */ +@Accessors(chain = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Data +public class PayRefundUnifiedReqDTO { + + /** + * 外部订单号 + * + * 对应 PayOrderExtensionDO 的 no 字段 + */ + @NotEmpty(message = "外部订单编号不能为空") + private String outTradeNo; + + /** + * 外部退款号 + * + * 对应 PayRefundDO 的 no 字段 + */ + @NotEmpty(message = "退款请求单号不能为空") + private String outRefundNo; + + /** + * 退款原因 + */ + @NotEmpty(message = "退款原因不能为空") + private String reason; + + /** + * 支付金额,单位:分 + * + * 目前微信支付在退款的时候,必须传递该字段 + */ + @NotNull(message = "支付金额不能为空") + @DecimalMin(value = "0", inclusive = false, message = "支付金额必须大于零") + private Integer payPrice; + /** + * 退款金额,单位:分 + */ + @NotNull(message = "退款金额不能为空") + @DecimalMin(value = "0", inclusive = false, message = "支付金额必须大于零") + private Integer refundPrice; + + /** + * 退款结果的 notify 回调地址 + */ + @NotEmpty(message = "支付结果的回调地址不能为空") + @URL(message = "支付结果的 notify 回调地址必须是 URL 格式") + private String notifyUrl; + +} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/dto/transfer/PayTransferRespDTO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/dto/transfer/PayTransferRespDTO.java new file mode 100644 index 0000000..81dbc9f --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/dto/transfer/PayTransferRespDTO.java @@ -0,0 +1,116 @@ +package cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.transfer; + +import cn.iocoder.yudao.module.pay.enums.transfer.PayTransferStatusEnum; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 统一转账 Response DTO + * + * @author jason + */ +@Data +public class PayTransferRespDTO { + + /** + * 转账状态 + * + * 关联 {@link PayTransferStatusEnum} + */ + private Integer status; + + /** + * 外部转账单号 + */ + private String outTransferNo; + + /** + * 支付渠道编号 + */ + private String channelTransferNo; + + /** + * 支付成功时间 + */ + private LocalDateTime successTime; + + /** + * 原始的返回结果 + */ + private Object rawData; + + /** + * 调用渠道的错误码 + */ + private String channelErrorCode; + /** + * 调用渠道报错时,错误信息 + */ + private String channelErrorMsg; + + /** + * 渠道 package 信息 + * + * 特殊:目前只有微信转账有这个东西!!! + * @see JSAPI 调起用户确认收款 + */ + private String channelPackageInfo; + + /** + * 创建【WAITING】状态的转账返回 + */ + public static PayTransferRespDTO waitingOf(String channelTransferNo, + String outTransferNo, Object rawData) { + PayTransferRespDTO respDTO = new PayTransferRespDTO(); + respDTO.status = PayTransferStatusEnum.WAITING.getStatus(); + respDTO.channelTransferNo = channelTransferNo; + respDTO.outTransferNo = outTransferNo; + respDTO.rawData = rawData; + return respDTO; + } + + /** + * 创建【IN_PROGRESS】状态的转账返回 + */ + public static PayTransferRespDTO processingOf(String channelTransferNo, + String outTransferNo, Object rawData) { + PayTransferRespDTO respDTO = new PayTransferRespDTO(); + respDTO.status = PayTransferStatusEnum.PROCESSING.getStatus(); + respDTO.channelTransferNo = channelTransferNo; + respDTO.outTransferNo = outTransferNo; + respDTO.rawData = rawData; + return respDTO; + } + + /** + * 创建【CLOSED】状态的转账返回 + */ + public static PayTransferRespDTO closedOf(String channelErrorCode, String channelErrorMsg, + String outTransferNo, Object rawData) { + PayTransferRespDTO respDTO = new PayTransferRespDTO(); + respDTO.status = PayTransferStatusEnum.CLOSED.getStatus(); + respDTO.channelErrorCode = channelErrorCode; + respDTO.channelErrorMsg = channelErrorMsg; + // 相对通用的字段 + respDTO.outTransferNo = outTransferNo; + respDTO.rawData = rawData; + return respDTO; + } + + /** + * 创建【SUCCESS】状态的转账返回 + */ + public static PayTransferRespDTO successOf(String channelTransferNo, LocalDateTime successTime, + String outTransferNo, Object rawData) { + PayTransferRespDTO respDTO = new PayTransferRespDTO(); + respDTO.status = PayTransferStatusEnum.SUCCESS.getStatus(); + respDTO.channelTransferNo = channelTransferNo; + respDTO.successTime = successTime; + // 相对通用的字段 + respDTO.outTransferNo = outTransferNo; + respDTO.rawData = rawData; + return respDTO; + } + +} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/dto/transfer/PayTransferUnifiedReqDTO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/dto/transfer/PayTransferUnifiedReqDTO.java new file mode 100644 index 0000000..d7ea772 --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/dto/transfer/PayTransferUnifiedReqDTO.java @@ -0,0 +1,73 @@ +package cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.transfer; + +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import org.hibernate.validator.constraints.Length; +import org.hibernate.validator.constraints.URL; + +import java.util.Map; + +/** + * 统一转账 Request DTO + * + * @author jason + */ +@Data +public class PayTransferUnifiedReqDTO { + + /** + * 用户 IP + */ + @NotEmpty(message = "用户 IP 不能为空") + private String userIp; + + /** + * 外部转账单编号 + */ + @NotEmpty(message = "外部转账单编号不能为空") + private String outTransferNo; + + /** + * 转账金额,单位:分 + */ + @NotNull(message = "转账金额不能为空") + @Min(value = 1, message = "转账金额必须大于零") + private Integer price; + + /** + * 转账标题 + */ + @NotEmpty(message = "转账标题不能为空") + @Length(max = 128, message = "转账标题不能超过 128") + private String subject; + + /** + * 收款人账号 + * + * 微信场景下:openid + * 支付宝场景下:支付宝账号 + */ + @NotEmpty(message = "收款人账号不能为空") + private String userAccount; + /** + * 收款人姓名 + */ + private String userName; + + /** + * 支付渠道的额外参数 + * + * 微信支付:sceneId 和 scene_report_infos 字段,必须传递;参考 按转账场景报备背景信息 + */ + private Map channelExtras; + + /** + * 转账结果的 notify 回调地址 + */ + @NotEmpty(message = "转账结果的回调地址不能为空") + @URL(message = "转账结果的 notify 回调地址必须是 URL 格式") + private String notifyUrl; + +} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/exception/PayClientException.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/exception/PayClientException.java new file mode 100644 index 0000000..8cedc4e --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/exception/PayClientException.java @@ -0,0 +1,17 @@ +package cn.iocoder.yudao.module.pay.framework.pay.core.client.exception; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 支付系统异常 Exception + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class PayClientException extends RuntimeException { + + public PayClientException(Throwable cause) { + super(cause); + } + +} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/AbstractPayClient.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/AbstractPayClient.java new file mode 100644 index 0000000..fce355e --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/AbstractPayClient.java @@ -0,0 +1,251 @@ +package cn.iocoder.yudao.module.pay.framework.pay.core.client.impl; + +import cn.iocoder.yudao.framework.common.exception.ServiceException; +import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.PayClient; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.PayClientConfig; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.order.PayOrderRespDTO; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.refund.PayRefundRespDTO; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.transfer.PayTransferRespDTO; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.transfer.PayTransferUnifiedReqDTO; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.exception.PayClientException; +import lombok.extern.slf4j.Slf4j; + +import java.util.Map; + +import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString; + +/** + * 支付客户端的抽象类,提供模板方法,减少子类的冗余代码 + * + * @author 芋道源码 + */ +@Slf4j +public abstract class AbstractPayClient implements PayClient { + + /** + * 渠道编号 + */ + private final Long channelId; + /** + * 渠道编码 + */ + @SuppressWarnings("FieldCanBeLocal") + private final String channelCode; + /** + * 支付配置 + */ + protected Config config; + + public AbstractPayClient(Long channelId, String channelCode, Config config) { + this.channelId = channelId; + this.channelCode = channelCode; + this.config = config; + } + + /** + * 初始化 + */ + public final void init() { + doInit(); + log.debug("[init][客户端({}) 初始化完成]", getId()); + } + + /** + * 自定义初始化 + */ + protected abstract void doInit(); + + public final void refresh(Config config) { + // 判断是否更新 + if (config.equals(this.config)) { + return; + } + log.info("[refresh][客户端({})发生变化,重新初始化]", getId()); + this.config = config; + // 初始化 + this.init(); + } + + @Override + public Long getId() { + return channelId; + } + + @Override + public Config getConfig() { + return config; + } + + // ============ 支付相关 ========== + + @Override + public final PayOrderRespDTO unifiedOrder(PayOrderUnifiedReqDTO reqDTO) { + ValidationUtils.validate(reqDTO); + // 执行统一下单 + PayOrderRespDTO resp; + try { + resp = doUnifiedOrder(reqDTO); + } catch (ServiceException ex) { // 业务异常,都是实现类已经翻译,所以直接抛出即可 + throw ex; + } catch (Throwable ex) { + // 系统异常,则包装成 PayException 异常抛出 + log.error("[unifiedOrder][客户端({}) request({}) 发起支付异常]", + getId(), toJsonString(reqDTO), ex); + throw buildPayException(ex); + } + return resp; + } + + protected abstract PayOrderRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) + throws Throwable; + + @Override + public final PayOrderRespDTO parseOrderNotify(Map params, String body, Map headers) { + try { + return doParseOrderNotify(params, body, headers); + } catch (ServiceException ex) { // 业务异常,都是实现类已经翻译,所以直接抛出即可 + throw ex; + } catch (Throwable ex) { + log.error("[parseOrderNotify][客户端({}) params({}) body({}) headers({}) 解析失败]", + getId(), params, body, headers, ex); + throw buildPayException(ex); + } + } + + protected abstract PayOrderRespDTO doParseOrderNotify(Map params, String body, Map headers) + throws Throwable; + + @Override + public final PayOrderRespDTO getOrder(String outTradeNo) { + try { + return doGetOrder(outTradeNo); + } catch (ServiceException ex) { // 业务异常,都是实现类已经翻译,所以直接抛出即可 + throw ex; + } catch (Throwable ex) { + log.error("[getOrder][客户端({}) outTradeNo({}) 查询支付单异常]", + getId(), outTradeNo, ex); + throw buildPayException(ex); + } + } + + protected abstract PayOrderRespDTO doGetOrder(String outTradeNo) + throws Throwable; + + // ============ 退款相关 ========== + + @Override + public final PayRefundRespDTO unifiedRefund(PayRefundUnifiedReqDTO reqDTO) { + ValidationUtils.validate(reqDTO); + // 执行统一退款 + PayRefundRespDTO resp; + try { + resp = doUnifiedRefund(reqDTO); + } catch (ServiceException ex) { // 业务异常,都是实现类已经翻译,所以直接抛出即可 + throw ex; + } catch (Throwable ex) { + // 系统异常,则包装成 PayException 异常抛出 + log.error("[unifiedRefund][客户端({}) request({}) 发起退款异常]", + getId(), toJsonString(reqDTO), ex); + throw buildPayException(ex); + } + return resp; + } + + protected abstract PayRefundRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws Throwable; + + @Override + public final PayRefundRespDTO parseRefundNotify(Map params, String body, Map headers) { + try { + return doParseRefundNotify(params, body, headers); + } catch (ServiceException ex) { // 业务异常,都是实现类已经翻译,所以直接抛出即可 + throw ex; + } catch (Throwable ex) { + log.error("[parseRefundNotify][客户端({}) params({}) body({}) headers({}) 解析失败]", + getId(), params, body, headers, ex); + throw buildPayException(ex); + } + } + + protected abstract PayRefundRespDTO doParseRefundNotify(Map params, String body, Map headers) + throws Throwable; + + @Override + public final PayRefundRespDTO getRefund(String outTradeNo, String outRefundNo) { + try { + return doGetRefund(outTradeNo, outRefundNo); + } catch (ServiceException ex) { // 业务异常,都是实现类已经翻译,所以直接抛出即可 + throw ex; + } catch (Throwable ex) { + log.error("[getRefund][客户端({}) outTradeNo({}) outRefundNo({}) 查询退款单异常]", + getId(), outTradeNo, outRefundNo, ex); + throw buildPayException(ex); + } + } + + protected abstract PayRefundRespDTO doGetRefund(String outTradeNo, String outRefundNo) + throws Throwable; + + @Override + public final PayTransferRespDTO unifiedTransfer(PayTransferUnifiedReqDTO reqDTO) { + PayTransferRespDTO resp; + try { + resp = doUnifiedTransfer(reqDTO); + } catch (ServiceException ex) { // 业务异常,都是实现类已经翻译,所以直接抛出即可 + throw ex; + } catch (Throwable ex) { + // 系统异常,则包装成 PayException 异常抛出 + log.error("[unifiedTransfer][客户端({}) request({}) 发起转账异常]", + getId(), toJsonString(reqDTO), ex); + throw buildPayException(ex); + } + return resp; + } + + @Override + public final PayTransferRespDTO parseTransferNotify(Map params, String body, Map headers) { + try { + return doParseTransferNotify(params, body, headers); + } catch (ServiceException ex) { // 业务异常,都是实现类已经翻译,所以直接抛出即可 + throw ex; + } catch (Throwable ex) { + log.error("[doParseTransferNotify][客户端({}) params({}) body({}) headers({}) 解析失败]", + getId(), params, body, headers, ex); + throw buildPayException(ex); + } + } + + protected abstract PayTransferRespDTO doParseTransferNotify(Map params, String body, Map headers) + throws Throwable; + + @Override + public final PayTransferRespDTO getTransfer(String outTradeNo) { + try { + return doGetTransfer(outTradeNo); + } catch (ServiceException ex) { // 业务异常,都是实现类已经翻译,所以直接抛出即可 + throw ex; + } catch (Throwable ex) { + log.error("[getTransfer][客户端({}) outTradeNo({}) 查询转账单异常]", + getId(), outTradeNo, ex); + throw buildPayException(ex); + } + } + + protected abstract PayTransferRespDTO doUnifiedTransfer(PayTransferUnifiedReqDTO reqDTO) + throws Throwable; + + protected abstract PayTransferRespDTO doGetTransfer(String outTradeNo) + throws Throwable; + + // ========== 各种工具方法 ========== + + private PayClientException buildPayException(Throwable ex) { + if (ex instanceof PayClientException) { + return (PayClientException) ex; + } + throw new PayClientException(ex); + } + +} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/NonePayClientConfig.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/NonePayClientConfig.java new file mode 100644 index 0000000..d4b352f --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/NonePayClientConfig.java @@ -0,0 +1,31 @@ +package cn.iocoder.yudao.module.pay.framework.pay.core.client.impl; + +import cn.iocoder.yudao.module.pay.framework.pay.core.client.PayClientConfig; +import lombok.Data; + +import jakarta.validation.Validator; + +/** + * 无需任何配置 PayClientConfig 实现类 + * + * @author jason + */ +@Data +public class NonePayClientConfig implements PayClientConfig { + + /** + * 配置名称 + *

+ * 如果不加任何属性,JsonUtils.parseObject2 解析会报错,所以暂时加个名称 + */ + private String name; + + public NonePayClientConfig(){ + this.name = "none-config"; + } + + @Override + public void validate(Validator validator) { + // 无任何配置不需要校验 + } +} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/PayClientFactoryImpl.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/PayClientFactoryImpl.java new file mode 100644 index 0000000..6b907cd --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/PayClientFactoryImpl.java @@ -0,0 +1,97 @@ +package cn.iocoder.yudao.module.pay.framework.pay.core.client.impl; + +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.ReflectUtil; +import cn.hutool.core.util.TypeUtil; +import cn.iocoder.yudao.module.pay.enums.PayChannelEnum; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.PayClient; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.PayClientConfig; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.PayClientFactory; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.impl.alipay.*; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.impl.wallet.WalletPayClient; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.impl.weixin.*; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.impl.mock.MockPayClient; +import lombok.extern.slf4j.Slf4j; + +import java.lang.reflect.Type; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import static cn.iocoder.yudao.module.pay.enums.PayChannelEnum.*; + +/** + * 支付客户端的工厂实现类 + * + * @author 芋道源码 + */ +@Slf4j +public class PayClientFactoryImpl implements PayClientFactory { + + /** + * 支付客户端 Map + * + * key:渠道编号 + */ + private final ConcurrentMap> clients = new ConcurrentHashMap<>(); + + /** + * 支付客户端 Class Map + */ + private final Map>> 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(WX_WAP, WxWapPayClient.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); + // 钱包支付客户端 + clientClass.put(WALLET, WalletPayClient.class); + // Mock 支付客户端 + clientClass.put(MOCK, MockPayClient.class); + } + + @Override + public PayClient getPayClient(Long channelId) { + AbstractPayClient client = clients.get(channelId); + if (client == null) { + log.error("[getPayClient][渠道编号({}) 找不到客户端]", channelId); + } + return client; + } + + @Override + @SuppressWarnings("unchecked") + public PayClient createOrUpdatePayClient(Long channelId, String channelCode, + Config config) { + AbstractPayClient client = (AbstractPayClient) clients.get(channelId); + if (client == null) { + client = this.createPayClient(channelId, channelCode, config); + client.init(); + clients.put(client.getId(), client); + } else { + client.refresh(config); + } + return client; + } + + @SuppressWarnings("unchecked") + private AbstractPayClient createPayClient(Long channelId, String channelCode, + Config config) { + PayChannelEnum channelEnum = PayChannelEnum.getByCode(channelCode); + Assert.notNull(channelEnum, String.format("支付渠道(%s) 为空", channelCode)); + Class payClientClass = clientClass.get(channelEnum); + Assert.notNull(payClientClass, String.format("支付渠道(%s) Class 为空", channelCode)); + return (AbstractPayClient) ReflectUtil.newInstance(payClientClass, channelId, config); + } + +} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AbstractAlipayPayClient.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AbstractAlipayPayClient.java new file mode 100644 index 0000000..fee7cdd --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AbstractAlipayPayClient.java @@ -0,0 +1,379 @@ +package cn.iocoder.yudao.module.pay.framework.pay.core.client.impl.alipay; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.date.LocalDateTimeUtil; +import cn.hutool.core.lang.Assert; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.http.HttpUtil; +import cn.iocoder.yudao.framework.common.util.json.JsonUtils; +import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; +import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.order.PayOrderRespDTO; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.refund.PayRefundRespDTO; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.transfer.PayTransferRespDTO; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.transfer.PayTransferUnifiedReqDTO; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.impl.AbstractPayClient; +import com.alipay.api.AlipayApiException; +import com.alipay.api.AlipayConfig; +import com.alipay.api.AlipayResponse; +import com.alipay.api.DefaultAlipayClient; +import com.alipay.api.domain.*; +import com.alipay.api.internal.util.AlipaySignature; +import com.alipay.api.internal.util.AntCertificationUtil; +import com.alipay.api.internal.util.codec.Base64; +import com.alipay.api.request.*; +import com.alipay.api.response.*; +import lombok.Getter; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; + +import java.nio.charset.StandardCharsets; +import java.security.cert.X509Certificate; +import java.time.LocalDateTime; +import java.util.Collections; +import java.util.Map; +import java.util.Objects; +import java.util.function.Supplier; + +import static cn.hutool.core.date.DatePattern.NORM_DATETIME_FORMATTER; +import static cn.iocoder.yudao.module.pay.framework.pay.core.client.impl.alipay.AlipayPayClientConfig.MODE_CERTIFICATE; +import static cn.iocoder.yudao.module.pay.framework.pay.core.client.impl.alipay.AlipayPayClientConfig.MODE_PUBLIC_KEY; + +/** + * 支付宝抽象类,实现支付宝统一的接口、以及部分实现(退款) + * + * @author jason + */ +@Slf4j +public abstract class AbstractAlipayPayClient extends AbstractPayClient { + + @Getter // 仅用于单测场景 + protected DefaultAlipayClient client; + + public AbstractAlipayPayClient(Long channelId, String channelCode, AlipayPayClientConfig config) { + super(channelId, channelCode, config); + } + + @Override + @SneakyThrows + protected void doInit() { + AlipayConfig alipayConfig = new AlipayConfig(); + BeanUtil.copyProperties(config, alipayConfig, false); + this.client = new DefaultAlipayClient(alipayConfig); + } + + // ============ 支付相关 ========== + + /** + * 构造支付关闭的 {@link PayOrderRespDTO} 对象 + * + * @return 支付关闭的 {@link PayOrderRespDTO} 对象 + */ + protected PayOrderRespDTO buildClosedPayOrderRespDTO(PayOrderUnifiedReqDTO reqDTO, AlipayResponse response) { + Assert.isFalse(response.isSuccess()); + return PayOrderRespDTO.closedOf(response.getSubCode(), response.getSubMsg(), + reqDTO.getOutTradeNo(), response); + } + + @Override + public PayOrderRespDTO doParseOrderNotify(Map params, String body, Map headers) throws Throwable { + // 1. 校验回调数据 + verifyNotifyData(params); + + // 2. 解析订单的状态 + // 额外说明:支付宝不仅仅支付成功会回调,再各种触发支付单数据变化时,都会进行回调,所以这里 status 的解析会写的比较复杂 + Map bodyObj = HttpUtil.decodeParamMap(body, StandardCharsets.UTF_8); + Integer status = parseStatus(bodyObj.get("trade_status")); + // 特殊逻辑: 支付宝没有退款成功的状态,所以,如果有退款金额,我们认为是退款成功 + if (MapUtil.getDouble(bodyObj, "refund_fee", 0D) > 0) { + status = PayOrderStatusEnum.REFUND.getStatus(); + } + Assert.notNull(status, (Supplier) () -> { + throw new IllegalArgumentException(StrUtil.format("body({}) 的 trade_status 不正确", body)); + }); + return PayOrderRespDTO.of(status, bodyObj.get("trade_no"), bodyObj.get("seller_id"), parseTime(params.get("gmt_payment")), + bodyObj.get("out_trade_no"), body); + } + + @Override + protected PayOrderRespDTO doGetOrder(String outTradeNo) throws Throwable { + // 1.1 构建 AlipayTradeRefundModel 请求 + AlipayTradeQueryModel model = new AlipayTradeQueryModel(); + model.setOutTradeNo(outTradeNo); + // 1.2 构建 AlipayTradeQueryRequest 请求 + AlipayTradeQueryRequest request = new AlipayTradeQueryRequest(); + request.setBizModel(model); + AlipayTradeQueryResponse response; + if (Objects.equals(config.getMode(), MODE_CERTIFICATE)) { + // 证书模式 + response = client.certificateExecute(request); + } else { + response = client.execute(request); + } + if (!response.isSuccess()) { // 不成功,例如说订单不存在 + return PayOrderRespDTO.closedOf(response.getSubCode(), response.getSubMsg(), + outTradeNo, response); + } + // 2.2 解析订单的状态 + Integer status = parseStatus(response.getTradeStatus()); + Assert.notNull(status, () -> { + throw new IllegalArgumentException(StrUtil.format("body({}) 的 trade_status 不正确", response.getBody())); + }); + return PayOrderRespDTO.of(status, response.getTradeNo(), response.getBuyerUserId(), LocalDateTimeUtil.of(response.getSendPayDate()), + outTradeNo, response); + } + + private static Integer parseStatus(String tradeStatus) { + return Objects.equals("WAIT_BUYER_PAY", tradeStatus) ? PayOrderStatusEnum.WAITING.getStatus() + : ObjectUtils.equalsAny(tradeStatus, "TRADE_FINISHED", "TRADE_SUCCESS") ? PayOrderStatusEnum.SUCCESS.getStatus() + : Objects.equals("TRADE_CLOSED", tradeStatus) ? PayOrderStatusEnum.CLOSED.getStatus() : null; + } + + // ============ 退款相关 ========== + + /** + * 支付宝统一的退款接口 alipay.trade.refund + * + * @param reqDTO 退款请求 request DTO + * @return 退款请求 Response + */ + @Override + protected PayRefundRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws AlipayApiException { + // 1.1 构建 AlipayTradeRefundModel 请求 + AlipayTradeRefundModel model = new AlipayTradeRefundModel(); + model.setOutTradeNo(reqDTO.getOutTradeNo()); + model.setOutRequestNo(reqDTO.getOutRefundNo()); + model.setRefundAmount(formatAmount(reqDTO.getRefundPrice())); + model.setRefundReason(reqDTO.getReason()); + // 1.2 构建 AlipayTradePayRequest 请求 + AlipayTradeRefundRequest request = new AlipayTradeRefundRequest(); + request.setBizModel(model); + + // 2.1 执行请求 + AlipayTradeRefundResponse response; + if (Objects.equals(config.getMode(), MODE_CERTIFICATE)) { // 证书模式 + response = client.certificateExecute(request); + } else { + response = client.execute(request); + } + if (!response.isSuccess()) { + // 当出现 ACQ.SYSTEM_ERROR, 退款可能成功也可能失败。 返回 WAIT 状态. 后续 job 会轮询 + if (ObjectUtils.equalsAny(response.getSubCode(), "ACQ.SYSTEM_ERROR", "SYSTEM_ERROR")) { + return PayRefundRespDTO.waitingOf(null, reqDTO.getOutRefundNo(), response); + } + return PayRefundRespDTO.failureOf(response.getSubCode(), response.getSubMsg(), reqDTO.getOutRefundNo(), response); + } + // 2.2 创建返回结果 + // 支付宝只要退款调用返回 success,就认为退款成功,不需要回调。具体可见 parseNotify 方法的说明。 + // 另外,支付宝没有退款单号,所以不用设置 + return PayRefundRespDTO.successOf(null, LocalDateTimeUtil.of(response.getGmtRefundPay()), + reqDTO.getOutRefundNo(), response); + } + + @Override + public PayRefundRespDTO doParseRefundNotify(Map params, String body, Map headers) { + // 补充说明:支付宝退款时,没有回调,这点和微信支付是不同的。并且,退款分成部分退款、和全部退款。 + // ① 部分退款:是会有回调,但是它回调的是订单状态的同步回调,不是退款订单的回调 + // ② 全部退款:Wap 支付有订单状态的同步回调,但是 PC/扫码又没有 + // 所以,这里在解析时,即使是退款导致的订单状态同步,我们也忽略不做为“退款同步”,而是订单的回调。 + // 实际上,支付宝退款只要发起成功,就可以认为退款成功,不需要等待回调。 + throw new UnsupportedOperationException("支付宝无退款回调"); + } + + @Override + protected PayRefundRespDTO doGetRefund(String outTradeNo, String outRefundNo) throws AlipayApiException { + // 1.1 构建 AlipayTradeFastpayRefundQueryModel 请求 + AlipayTradeFastpayRefundQueryModel model = new AlipayTradeFastpayRefundQueryModel(); + model.setOutTradeNo(outTradeNo); + model.setOutRequestNo(outRefundNo); + model.setQueryOptions(Collections.singletonList("gmt_refund_pay")); + // 1.2 构建 AlipayTradeFastpayRefundQueryRequest 请求 + AlipayTradeFastpayRefundQueryRequest request = new AlipayTradeFastpayRefundQueryRequest(); + request.setBizModel(model); + + // 2.1 执行请求 + AlipayTradeFastpayRefundQueryResponse response; + if (Objects.equals(config.getMode(), MODE_CERTIFICATE)) { // 证书模式 + response = client.certificateExecute(request); + } else { + response = client.execute(request); + } + if (!response.isSuccess()) { + // 明确不存在的情况,应该就是失败,可进行关闭 + if (ObjectUtils.equalsAny(response.getSubCode(), "TRADE_NOT_EXIST", "ACQ.TRADE_NOT_EXIST")) { + return PayRefundRespDTO.failureOf(outRefundNo, response); + } + // 可能存在“ACQ.SYSTEM_ERROR”系统错误等情况,所以返回 WAIT 继续等待 + return PayRefundRespDTO.waitingOf(null, outRefundNo, response); + } + // 2.2 创建返回结果 + if (Objects.equals(response.getRefundStatus(), "REFUND_SUCCESS")) { + return PayRefundRespDTO.successOf(null, LocalDateTimeUtil.of(response.getGmtRefundPay()), + outRefundNo, response); + } + return PayRefundRespDTO.waitingOf(null, outRefundNo, response); + } + + @Override + protected PayTransferRespDTO doUnifiedTransfer(PayTransferUnifiedReqDTO reqDTO) throws AlipayApiException { + // 补充说明:https://opendocs.alipay.com/open/03dcrm?pathHash=4ba3b20b + // 沙箱环境:可通过 公钥模式 或 公钥证书模式 加签进行调试 + // 生产环境:必须使用 公钥证书模式 加签请求强校验请求 + + // 1.1 构建 AlipayFundTransUniTransferModel + AlipayFundTransUniTransferModel model = new AlipayFundTransUniTransferModel(); + // ① 通用的参数 + model.setTransAmount(formatAmount(reqDTO.getPrice())); // 转账金额 + model.setOrderTitle(reqDTO.getSubject()); // 转账业务的标题,用于在支付宝用户的账单里显示。 + model.setOutBizNo(reqDTO.getOutTransferNo()); + model.setProductCode("TRANS_ACCOUNT_NO_PWD"); // 销售产品码。单笔无密转账固定为 TRANS_ACCOUNT_NO_PWD + model.setBizScene("DIRECT_TRANSFER"); // 业务场景 单笔无密转账固定为 DIRECT_TRANSFER + if (reqDTO.getChannelExtras() != null) { + model.setBusinessParams(JsonUtils.toJsonString(reqDTO.getChannelExtras())); + } + // ② 个性化的参数 + Participant payeeInfo = new Participant(); + payeeInfo.setIdentityType("ALIPAY_LOGON_ID"); // 暂时只考虑转账到支付宝,银行没有权限 https://opendocs.alipay.com/open/02byvc?scene=66dd06f5a923403393b85de68d3c0055 + payeeInfo.setIdentity(reqDTO.getUserAccount()); // 支付宝登录号 + payeeInfo.setName(reqDTO.getUserName()); // 支付宝账号姓名 + model.setPayeeInfo(payeeInfo); + // 1.2 构建 AlipayFundTransUniTransferRequest + AlipayFundTransUniTransferRequest request = new AlipayFundTransUniTransferRequest(); + request.setBizModel(model); + + // 2.1 执行请求 + AlipayFundTransUniTransferResponse response; + if (Objects.equals(config.getMode(), MODE_CERTIFICATE)) { // 证书模式 + response = client.certificateExecute(request); + } else { + response = client.execute(request); + } + if (!response.isSuccess()) { + // 当出现 SYSTEM_ERROR, 转账可能成功也可能失败。 返回 WAIT 状态. 后续 job 会轮询,或相同 outBizNo 重新发起转账 + // 发现 outBizNo 相同 两次请求参数相同. 会返回 "PAYMENT_INFO_INCONSISTENCY", 不知道哪里的问题. 暂时返回 WAIT. 后续job 会轮询 + if (ObjectUtils.equalsAny(response.getSubCode(),"PAYMENT_INFO_INCONSISTENCY", "SYSTEM_ERROR", "ACQ.SYSTEM_ERROR")) { + return PayTransferRespDTO.waitingOf(null, reqDTO.getOutTransferNo(), response); + } + return PayTransferRespDTO.closedOf(response.getSubCode(), response.getSubMsg(), + reqDTO.getOutTransferNo(), response); + } + // 2.2 处理结果 + if (ObjectUtils.equalsAny(response.getStatus(), "REFUND", "FAIL")) { // 转账到银行卡会出现 "REFUND" "FAIL" + return PayTransferRespDTO.closedOf(response.getSubCode(), response.getSubMsg(), + reqDTO.getOutTransferNo(), response); + } + if (Objects.equals(response.getStatus(), "DEALING")) { // 转账到银行卡会出现 "DEALING" 处理中 + return PayTransferRespDTO.processingOf(response.getOrderId(), reqDTO.getOutTransferNo(), response); + } + return PayTransferRespDTO.successOf(response.getOrderId(), parseTime(response.getTransDate()), + response.getOutBizNo(), response); + } + + @Override + protected PayTransferRespDTO doGetTransfer(String outTradeNo) throws Throwable { + // 1.1 构建 AlipayFundTransCommonQueryModel + AlipayFundTransCommonQueryModel model = new AlipayFundTransCommonQueryModel(); + model.setProductCode("TRANS_ACCOUNT_NO_PWD"); + model.setBizScene("DIRECT_TRANSFER"); //业务场景 + model.setOutBizNo(outTradeNo); + // 1.2 构建 AlipayFundTransCommonQueryRequest + AlipayFundTransCommonQueryRequest request = new AlipayFundTransCommonQueryRequest(); + request.setBizModel(model); + + // 2.1 执行请求 + AlipayFundTransCommonQueryResponse response; + if (Objects.equals(config.getMode(), MODE_CERTIFICATE)) { // 证书模式 + response = client.certificateExecute(request); + } else { + response = client.execute(request); + } + if (!response.isSuccess()) { + // 当出现 SYSTEM_ERROR, 转账可能成功也可能失败。 返回 WAIT 状态. 后续 job 会轮询, 或相同 outBizNo 重新发起转账 + // 当出现 ORDER_NOT_EXIST 可能是转账还在处理中,也可能是转账处理失败. 返回 WAIT 状态. 后续 job 会轮询, 或相同 outBizNo 重新发起转账 + if (ObjectUtils.equalsAny(response.getSubCode(), "ORDER_NOT_EXIST", "SYSTEM_ERROR", "ACQ.SYSTEM_ERROR")) { + return PayTransferRespDTO.waitingOf(null, outTradeNo, response); + } + return PayTransferRespDTO.closedOf(response.getSubCode(), response.getSubMsg(), + outTradeNo, response); + } + // 2.2 处理返回结果 + if (ObjectUtils.equalsAny(response.getStatus(), "REFUND", "FAIL")) { // 转账到银行卡会出现 "REFUND" "FAIL" + return PayTransferRespDTO.closedOf(response.getSubCode(), response.getSubMsg(), + outTradeNo, response); + } + if (Objects.equals(response.getStatus(), "DEALING")) { // 转账到银行卡会出现 "DEALING" 处理中 + return PayTransferRespDTO.processingOf(response.getOrderId(), outTradeNo, response); + } + return PayTransferRespDTO.successOf(response.getOrderId(), parseTime(response.getPayDate()), + response.getOutBizNo(), response); + } + + // TODO @芋艿:由于支付宝一直没触发回调,这个方法暂时没办法测试 + @Override + protected PayTransferRespDTO doParseTransferNotify(Map params, String body, Map headers) + throws Throwable { + // 1. 校验回调数据 + verifyNotifyData(params); + + // 2. 解析转账状态 + Map bodyObj = HttpUtil.decodeParamMap(body, StandardCharsets.UTF_8); + String status = bodyObj.get("status"); + String outBizNo = bodyObj.get("out_biz_no"); + String orderId = bodyObj.get("order_id"); + String payDate = bodyObj.get("pay_date"); + + // 3. 根据状态返回对应的结果 + if (Objects.equals(status, "SUCCESS")) { + return PayTransferRespDTO.successOf(orderId, parseTime(payDate), outBizNo, bodyObj); + } + if (Objects.equals(status, "DEALING")) { + return PayTransferRespDTO.processingOf(orderId, outBizNo, bodyObj); + } + if (ObjectUtils.equalsAny(status, "REFUND", "FAIL")) { + return PayTransferRespDTO.closedOf(bodyObj.get("sub_code"), bodyObj.get("sub_msg"), + outBizNo, bodyObj); + } + return PayTransferRespDTO.waitingOf(orderId, outBizNo, bodyObj); + } + + /** + * 校验回调数据 + * + * @param params 回调参数 + * @throws Throwable 验签失败时抛出异常 + */ + protected void verifyNotifyData(Map params) throws Throwable { + boolean verify; + if (Objects.equals(config.getMode(), MODE_PUBLIC_KEY)) { + verify = AlipaySignature.rsaCheckV1(params, config.getAlipayPublicKey(), + StandardCharsets.UTF_8.name(), config.getSignType()); + } else if (Objects.equals(config.getMode(), MODE_CERTIFICATE)) { + // 由于 rsaCertCheckV1 的第二个参数是 path,所以不能这么调用!!!通过阅读源码,发现可以采用如下方式! + X509Certificate cert = AntCertificationUtil.getCertFromContent(config.getAlipayPublicCertContent()); + String publicKey = Base64.encodeBase64String(cert.getEncoded()); + verify = AlipaySignature.rsaCheckV1(params, publicKey, + StandardCharsets.UTF_8.name(), config.getSignType()); + } else { + throw new IllegalArgumentException("未知的公钥类型:" + config.getMode()); + } + Assert.isTrue(verify, "验签结果不通过"); + } + + // ========== 各种工具方法 ========== + + protected String formatAmount(Integer amount) { + return String.valueOf(amount / 100.0); + } + + protected String formatTime(LocalDateTime time) { + return LocalDateTimeUtil.format(time, NORM_DATETIME_FORMATTER); + } + + protected LocalDateTime parseTime(String str) { + return LocalDateTimeUtil.parse(str, NORM_DATETIME_FORMATTER); + } + +} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AlipayAppPayClient.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AlipayAppPayClient.java new file mode 100644 index 0000000..ff878b1 --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AlipayAppPayClient.java @@ -0,0 +1,60 @@ +package cn.iocoder.yudao.module.pay.framework.pay.core.client.impl.alipay; + +import cn.iocoder.yudao.module.pay.enums.PayChannelEnum; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.order.PayOrderRespDTO; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO; +import cn.iocoder.yudao.module.pay.framework.pay.core.enums.PayOrderDisplayModeEnum; +import com.alipay.api.AlipayApiException; +import com.alipay.api.domain.AlipayTradeAppPayModel; +import com.alipay.api.request.AlipayTradeAppPayRequest; +import com.alipay.api.response.AlipayTradeAppPayResponse; +import lombok.extern.slf4j.Slf4j; + +/** + * 支付宝【App 支付】的 PayClient 实现类 + * + * 文档:App 支付 + * + * // TODO 芋艿:未详细测试,因为手头没 App + * + * @author 芋道源码 + */ +@Slf4j +public class AlipayAppPayClient extends AbstractAlipayPayClient { + + public AlipayAppPayClient(Long channelId, AlipayPayClientConfig config) { + super(channelId, PayChannelEnum.ALIPAY_APP.getCode(), config); + } + + @Override + public PayOrderRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) throws AlipayApiException { + // 1.1 构建 AlipayTradeAppPayModel 请求 + AlipayTradeAppPayModel model = new AlipayTradeAppPayModel(); + // ① 通用的参数 + model.setOutTradeNo(reqDTO.getOutTradeNo()); + model.setSubject(reqDTO.getSubject()); + model.setBody(reqDTO.getBody() + "test"); + model.setTotalAmount(formatAmount(reqDTO.getPrice())); + model.setTimeExpire(formatTime(reqDTO.getExpireTime())); + model.setProductCode("QUICK_MSECURITY_PAY"); // 销售产品码:无线快捷支付产品 + // ② 个性化的参数【无】 + // ③ 支付宝扫码支付只有一种展示 + String displayMode = PayOrderDisplayModeEnum.APP.getMode(); + + // 1.2 构建 AlipayTradePrecreateRequest 请求 + AlipayTradeAppPayRequest request = new AlipayTradeAppPayRequest(); + request.setBizModel(model); + request.setNotifyUrl(reqDTO.getNotifyUrl()); + request.setReturnUrl(reqDTO.getReturnUrl()); + + // 2.1 执行请求 + AlipayTradeAppPayResponse response = client.sdkExecute(request); + // 2.2 处理结果 + if (!response.isSuccess()) { + return buildClosedPayOrderRespDTO(reqDTO, response); + } + return PayOrderRespDTO.waitingOf(displayMode, response.getBody(), + reqDTO.getOutTradeNo(), response); + } + +} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AlipayBarPayClient.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AlipayBarPayClient.java new file mode 100644 index 0000000..2f5245f --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AlipayBarPayClient.java @@ -0,0 +1,86 @@ +package cn.iocoder.yudao.module.pay.framework.pay.core.client.impl.alipay; + +import cn.hutool.core.date.LocalDateTimeUtil; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.module.pay.enums.PayChannelEnum; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.order.PayOrderRespDTO; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO; +import cn.iocoder.yudao.module.pay.framework.pay.core.enums.PayOrderDisplayModeEnum; +import com.alipay.api.AlipayApiException; +import com.alipay.api.domain.AlipayTradePayModel; +import com.alipay.api.request.AlipayTradePayRequest; +import com.alipay.api.response.AlipayTradePayResponse; +import lombok.extern.slf4j.Slf4j; + +import java.time.LocalDateTime; +import java.util.Objects; + +import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.BAD_REQUEST; +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception0; +import static cn.iocoder.yudao.module.pay.framework.pay.core.client.impl.alipay.AlipayPayClientConfig.MODE_CERTIFICATE; + +/** + * 支付宝【条码支付】的 PayClient 实现类 + * + * 文档:当面付 + * + * @author 芋道源码 + */ +@Slf4j +public class AlipayBarPayClient extends AbstractAlipayPayClient { + + public AlipayBarPayClient(Long channelId, AlipayPayClientConfig config) { + super(channelId, PayChannelEnum.ALIPAY_BAR.getCode(), config); + } + + @Override + public PayOrderRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) throws AlipayApiException { + String authCode = MapUtil.getStr(reqDTO.getChannelExtras(), "auth_code"); + if (StrUtil.isEmpty(authCode)) { + throw exception0(BAD_REQUEST.getCode(), "条形码不能为空"); + } + + // 1.1 构建 AlipayTradePayModel 请求 + AlipayTradePayModel model = new AlipayTradePayModel(); + // ① 通用的参数 + model.setOutTradeNo(reqDTO.getOutTradeNo()); + model.setSubject(reqDTO.getSubject()); + model.setBody(reqDTO.getBody()); + model.setTotalAmount(formatAmount(reqDTO.getPrice())); + model.setScene("bar_code"); // 当面付条码支付场景 + // ② 个性化的参数 + model.setAuthCode(authCode); + // ③ 支付宝条码支付只有一种展示 + String displayMode = PayOrderDisplayModeEnum.BAR_CODE.getMode(); + + // 1.2 构建 AlipayTradePayRequest 请求 + AlipayTradePayRequest request = new AlipayTradePayRequest(); + request.setBizModel(model); + request.setNotifyUrl(reqDTO.getNotifyUrl()); + request.setReturnUrl(reqDTO.getReturnUrl()); + + // 2.1 执行请求 + AlipayTradePayResponse response; + if (Objects.equals(config.getMode(), MODE_CERTIFICATE)) { + // 证书模式 + response = client.certificateExecute(request); + } else { + response = client.execute(request); + } + // 2.2 处理结果 + if (!response.isSuccess()) { + return buildClosedPayOrderRespDTO(reqDTO, response); + } + if ("10000".equals(response.getCode())) { // 免密支付 + 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, "", + reqDTO.getOutTradeNo(), response); + } + +} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AlipayPayClientConfig.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AlipayPayClientConfig.java new file mode 100644 index 0000000..431cf17 --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AlipayPayClientConfig.java @@ -0,0 +1,128 @@ +package cn.iocoder.yudao.module.pay.framework.pay.core.client.impl.alipay; + +import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.PayClientConfig; +import lombok.Data; + +import jakarta.validation.Validator; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; + +/** + * 支付宝的 PayClientConfig 实现类 + * 属性主要来自 {@link com.alipay.api.AlipayConfig} 的必要属性 + * + * @author 芋道源码 + */ +@Data +public class AlipayPayClientConfig implements PayClientConfig { + + /** + * 公钥类型 - 公钥模式 + */ + public static final Integer MODE_PUBLIC_KEY = 1; + /** + * 公钥类型 - 证书模式 + */ + public static final Integer MODE_CERTIFICATE = 2; + + /** + * 接口内容加密方式 - AES 加密 + */ + public static final String ENC_TYPE_AES = "AES"; + + /** + * 签名算法类型 - RSA + */ + public static final String SIGN_TYPE_DEFAULT = "RSA2"; + + /** + * 网关地址 + * + * 1. 生产环境 + * 2. 沙箱环境 + */ + @NotBlank(message = "网关地址不能为空", groups = {ModePublicKey.class, ModeCertificate.class}) + private String serverUrl; + + /** + * 开放平台上创建的应用的 ID + */ + @NotBlank(message = "开放平台上创建的应用的 ID不能为空", groups = {ModePublicKey.class, ModeCertificate.class}) + private String appId; + + /** + * 签名算法类型,推荐:RSA2 + *

+ * {@link #SIGN_TYPE_DEFAULT} + */ + @NotBlank(message = "签名算法类型不能为空", groups = {ModePublicKey.class, ModeCertificate.class}) + private String signType; + + /** + * 公钥类型 + * 1. {@link #MODE_PUBLIC_KEY} 情况,privateKey + alipayPublicKey + * 2. {@link #MODE_CERTIFICATE} 情况,appCertContent + alipayPublicCertContent + rootCertContent + */ + @NotNull(message = "公钥类型不能为空", groups = {ModePublicKey.class, ModeCertificate.class}) + private Integer mode; + + // ========== 公钥模式 ========== + /** + * 商户私钥 + */ + @NotBlank(message = "商户私钥不能为空", groups = {ModePublicKey.class}) + private String privateKey; + + /** + * 支付宝公钥字符串 + */ + @NotBlank(message = "支付宝公钥字符串不能为空", groups = {ModePublicKey.class}) + private String alipayPublicKey; + + // ========== 证书模式 ========== + /** + * 指定商户公钥应用证书内容字符串 + */ + @NotBlank(message = "指定商户公钥应用证书内容不能为空", groups = {ModeCertificate.class}) + private String appCertContent; + /** + * 指定支付宝公钥证书内容字符串 + */ + @NotBlank(message = "指定支付宝公钥证书内容不能为空", groups = {ModeCertificate.class}) + private String alipayPublicCertContent; + /** + * 指定根证书内容字符串 + */ + @NotBlank(message = "指定根证书内容字符串不能为空", groups = {ModeCertificate.class}) + private String rootCertContent; + + /** + * 接口内容加密方式 + * + * 1. 如果为空,将使用无加密方式 + * 2. 如果要加密,目前支付宝只有 AES 一种加密方式 + * + * @see 支付宝开放平台 + * @see AlipayPayClientConfig#ENC_TYPE_AES + */ + private String encryptType; + + /** + * 接口内容加密的私钥 + */ + private String encryptKey; + + public interface ModePublicKey { + } + + public interface ModeCertificate { + } + + @Override + public void validate(Validator validator) { + ValidationUtils.validate(validator, this, + MODE_PUBLIC_KEY.equals(this.getMode()) ? ModePublicKey.class : ModeCertificate.class); + } + +} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AlipayPcPayClient.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AlipayPcPayClient.java new file mode 100644 index 0000000..9d90879 --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AlipayPcPayClient.java @@ -0,0 +1,70 @@ +package cn.iocoder.yudao.module.pay.framework.pay.core.client.impl.alipay; + +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.http.Method; +import cn.iocoder.yudao.module.pay.enums.PayChannelEnum; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.order.PayOrderRespDTO; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO; +import cn.iocoder.yudao.module.pay.framework.pay.core.enums.PayOrderDisplayModeEnum; +import com.alipay.api.AlipayApiException; +import com.alipay.api.domain.AlipayTradePagePayModel; +import com.alipay.api.request.AlipayTradePagePayRequest; +import com.alipay.api.response.AlipayTradePagePayResponse; +import lombok.extern.slf4j.Slf4j; + +import java.util.Objects; + +/** + * 支付宝【PC 网站】的 PayClient 实现类 + * + * 文档:电脑网站支付 + * + * @author XGD + */ +@Slf4j +public class AlipayPcPayClient extends AbstractAlipayPayClient { + + public AlipayPcPayClient(Long channelId, AlipayPayClientConfig config) { + super(channelId, PayChannelEnum.ALIPAY_PC.getCode(), config); + } + + @Override + public PayOrderRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) throws AlipayApiException { + // 1.1 构建 AlipayTradePagePayModel 请求 + AlipayTradePagePayModel model = new AlipayTradePagePayModel(); + // ① 通用的参数 + model.setOutTradeNo(reqDTO.getOutTradeNo()); + model.setSubject(reqDTO.getSubject()); + model.setBody(reqDTO.getBody()); + model.setTotalAmount(formatAmount(reqDTO.getPrice())); + model.setTimeExpire(formatTime(reqDTO.getExpireTime())); + model.setProductCode("FAST_INSTANT_TRADE_PAY"); // 销售产品码. 目前 PC 支付场景下仅支持 FAST_INSTANT_TRADE_PAY + // ② 个性化的参数 + // 如果想弄更多个性化的参数,可参考 https://www.pingxx.com/api/支付渠道 extra 参数说明.html 的 alipay_pc_direct 部分进行拓展 + model.setQrPayMode("2"); // 跳转模式 - 订单码,效果参见:https://help.pingxx.com/article/1137360/ + // ③ 支付宝 PC 支付有两种展示模式:FORM、URL + String displayMode = ObjectUtil.defaultIfNull(reqDTO.getDisplayMode(), + PayOrderDisplayModeEnum.URL.getMode()); + + // 1.2 构建 AlipayTradePagePayRequest 请求 + AlipayTradePagePayRequest request = new AlipayTradePagePayRequest(); + request.setBizModel(model); + request.setNotifyUrl(reqDTO.getNotifyUrl()); + request.setReturnUrl(reqDTO.getReturnUrl()); + + // 2.1 执行请求 + AlipayTradePagePayResponse response; + if (Objects.equals(displayMode, PayOrderDisplayModeEnum.FORM.getMode())) { + response = client.pageExecute(request, Method.POST.name()); // 需要特殊使用 POST 请求 + } else { + response = client.pageExecute(request, Method.GET.name()); + } + // 2.2 处理结果 + if (!response.isSuccess()) { + return buildClosedPayOrderRespDTO(reqDTO, response); + } + return PayOrderRespDTO.waitingOf(displayMode, response.getBody(), + reqDTO.getOutTradeNo(), response); + } + +} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AlipayQrPayClient.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AlipayQrPayClient.java new file mode 100644 index 0000000..549e7d0 --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AlipayQrPayClient.java @@ -0,0 +1,66 @@ +package cn.iocoder.yudao.module.pay.framework.pay.core.client.impl.alipay; + +import cn.iocoder.yudao.module.pay.enums.PayChannelEnum; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.order.PayOrderRespDTO; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO; +import cn.iocoder.yudao.module.pay.framework.pay.core.enums.PayOrderDisplayModeEnum; +import com.alipay.api.AlipayApiException; +import com.alipay.api.domain.AlipayTradePrecreateModel; +import com.alipay.api.request.AlipayTradePrecreateRequest; +import com.alipay.api.response.AlipayTradePrecreateResponse; +import lombok.extern.slf4j.Slf4j; + +import java.util.Objects; + +import static cn.iocoder.yudao.module.pay.framework.pay.core.client.impl.alipay.AlipayPayClientConfig.MODE_CERTIFICATE; + +/** + * 支付宝【扫码支付】的 PayClient 实现类 + * + * 文档:扫码支付 + * + * @author 芋道源码 + */ +@Slf4j +public class AlipayQrPayClient extends AbstractAlipayPayClient { + + public AlipayQrPayClient(Long channelId, AlipayPayClientConfig config) { + super(channelId, PayChannelEnum.ALIPAY_QR.getCode(), config); + } + + @Override + public PayOrderRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) throws AlipayApiException { + // 1.1 构建 AlipayTradePrecreateModel 请求 + AlipayTradePrecreateModel model = new AlipayTradePrecreateModel(); + // ① 通用的参数 + model.setOutTradeNo(reqDTO.getOutTradeNo()); + model.setSubject(reqDTO.getSubject()); + model.setBody(reqDTO.getBody()); + model.setTotalAmount(formatAmount(reqDTO.getPrice())); + model.setProductCode("FACE_TO_FACE_PAYMENT"); // 销售产品码. 目前扫码支付场景下仅支持 FACE_TO_FACE_PAYMENT + // ② 个性化的参数【无】 + // ③ 支付宝扫码支付只有一种展示,考虑到前端可能希望二维码扫描后,手机打开 + String displayMode = PayOrderDisplayModeEnum.QR_CODE.getMode(); + + // 1.2 构建 AlipayTradePrecreateRequest 请求 + AlipayTradePrecreateRequest request = new AlipayTradePrecreateRequest(); + request.setBizModel(model); + request.setNotifyUrl(reqDTO.getNotifyUrl()); + request.setReturnUrl(reqDTO.getReturnUrl()); + + // 2.1 执行请求 + AlipayTradePrecreateResponse response; + if (Objects.equals(config.getMode(), MODE_CERTIFICATE)) { + // 证书模式 + response = client.certificateExecute(request); + } else { + response = client.execute(request); + } + // 2.2 处理结果 + if (!response.isSuccess()) { + return buildClosedPayOrderRespDTO(reqDTO, response); + } + return PayOrderRespDTO.waitingOf(displayMode, response.getQrCode(), + reqDTO.getOutTradeNo(), response); + } +} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AlipayWapPayClient.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AlipayWapPayClient.java new file mode 100644 index 0000000..5798d98 --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AlipayWapPayClient.java @@ -0,0 +1,59 @@ +package cn.iocoder.yudao.module.pay.framework.pay.core.client.impl.alipay; + +import cn.hutool.http.Method; +import cn.iocoder.yudao.module.pay.enums.PayChannelEnum; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.order.PayOrderRespDTO; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO; +import cn.iocoder.yudao.module.pay.framework.pay.core.enums.PayOrderDisplayModeEnum; +import com.alipay.api.AlipayApiException; +import com.alipay.api.domain.AlipayTradeWapPayModel; +import com.alipay.api.request.AlipayTradeWapPayRequest; +import com.alipay.api.response.AlipayTradeWapPayResponse; +import lombok.extern.slf4j.Slf4j; + +/** + * 支付宝【Wap 网站】的 PayClient 实现类 + * + * 文档:手机网站支付接口 + * + * @author 芋道源码 + */ +@Slf4j +public class AlipayWapPayClient extends AbstractAlipayPayClient { + + public AlipayWapPayClient(Long channelId, AlipayPayClientConfig config) { + super(channelId, PayChannelEnum.ALIPAY_WAP.getCode(), config); + } + + @Override + public PayOrderRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) throws AlipayApiException { + // 1.1 构建 AlipayTradeWapPayModel 请求 + AlipayTradeWapPayModel model = new AlipayTradeWapPayModel(); + // ① 通用的参数 + model.setOutTradeNo(reqDTO.getOutTradeNo()); + model.setSubject(reqDTO.getSubject()); + model.setBody(reqDTO.getBody()); + model.setTotalAmount(formatAmount(reqDTO.getPrice())); + model.setProductCode("QUICK_WAP_PAY"); // 销售产品码. 目前 Wap 支付场景下仅支持 QUICK_WAP_PAY + // ② 个性化的参数【无】 + // ③ 支付宝 Wap 支付只有一种展示:URL + String displayMode = PayOrderDisplayModeEnum.URL.getMode(); + + // 1.2 构建 AlipayTradeWapPayRequest 请求 + AlipayTradeWapPayRequest request = new AlipayTradeWapPayRequest(); + request.setBizModel(model); + request.setNotifyUrl(reqDTO.getNotifyUrl()); + request.setReturnUrl(reqDTO.getReturnUrl()); + model.setQuitUrl(reqDTO.getReturnUrl()); + model.setTimeExpire(formatTime(reqDTO.getExpireTime())); + + // 2.1 执行请求 + AlipayTradeWapPayResponse response = client.pageExecute(request, Method.GET.name()); + // 2.2 处理结果 + if (!response.isSuccess()) { + return buildClosedPayOrderRespDTO(reqDTO, response); + } + return PayOrderRespDTO.waitingOf(displayMode, response.getBody(), + reqDTO.getOutTradeNo(), response); + } +} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/mock/MockPayClient.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/mock/MockPayClient.java new file mode 100644 index 0000000..01ba8c5 --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/mock/MockPayClient.java @@ -0,0 +1,84 @@ +package cn.iocoder.yudao.module.pay.framework.pay.core.client.impl.mock; + +import cn.iocoder.yudao.module.pay.enums.PayChannelEnum; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.order.PayOrderRespDTO; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.refund.PayRefundRespDTO; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.transfer.PayTransferRespDTO; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.transfer.PayTransferUnifiedReqDTO; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.impl.AbstractPayClient; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.impl.NonePayClientConfig; + +import java.time.LocalDateTime; +import java.util.Map; + +/** + * 模拟支付的 PayClient 实现类 + * + * 模拟支付返回结果都是成功,方便大家日常流畅 + * + * @author jason + */ +public class MockPayClient extends AbstractPayClient { + + private static final String MOCK_RESP_SUCCESS_DATA = "MOCK_SUCCESS"; + + public MockPayClient(Long channelId, NonePayClientConfig config) { + super(channelId, PayChannelEnum.MOCK.getCode(), config); + } + + @Override + protected void doInit() { + } + + @Override + protected PayOrderRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) { + return PayOrderRespDTO.successOf("MOCK-P-" + reqDTO.getOutTradeNo(), "", LocalDateTime.now(), + reqDTO.getOutTradeNo(), MOCK_RESP_SUCCESS_DATA); + } + + @Override + protected PayOrderRespDTO doGetOrder(String outTradeNo) { + return PayOrderRespDTO.successOf("MOCK-P-" + outTradeNo, "", LocalDateTime.now(), + outTradeNo, MOCK_RESP_SUCCESS_DATA); + } + + @Override + protected PayRefundRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) { + return PayRefundRespDTO.successOf("MOCK-R-" + reqDTO.getOutRefundNo(), LocalDateTime.now(), + reqDTO.getOutRefundNo(), MOCK_RESP_SUCCESS_DATA); + } + + @Override + protected PayRefundRespDTO doGetRefund(String outTradeNo, String outRefundNo) { + return PayRefundRespDTO.successOf("MOCK-R-" + outRefundNo, LocalDateTime.now(), + outRefundNo, MOCK_RESP_SUCCESS_DATA); + } + + @Override + protected PayTransferRespDTO doParseTransferNotify(Map params, String body, Map headers) { + throw new UnsupportedOperationException("未实现"); + } + + @Override + protected PayRefundRespDTO doParseRefundNotify(Map params, String body, Map headers) { + throw new UnsupportedOperationException("模拟支付无退款回调"); + } + + @Override + protected PayOrderRespDTO doParseOrderNotify(Map params, String body, Map headers) { + throw new UnsupportedOperationException("模拟支付无支付回调"); + } + + @Override + protected PayTransferRespDTO doUnifiedTransfer(PayTransferUnifiedReqDTO reqDTO) { + throw new UnsupportedOperationException("待实现"); + } + + @Override + protected PayTransferRespDTO doGetTransfer(String outTradeNo) { + throw new UnsupportedOperationException("待实现"); + } + +} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/wallet/WalletPayClient.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/wallet/WalletPayClient.java new file mode 100644 index 0000000..8021dc7 --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/wallet/WalletPayClient.java @@ -0,0 +1,251 @@ +package cn.iocoder.yudao.module.pay.framework.pay.core.client.impl.wallet; + +import cn.hutool.core.lang.Assert; +import cn.hutool.core.map.MapUtil; +import cn.hutool.extra.spring.SpringUtil; +import cn.iocoder.yudao.framework.common.exception.ServiceException; +import cn.iocoder.yudao.module.pay.enums.PayChannelEnum; +import cn.iocoder.yudao.module.pay.enums.refund.PayRefundStatusEnum; +import cn.iocoder.yudao.module.pay.enums.transfer.PayTransferStatusEnum; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.order.PayOrderRespDTO; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.refund.PayRefundRespDTO; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.transfer.PayTransferRespDTO; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.transfer.PayTransferUnifiedReqDTO; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.impl.AbstractPayClient; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.impl.NonePayClientConfig; +import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderExtensionDO; +import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO; +import cn.iocoder.yudao.module.pay.dal.dataobject.transfer.PayTransferDO; +import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletTransactionDO; +import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum; +import cn.iocoder.yudao.module.pay.enums.wallet.PayWalletBizTypeEnum; +import cn.iocoder.yudao.module.pay.service.order.PayOrderService; +import cn.iocoder.yudao.module.pay.service.refund.PayRefundService; +import cn.iocoder.yudao.module.pay.service.transfer.PayTransferService; +import cn.iocoder.yudao.module.pay.service.wallet.PayWalletService; +import cn.iocoder.yudao.module.pay.service.wallet.PayWalletTransactionService; +import lombok.extern.slf4j.Slf4j; + +import java.util.Map; + +import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR; +import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.PAY_ORDER_EXTENSION_NOT_FOUND; +import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.REFUND_NOT_FOUND; + +/** + * 钱包支付的 PayClient 实现类 + * + * @author jason + */ +@Slf4j +public class WalletPayClient extends AbstractPayClient { + + public static final String WALLET_ID_KEY = "walletId"; + + private PayWalletService wallService; + private PayWalletTransactionService walletTransactionService; + + private PayOrderService orderService; + private PayRefundService refundService; + private PayTransferService transferService; + + public WalletPayClient(Long channelId, NonePayClientConfig config) { + super(channelId, PayChannelEnum.WALLET.getCode(), config); + } + + @Override + protected void doInit() { + if (wallService == null) { + wallService = SpringUtil.getBean(PayWalletService.class); + } + if (walletTransactionService == null) { + walletTransactionService = SpringUtil.getBean(PayWalletTransactionService.class); + } + } + + @Override + @SuppressWarnings("PatternVariableCanBeUsed") + protected PayOrderRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) { + try { + Long walletId = MapUtil.getLong(reqDTO.getChannelExtras(), WALLET_ID_KEY); + Assert.notNull(walletId, "钱包编号"); + PayWalletTransactionDO transaction = wallService.orderPay(walletId, + reqDTO.getOutTradeNo(), reqDTO.getPrice()); + return PayOrderRespDTO.successOf(transaction.getNo(), transaction.getCreator(), + transaction.getCreateTime(), + reqDTO.getOutTradeNo(), transaction); + } catch (Throwable ex) { + log.error("[doUnifiedOrder][reqDTO({}) 异常]", reqDTO, ex); + Integer errorCode = INTERNAL_SERVER_ERROR.getCode(); + String errorMsg = INTERNAL_SERVER_ERROR.getMsg(); + if (ex instanceof ServiceException) { + ServiceException serviceException = (ServiceException) ex; + errorCode = serviceException.getCode(); + errorMsg = serviceException.getMessage(); + } + return PayOrderRespDTO.closedOf(String.valueOf(errorCode), errorMsg, + reqDTO.getOutTradeNo(), ""); + } + } + + @Override + protected PayOrderRespDTO doParseOrderNotify(Map params, String body, Map headers) { + throw new UnsupportedOperationException("钱包支付无支付回调"); + } + + @Override + protected PayOrderRespDTO doGetOrder(String outTradeNo) { + if (orderService == null) { + orderService = SpringUtil.getBean(PayOrderService.class); + } + PayOrderExtensionDO orderExtension = orderService.getOrderExtensionByNo(outTradeNo); + // 支付交易拓展单不存在, 返回关闭状态 + if (orderExtension == null) { + return PayOrderRespDTO.closedOf(String.valueOf(PAY_ORDER_EXTENSION_NOT_FOUND.getCode()), + PAY_ORDER_EXTENSION_NOT_FOUND.getMsg(), outTradeNo, ""); + } + // 关闭状态 + if (PayOrderStatusEnum.isClosed(orderExtension.getStatus())) { + return PayOrderRespDTO.closedOf(orderExtension.getChannelErrorCode(), + orderExtension.getChannelErrorMsg(), outTradeNo, ""); + } + // 成功状态 + if (PayOrderStatusEnum.isSuccess(orderExtension.getStatus())) { + PayWalletTransactionDO walletTransaction = walletTransactionService.getWalletTransaction( + String.valueOf(orderExtension.getOrderId()), PayWalletBizTypeEnum.PAYMENT); + Assert.notNull(walletTransaction, "支付单 {} 钱包流水不能为空", outTradeNo); + return PayOrderRespDTO.successOf(walletTransaction.getNo(), walletTransaction.getCreator(), + walletTransaction.getCreateTime(), outTradeNo, walletTransaction); + } + // 其它状态为无效状态 + log.error("[doGetOrder] 支付单 {} 的状态不正确", outTradeNo); + throw new IllegalStateException(String.format("支付单[%s] 状态不正确", outTradeNo)); + } + + @Override + @SuppressWarnings("PatternVariableCanBeUsed") + protected PayRefundRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) { + try { + PayWalletTransactionDO payWalletTransaction = wallService.orderRefund(reqDTO.getOutRefundNo(), + reqDTO.getRefundPrice(), reqDTO.getReason()); + return PayRefundRespDTO.successOf(payWalletTransaction.getNo(), payWalletTransaction.getCreateTime(), + reqDTO.getOutRefundNo(), payWalletTransaction); + } catch (Throwable ex) { + log.error("[doUnifiedRefund][reqDOT({}) 异常]", reqDTO, ex); + Integer errorCode = INTERNAL_SERVER_ERROR.getCode(); + String errorMsg = INTERNAL_SERVER_ERROR.getMsg(); + if (ex instanceof ServiceException) { + ServiceException serviceException = (ServiceException) ex; + errorCode = serviceException.getCode(); + errorMsg = serviceException.getMessage(); + } + return PayRefundRespDTO.failureOf(String.valueOf(errorCode), errorMsg, + reqDTO.getOutRefundNo(), ""); + } + } + + @Override + protected PayRefundRespDTO doParseRefundNotify(Map params, String body, Map headers) { + throw new UnsupportedOperationException("钱包支付无退款回调"); + } + + @Override + protected PayRefundRespDTO doGetRefund(String outTradeNo, String outRefundNo) { + if (refundService == null) { + refundService = SpringUtil.getBean(PayRefundService.class); + } + PayRefundDO payRefund = refundService.getRefundByNo(outRefundNo); + // 支付退款单不存在, 返回退款失败状态 + if (payRefund == null) { + return PayRefundRespDTO.failureOf(String.valueOf(REFUND_NOT_FOUND), REFUND_NOT_FOUND.getMsg(), + outRefundNo, ""); + } + // 退款失败 + if (PayRefundStatusEnum.isFailure(payRefund.getStatus())) { + return PayRefundRespDTO.failureOf(payRefund.getChannelErrorCode(), payRefund.getChannelErrorMsg(), + outRefundNo, ""); + } + // 退款成功 + if (PayRefundStatusEnum.isSuccess(payRefund.getStatus())) { + PayWalletTransactionDO walletTransaction = walletTransactionService.getWalletTransaction( + String.valueOf(payRefund.getId()), PayWalletBizTypeEnum.PAYMENT_REFUND); + Assert.notNull(walletTransaction, "支付退款单 {} 钱包流水不能为空", outRefundNo); + return PayRefundRespDTO.successOf(walletTransaction.getNo(), walletTransaction.getCreateTime(), + outRefundNo, walletTransaction); + } + // 其它状态为无效状态 + log.error("[doGetRefund] 支付退款单 {} 的状态不正确", outRefundNo); + throw new IllegalStateException(String.format("支付退款单[%s] 状态不正确", outRefundNo)); + } + + @Override + @SuppressWarnings("PatternVariableCanBeUsed") + public PayTransferRespDTO doUnifiedTransfer(PayTransferUnifiedReqDTO reqDTO) { + try { + Long walletId = Long.parseLong(reqDTO.getUserAccount()); + PayWalletTransactionDO transaction = wallService.addWalletBalance(walletId, String.valueOf(reqDTO.getOutTransferNo()), + PayWalletBizTypeEnum.TRANSFER, reqDTO.getPrice()); + return PayTransferRespDTO.successOf(transaction.getNo(), transaction.getCreateTime(), + reqDTO.getOutTransferNo(), transaction); + } catch (Throwable ex) { + log.error("[doUnifiedTransfer][reqDTO({}) 异常]", reqDTO, ex); + Integer errorCode = INTERNAL_SERVER_ERROR.getCode(); + String errorMsg = INTERNAL_SERVER_ERROR.getMsg(); + if (ex instanceof ServiceException) { + ServiceException serviceException = (ServiceException) ex; + errorCode = serviceException.getCode(); + errorMsg = serviceException.getMessage(); + } + return PayTransferRespDTO.closedOf(String.valueOf(errorCode), errorMsg, + reqDTO.getOutTransferNo(), ""); + } + } + + @Override + protected PayTransferRespDTO doParseTransferNotify(Map params, String body, Map headers) { + throw new UnsupportedOperationException("钱包支付无转账回调"); + } + + @Override + protected PayTransferRespDTO doGetTransfer(String outTradeNo) { + if (transferService == null) { + transferService = SpringUtil.getBean(PayTransferService.class); + } + // 获取转账单 + PayTransferDO transfer = transferService.getTransferByNo(outTradeNo); + // 转账单不存在,返回关闭状态 + if (transfer == null) { + return PayTransferRespDTO.closedOf(String.valueOf(PAY_ORDER_EXTENSION_NOT_FOUND.getCode()), + PAY_ORDER_EXTENSION_NOT_FOUND.getMsg(), outTradeNo, ""); + } + // 关闭状态 + if (PayTransferStatusEnum.isClosed(transfer.getStatus())) { + return PayTransferRespDTO.closedOf(transfer.getChannelErrorCode(), + transfer.getChannelErrorMsg(), outTradeNo, ""); + } + // 成功状态 + if (PayTransferStatusEnum.isSuccess(transfer.getStatus())) { + PayWalletTransactionDO walletTransaction = walletTransactionService.getWalletTransaction( + String.valueOf(transfer.getId()), PayWalletBizTypeEnum.TRANSFER); + Assert.notNull(walletTransaction, "转账单 {} 钱包流水不能为空", outTradeNo); + return PayTransferRespDTO.successOf(walletTransaction.getNo(), walletTransaction.getCreateTime(), + outTradeNo, walletTransaction); + } + // 处理中状态 + if (PayTransferStatusEnum.isProcessing(transfer.getStatus())) { + return PayTransferRespDTO.processingOf(transfer.getChannelTransferNo(), + outTradeNo, transfer); + } + // 等待状态 + if (PayTransferStatusEnum.isWaiting(transfer.getStatus())) { + return PayTransferRespDTO.waitingOf(transfer.getChannelTransferNo(), + outTradeNo, transfer); + } + // 其它状态为无效状态 + log.error("[doGetTransfer] 转账单 {} 的状态不正确", outTradeNo); + throw new IllegalStateException(String.format("转账单[%s] 状态不正确", outTradeNo)); + } + +} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/weixin/AbstractWxPayClient.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/weixin/AbstractWxPayClient.java new file mode 100644 index 0000000..5f38b1a --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/weixin/AbstractWxPayClient.java @@ -0,0 +1,597 @@ +package cn.iocoder.yudao.module.pay.framework.pay.core.client.impl.weixin; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.codec.Base64; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.date.LocalDateTimeUtil; +import cn.hutool.core.date.TemporalAccessorUtil; +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.framework.common.util.io.FileUtils; +import cn.iocoder.yudao.framework.common.util.json.JsonUtils; +import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; +import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.order.PayOrderRespDTO; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.refund.PayRefundRespDTO; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.transfer.PayTransferRespDTO; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.transfer.PayTransferUnifiedReqDTO; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.impl.AbstractPayClient; +import com.github.binarywang.wxpay.bean.notify.*; +import com.github.binarywang.wxpay.bean.request.*; +import com.github.binarywang.wxpay.bean.result.*; +import com.github.binarywang.wxpay.bean.transfer.TransferBillsGetResult; +import com.github.binarywang.wxpay.bean.transfer.TransferBillsNotifyResult; +import com.github.binarywang.wxpay.bean.transfer.TransferBillsRequest; +import com.github.binarywang.wxpay.bean.transfer.TransferBillsResult; +import com.github.binarywang.wxpay.config.WxPayConfig; +import com.github.binarywang.wxpay.exception.WxPayException; +import com.github.binarywang.wxpay.service.WxPayService; +import com.github.binarywang.wxpay.service.impl.WxPayServiceImpl; +import lombok.extern.slf4j.Slf4j; + +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.Map; +import java.util.Objects; + +import static cn.hutool.core.date.DatePattern.*; +import static cn.iocoder.yudao.module.pay.framework.pay.core.client.impl.weixin.WxPayClientConfig.API_VERSION_V2; +import static cn.iocoder.yudao.module.pay.framework.pay.core.client.impl.weixin.WxPayClientConfig.API_VERSION_V3; + +/** + * 微信支付抽象类,实现微信统一的接口、以及部分实现(退款) + * + * @author 芋道源码 + */ +@Slf4j +public abstract class AbstractWxPayClient extends AbstractPayClient { + + protected WxPayService client; + + public AbstractWxPayClient(Long channelId, String channelCode, WxPayClientConfig config) { + super(channelId, channelCode, config); + } + + /** + * 初始化 client 客户端 + * + * @param tradeType 交易类型 + */ + protected void doInit(String tradeType) { + // 创建 config 配置 + WxPayConfig payConfig = new WxPayConfig(); + BeanUtil.copyProperties(config, payConfig, "keyContent", "privateKeyContent", "publicKeyContent"); + payConfig.setTradeType(tradeType); + // weixin-pay-java 无法设置内容,只允许读取文件,所以这里要创建临时文件来解决 + if (Objects.equals(config.getApiVersion(), API_VERSION_V2)) { + payConfig.setKeyPath(FileUtils.createTempFile(Base64.decode(config.getKeyContent())).getPath()); + } else if (Objects.equals(config.getApiVersion(), API_VERSION_V3)) { + payConfig.setPrivateKeyPath(FileUtils.createTempFile(config.getPrivateKeyContent()).getPath()); + payConfig.setPublicKeyPath(FileUtils.createTempFile(config.getPublicKeyContent()).getPath()); + // 特殊:强制使用微信公用模式,避免灰度期间的问题!!! + payConfig.setStrictlyNeedWechatPaySerial(true); + } + + // 创建 client 客户端 + client = new WxPayServiceImpl(); + client.setConfig(payConfig); + } + + // ============ 支付相关 ========== + + @Override + protected PayOrderRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) throws Exception { + try { + switch (config.getApiVersion()) { + case API_VERSION_V2: + return doUnifiedOrderV2(reqDTO); + case API_VERSION_V3: + // TODO @芋艿:【可能是 wxjava 的 bug】参考 https://github.com/binarywang/WxJava/issues/1557 + client.getConfig().setApiV3HttpClient(null); + return doUnifiedOrderV3(reqDTO); + default: + throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion())); + } + } catch (WxPayException e) { + log.error("[doUnifiedOrder][支付({}) 发起微信支付异常", reqDTO, e); + String errorCode = getErrorCode(e); + String errorMessage = getErrorMessage(e); + return PayOrderRespDTO.closedOf(errorCode, errorMessage, + reqDTO.getOutTradeNo(), e.getXmlString()); + } + } + + /** + * 【V2】调用支付渠道,统一下单 + * + * @param reqDTO 下单信息 + * @return 各支付渠道的返回结果 + */ + protected abstract PayOrderRespDTO doUnifiedOrderV2(PayOrderUnifiedReqDTO reqDTO) + throws Exception; + + /** + * 【V3】调用支付渠道,统一下单 + * + * @param reqDTO 下单信息 + * @return 各支付渠道的返回结果 + */ + protected abstract PayOrderRespDTO doUnifiedOrderV3(PayOrderUnifiedReqDTO reqDTO) + throws WxPayException; + + /** + * 【V2】创建微信下单请求 + * + * @param reqDTO 下信息 + * @return 下单请求 + */ + protected WxPayUnifiedOrderRequest buildPayUnifiedOrderRequestV2(PayOrderUnifiedReqDTO reqDTO) { + return WxPayUnifiedOrderRequest.newBuilder() + .outTradeNo(reqDTO.getOutTradeNo()) + .body(reqDTO.getSubject()) + .detail(reqDTO.getBody()) + .totalFee(reqDTO.getPrice()) // 单位分 + .timeExpire(formatDateV2(reqDTO.getExpireTime())) + .spbillCreateIp(reqDTO.getUserIp()) + .notifyUrl(reqDTO.getNotifyUrl()) + .build(); + } + + /** + * 【V3】创建微信下单请求 + * + * @param reqDTO 下信息 + * @return 下单请求 + */ + protected WxPayUnifiedOrderV3Request buildPayUnifiedOrderRequestV3(PayOrderUnifiedReqDTO reqDTO) { + WxPayUnifiedOrderV3Request request = new WxPayUnifiedOrderV3Request(); + request.setOutTradeNo(reqDTO.getOutTradeNo()); + request.setDescription(reqDTO.getSubject()); + request.setAmount(new WxPayUnifiedOrderV3Request.Amount().setTotal(reqDTO.getPrice())); // 单位分 + request.setTimeExpire(formatDateV3(reqDTO.getExpireTime())); + request.setSceneInfo(new WxPayUnifiedOrderV3Request.SceneInfo().setPayerClientIp(reqDTO.getUserIp())); + request.setNotifyUrl(reqDTO.getNotifyUrl()); + return request; + } + + @Override + public PayOrderRespDTO doParseOrderNotify(Map params, String body, Map headers) throws WxPayException { + switch (config.getApiVersion()) { + case API_VERSION_V2: + return doParseOrderNotifyV2(body); + case API_VERSION_V3: + return doParseOrderNotifyV3(body, headers); + default: + throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion())); + } + } + + private PayOrderRespDTO doParseOrderNotifyV2(String body) throws WxPayException { + // 1. 解析回调 + WxPayOrderNotifyResult response = client.parseOrderNotifyResult(body); + // 2. 构建结果 + // V2 微信支付的回调,只有 SUCCESS 支付成功、CLOSED 支付失败两种情况,无需像支付宝一样解析的比较复杂 + Integer status = Objects.equals(response.getResultCode(), "SUCCESS") ? + PayOrderStatusEnum.SUCCESS.getStatus() : PayOrderStatusEnum.CLOSED.getStatus(); + return PayOrderRespDTO.of(status, response.getTransactionId(), response.getOpenid(), parseDateV2(response.getTimeEnd()), + response.getOutTradeNo(), body); + } + + private PayOrderRespDTO doParseOrderNotifyV3(String body, Map headers) throws WxPayException { + // 1. 解析回调 + SignatureHeader signatureHeader = getRequestHeader(headers); + WxPayNotifyV3Result response = client.parseOrderNotifyV3Result(body, signatureHeader); + WxPayNotifyV3Result.DecryptNotifyResult result = response.getResult(); + // 2. 构建结果 + Integer status = parseStatus(result.getTradeState()); + String openid = result.getPayer() != null ? result.getPayer().getOpenid() : null; + return PayOrderRespDTO.of(status, result.getTransactionId(), openid, parseDateV3(result.getSuccessTime()), + result.getOutTradeNo(), body); + } + + @Override + protected PayOrderRespDTO doGetOrder(String outTradeNo) throws Throwable { + try { + switch (config.getApiVersion()) { + case API_VERSION_V2: + return doGetOrderV2(outTradeNo); + case API_VERSION_V3: + return doGetOrderV3(outTradeNo); + default: + throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion())); + } + } catch (WxPayException e) { + if (ObjectUtils.equalsAny(e.getErrCode(), "ORDERNOTEXIST", "ORDER_NOT_EXIST")) { + String errorCode = getErrorCode(e); + String errorMessage = getErrorMessage(e); + return PayOrderRespDTO.closedOf(errorCode, errorMessage, + outTradeNo, e.getXmlString()); + } + throw e; + } + } + + private PayOrderRespDTO doGetOrderV2(String outTradeNo) throws WxPayException { + // 构建 WxPayUnifiedOrderRequest 对象 + WxPayOrderQueryRequest request = WxPayOrderQueryRequest.newBuilder() + .outTradeNo(outTradeNo).build(); + // 执行请求 + WxPayOrderQueryResult response = client.queryOrder(request); + + // 转换结果 + Integer status = parseStatus(response.getTradeState()); + return PayOrderRespDTO.of(status, response.getTransactionId(), response.getOpenid(), parseDateV2(response.getTimeEnd()), + outTradeNo, response); + } + + private PayOrderRespDTO doGetOrderV3(String outTradeNo) throws WxPayException { + fixV3HttpClientConnectionPoolShutDown(); + // 构建 WxPayUnifiedOrderRequest 对象 + WxPayOrderQueryV3Request request = new WxPayOrderQueryV3Request() + .setOutTradeNo(outTradeNo); + // 执行请求 + WxPayOrderQueryV3Result response = client.queryOrderV3(request); + + // 转换结果 + Integer status = parseStatus(response.getTradeState()); + String openid = response.getPayer() != null ? response.getPayer().getOpenid() : null; + return PayOrderRespDTO.of(status, response.getTransactionId(), openid, parseDateV3(response.getSuccessTime()), + outTradeNo, response); + } + + private static Integer parseStatus(String tradeState) { + switch (tradeState) { + case "NOTPAY": + case "USERPAYING": // 支付中,等待用户输入密码(条码支付独有) + return PayOrderStatusEnum.WAITING.getStatus(); + case "SUCCESS": + return PayOrderStatusEnum.SUCCESS.getStatus(); + case "REFUND": + return PayOrderStatusEnum.REFUND.getStatus(); + case "CLOSED": + case "REVOKED": // 已撤销(刷卡支付独有) + case "PAYERROR": // 支付失败(其它原因,如银行返回失败) + return PayOrderStatusEnum.CLOSED.getStatus(); + default: + throw new IllegalArgumentException(StrUtil.format("未知的支付状态({})", tradeState)); + } + } + + // ============ 退款相关 ========== + + @Override + protected PayRefundRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws Throwable { + try { + switch (config.getApiVersion()) { + case API_VERSION_V2: + return doUnifiedRefundV2(reqDTO); + case API_VERSION_V3: + return doUnifiedRefundV3(reqDTO); + default: + throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion())); + } + } catch (WxPayException e) { + String errorCode = getErrorCode(e); + String errorMessage = getErrorMessage(e); + return PayRefundRespDTO.failureOf(errorCode, errorMessage, + reqDTO.getOutRefundNo(), e.getXmlString()); + } + } + + private PayRefundRespDTO doUnifiedRefundV2(PayRefundUnifiedReqDTO reqDTO) throws Throwable { + // 1. 构建 WxPayRefundRequest 请求 + WxPayRefundRequest request = new WxPayRefundRequest() + .setOutTradeNo(reqDTO.getOutTradeNo()) + .setOutRefundNo(reqDTO.getOutRefundNo()) + .setRefundFee(reqDTO.getRefundPrice()) + .setRefundDesc(reqDTO.getReason()) + .setTotalFee(reqDTO.getPayPrice()) + .setNotifyUrl(reqDTO.getNotifyUrl()); + // 2.1 执行请求 + WxPayRefundResult response = client.refundV2(request); + // 2.2 创建返回结果 + if (Objects.equals("SUCCESS", response.getResultCode())) { // V2 情况下,不直接返回退款成功,而是等待异步通知 + return PayRefundRespDTO.waitingOf(response.getRefundId(), + reqDTO.getOutRefundNo(), response); + } + return PayRefundRespDTO.failureOf(reqDTO.getOutRefundNo(), response); + } + + private PayRefundRespDTO doUnifiedRefundV3(PayRefundUnifiedReqDTO reqDTO) throws Throwable { + fixV3HttpClientConnectionPoolShutDown(); + // 1. 构建 WxPayRefundRequest 请求 + WxPayRefundV3Request request = new WxPayRefundV3Request() + .setOutTradeNo(reqDTO.getOutTradeNo()) + .setOutRefundNo(reqDTO.getOutRefundNo()) + .setAmount(new WxPayRefundV3Request.Amount().setRefund(reqDTO.getRefundPrice()) + .setTotal(reqDTO.getPayPrice()).setCurrency("CNY")) + .setReason(reqDTO.getReason()) + .setNotifyUrl(reqDTO.getNotifyUrl()); + // 2.1 执行请求 + WxPayRefundV3Result response = client.refundV3(request); + // 2.2 创建返回结果 + if (Objects.equals("SUCCESS", response.getStatus())) { + return PayRefundRespDTO.successOf(response.getRefundId(), parseDateV3(response.getSuccessTime()), + reqDTO.getOutRefundNo(), response); + } + if (Objects.equals("PROCESSING", response.getStatus())) { + return PayRefundRespDTO.waitingOf(response.getRefundId(), + reqDTO.getOutRefundNo(), response); + } + return PayRefundRespDTO.failureOf(reqDTO.getOutRefundNo(), response); + } + + @Override + public PayRefundRespDTO doParseRefundNotify(Map params, String body, Map headers) throws WxPayException { + switch (config.getApiVersion()) { + case API_VERSION_V2: + return doParseRefundNotifyV2(body); + case API_VERSION_V3: + return parseRefundNotifyV3(body, headers); + default: + throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion())); + } + } + + private PayRefundRespDTO doParseRefundNotifyV2(String body) throws WxPayException { + // 1. 解析回调 + WxPayRefundNotifyResult response = client.parseRefundNotifyResult(body); + WxPayRefundNotifyResult.ReqInfo result = response.getReqInfo(); + // 2. 构建结果 + if (Objects.equals("SUCCESS", result.getRefundStatus())) { + return PayRefundRespDTO.successOf(result.getRefundId(), parseDateV2B(result.getSuccessTime()), + result.getOutRefundNo(), response); + } + return PayRefundRespDTO.failureOf(result.getOutRefundNo(), response); + } + + private PayRefundRespDTO parseRefundNotifyV3(String body, Map headers) throws WxPayException { + // 1. 解析回调 + SignatureHeader signatureHeader = getRequestHeader(headers); + WxPayRefundNotifyV3Result response = client.parseRefundNotifyV3Result(body, signatureHeader); + WxPayRefundNotifyV3Result.DecryptNotifyResult result = response.getResult(); + // 2. 构建结果 + if (Objects.equals("SUCCESS", result.getRefundStatus())) { + return PayRefundRespDTO.successOf(result.getRefundId(), parseDateV3(result.getSuccessTime()), + result.getOutRefundNo(), response); + } + return PayRefundRespDTO.failureOf(result.getOutRefundNo(), response); + } + + @Override + protected PayRefundRespDTO doGetRefund(String outTradeNo, String outRefundNo) throws WxPayException { + try { + switch (config.getApiVersion()) { + case API_VERSION_V2: + return doGetRefundV2(outTradeNo, outRefundNo); + case API_VERSION_V3: + return doGetRefundV3(outTradeNo, outRefundNo); + default: + throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion())); + } + } catch (WxPayException e) { + if (ObjectUtils.equalsAny(e.getErrCode(), "REFUNDNOTEXIST", "RESOURCE_NOT_EXISTS")) { + String errorCode = getErrorCode(e); + String errorMessage = getErrorMessage(e); + return PayRefundRespDTO.failureOf(errorCode, errorMessage, + outRefundNo, e.getXmlString()); + } + throw e; + } + } + + private PayRefundRespDTO doGetRefundV2(String outTradeNo, String outRefundNo) throws WxPayException { + // 1. 构建 WxPayRefundRequest 请求 + WxPayRefundQueryRequest request = WxPayRefundQueryRequest.newBuilder() + .outTradeNo(outTradeNo) + .outRefundNo(outRefundNo) + .build(); + // 2.1 执行请求 + WxPayRefundQueryResult response = client.refundQuery(request); + // 2.2 创建返回结果 + if (!Objects.equals("SUCCESS", response.getResultCode())) { + return PayRefundRespDTO.waitingOf(null, + outRefundNo, response); + } + WxPayRefundQueryResult.RefundRecord refund = CollUtil.findOne(response.getRefundRecords(), + record -> record.getOutRefundNo().equals(outRefundNo)); + if (refund == null) { + return PayRefundRespDTO.failureOf(outRefundNo, response); + } + switch (refund.getRefundStatus()) { + case "SUCCESS": + return PayRefundRespDTO.successOf(refund.getRefundId(), parseDateV2B(refund.getRefundSuccessTime()), + outRefundNo, response); + case "PROCESSING": + return PayRefundRespDTO.waitingOf(refund.getRefundId(), + outRefundNo, response); + case "CHANGE": // 退款到银行发现用户的卡作废或者冻结了,导致原路退款银行卡失败,资金回流到商户的现金帐号,需要商户人工干预,通过线下或者财付通转账的方式进行退款 + case "FAIL": + return PayRefundRespDTO.failureOf(outRefundNo, response); + default: + throw new IllegalArgumentException(String.format("未知的退款状态(%s)", refund.getRefundStatus())); + } + } + + private PayRefundRespDTO doGetRefundV3(String outTradeNo, String outRefundNo) throws WxPayException { + fixV3HttpClientConnectionPoolShutDown(); + // 1. 构建 WxPayRefundRequest 请求 + WxPayRefundQueryV3Request request = new WxPayRefundQueryV3Request(); + request.setOutRefundNo(outRefundNo); + // 2.1 执行请求 + WxPayRefundQueryV3Result response = client.refundQueryV3(request); + // 2.2 创建返回结果 + switch (response.getStatus()) { + case "SUCCESS": + return PayRefundRespDTO.successOf(response.getRefundId(), parseDateV3(response.getSuccessTime()), + outRefundNo, response); + case "PROCESSING": + return PayRefundRespDTO.waitingOf(response.getRefundId(), + outRefundNo, response); + case "ABNORMAL": // 退款异常 + case "CLOSED": + return PayRefundRespDTO.failureOf(outRefundNo, response); + default: + throw new IllegalArgumentException(String.format("未知的退款状态(%s)", response.getStatus())); + } + } + + @Override + protected PayTransferRespDTO doUnifiedTransfer(PayTransferUnifiedReqDTO reqDTO) throws WxPayException { + fixV3HttpClientConnectionPoolShutDown(); + // 1. 构建 TransferBillsRequest 请求 + TransferBillsRequest request = TransferBillsRequest.newBuilder() + .appid(this.config.getAppId()) + .outBillNo(reqDTO.getOutTransferNo()) + .transferAmount(reqDTO.getPrice()) + .transferRemark(reqDTO.getSubject()) + .transferSceneId(reqDTO.getChannelExtras().get("sceneId")) + .openid(reqDTO.getUserAccount()) + .userName(reqDTO.getUserName()) + .transferSceneReportInfos(JsonUtils.parseArray(reqDTO.getChannelExtras().get("sceneReportInfos"), + TransferBillsRequest.TransferSceneReportInfo.class)) + .notifyUrl(reqDTO.getNotifyUrl()) + .build(); + // 特殊:微信转账,必须 0.3 元起,才允许传入姓名 + if (reqDTO.getPrice() < 30) { + request.setUserName(null); + } + + // 2.1 执行请求 + try { + TransferBillsResult response = client.getTransferService().transferBills(request); + + // 2.2 创建返回结果 + String state = response.getState(); + if (ObjectUtils.equalsAny(state, "ACCEPTED", "PROCESSING", "WAIT_USER_CONFIRM", "TRANSFERING")) { + return PayTransferRespDTO.processingOf(response.getTransferBillNo(), response.getOutBillNo(), response) + .setChannelPackageInfo(response.getPackageInfo()); // 一般情况下,只有 WAIT_USER_CONFIRM 会有! + } + if (Objects.equals("SUCCESS", state)) { + return PayTransferRespDTO.successOf(response.getTransferBillNo(), parseDateV3(response.getCreateTime()), + response.getOutBillNo(), response); + } + return PayTransferRespDTO.closedOf(state, response.getFailReason(), + response.getOutBillNo(), response); + } catch (WxPayException e) { + log.error("[doUnifiedTransfer][转账({}) 发起微信支付异常", reqDTO, e); + String errorCode = getErrorCode(e); + String errorMessage = getErrorMessage(e); + return PayTransferRespDTO.closedOf(errorCode, errorMessage, + reqDTO.getOutTransferNo(), e.getXmlString()); + } + } + + @Override + protected PayTransferRespDTO doGetTransfer(String outTradeNo) throws WxPayException { + fixV3HttpClientConnectionPoolShutDown(); + // 1. 执行请求 + TransferBillsGetResult response = client.getTransferService().getBillsByOutBillNo(outTradeNo); + + // 2. 创建返回结果 + String state = response.getState(); + if (ObjectUtils.equalsAny(state, "ACCEPTED", "PROCESSING", "WAIT_USER_CONFIRM", "TRANSFERING")) { + return PayTransferRespDTO.processingOf(response.getTransferBillNo(), response.getOutBillNo(), response); + } + if (Objects.equals("SUCCESS", state)) { + return PayTransferRespDTO.successOf(response.getTransferBillNo(), parseDateV3(response.getUpdateTime()), + response.getOutBillNo(), response); + } + return PayTransferRespDTO.closedOf(state, response.getFailReason(), + response.getOutBillNo(), response); + } + + @Override + public PayTransferRespDTO doParseTransferNotify(Map params, String body, Map headers) throws WxPayException { + switch (config.getApiVersion()) { + case API_VERSION_V3: + return parseTransferNotifyV3(body, headers); + case API_VERSION_V2: + throw new UnsupportedOperationException("V2 版本暂不支持,建议使用 V3 版本"); + default: + throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion())); + } + } + + private PayTransferRespDTO parseTransferNotifyV3(String body, Map headers) throws WxPayException { + // 1. 解析回调 + SignatureHeader signatureHeader = getRequestHeader(headers); + TransferBillsNotifyResult response = client.getTransferService().parseTransferBillsNotifyResult(body, signatureHeader); + TransferBillsNotifyResult.DecryptNotifyResult result = response.getResult(); + + // 2. 创建返回结果 + String state = result.getState(); + if (ObjectUtils.equalsAny(state, "ACCEPTED", "PROCESSING", "WAIT_USER_CONFIRM", "TRANSFERING")) { + return PayTransferRespDTO.processingOf(result.getTransferBillNo(), result.getOutBillNo(), response); + } + if (Objects.equals("SUCCESS", state)) { + return PayTransferRespDTO.successOf(result.getTransferBillNo(), parseDateV3(result.getUpdateTime()), + result.getOutBillNo(), response); + } + return PayTransferRespDTO.closedOf(state, result.getFailReason(), + result.getOutBillNo(), response); + } + + // ========== 各种工具方法 ========== + + /** + * 组装请求头重的签名信息 + * + * @see 官方示例 + */ + private SignatureHeader getRequestHeader(Map headers) { + return SignatureHeader.builder() + .signature(headers.get("wechatpay-signature")) + .nonce(headers.get("wechatpay-nonce")) + .serial(headers.get("wechatpay-serial")) + .timeStamp(headers.get("wechatpay-timestamp")) + .build(); + } + + // TODO @芋艿:可能是 wxjava 的 bug:https://github.com/binarywang/WxJava/issues/1557 + private void fixV3HttpClientConnectionPoolShutDown() { + client.getConfig().setApiV3HttpClient(null); + } + + static String formatDateV2(LocalDateTime time) { + return TemporalAccessorUtil.format(time.atZone(ZoneId.systemDefault()), PURE_DATETIME_PATTERN); + } + + static LocalDateTime parseDateV2(String time) { + return LocalDateTimeUtil.parse(time, PURE_DATETIME_PATTERN); + } + + static LocalDateTime parseDateV2B(String time) { + return LocalDateTimeUtil.parse(time, NORM_DATETIME_PATTERN); + } + + static String formatDateV3(LocalDateTime time) { + return TemporalAccessorUtil.format(time.atZone(ZoneId.systemDefault()), UTC_WITH_XXX_OFFSET_PATTERN); + } + + static LocalDateTime parseDateV3(String time) { + return LocalDateTimeUtil.parse(time, UTC_WITH_XXX_OFFSET_PATTERN); + } + + static String getErrorCode(WxPayException e) { + if (StrUtil.isNotEmpty(e.getErrCode())) { + return e.getErrCode(); + } + if (StrUtil.isNotEmpty(e.getCustomErrorMsg())) { + return "CUSTOM_ERROR"; + } + return e.getReturnCode(); + } + + static String getErrorMessage(WxPayException e) { + if (StrUtil.isNotEmpty(e.getErrCode())) { + return e.getErrCodeDes(); + } + if (StrUtil.isNotEmpty(e.getCustomErrorMsg())) { + return e.getCustomErrorMsg(); + } + return e.getReturnMsg(); + } + +} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/weixin/WxAppPayClient.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/weixin/WxAppPayClient.java new file mode 100644 index 0000000..ae60f29 --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/weixin/WxAppPayClient.java @@ -0,0 +1,63 @@ +package cn.iocoder.yudao.module.pay.framework.pay.core.client.impl.weixin; + +import cn.iocoder.yudao.module.pay.enums.PayChannelEnum; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.order.PayOrderRespDTO; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO; +import cn.iocoder.yudao.module.pay.framework.pay.core.enums.PayOrderDisplayModeEnum; +import com.github.binarywang.wxpay.bean.order.WxPayAppOrderResult; +import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderRequest; +import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderV3Request; +import com.github.binarywang.wxpay.bean.result.WxPayUnifiedOrderV3Result; +import com.github.binarywang.wxpay.bean.result.enums.TradeTypeEnum; +import com.github.binarywang.wxpay.constant.WxPayConstants; +import com.github.binarywang.wxpay.exception.WxPayException; +import lombok.extern.slf4j.Slf4j; + +import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString; + +/** + * 微信支付【App 支付】的 PayClient 实现类 + * + * 文档:App 支付 + * + * // TODO 芋艿:未详细测试,因为手头没 App + * + * @author 芋道源码 + */ +@Slf4j +public class WxAppPayClient extends AbstractWxPayClient { + + public WxAppPayClient(Long channelId, WxPayClientConfig config) { + super(channelId, PayChannelEnum.WX_APP.getCode(), config); + } + + @Override + protected void doInit() { + super.doInit(WxPayConstants.TradeType.APP); + } + + @Override + protected PayOrderRespDTO doUnifiedOrderV2(PayOrderUnifiedReqDTO reqDTO) throws WxPayException { + // 构建 WxPayUnifiedOrderRequest 对象 + WxPayUnifiedOrderRequest request = buildPayUnifiedOrderRequestV2(reqDTO); + // 执行请求 + WxPayAppOrderResult response = client.createOrder(request); + + // 转换结果 + return PayOrderRespDTO.waitingOf(PayOrderDisplayModeEnum.APP.getMode(), toJsonString(response), + reqDTO.getOutTradeNo(), response); + } + + @Override + protected PayOrderRespDTO doUnifiedOrderV3(PayOrderUnifiedReqDTO reqDTO) throws WxPayException { + // 构建 WxPayUnifiedOrderV3Request 对象 + WxPayUnifiedOrderV3Request request = buildPayUnifiedOrderRequestV3(reqDTO); + // 执行请求 + WxPayUnifiedOrderV3Result.AppResult response = client.createOrderV3(TradeTypeEnum.APP, request); + + // 转换结果 + return PayOrderRespDTO.waitingOf(PayOrderDisplayModeEnum.APP.getMode(), toJsonString(response), + reqDTO.getOutTradeNo(), response); + } + +} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/weixin/WxBarPayClient.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/weixin/WxBarPayClient.java new file mode 100644 index 0000000..813342c --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/weixin/WxBarPayClient.java @@ -0,0 +1,107 @@ +package cn.iocoder.yudao.module.pay.framework.pay.core.client.impl.weixin; + +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.thread.ThreadUtil; +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils; +import cn.iocoder.yudao.module.pay.enums.PayChannelEnum; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.order.PayOrderRespDTO; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO; +import cn.iocoder.yudao.module.pay.framework.pay.core.enums.PayOrderDisplayModeEnum; +import com.github.binarywang.wxpay.bean.request.WxPayMicropayRequest; +import com.github.binarywang.wxpay.bean.result.WxPayMicropayResult; +import com.github.binarywang.wxpay.constant.WxPayConstants; +import com.github.binarywang.wxpay.exception.WxPayException; +import lombok.extern.slf4j.Slf4j; + +import java.time.Duration; +import java.time.LocalDateTime; +import java.util.concurrent.TimeUnit; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.invalidParamException; +import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString; + +/** + * 微信支付【付款码支付】的 PayClient 实现类 + * + * 文档:付款码支付 + * + * @author 芋道源码 + */ +@Slf4j +public class WxBarPayClient extends AbstractWxPayClient { + + /** + * 微信付款码的过期时间 + */ + private static final Duration AUTH_CODE_EXPIRE = Duration.ofMinutes(3); + + public WxBarPayClient(Long channelId, WxPayClientConfig config) { + super(channelId, PayChannelEnum.WX_BAR.getCode(), config); + } + + @Override + protected void doInit() { + super.doInit(WxPayConstants.TradeType.MICROPAY); + } + + @Override + protected PayOrderRespDTO doUnifiedOrderV2(PayOrderUnifiedReqDTO reqDTO) throws WxPayException { + // 由于付款码需要不断轮询,所以需要在较短的时间完成支付 + LocalDateTime expireTime = LocalDateTimeUtils.addTime(AUTH_CODE_EXPIRE); + if (expireTime.isAfter(reqDTO.getExpireTime())) { + expireTime = reqDTO.getExpireTime(); + } + // 构建 WxPayMicropayRequest 对象 + WxPayMicropayRequest request = WxPayMicropayRequest.newBuilder() + .outTradeNo(reqDTO.getOutTradeNo()) + .body(reqDTO.getSubject()) + .detail(reqDTO.getBody()) + .totalFee(reqDTO.getPrice()) // 单位分 + .timeExpire(formatDateV2(expireTime)) + .spbillCreateIp(reqDTO.getUserIp()) + .authCode(getAuthCode(reqDTO)) + .build(); + // 执行请求,重试直到失败(过期),或者成功 + WxPayException lastWxPayException = null; + for (int i = 1; i < Byte.MAX_VALUE; i++) { + try { + WxPayMicropayResult response = client.micropay(request); + // 支付成功,例如说:1)用户输入了密码;2)用户免密支付 + return PayOrderRespDTO.successOf(response.getTransactionId(), response.getOpenid(), parseDateV2(response.getTimeEnd()), + response.getOutTradeNo(), response) + .setDisplayMode(PayOrderDisplayModeEnum.BAR_CODE.getMode()); + } catch (WxPayException ex) { + lastWxPayException = ex; + // 如果不满足这 3 种任一的,则直接抛出 WxPayException 异常,不仅需处理 + // 1. SYSTEMERROR:接口返回错误:请立即调用被扫订单结果查询API,查询当前订单状态,并根据订单的状态决定下一步的操作。 + // 2. USERPAYING:用户支付中,需要输入密码:等待 5 秒,然后调用被扫订单结果查询 API,查询当前订单的不同状态,决定下一步的操作。 + // 3. BANKERROR:银行系统异常:请立即调用被扫订单结果查询 API,查询当前订单的不同状态,决定下一步的操作。 + if (!StrUtil.equalsAny(ex.getErrCode(), "SYSTEMERROR", "USERPAYING", "BANKERROR")) { + throw ex; + } + // 等待 5 秒,继续下一轮重新发起支付 + log.info("[doUnifiedOrderV2][发起微信 Bar 支付第({})失败,等待下一轮重试,请求({}),响应({})]", i, + toJsonString(request), ex.getMessage()); + ThreadUtil.sleep(5, TimeUnit.SECONDS); + } + } + throw lastWxPayException; + } + + @Override + protected PayOrderRespDTO doUnifiedOrderV3(PayOrderUnifiedReqDTO reqDTO) throws WxPayException { + return doUnifiedOrderV2(reqDTO); + } + + // ========== 各种工具方法 ========== + + static String getAuthCode(PayOrderUnifiedReqDTO reqDTO) { + String authCode = MapUtil.getStr(reqDTO.getChannelExtras(), "authCode"); + if (StrUtil.isEmpty(authCode)) { + throw invalidParamException("支付请求的 authCode 不能为空!"); + } + return authCode; + } + +} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/weixin/WxLitePayClient.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/weixin/WxLitePayClient.java new file mode 100644 index 0000000..2f2560b --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/weixin/WxLitePayClient.java @@ -0,0 +1,22 @@ +package cn.iocoder.yudao.module.pay.framework.pay.core.client.impl.weixin; + +import cn.iocoder.yudao.module.pay.enums.PayChannelEnum; +import lombok.extern.slf4j.Slf4j; + +/** + * 微信支付【小程序】的 PayClient 实现类 + * + * 由于公众号和小程序的微信支付逻辑一致,所以直接进行继承 + * + * 文档:JSAPI 下单 + * + * @author zwy + */ +@Slf4j +public class WxLitePayClient extends WxPubPayClient { + + public WxLitePayClient(Long channelId, WxPayClientConfig config) { + super(channelId, PayChannelEnum.WX_LITE.getCode(), config); + } + +} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/weixin/WxNativePayClient.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/weixin/WxNativePayClient.java new file mode 100644 index 0000000..bc7e4fe --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/weixin/WxNativePayClient.java @@ -0,0 +1,59 @@ +package cn.iocoder.yudao.module.pay.framework.pay.core.client.impl.weixin; + +import cn.iocoder.yudao.module.pay.enums.PayChannelEnum; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.order.PayOrderRespDTO; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO; +import cn.iocoder.yudao.module.pay.framework.pay.core.enums.PayOrderDisplayModeEnum; +import com.github.binarywang.wxpay.bean.order.WxPayNativeOrderResult; +import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderRequest; +import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderV3Request; +import com.github.binarywang.wxpay.bean.result.enums.TradeTypeEnum; +import com.github.binarywang.wxpay.constant.WxPayConstants; +import com.github.binarywang.wxpay.exception.WxPayException; +import lombok.extern.slf4j.Slf4j; + +/** + * 微信支付【Native 二维码】的 PayClient 实现类 + * + * 文档:Native 下单 + * + * @author zwy + */ +@Slf4j +public class WxNativePayClient extends AbstractWxPayClient { + + public WxNativePayClient(Long channelId, WxPayClientConfig config) { + super(channelId, PayChannelEnum.WX_NATIVE.getCode(), config); + } + + @Override + protected void doInit() { + super.doInit(WxPayConstants.TradeType.NATIVE); + } + + @Override + protected PayOrderRespDTO doUnifiedOrderV2(PayOrderUnifiedReqDTO reqDTO) throws WxPayException { + // 构建 WxPayUnifiedOrderRequest 对象 + WxPayUnifiedOrderRequest request = buildPayUnifiedOrderRequestV2(reqDTO) + .setProductId(reqDTO.getOutTradeNo()); // V2 必须传递 productId,无需在微信配置。该参数在 V3 简化,无需传递! + // 执行请求 + WxPayNativeOrderResult response = client.createOrder(request); + + // 转换结果 + return PayOrderRespDTO.waitingOf(PayOrderDisplayModeEnum.QR_CODE.getMode(), response.getCodeUrl(), + reqDTO.getOutTradeNo(), response); + } + + @Override + protected PayOrderRespDTO doUnifiedOrderV3(PayOrderUnifiedReqDTO reqDTO) throws WxPayException { + // 构建 WxPayUnifiedOrderV3Request 对象 + WxPayUnifiedOrderV3Request request = buildPayUnifiedOrderRequestV3(reqDTO); + // 执行请求 + String response = client.createOrderV3(TradeTypeEnum.NATIVE, request); + + // 转换结果 + return PayOrderRespDTO.waitingOf(PayOrderDisplayModeEnum.QR_CODE.getMode(), response, + reqDTO.getOutTradeNo(), response); + } + +} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/weixin/WxPayClientConfig.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/weixin/WxPayClientConfig.java new file mode 100644 index 0000000..5aa812b --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/weixin/WxPayClientConfig.java @@ -0,0 +1,107 @@ +package cn.iocoder.yudao.module.pay.framework.pay.core.client.impl.weixin; + +import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.PayClientConfig; +import jakarta.validation.Validator; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +/** + * 微信支付的 PayClientConfig 实现类 + * 属性主要来自 {@link com.github.binarywang.wxpay.config.WxPayConfig} 的必要属性 + * + * @author 芋道源码 + */ +@Data +public class WxPayClientConfig implements PayClientConfig { + + /** + * API 版本 - V2 + * + * V2 协议说明 + */ + public static final String API_VERSION_V2 = "v2"; + /** + * API 版本 - V3 + * + * V3 协议说明 + */ + public static final String API_VERSION_V3 = "v3"; + + /** + * 公众号或者小程序的 appid + * + * 只有公众号或小程序需要该字段 + */ + @NotBlank(message = "APPID 不能为空", groups = {V2.class, V3.class}) + private String appId; + /** + * 商户号 + */ + @NotBlank(message = "商户号不能为空", groups = {V2.class, V3.class}) + private String mchId; + /** + * API 版本 + */ + @NotBlank(message = "API 版本不能为空", groups = {V2.class, V3.class}) + private String apiVersion; + + // ========== V2 版本的参数 ========== + + /** + * 商户密钥 + */ + @NotBlank(message = "商户密钥不能为空", groups = V2.class) + private String mchKey; + /** + * apiclient_cert.p12 证书文件的对应字符串【base64 格式】 + * + * 为什么采用 base64 格式?因为 p12 读取后是二进制,需要转换成 base64 格式才好传输和存储 + */ + @NotBlank(message = "apiclient_cert.p12 不能为空", groups = V2.class) + private String keyContent; + + // ========== V3 版本的参数 ========== + /** + * apiclient_key.pem 证书文件的对应字符串 + */ + @NotBlank(message = "apiclient_key 不能为空", groups = V3.class) + private String privateKeyContent; + /** + * apiV3 密钥值 + */ + @NotBlank(message = "apiV3 密钥值不能为空", groups = V3.class) + private String apiV3Key; + /** + * 证书序列号(merchantSerialNumber) + */ + @NotBlank(message = "证书序列号不能为空", groups = V3.class) + private String certSerialNo; + + /** + * pub_key.pem 证书文件的对应字符串 + */ + @NotBlank(message = "pub_key.pem 不能为空", groups = V3.class) + private String publicKeyContent; + @NotBlank(message = "publicKeyId 不能为空", groups = V3.class) + private String publicKeyId; + + /** + * 分组校验 v2版本 + */ + public interface V2 { + } + + /** + * 分组校验 v3版本 + */ + public interface V3 { + } + + @Override + public void validate(Validator validator) { + ValidationUtils.validate(validator, this, + API_VERSION_V2.equals(this.getApiVersion()) ? V2.class : V3.class); + } + +} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/weixin/WxPubPayClient.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/weixin/WxPubPayClient.java new file mode 100644 index 0000000..322e9b1 --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/weixin/WxPubPayClient.java @@ -0,0 +1,81 @@ +package cn.iocoder.yudao.module.pay.framework.pay.core.client.impl.weixin; + +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.module.pay.enums.PayChannelEnum; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.order.PayOrderRespDTO; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO; +import cn.iocoder.yudao.module.pay.framework.pay.core.enums.PayOrderDisplayModeEnum; +import com.github.binarywang.wxpay.bean.order.WxPayMpOrderResult; +import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderRequest; +import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderV3Request; +import com.github.binarywang.wxpay.bean.result.WxPayUnifiedOrderV3Result; +import com.github.binarywang.wxpay.bean.result.enums.TradeTypeEnum; +import com.github.binarywang.wxpay.constant.WxPayConstants; +import com.github.binarywang.wxpay.exception.WxPayException; +import lombok.extern.slf4j.Slf4j; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.invalidParamException; +import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString; + +/** + * 微信支付(公众号)的 PayClient 实现类 + * + * 文档:JSAPI 下单 + * + * @author 芋道源码 + */ +@Slf4j +public class WxPubPayClient extends AbstractWxPayClient { + + @SuppressWarnings("unused") // 反射会调用到,所以不能删除 + public WxPubPayClient(Long channelId, WxPayClientConfig config) { + super(channelId, PayChannelEnum.WX_PUB.getCode(), config); + } + + protected WxPubPayClient(Long channelId, String channelCode, WxPayClientConfig config) { + super(channelId, channelCode, config); + } + + @Override + protected void doInit() { + super.doInit(WxPayConstants.TradeType.JSAPI); + } + + @Override + protected PayOrderRespDTO doUnifiedOrderV2(PayOrderUnifiedReqDTO reqDTO) throws WxPayException { + // 构建 WxPayUnifiedOrderRequest 对象 + WxPayUnifiedOrderRequest request = buildPayUnifiedOrderRequestV2(reqDTO) + .setOpenid(getOpenid(reqDTO)); + // 执行请求 + WxPayMpOrderResult response = client.createOrder(request); + + // 转换结果 + return PayOrderRespDTO.waitingOf(PayOrderDisplayModeEnum.APP.getMode(), toJsonString(response), + reqDTO.getOutTradeNo(), response); + } + + @Override + protected PayOrderRespDTO doUnifiedOrderV3(PayOrderUnifiedReqDTO reqDTO) throws WxPayException { + // 构建 WxPayUnifiedOrderRequest 对象 + WxPayUnifiedOrderV3Request request = buildPayUnifiedOrderRequestV3(reqDTO) + .setPayer(new WxPayUnifiedOrderV3Request.Payer().setOpenid(getOpenid(reqDTO))); + // 执行请求 + WxPayUnifiedOrderV3Result.JsapiResult response = client.createOrderV3(TradeTypeEnum.JSAPI, request); + + // 转换结果 + return PayOrderRespDTO.waitingOf(PayOrderDisplayModeEnum.APP.getMode(), toJsonString(response), + reqDTO.getOutTradeNo(), response); + } + + // ========== 各种工具方法 ========== + + static String getOpenid(PayOrderUnifiedReqDTO reqDTO) { + String openid = MapUtil.getStr(reqDTO.getChannelExtras(), "openid"); + if (StrUtil.isEmpty(openid)) { + throw invalidParamException("支付请求的 openid 不能为空!"); + } + return openid; + } + +} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/weixin/WxWapPayClient.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/weixin/WxWapPayClient.java new file mode 100644 index 0000000..7cfbca4 --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/weixin/WxWapPayClient.java @@ -0,0 +1,62 @@ +package cn.iocoder.yudao.module.pay.framework.pay.core.client.impl.weixin; + +import cn.iocoder.yudao.module.pay.enums.PayChannelEnum; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.order.PayOrderRespDTO; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO; +import cn.iocoder.yudao.module.pay.framework.pay.core.enums.PayOrderDisplayModeEnum; +import com.github.binarywang.wxpay.bean.order.WxPayMwebOrderResult; +import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderRequest; +import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderV3Request; +import com.github.binarywang.wxpay.bean.result.enums.TradeTypeEnum; +import com.github.binarywang.wxpay.constant.WxPayConstants; +import com.github.binarywang.wxpay.exception.WxPayException; +import lombok.extern.slf4j.Slf4j; + +/** + * 微信支付(H5 网页)的 PayClient 实现类 + * + * 文档:H5下单API + * + * @author YYQ + */ +@Slf4j +public class WxWapPayClient extends AbstractWxPayClient { + + public WxWapPayClient(Long channelId, WxPayClientConfig config) { + super(channelId, PayChannelEnum.WX_WAP.getCode(), config); + } + + protected WxWapPayClient(Long channelId, String channelCode, WxPayClientConfig config) { + super(channelId, channelCode, config); + } + + @Override + protected void doInit() { + super.doInit(WxPayConstants.TradeType.MWEB); + } + + @Override + protected PayOrderRespDTO doUnifiedOrderV2(PayOrderUnifiedReqDTO reqDTO) throws WxPayException { + // 构建 WxPayUnifiedOrderRequest 对象 + WxPayUnifiedOrderRequest request = buildPayUnifiedOrderRequestV2(reqDTO); + // 执行请求 + WxPayMwebOrderResult response = client.createOrder(request); + + // 转换结果 + return PayOrderRespDTO.waitingOf(PayOrderDisplayModeEnum.URL.getMode(), response.getMwebUrl(), + reqDTO.getOutTradeNo(), response); + } + + @Override + protected PayOrderRespDTO doUnifiedOrderV3(PayOrderUnifiedReqDTO reqDTO) throws WxPayException { + // 构建 WxPayUnifiedOrderRequest 对象 + WxPayUnifiedOrderV3Request request = buildPayUnifiedOrderRequestV3(reqDTO); + // 执行请求 + String response = client.createOrderV3(TradeTypeEnum.H5, request); + + // 转换结果 + return PayOrderRespDTO.waitingOf(PayOrderDisplayModeEnum.URL.getMode(), response, + reqDTO.getOutTradeNo(), response); + } + +} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/enums/PayOrderDisplayModeEnum.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/enums/PayOrderDisplayModeEnum.java new file mode 100644 index 0000000..182defb --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/enums/PayOrderDisplayModeEnum.java @@ -0,0 +1,29 @@ +package cn.iocoder.yudao.module.pay.framework.pay.core.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 支付 UI 展示模式 + * + * @author 芋道源码 + */ +@Getter +@AllArgsConstructor +public enum PayOrderDisplayModeEnum { + + URL("url"), // Redirect 跳转链接的方式 + IFRAME("iframe"), // IFrame 内嵌链接的方式【目前暂时用不到】 + FORM("form"), // HTML 表单提交 + QR_CODE("qr_code"), // 二维码的文字内容 + QR_CODE_URL("qr_code_url"), // 二维码的图片链接 + BAR_CODE("bar_code"), // 条形码 + APP("app"), // 应用:Android、iOS、微信小程序、微信公众号等,需要做自定义处理的 + ; + + /** + * 展示模式 + */ + private final String mode; + +} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/package-info.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/package-info.java new file mode 100644 index 0000000..90f4577 --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/package-info.java @@ -0,0 +1,6 @@ +/** + * 支付客户端,接入国内多个支付渠道: + * 1. 支付宝,基于官方 SDK 接入 + * 2. 微信支付,基于 weixin-java-pay 接入 + */ +package cn.iocoder.yudao.module.pay.framework.pay; \ No newline at end of file diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/channel/PayChannelService.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/channel/PayChannelService.java index dcd075d..c397986 100644 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/channel/PayChannelService.java +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/channel/PayChannelService.java @@ -1,7 +1,7 @@ package cn.iocoder.yudao.module.pay.service.channel; import cn.iocoder.yudao.framework.common.exception.ServiceException; -import cn.iocoder.yudao.framework.pay.core.client.PayClient; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.PayClient; import cn.iocoder.yudao.module.pay.controller.admin.channel.vo.PayChannelCreateReqVO; import cn.iocoder.yudao.module.pay.controller.admin.channel.vo.PayChannelUpdateReqVO; import cn.iocoder.yudao.module.pay.dal.dataobject.channel.PayChannelDO; diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/channel/PayChannelServiceImpl.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/channel/PayChannelServiceImpl.java index ba2bef7..56ff5e5 100644 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/channel/PayChannelServiceImpl.java +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/channel/PayChannelServiceImpl.java @@ -4,17 +4,18 @@ import cn.hutool.core.lang.Assert; import cn.hutool.core.util.ObjectUtil; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.common.util.json.JsonUtils; -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.enums.channel.PayChannelEnum; +import cn.iocoder.yudao.module.pay.enums.PayChannelEnum; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.PayClient; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.PayClientConfig; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.PayClientFactory; import cn.iocoder.yudao.module.pay.controller.admin.channel.vo.PayChannelCreateReqVO; import cn.iocoder.yudao.module.pay.controller.admin.channel.vo.PayChannelUpdateReqVO; import cn.iocoder.yudao.module.pay.convert.channel.PayChannelConvert; import cn.iocoder.yudao.module.pay.dal.dataobject.channel.PayChannelDO; import cn.iocoder.yudao.module.pay.dal.mysql.channel.PayChannelMapper; -import cn.iocoder.yudao.module.pay.framework.pay.core.WalletPayClient; -import jakarta.annotation.PostConstruct; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.impl.NonePayClientConfig; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.impl.alipay.AlipayPayClientConfig; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.impl.weixin.WxPayClientConfig; import jakarta.annotation.Resource; import jakarta.validation.Validator; import lombok.extern.slf4j.Slf4j; @@ -46,14 +47,6 @@ public class PayChannelServiceImpl implements PayChannelService { @Resource private Validator validator; - /** - * 初始化,为了注册钱包 - */ - @PostConstruct - public void init() { - payClientFactory.registerPayClientClass(PayChannelEnum.WALLET, WalletPayClient.class); - } - @Override public Long createChannel(PayChannelCreateReqVO reqVO) { // 断言是否有重复的 @@ -89,7 +82,9 @@ public class PayChannelServiceImpl implements PayChannelService { */ private PayClientConfig parseConfig(String code, String configStr) { // 解析配置 - Class payClass = PayChannelEnum.getByCode(code).getConfigClass(); + Class payClass = PayChannelEnum.isAlipay(configStr) ? AlipayPayClientConfig.class + : PayChannelEnum.isWeixin(configStr) ? WxPayClientConfig.class + : NonePayClientConfig.class; if (ObjectUtil.isNull(payClass)) { throw exception(CHANNEL_NOT_FOUND); } diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/order/PayOrderService.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/order/PayOrderService.java index 9971ba5..c4496f8 100755 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/order/PayOrderService.java +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/order/PayOrderService.java @@ -1,7 +1,7 @@ package cn.iocoder.yudao.module.pay.service.order; import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.order.PayOrderRespDTO; import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderCreateReqDTO; import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderExportReqVO; import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderPageReqVO; diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/order/PayOrderServiceImpl.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/order/PayOrderServiceImpl.java index 5ed2ec7..f468355 100755 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/order/PayOrderServiceImpl.java +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/order/PayOrderServiceImpl.java @@ -7,10 +7,9 @@ import cn.hutool.extra.spring.SpringUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils; import cn.iocoder.yudao.framework.common.util.number.MoneyUtils; -import cn.iocoder.yudao.framework.pay.core.client.PayClient; -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.enums.order.PayOrderStatusRespEnum; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.PayClient; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.order.PayOrderRespDTO; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO; import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils; import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderCreateReqDTO; import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderExportReqVO; @@ -218,13 +217,13 @@ public class PayOrderServiceImpl implements PayOrderService { throw exception(PAY_ORDER_EXTENSION_IS_PAID); } // 情况二:调用三方接口,查询支付单状态,是不是已支付 - PayClient payClient = channelService.getPayClient(orderExtension.getChannelId()); + PayClient payClient = channelService.getPayClient(orderExtension.getChannelId()); if (payClient == null) { log.error("[validateOrderCanSubmit][渠道编号({}) 找不到对应的支付客户端]", orderExtension.getChannelId()); return; } PayOrderRespDTO respDTO = payClient.getOrder(orderExtension.getNo()); - if (respDTO != null && PayOrderStatusRespEnum.isSuccess(respDTO.getStatus())) { + if (respDTO != null && PayOrderStatusEnum.isSuccess(respDTO.getStatus())) { log.warn("[validateOrderCanSubmit][order({}) 的 PayOrderRespDTO({}) 已支付,可能是回调延迟]", id, toJsonString(respDTO)); throw exception(PAY_ORDER_EXTENSION_IS_PAID); @@ -273,12 +272,12 @@ public class PayOrderServiceImpl implements PayOrderService { // 注意,如果是方法内调用该方法,需要通过 getSelf().notifyPayOrder(channel, notify) 调用,否则事务不生效 public void notifyOrder(PayChannelDO channel, PayOrderRespDTO notify) { // 情况一:支付成功的回调 - if (PayOrderStatusRespEnum.isSuccess(notify.getStatus())) { + if (PayOrderStatusEnum.isSuccess(notify.getStatus())) { notifyOrderSuccess(channel, notify); return; } // 情况二:支付失败的回调 - if (PayOrderStatusRespEnum.isClosed(notify.getStatus())) { + if (PayOrderStatusEnum.isClosed(notify.getStatus())) { notifyOrderClosed(channel, notify); } // 情况三:WAITING:无需处理 @@ -373,6 +372,7 @@ public class PayOrderServiceImpl implements PayOrderService { updateOrderExtensionClosed(channel, notify); } + @SuppressWarnings("unused") private void updateOrderExtensionClosed(PayChannelDO channel, PayOrderRespDTO notify) { // 1. 查询 PayOrderExtensionDO PayOrderExtensionDO orderExtension = orderExtensionMapper.selectByNo(notify.getOutTradeNo()); @@ -488,7 +488,7 @@ public class PayOrderServiceImpl implements PayOrderService { private boolean syncOrder(PayOrderExtensionDO orderExtension) { try { // 1.1 查询支付订单信息 - PayClient payClient = channelService.getPayClient(orderExtension.getChannelId()); + PayClient payClient = channelService.getPayClient(orderExtension.getChannelId()); if (payClient == null) { log.error("[syncOrder][渠道编号({}) 找不到对应的支付客户端]", orderExtension.getChannelId()); return false; @@ -499,14 +499,14 @@ public class PayOrderServiceImpl implements PayOrderService { // 当用户支付成功之后,该订单状态在渠道的回调中无法从已关闭改为已支付,造成重大影响。 // 考虑此定时任务是异常场景的兜底操作,因此这里不做变更,优先以回调为准。 // 让订单自动随着支付渠道那边一起等到过期,确保渠道先过期关闭支付入口,而后通过订单过期定时任务关闭自己的订单。 - if (PayOrderStatusRespEnum.isClosed(respDTO.getStatus())) { + if (PayOrderStatusEnum.isClosed(respDTO.getStatus())) { return false; } // 1.2 回调支付结果 notifyOrder(orderExtension.getChannelId(), respDTO); // 2. 如果是已支付,则返回 true - return PayOrderStatusRespEnum.isSuccess(respDTO.getStatus()); + return PayOrderStatusEnum.isSuccess(respDTO.getStatus()); } catch (Throwable e) { log.error("[syncOrder][orderExtension({}) 同步支付状态异常]", orderExtension.getId(), e); return false; @@ -551,20 +551,20 @@ public class PayOrderServiceImpl implements PayOrderService { return false; } // 情况二:调用三方接口,查询支付单状态,是不是已支付/已退款 - PayClient payClient = channelService.getPayClient(orderExtension.getChannelId()); + PayClient payClient = channelService.getPayClient(orderExtension.getChannelId()); if (payClient == null) { log.error("[expireOrder][渠道编号({}) 找不到对应的支付客户端]", orderExtension.getChannelId()); return false; } PayOrderRespDTO respDTO = payClient.getOrder(orderExtension.getNo()); - if (PayOrderStatusRespEnum.isRefund(respDTO.getStatus())) { + if (PayOrderStatusEnum.isRefund(respDTO.getStatus())) { // 补充说明:按道理,应该是 WAITING => SUCCESS => REFUND 状态,如果直接 WAITING => REFUND 状态,说明中间丢了过程 // 此时,需要人工介入,手工补齐数据,保持 WAITING => SUCCESS => REFUND 的过程 log.error("[expireOrder][extension({}) 的 PayOrderRespDTO({}) 已退款,可能是回调延迟]", orderExtension.getId(), toJsonString(respDTO)); return false; } - if (PayOrderStatusRespEnum.isSuccess(respDTO.getStatus())) { + if (PayOrderStatusEnum.isSuccess(respDTO.getStatus())) { notifyOrder(orderExtension.getChannelId(), respDTO); return false; } diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundService.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundService.java index bf872eb..38b6e15 100755 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundService.java +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundService.java @@ -1,7 +1,7 @@ package cn.iocoder.yudao.module.pay.service.refund; import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundRespDTO; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.refund.PayRefundRespDTO; import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundCreateReqDTO; import cn.iocoder.yudao.module.pay.controller.admin.refund.vo.PayRefundExportReqVO; import cn.iocoder.yudao.module.pay.controller.admin.refund.vo.PayRefundPageReqVO; diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundServiceImpl.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundServiceImpl.java index 1885cad..051d46c 100755 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundServiceImpl.java +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundServiceImpl.java @@ -3,10 +3,9 @@ package cn.iocoder.yudao.module.pay.service.refund; import cn.hutool.core.collection.CollUtil; import cn.hutool.extra.spring.SpringUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.pay.core.client.PayClient; -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.enums.refund.PayRefundStatusRespEnum; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.PayClient; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.refund.PayRefundRespDTO; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO; import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils; import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundCreateReqDTO; import cn.iocoder.yudao.module.pay.controller.admin.refund.vo.PayRefundExportReqVO; @@ -203,12 +202,12 @@ public class PayRefundServiceImpl implements PayRefundService { @Transactional(rollbackFor = Exception.class) public void notifyRefund(PayChannelDO channel, PayRefundRespDTO notify) { // 情况一:退款成功 - if (PayRefundStatusRespEnum.isSuccess(notify.getStatus())) { + if (PayRefundStatusEnum.isSuccess(notify.getStatus())) { notifyRefundSuccess(channel, notify); return; } // 情况二:退款失败 - if (PayRefundStatusRespEnum.isFailure(notify.getStatus())) { + if (PayRefundStatusEnum.isFailure(notify.getStatus())) { notifyRefundFailure(channel, notify); } } @@ -302,7 +301,7 @@ public class PayRefundServiceImpl implements PayRefundService { private boolean syncRefund(PayRefundDO refund) { try { // 1.1 查询退款订单信息 - PayClient payClient = channelService.getPayClient(refund.getChannelId()); + PayClient payClient = channelService.getPayClient(refund.getChannelId()); if (payClient == null) { log.error("[syncRefund][渠道编号({}) 找不到对应的支付客户端]", refund.getChannelId()); return false; diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/transfer/PayTransferService.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/transfer/PayTransferService.java index 7169d1a..e2a1dbd 100644 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/transfer/PayTransferService.java +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/transfer/PayTransferService.java @@ -1,7 +1,7 @@ package cn.iocoder.yudao.module.pay.service.transfer; import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferRespDTO; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.transfer.PayTransferRespDTO; import cn.iocoder.yudao.module.pay.api.transfer.dto.PayTransferCreateReqDTO; import cn.iocoder.yudao.module.pay.api.transfer.dto.PayTransferCreateRespDTO; import cn.iocoder.yudao.module.pay.controller.admin.transfer.vo.PayTransferPageReqVO; diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/transfer/PayTransferServiceImpl.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/transfer/PayTransferServiceImpl.java index c8f3a48..dbd12c7 100644 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/transfer/PayTransferServiceImpl.java +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/transfer/PayTransferServiceImpl.java @@ -6,10 +6,9 @@ import cn.hutool.extra.spring.SpringUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; -import cn.iocoder.yudao.framework.pay.core.client.PayClient; -import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferRespDTO; -import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferUnifiedReqDTO; -import cn.iocoder.yudao.framework.pay.core.enums.transfer.PayTransferStatusRespEnum; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.PayClient; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.transfer.PayTransferRespDTO; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.transfer.PayTransferUnifiedReqDTO; import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils; import cn.iocoder.yudao.module.pay.api.transfer.dto.PayTransferCreateReqDTO; import cn.iocoder.yudao.module.pay.api.transfer.dto.PayTransferCreateRespDTO; @@ -142,15 +141,15 @@ public class PayTransferServiceImpl implements PayTransferService { // 注意,如果是方法内调用该方法,需要通过 getSelf().notifyTransfer(channel, notify) 调用,否则事务不生效 public void notifyTransfer(PayChannelDO channel, PayTransferRespDTO notify) { // 转账成功的回调 - if (PayTransferStatusRespEnum.isSuccess(notify.getStatus())) { + if (PayTransferStatusEnum.isSuccess(notify.getStatus())) { notifyTransferSuccess(channel, notify); } // 转账关闭的回调 - if (PayTransferStatusRespEnum.isClosed(notify.getStatus())) { + if (PayTransferStatusEnum.isClosed(notify.getStatus())) { notifyTransferClosed(channel, notify); } // 转账处理中的回调 - if (PayTransferStatusRespEnum.isProcessing(notify.getStatus())) { + if (PayTransferStatusEnum.isProcessing(notify.getStatus())) { notifyTransferProgressing(channel, notify); } // WAITING 状态无需处理 @@ -162,7 +161,7 @@ public class PayTransferServiceImpl implements PayTransferService { if (transfer == null) { throw exception(PAY_TRANSFER_NOT_FOUND); } - if (PayTransferStatusEnum.isProgressing(transfer.getStatus())) { // 如果已经是转账中,直接返回,不用重复更新 + if (PayTransferStatusEnum.isProcessing(transfer.getStatus())) { // 如果已经是转账中,直接返回,不用重复更新 log.info("[notifyTransferProgressing][transfer({}) 已经是转账中状态,无需更新]", transfer.getId()); return; } diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletRechargeServiceImpl.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletRechargeServiceImpl.java index 9f575d7..e4cda98 100644 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletRechargeServiceImpl.java +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletRechargeServiceImpl.java @@ -6,7 +6,6 @@ import cn.hutool.core.util.ObjectUtil; import cn.hutool.extra.spring.SpringUtil; import cn.iocoder.yudao.framework.common.pojo.PageParam; import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.pay.core.enums.refund.PayRefundStatusRespEnum; import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderCreateReqDTO; import cn.iocoder.yudao.module.pay.api.refund.PayRefundApi; import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundCreateReqDTO; @@ -224,7 +223,7 @@ public class PayWalletRechargeServiceImpl implements PayWalletRechargeService { .setRefundTotalPrice(walletRecharge.getTotalPrice()).setRefundPayPrice(walletRecharge.getPayPrice()) .setRefundBonusPrice(walletRecharge.getBonusPrice()); // 情况二:退款失败 - } else if (PayRefundStatusRespEnum.isFailure(payRefund.getStatus())) { + } else if (PayRefundStatusEnum.isFailure(payRefund.getStatus())) { // 2.2 解冻余额 payWalletService.unfreezePrice(walletRecharge.getWalletId(), walletRecharge.getTotalPrice()); diff --git a/yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/PayClientFactoryImplIntegrationTest.java b/yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/PayClientFactoryImplIntegrationTest.java new file mode 100644 index 0000000..20a4b70 --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/PayClientFactoryImplIntegrationTest.java @@ -0,0 +1,134 @@ +package cn.iocoder.yudao.module.pay.framework.pay.core.client.impl; + +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.util.RandomUtil; +import cn.iocoder.yudao.module.pay.enums.PayChannelEnum; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.PayClient; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.impl.PayClientFactoryImpl; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.impl.alipay.AlipayPayClientConfig; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.impl.alipay.AlipayQrPayClient; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.impl.alipay.AlipayWapPayClient; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.impl.weixin.WxPayClientConfig; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.impl.weixin.WxPubPayClient; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import java.io.FileInputStream; +import java.io.FileNotFoundException; + +/** + * {@link PayClientFactoryImpl} 的集成测试 + * + * @author 芋道源码 + */ +@Disabled +public class PayClientFactoryImplIntegrationTest { + + private static final String SERVER_URL_SANDBOX = "https://openapi.alipaydev.com/gateway.do"; + + private final PayClientFactoryImpl payClientFactory = new PayClientFactoryImpl(); + + /** + * {@link WxPubPayClient} 的 V2 版本 + */ + @Test + public void testCreatePayClient_WX_PUB_V2() { + // 创建配置 + WxPayClientConfig config = new WxPayClientConfig(); + config.setAppId("wx041349c6f39b268b"); + config.setMchId("1545083881"); + config.setApiVersion(WxPayClientConfig.API_VERSION_V2); + config.setMchKey("0alL64UDQdlCwiKZ73ib7ypaIjMns06p"); + // 创建客户端 + Long channelId = RandomUtil.randomLong(); + payClientFactory.createOrUpdatePayClient(channelId, PayChannelEnum.WX_PUB.getCode(), config); + PayClient client = payClientFactory.getPayClient(channelId); + // 发起支付 + PayOrderUnifiedReqDTO reqDTO = buildPayOrderUnifiedReqDTO(); +// CommonResult result = client.unifiedOrder(reqDTO); +// System.out.println(result); + } + + /** + * {@link WxPubPayClient} 的 V3 版本 + */ + @Test + public void testCreatePayClient_WX_PUB_V3() throws FileNotFoundException { + // 创建配置 + WxPayClientConfig config = new WxPayClientConfig(); + config.setAppId("wx041349c6f39b268b"); + config.setMchId("1545083881"); + config.setApiVersion(WxPayClientConfig.API_VERSION_V3); + config.setPrivateKeyContent(IoUtil.readUtf8(new FileInputStream("/Users/yunai/Downloads/wx_pay/apiclient_key.pem"))); +// config.setPrivateCertContent(IoUtil.readUtf8(new FileInputStream("/Users/yunai/Downloads/wx_pay/apiclient_cert.pem"))); + config.setApiV3Key("joerVi8y5DJ3o4ttA0o1uH47Xz1u2Ase"); + // 创建客户端 + Long channelId = RandomUtil.randomLong(); + payClientFactory.createOrUpdatePayClient(channelId, PayChannelEnum.WX_PUB.getCode(), config); + PayClient client = payClientFactory.getPayClient(channelId); + // 发起支付 + PayOrderUnifiedReqDTO reqDTO = buildPayOrderUnifiedReqDTO(); +// CommonResult result = client.unifiedOrder(reqDTO); +// System.out.println(result); + } + + /** + * {@link AlipayQrPayClient} + */ + @Test + @SuppressWarnings("unchecked") + public void testCreatePayClient_ALIPAY_QR() { + // 创建配置 + AlipayPayClientConfig config = new AlipayPayClientConfig(); + config.setAppId("2021000118634035"); + config.setServerUrl(SERVER_URL_SANDBOX); + config.setSignType(AlipayPayClientConfig.SIGN_TYPE_DEFAULT); + config.setPrivateKey("MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCHsEV1cDupwJv890x84qbppUtRIfhaKSwSVN0thCcsDCaAsGR5MZslDkO8NCT9V4r2SVXjyY7eJUZlZd1M0C8T01Tg4UOx5LUbic0O3A1uJMy6V1n9IyYwbAW3AEZhBd5bSbPgrqvmv3NeWSTQT6Anxnllf+2iDH6zyA2fPl7cYyQtbZoDJQFGqr4F+cGh2R6akzRKNoBkAeMYwoY6es2lX8sJxCVPWUmxNUoL3tScwlSpd7Bxw0q9c/X01jMwuQ0+Va358zgFiGERTE6yD01eu40OBDXOYO3z++y+TAYHlQQ2toMO63trepo88X3xV3R44/1DH+k2pAm2IF5ixiLrAgMBAAECggEAPx3SoXcseaD7rmcGcE0p4SMfbsUDdkUSmBBbtfF0GzwnqNLkWa+mgE0rWt9SmXngTQH97vByAYmLPl1s3G82ht1V7Sk7yQMe74lhFllr8eEyTjeVx3dTK1EEM4TwN+936DTXdFsr4TELJEcJJdD0KaxcCcfBLRDs2wnitEFZ9N+GoZybVmY8w0e0MI7PLObUZ2l0X4RurQnfG9ZxjXjC7PkeMVv7cGGylpNFi3BbvkRhdhLPDC2E6wqnr9e7zk+hiENivAezXrtxtwKovzCtnWJ1r0IO14Rh47H509Ic0wFnj+o5YyUL4LdmpL7yaaH6fM7zcSLFjNZPHvZCKPwYcQKBgQDQFho98QvnL8ex4v6cry4VitGpjSXm1qP3vmMQk4rTsn8iPWtcxPjqGEqOQJjdi4Mi0VZKQOLFwlH0kl95wNrD/isJ4O1yeYfX7YAXApzHqYNINzM79HemO3Yx1qLMW3okRFJ9pPRzbQ9qkTpsaegsmyX316zOBhzGRYjKbutTYwKBgQCm7phr9XdFW5Vh+XR90mVs483nrLmMiDKg7YKxSLJ8amiDjzPejCn7i95Hah08P+2MIZLIPbh2VLacczR6ltRRzN5bg5etFuqSgfkuHyxpoDmpjbe08+Q2h8JBYqcC5Nhv1AKU4iOUhVLHo/FBAQliMcGc/J3eiYTFC7EsNx382QKBgClb20doe7cttgFTXswBvaUmfFm45kmla924B7SpvrQpDD/f+VDtDZRp05fGmxuduSjYdtA3aVtpLiTwWu22OUUvZZqHDGruYOO4Hvdz23mL5b4ayqImCwoNU4bAZIc9v18p/UNf3/55NNE3oGcf/bev9rH2OjCQ4nM+Ktwhg8CFAoGACSgvbkShzUkv0ZcIf9ppu+ZnJh1AdGgINvGwaJ8vQ0nm/8h8NOoFZ4oNoGc+wU5Ubops7dUM6FjPR5e+OjdJ4E7Xp7d5O4J1TaIZlCEbo5OpdhaTDDcQvrkFu+Z4eN0qzj+YAKjDAOOrXc4tbr5q0FsgXscwtcNfaBuzFVTUrUkCgYEAwzPnMNhWG3zOWLUs2QFA2GP4Y+J8cpUYfj6pbKKzeLwyG9qBwF1NJpN8m+q9q7V9P2LY+9Lp9e1mGsGeqt5HMEA3P6vIpcqLJLqE/4PBLLRzfccTcmqb1m71+erxTRhHBRkGS+I7dZEb3olQfnS1Y1tpMBxiwYwR3LW4oXuJwj8="); + config.setAlipayPublicKey("MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnq90KnF4dTnlzzmxpujbI05OYqi5WxAS6cL0gnZFv2gK51HExF8v/BaP7P979PhFMgWTqmOOI+Dtno5s+yD09XTY1WkshbLk6i4g2Xlr8fyW9ODnkU88RI2w9UdPhQU4cPPwBNlrsYhKkVK2OxwM3kFqjoBBY0CZoZCsSQ3LDH5WeZqPArlsS6xa2zqJBuuoKjMrdpELl3eXSjP8K54eDJCbeetCZNKWLL3DPahTPB7LZikfYmslb0QUvCgGapD0xkS7eVq70NaL1G57MWABs4tbfWgxike4Daj3EfUrzIVspQxj7w8HEj9WozJPgL88kSJSits0pqD3n5r8HSuseQIDAQAB"); + // 创建客户端 + Long channelId = RandomUtil.randomLong(); + payClientFactory.createOrUpdatePayClient(channelId, PayChannelEnum.ALIPAY_QR.getCode(), config); + PayClient client = payClientFactory.getPayClient(channelId); + // 发起支付 + PayOrderUnifiedReqDTO reqDTO = buildPayOrderUnifiedReqDTO(); + reqDTO.setNotifyUrl("http://yunai.natapp1.cc/admin-api/pay/notify/callback/18"); // TODO @tina: 这里改成你的 natapp 回调地址 +// CommonResult result = (CommonResult) client.unifiedOrder(reqDTO); +// System.out.println(JsonUtils.toJsonString(result)); +// System.out.println(result.getData().getQrCode()); + } + + /** + * {@link AlipayWapPayClient} + */ + @Test + public void testCreatePayClient_ALIPAY_WAP() { + // 创建配置 + AlipayPayClientConfig config = new AlipayPayClientConfig(); + config.setAppId("2021000118634035"); + config.setServerUrl(SERVER_URL_SANDBOX); + config.setSignType(AlipayPayClientConfig.SIGN_TYPE_DEFAULT); + config.setPrivateKey("MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCHsEV1cDupwJv890x84qbppUtRIfhaKSwSVN0thCcsDCaAsGR5MZslDkO8NCT9V4r2SVXjyY7eJUZlZd1M0C8T01Tg4UOx5LUbic0O3A1uJMy6V1n9IyYwbAW3AEZhBd5bSbPgrqvmv3NeWSTQT6Anxnllf+2iDH6zyA2fPl7cYyQtbZoDJQFGqr4F+cGh2R6akzRKNoBkAeMYwoY6es2lX8sJxCVPWUmxNUoL3tScwlSpd7Bxw0q9c/X01jMwuQ0+Va358zgFiGERTE6yD01eu40OBDXOYO3z++y+TAYHlQQ2toMO63trepo88X3xV3R44/1DH+k2pAm2IF5ixiLrAgMBAAECggEAPx3SoXcseaD7rmcGcE0p4SMfbsUDdkUSmBBbtfF0GzwnqNLkWa+mgE0rWt9SmXngTQH97vByAYmLPl1s3G82ht1V7Sk7yQMe74lhFllr8eEyTjeVx3dTK1EEM4TwN+936DTXdFsr4TELJEcJJdD0KaxcCcfBLRDs2wnitEFZ9N+GoZybVmY8w0e0MI7PLObUZ2l0X4RurQnfG9ZxjXjC7PkeMVv7cGGylpNFi3BbvkRhdhLPDC2E6wqnr9e7zk+hiENivAezXrtxtwKovzCtnWJ1r0IO14Rh47H509Ic0wFnj+o5YyUL4LdmpL7yaaH6fM7zcSLFjNZPHvZCKPwYcQKBgQDQFho98QvnL8ex4v6cry4VitGpjSXm1qP3vmMQk4rTsn8iPWtcxPjqGEqOQJjdi4Mi0VZKQOLFwlH0kl95wNrD/isJ4O1yeYfX7YAXApzHqYNINzM79HemO3Yx1qLMW3okRFJ9pPRzbQ9qkTpsaegsmyX316zOBhzGRYjKbutTYwKBgQCm7phr9XdFW5Vh+XR90mVs483nrLmMiDKg7YKxSLJ8amiDjzPejCn7i95Hah08P+2MIZLIPbh2VLacczR6ltRRzN5bg5etFuqSgfkuHyxpoDmpjbe08+Q2h8JBYqcC5Nhv1AKU4iOUhVLHo/FBAQliMcGc/J3eiYTFC7EsNx382QKBgClb20doe7cttgFTXswBvaUmfFm45kmla924B7SpvrQpDD/f+VDtDZRp05fGmxuduSjYdtA3aVtpLiTwWu22OUUvZZqHDGruYOO4Hvdz23mL5b4ayqImCwoNU4bAZIc9v18p/UNf3/55NNE3oGcf/bev9rH2OjCQ4nM+Ktwhg8CFAoGACSgvbkShzUkv0ZcIf9ppu+ZnJh1AdGgINvGwaJ8vQ0nm/8h8NOoFZ4oNoGc+wU5Ubops7dUM6FjPR5e+OjdJ4E7Xp7d5O4J1TaIZlCEbo5OpdhaTDDcQvrkFu+Z4eN0qzj+YAKjDAOOrXc4tbr5q0FsgXscwtcNfaBuzFVTUrUkCgYEAwzPnMNhWG3zOWLUs2QFA2GP4Y+J8cpUYfj6pbKKzeLwyG9qBwF1NJpN8m+q9q7V9P2LY+9Lp9e1mGsGeqt5HMEA3P6vIpcqLJLqE/4PBLLRzfccTcmqb1m71+erxTRhHBRkGS+I7dZEb3olQfnS1Y1tpMBxiwYwR3LW4oXuJwj8="); + config.setAlipayPublicKey("MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnq90KnF4dTnlzzmxpujbI05OYqi5WxAS6cL0gnZFv2gK51HExF8v/BaP7P979PhFMgWTqmOOI+Dtno5s+yD09XTY1WkshbLk6i4g2Xlr8fyW9ODnkU88RI2w9UdPhQU4cPPwBNlrsYhKkVK2OxwM3kFqjoBBY0CZoZCsSQ3LDH5WeZqPArlsS6xa2zqJBuuoKjMrdpELl3eXSjP8K54eDJCbeetCZNKWLL3DPahTPB7LZikfYmslb0QUvCgGapD0xkS7eVq70NaL1G57MWABs4tbfWgxike4Daj3EfUrzIVspQxj7w8HEj9WozJPgL88kSJSits0pqD3n5r8HSuseQIDAQAB"); + // 创建客户端 + Long channelId = RandomUtil.randomLong(); + payClientFactory.createOrUpdatePayClient(channelId, PayChannelEnum.ALIPAY_WAP.getCode(), config); + PayClient client = payClientFactory.getPayClient(channelId); + // 发起支付 + PayOrderUnifiedReqDTO reqDTO = buildPayOrderUnifiedReqDTO(); +// CommonResult result = client.unifiedOrder(reqDTO); +// System.out.println(JsonUtils.toJsonString(result)); + } + + private static PayOrderUnifiedReqDTO buildPayOrderUnifiedReqDTO() { + PayOrderUnifiedReqDTO reqDTO = new PayOrderUnifiedReqDTO(); + reqDTO.setPrice(123); + reqDTO.setSubject("IPhone 13"); + reqDTO.setBody("biubiubiu"); + reqDTO.setOutTradeNo(String.valueOf(System.currentTimeMillis())); + reqDTO.setUserIp("127.0.0.1"); + reqDTO.setNotifyUrl("http://127.0.0.1:8080"); + return reqDTO; + } + +} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AbstractAlipayClientTest.java b/yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AbstractAlipayClientTest.java new file mode 100644 index 0000000..cb723c9 --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AbstractAlipayClientTest.java @@ -0,0 +1,221 @@ +package cn.iocoder.yudao.module.pay.framework.pay.core.client.impl.alipay; + +import cn.hutool.core.date.LocalDateTimeUtil; +import cn.hutool.core.util.RandomUtil; +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; +import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; +import cn.iocoder.yudao.module.pay.enums.refund.PayRefundStatusEnum; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.refund.PayRefundRespDTO; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.exception.PayClientException; +import com.alipay.api.AlipayApiException; +import com.alipay.api.DefaultAlipayClient; +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; +import org.mockito.ArgumentMatcher; +import org.mockito.Mock; + +import jakarta.validation.ConstraintViolationException; +import java.util.Date; + +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*; +import static cn.iocoder.yudao.module.pay.framework.pay.core.client.impl.alipay.AlipayPayClientConfig.MODE_PUBLIC_KEY; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.Mockito.when; + +/** + * 支付宝 Client 的测试基类 + * + * @author jason + */ +public abstract class AbstractAlipayClientTest extends BaseMockitoUnitTest { + + 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; + + /** + * 子类需要实现该方法. 设置 client 的具体实现 + */ + @BeforeEach + public abstract void setUp(); + + @Test + @DisplayName("支付宝 Client 初始化") + public void testDoInit() { + // 调用 + client.doInit(); + // 断言 + DefaultAlipayClient realClient = client.getClient(); + assertNotSame(defaultAlipayClient, realClient); + assertInstanceOf(DefaultSigner.class, realClient.getSigner()); + assertEquals(config.getPrivateKey(), ((DefaultSigner) realClient.getSigner()).getPrivateKey()); + } + + @Test + @DisplayName("支付宝 Client 统一退款:成功") + public void testUnifiedRefund_success() throws AlipayApiException { + // mock 方法 + String notifyUrl = randomURL(); + Date refundTime = randomDate(); + String outRefundNo = randomString(); + String outTradeNo = randomString(); + Integer refundAmount = randomInteger(); + AlipayTradeRefundResponse response = randomPojo(AlipayTradeRefundResponse.class, o -> { + o.setSubCode(""); + o.setGmtRefundPay(refundTime); + }); + when(defaultAlipayClient.execute(argThat((ArgumentMatcher) request -> { + assertInstanceOf(AlipayTradeRefundModel.class, request.getBizModel()); + AlipayTradeRefundModel bizModel = (AlipayTradeRefundModel) request.getBizModel(); + assertEquals(outRefundNo, bizModel.getOutRequestNo()); + assertEquals(outTradeNo, bizModel.getOutTradeNo()); + assertEquals(String.valueOf(refundAmount / 100.0), bizModel.getRefundAmount()); + return true; + }))).thenReturn(response); + // 准备请求参数 + PayRefundUnifiedReqDTO refundReqDTO = randomPojo(PayRefundUnifiedReqDTO.class, o -> { + o.setOutRefundNo(outRefundNo); + o.setOutTradeNo(outTradeNo); + o.setNotifyUrl(notifyUrl); + o.setRefundPrice(refundAmount); + }); + + // 调用 + PayRefundRespDTO resp = client.unifiedRefund(refundReqDTO); + // 断言 + assertEquals(PayRefundStatusEnum.SUCCESS.getStatus(), resp.getStatus()); + assertEquals(outRefundNo, resp.getOutRefundNo()); + assertNull(resp.getChannelRefundNo()); + assertEquals(LocalDateTimeUtil.of(refundTime), resp.getSuccessTime()); + assertSame(response, resp.getRawData()); + assertNull(resp.getChannelErrorCode()); + assertNull(resp.getChannelErrorMsg()); + } + + @Test + @DisplayName("支付宝 Client 统一退款:渠道返回失败") + public void test_unified_refund_channel_failed() throws AlipayApiException { + // mock 方法 + String notifyUrl = randomURL(); + String subCode = randomString(); + String subMsg = randomString(); + AlipayTradeRefundResponse response = randomPojo(AlipayTradeRefundResponse.class, o -> { + o.setSubCode(subCode); + o.setSubMsg(subMsg); + }); + when(defaultAlipayClient.execute(argThat((ArgumentMatcher) request -> { + assertInstanceOf(AlipayTradeRefundModel.class, request.getBizModel()); + return true; + }))).thenReturn(response); + // 准备请求参数 + String outRefundNo = randomString(); + String outTradeNo = randomString(); + PayRefundUnifiedReqDTO refundReqDTO = randomPojo(PayRefundUnifiedReqDTO.class, o -> { + o.setOutRefundNo(outRefundNo); + o.setOutTradeNo(outTradeNo); + o.setNotifyUrl(notifyUrl); + }); + + // 调用 + PayRefundRespDTO resp = client.unifiedRefund(refundReqDTO); + // 断言 + assertEquals(PayRefundStatusEnum.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()); + } + + @Test + @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 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 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(PayClientException.class, () -> client.unifiedRefund(refundReqDTO)); + } + + @Test + @DisplayName("支付宝 Client 统一下单:参数校验不通过") + public void testUnifiedOrder_paramInvalidate() { + // 准备请求参数 + String outTradeNo = randomString(); + String notifyUrl = randomURL(); + PayOrderUnifiedReqDTO reqDTO = randomPojo(PayOrderUnifiedReqDTO.class, o -> { + o.setOutTradeNo(outTradeNo); + o.setNotifyUrl(notifyUrl); + }); + + // 调用,并断言 + assertThrows(ConstraintViolationException.class, () -> client.unifiedOrder(reqDTO)); + } + + protected PayOrderUnifiedReqDTO buildOrderUnifiedReqDTO(String notifyUrl, String outTradeNo, Integer price) { + return randomPojo(PayOrderUnifiedReqDTO.class, o -> { + o.setOutTradeNo(outTradeNo); + o.setNotifyUrl(notifyUrl); + o.setPrice(price); + o.setSubject(RandomUtil.randomString(32)); + o.setBody(RandomUtil.randomString(32)); + }); + } + +} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AlipayBarPayClientTest.java b/yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AlipayBarPayClientTest.java new file mode 100644 index 0000000..3e6dda1 --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AlipayBarPayClientTest.java @@ -0,0 +1,170 @@ +package cn.iocoder.yudao.module.pay.framework.pay.core.client.impl.alipay; + +import cn.hutool.core.date.LocalDateTimeUtil; +import cn.iocoder.yudao.framework.common.exception.ServiceException; +import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.order.PayOrderRespDTO; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO; +import cn.iocoder.yudao.module.pay.framework.pay.core.enums.PayOrderDisplayModeEnum; +import com.alipay.api.AlipayApiException; +import com.alipay.api.domain.AlipayTradePayModel; +import com.alipay.api.request.AlipayTradePayRequest; +import com.alipay.api.response.AlipayTradePayResponse; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentMatcher; +import org.mockito.InjectMocks; + +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +import static cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum.CLOSED; +import static cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum.WAITING; +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.Mockito.when; + +/** + * {@link AlipayBarPayClient} 单元测试 + * + * @author jason + */ +public class AlipayBarPayClientTest extends AbstractAlipayClientTest { + + @InjectMocks + private AlipayBarPayClient client = new AlipayBarPayClient(randomLongId(), config); + + @Override + @BeforeEach + public void setUp() { + setClient(client); + } + + @Test + @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("")); + when(defaultAlipayClient.execute(argThat((ArgumentMatcher) request -> { + assertInstanceOf(AlipayTradePayModel.class, request.getBizModel()); + assertEquals(notifyUrl, request.getNotifyUrl()); + AlipayTradePayModel model = (AlipayTradePayModel) request.getBizModel(); + assertEquals(outTradeNo, model.getOutTradeNo()); + assertEquals(String.valueOf(price / 100.0), model.getTotalAmount()); + assertEquals(authCode, model.getAuthCode()); + return true; + }))).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(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 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"); + o.setOutTradeNo(outTradeNo); + o.setTradeNo(channelNo); + o.setBuyerUserId(channelUserId); + o.setGmtPayment(payTime); + }); + 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); + // 断言 + assertEquals(PayOrderStatusEnum.SUCCESS.getStatus(), resp.getStatus()); + assertEquals(outTradeNo, resp.getOutTradeNo()); + 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 testUnifiedOrder_emptyAuthCode() { + // 准备参数 + PayOrderUnifiedReqDTO reqDTO = buildOrderUnifiedReqDTO(randomURL(), randomString(), randomInteger()); + + // 调用,并断言 + assertThrows(ServiceException.class, () -> client.unifiedOrder(reqDTO)); + } + + @Test + @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); + }); + 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()); + } + +} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AlipayPcPayClientTest.java b/yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AlipayPcPayClientTest.java new file mode 100644 index 0000000..62041f9 --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AlipayPcPayClientTest.java @@ -0,0 +1,131 @@ +package cn.iocoder.yudao.module.pay.framework.pay.core.client.impl.alipay; + +import cn.hutool.http.Method; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.order.PayOrderRespDTO; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO; +import cn.iocoder.yudao.module.pay.framework.pay.core.enums.PayOrderDisplayModeEnum; +import com.alipay.api.AlipayApiException; +import com.alipay.api.request.AlipayTradePagePayRequest; +import com.alipay.api.response.AlipayTradePagePayResponse; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentMatcher; +import org.mockito.InjectMocks; + +import static cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum.CLOSED; +import static cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum.WAITING; +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*; +import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; + +/** + * {@link AlipayPcPayClient} 单元测试 + * + * @author jason + */ +public class AlipayPcPayClientTest extends AbstractAlipayClientTest { + + @InjectMocks + private AlipayPcPayClient client = new AlipayPcPayClient(randomLongId(), config); + + @Override + @BeforeEach + public void setUp() { + setClient(client); + } + + @Test + @DisplayName("支付宝 PC 网站支付:URL Display Mode 下单成功") + public void testUnifiedOrder_urlSuccess() throws AlipayApiException { + // mock 方法 + String notifyUrl = randomURL(); + AlipayTradePagePayResponse response = randomPojo(AlipayTradePagePayResponse.class, o -> o.setSubCode("")); + when(defaultAlipayClient.pageExecute(argThat((ArgumentMatcher) request -> true), + eq(Method.GET.name()))).thenReturn(response); + // 准备请求参数 + String outTradeNo = randomString(); + Integer price = randomInteger(); + PayOrderUnifiedReqDTO reqDTO = buildOrderUnifiedReqDTO(notifyUrl, outTradeNo, price); + reqDTO.setDisplayMode(null); + + // 调用 + PayOrderRespDTO resp = client.unifiedOrder(reqDTO); + // 断言 + assertEquals(WAITING.getStatus(), resp.getStatus()); + assertEquals(outTradeNo, resp.getOutTradeNo()); + 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("支付宝 PC 网站支付:Form Display Mode 下单成功") + public void testUnifiedOrder_formSuccess() throws AlipayApiException { + // mock 方法 + String notifyUrl = randomURL(); + AlipayTradePagePayResponse response = randomPojo(AlipayTradePagePayResponse.class, o -> o.setSubCode("")); + when(defaultAlipayClient.pageExecute(argThat((ArgumentMatcher) request -> true), + eq(Method.POST.name()))).thenReturn(response); + // 准备请求参数 + String outTradeNo = randomString(); + Integer price = randomInteger(); + PayOrderUnifiedReqDTO reqDTO = buildOrderUnifiedReqDTO(notifyUrl, outTradeNo, price); + reqDTO.setDisplayMode(PayOrderDisplayModeEnum.FORM.getMode()); + + // 调用 + PayOrderRespDTO resp = client.unifiedOrder(reqDTO); + // 断言 + assertEquals(WAITING.getStatus(), resp.getStatus()); + assertEquals(outTradeNo, resp.getOutTradeNo()); + assertNull(resp.getChannelOrderNo()); + assertNull(resp.getChannelUserId()); + assertNull(resp.getSuccessTime()); + assertEquals(PayOrderDisplayModeEnum.FORM.getMode(), resp.getDisplayMode()); + assertEquals(response.getBody(), resp.getDisplayContent()); + assertSame(response, resp.getRawData()); + assertNull(resp.getChannelErrorCode()); + assertNull(resp.getChannelErrorMsg()); + } + + @Test + @DisplayName("支付宝 PC 网站支付:渠道返回失败") + public void testUnifiedOrder_channelFailed() throws AlipayApiException { + // mock 方法 + String subCode = randomString(); + String subMsg = randomString(); + AlipayTradePagePayResponse response = randomPojo(AlipayTradePagePayResponse.class, o -> { + o.setSubCode(subCode); + o.setSubMsg(subMsg); + }); + when(defaultAlipayClient.pageExecute(argThat((ArgumentMatcher) request -> true), + eq(Method.GET.name()))).thenReturn(response); + // 准备请求参数 + String outTradeNo = randomString(); + PayOrderUnifiedReqDTO reqDTO = buildOrderUnifiedReqDTO(randomURL(), outTradeNo, randomInteger()); + reqDTO.setDisplayMode(PayOrderDisplayModeEnum.URL.getMode()); + + // 调用 + 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()); + } + +} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AlipayQrPayClientTest.java b/yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AlipayQrPayClientTest.java new file mode 100644 index 0000000..c82a004 --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AlipayQrPayClientTest.java @@ -0,0 +1,147 @@ +package cn.iocoder.yudao.module.pay.framework.pay.core.client.impl.alipay; + +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; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.order.PayOrderRespDTO; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.exception.PayClientException; +import cn.iocoder.yudao.module.pay.framework.pay.core.enums.PayOrderDisplayModeEnum; +import com.alipay.api.AlipayApiException; +import com.alipay.api.request.AlipayTradePrecreateRequest; +import com.alipay.api.response.AlipayTradePrecreateResponse; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentMatcher; +import org.mockito.InjectMocks; + +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*; +import static cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum.CLOSED; +import static cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum.WAITING; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.Mockito.when; + +/** + * {@link AlipayQrPayClient} 单元测试 + * + * @author jason + */ +public class AlipayQrPayClientTest extends AbstractAlipayClientTest { + + @InjectMocks + private AlipayQrPayClient client = new AlipayQrPayClient(randomLongId(), config); + + @BeforeEach + public void setUp() { + setClient(client); + } + + @Test + @DisplayName("支付宝扫描支付:下单成功") + public void testUnifiedOrder_success() throws AlipayApiException { + // mock 方法 + String notifyUrl = randomURL(); + String qrCode = randomString(); + Integer price = randomInteger(); + AlipayTradePrecreateResponse response = randomPojo(AlipayTradePrecreateResponse.class, o -> { + o.setQrCode(qrCode); + o.setSubCode(""); + }); + when(defaultAlipayClient.execute(argThat((ArgumentMatcher) request -> { + assertEquals(notifyUrl, request.getNotifyUrl()); + return true; + }))).thenReturn(response); + // 准备请求参数 + String outTradeNo = randomString(); + PayOrderUnifiedReqDTO reqDTO = buildOrderUnifiedReqDTO(notifyUrl, outTradeNo, price); + + // 调用 + PayOrderRespDTO resp = client.unifiedOrder(reqDTO); + // 断言 + assertEquals(WAITING.getStatus(), resp.getStatus()); + assertEquals(outTradeNo, resp.getOutTradeNo()); + 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 testUnifiedOrder_channelFailed() throws AlipayApiException { + // mock 方法 + String notifyUrl = randomURL(); + String subCode = randomString(); + String subMsg = randomString(); + Integer price = randomInteger(); + AlipayTradePrecreateResponse response = randomPojo(AlipayTradePrecreateResponse.class, o -> { + o.setSubCode(subCode); + o.setSubMsg(subMsg); + }); + // mock + when(defaultAlipayClient.execute(argThat((ArgumentMatcher) request -> { + assertEquals(notifyUrl, request.getNotifyUrl()); + return true; + }))).thenReturn(response); + // 准备请求参数 + 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()); + } + + @Test + @DisplayName("支付宝扫描支付, 抛出系统异常") + public void testUnifiedOrder_throwPayException() throws AlipayApiException { + // mock 方法 + String outTradeNo = randomString(); + String notifyUrl = randomURL(); + Integer price = randomInteger(); + when(defaultAlipayClient.execute(argThat((ArgumentMatcher) request -> { + assertEquals(notifyUrl, request.getNotifyUrl()); + return true; + }))).thenThrow(new RuntimeException("系统异常")); + // 准备请求参数 + PayOrderUnifiedReqDTO reqDTO = buildOrderUnifiedReqDTO(notifyUrl, outTradeNo,price); + + // 调用,并断言 + assertThrows(PayClientException.class, () -> client.unifiedOrder(reqDTO)); + } + + @Test + @DisplayName("支付宝 Client 统一下单:抛出业务异常") + public void testUnifiedOrder_throwServiceException() throws AlipayApiException { + // mock 方法 + String outTradeNo = randomString(); + String notifyUrl = randomURL(); + Integer price = randomInteger(); + when(defaultAlipayClient.execute(argThat((ArgumentMatcher) 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)); + } + +} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AlipayWapPayClientTest.java b/yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AlipayWapPayClientTest.java new file mode 100644 index 0000000..a094b68 --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AlipayWapPayClientTest.java @@ -0,0 +1,111 @@ +package cn.iocoder.yudao.module.pay.framework.pay.core.client.impl.alipay; + +import cn.hutool.http.Method; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.order.PayOrderRespDTO; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO; +import cn.iocoder.yudao.module.pay.framework.pay.core.enums.PayOrderDisplayModeEnum; +import com.alipay.api.AlipayApiException; +import com.alipay.api.domain.AlipayTradeWapPayModel; +import com.alipay.api.request.AlipayTradeWapPayRequest; +import com.alipay.api.response.AlipayTradeWapPayResponse; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentMatcher; +import org.mockito.InjectMocks; + +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*; +import static cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum.CLOSED; +import static cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum.WAITING; +import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; + +/** + * {@link AlipayWapPayClient} 单元测试 + * + * @author jason + */ +public class AlipayWapPayClientTest extends AbstractAlipayClientTest { + + /** + * 支付宝 H5 支付 Client + */ + @InjectMocks + private AlipayWapPayClient client = new AlipayWapPayClient(randomLongId(), config); + + @BeforeEach + public void setUp() { + setClient(client); + } + + @Test + @DisplayName("支付宝 H5 支付:下单成功") + public void testUnifiedOrder_success() throws AlipayApiException { + // mock 方法 + String h5Body = randomString(); + Integer price = randomInteger(); + AlipayTradeWapPayResponse response = randomPojo(AlipayTradeWapPayResponse.class, o -> { + o.setSubCode(""); + o.setBody(h5Body); + }); + String notifyUrl = randomURL(); + when(defaultAlipayClient.pageExecute(argThat((ArgumentMatcher) request -> { + assertInstanceOf(AlipayTradeWapPayModel.class, request.getBizModel()); + AlipayTradeWapPayModel bizModel = (AlipayTradeWapPayModel) request.getBizModel(); + assertEquals(String.valueOf(price / 100.0), bizModel.getTotalAmount()); + 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(outTradeNo, resp.getOutTradeNo()); + 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 支付:渠道返回失败") + 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); + }); + when(defaultAlipayClient.pageExecute(argThat((ArgumentMatcher) request -> true), + eq(Method.GET.name()))).thenReturn(response); + 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()); + } + +} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/weixin/WxBarPayClientIntegrationTest.java b/yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/weixin/WxBarPayClientIntegrationTest.java new file mode 100644 index 0000000..9e79bb3 --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/weixin/WxBarPayClientIntegrationTest.java @@ -0,0 +1,123 @@ +package cn.iocoder.yudao.module.pay.framework.pay.core.client.impl.weixin; + +import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils; +import cn.iocoder.yudao.framework.common.util.json.JsonUtils; +import com.github.binarywang.wxpay.bean.notify.WxPayRefundNotifyResult; +import com.github.binarywang.wxpay.bean.request.WxPayMicropayRequest; +import com.github.binarywang.wxpay.bean.request.WxPayRefundRequest; +import com.github.binarywang.wxpay.bean.request.WxPayRefundV3Request; +import com.github.binarywang.wxpay.bean.result.WxPayMicropayResult; +import com.github.binarywang.wxpay.bean.result.WxPayRefundResult; +import com.github.binarywang.wxpay.bean.result.WxPayRefundV3Result; +import com.github.binarywang.wxpay.config.WxPayConfig; +import com.github.binarywang.wxpay.exception.WxPayException; +import com.github.binarywang.wxpay.service.WxPayService; +import com.github.binarywang.wxpay.service.impl.WxPayServiceImpl; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import java.time.Duration; + +import static cn.iocoder.yudao.module.pay.framework.pay.core.client.impl.weixin.AbstractWxPayClient.formatDateV2; + +/** + * {@link WxBarPayClient} 的集成测试,用于快速调试微信条码支付 + * + * @author 芋道源码 + */ +@Disabled +public class WxBarPayClientIntegrationTest { + + @Test + public void testPayV2() throws WxPayException { + // 创建 config 配置 + WxPayConfig config = buildWxPayConfigV2(); + // 创建 WxPayService 客户端 + WxPayService client = new WxPayServiceImpl(); + client.setConfig(config); + + // 执行发起支付 + WxPayMicropayRequest request = WxPayMicropayRequest.newBuilder() + .outTradeNo(String.valueOf(System.currentTimeMillis())) + .body("测试支付-body") + .detail("测试支付-detail") + .totalFee(1) // 单位分 + .timeExpire(formatDateV2(LocalDateTimeUtils.addTime(Duration.ofMinutes(2)))) + .spbillCreateIp("127.0.0.1") + .authCode("134298744426278497") + .build(); + System.out.println("========= request =========="); + System.out.println(JsonUtils.toJsonPrettyString(request)); + WxPayMicropayResult response = client.micropay(request); + System.out.println("========= response =========="); + System.out.println(JsonUtils.toJsonPrettyString(response)); + } + + @Test + public void testParseRefundNotifyV2() throws WxPayException { + // 创建 config 配置 + WxPayConfig config = buildWxPayConfigV2(); + // 创建 WxPayService 客户端 + WxPayService client = new WxPayServiceImpl(); + client.setConfig(config); + + // 执行解析 + String xml = "SUCCESS"; + WxPayRefundNotifyResult response = client.parseRefundNotifyResult(xml); + System.out.println(response.getReqInfo()); + } + + @Test + public void testRefundV2() throws WxPayException { + // 创建 config 配置 + WxPayConfig config = buildWxPayConfigV2(); + // 创建 WxPayService 客户端 + WxPayService client = new WxPayServiceImpl(); + client.setConfig(config); + + // 执行发起退款 + WxPayRefundRequest request = new WxPayRefundRequest() + .setOutTradeNo("1689545667276") + .setOutRefundNo(String.valueOf(System.currentTimeMillis())) + .setRefundFee(1) + .setRefundDesc("就是想退了") + .setTotalFee(1); + System.out.println("========= request =========="); + System.out.println(JsonUtils.toJsonPrettyString(request)); + WxPayRefundResult response = client.refund(request); + System.out.println("========= response =========="); + System.out.println(JsonUtils.toJsonPrettyString(response)); + } + + @Test + public void testRefundV3() throws WxPayException { + // 创建 config 配置 + WxPayConfig config = buildWxPayConfigV2(); + // 创建 WxPayService 客户端 + WxPayService client = new WxPayServiceImpl(); + client.setConfig(config); + + // 执行发起退款 + WxPayRefundV3Request request = new WxPayRefundV3Request() + .setOutTradeNo("1689506325635") + .setOutRefundNo(String.valueOf(System.currentTimeMillis())) + .setAmount(new WxPayRefundV3Request.Amount().setTotal(1).setRefund(1).setCurrency("CNY")) + .setReason("就是想退了"); + System.out.println("========= request =========="); + System.out.println(JsonUtils.toJsonPrettyString(request)); + WxPayRefundV3Result response = client.refundV3(request); + System.out.println("========= response =========="); + System.out.println(JsonUtils.toJsonPrettyString(response)); + } + + private WxPayConfig buildWxPayConfigV2() { + WxPayConfig config = new WxPayConfig(); + config.setAppId("wx62056c0d5e8db250"); + config.setMchId("1545083881"); + config.setMchKey("dS1ngeN63JLr3NRbvPH9AJy3MyUxZdim"); +// config.setSignType(WxPayConstants.SignType.MD5); + config.setKeyPath("/Users/yunai/Downloads/wx_pay/apiclient_cert.p12"); + return config; + } + +} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/weixin/WxNativePayClientIntegrationTest.java b/yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/weixin/WxNativePayClientIntegrationTest.java new file mode 100644 index 0000000..4dd13eb --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/weixin/WxNativePayClientIntegrationTest.java @@ -0,0 +1,84 @@ +package cn.iocoder.yudao.module.pay.framework.pay.core.client.impl.weixin; + +import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils; +import cn.iocoder.yudao.framework.common.util.json.JsonUtils; +import com.github.binarywang.wxpay.bean.request.WxPayRefundV3Request; +import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderV3Request; +import com.github.binarywang.wxpay.bean.result.WxPayRefundV3Result; +import com.github.binarywang.wxpay.bean.result.enums.TradeTypeEnum; +import com.github.binarywang.wxpay.config.WxPayConfig; +import com.github.binarywang.wxpay.exception.WxPayException; +import com.github.binarywang.wxpay.service.WxPayService; +import com.github.binarywang.wxpay.service.impl.WxPayServiceImpl; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import java.time.Duration; + +import static cn.iocoder.yudao.module.pay.framework.pay.core.client.impl.weixin.AbstractWxPayClient.formatDateV3; + + +/** + * {@link WxNativePayClient} 的集成测试,用于快速调试微信扫码支付 + * + * @author 芋道源码 + */ +@Disabled +public class WxNativePayClientIntegrationTest { + + @Test + public void testPayV3() throws WxPayException { + // 创建 config 配置 + WxPayConfig config = buildWxPayConfigV3(); + // 创建 WxPayService 客户端 + WxPayService client = new WxPayServiceImpl(); + client.setConfig(config); + + // 执行发起支付 + WxPayUnifiedOrderV3Request request = new WxPayUnifiedOrderV3Request() + .setOutTradeNo(String.valueOf(System.currentTimeMillis())) + .setDescription("测试支付-body") + .setAmount(new WxPayUnifiedOrderV3Request.Amount().setTotal(1)) // 单位分 + .setTimeExpire(formatDateV3(LocalDateTimeUtils.addTime(Duration.ofMinutes(2)))) + .setSceneInfo(new WxPayUnifiedOrderV3Request.SceneInfo().setPayerClientIp("127.0.0.1")) + .setNotifyUrl("http://127.0.0.1:48080"); + System.out.println("========= request =========="); + System.out.println(JsonUtils.toJsonPrettyString(request)); + String response = client.createOrderV3(TradeTypeEnum.NATIVE, request); + System.out.println("========= response =========="); + System.out.println(JsonUtils.toJsonPrettyString(response)); + } + + @Test + public void testRefundV3() throws WxPayException { + // 创建 config 配置 + WxPayConfig config = buildWxPayConfigV3(); + // 创建 WxPayService 客户端 + WxPayService client = new WxPayServiceImpl(); + client.setConfig(config); + + // 执行发起退款 + WxPayRefundV3Request request = new WxPayRefundV3Request() + .setOutTradeNo("1689545729695") + .setOutRefundNo(String.valueOf(System.currentTimeMillis())) + .setAmount(new WxPayRefundV3Request.Amount().setTotal(1).setRefund(1).setCurrency("CNY")) + .setReason("就是想退了"); + System.out.println("========= request =========="); + System.out.println(JsonUtils.toJsonPrettyString(request)); + WxPayRefundV3Result response = client.refundV3(request); + System.out.println("========= response =========="); + System.out.println(JsonUtils.toJsonPrettyString(response)); + } + + private WxPayConfig buildWxPayConfigV3() { + WxPayConfig config = new WxPayConfig(); + config.setAppId("wx62056c0d5e8db250"); + config.setMchId("1545083881"); + config.setApiV3Key("459arNsYHl1mgkiO6H9ZH5KkhFXSxaA4"); +// config.setCertSerialNo(serialNo); + config.setPrivateCertPath("/Users/yunai/Downloads/wx_pay/apiclient_cert.pem"); + config.setPrivateKeyPath("/Users/yunai/Downloads/wx_pay/apiclient_key.pem"); + return config; + } + +} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/service/channel/PayChannelServiceTest.java b/yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/service/channel/PayChannelServiceTest.java index fe8dbc3..4e05b87 100644 --- a/yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/service/channel/PayChannelServiceTest.java +++ b/yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/service/channel/PayChannelServiceTest.java @@ -2,11 +2,11 @@ package cn.iocoder.yudao.module.pay.service.channel; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.common.util.json.JsonUtils; -import cn.iocoder.yudao.framework.pay.core.client.PayClient; -import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory; -import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayPayClientConfig; -import cn.iocoder.yudao.framework.pay.core.client.impl.weixin.WxPayClientConfig; -import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum; +import cn.iocoder.yudao.module.pay.enums.PayChannelEnum; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.PayClient; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.PayClientFactory; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.impl.alipay.AlipayPayClientConfig; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.impl.weixin.WxPayClientConfig; import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; import cn.iocoder.yudao.module.pay.controller.admin.channel.vo.PayChannelCreateReqVO; import cn.iocoder.yudao.module.pay.controller.admin.channel.vo.PayChannelUpdateReqVO; diff --git a/yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/service/order/PayOrderServiceTest.java b/yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/service/order/PayOrderServiceTest.java index 290aaa5..d5e449a 100755 --- a/yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/service/order/PayOrderServiceTest.java +++ b/yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/service/order/PayOrderServiceTest.java @@ -2,11 +2,10 @@ package cn.iocoder.yudao.module.pay.service.order; import cn.hutool.extra.spring.SpringUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.pay.core.client.PayClient; -import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO; -import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum; -import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderDisplayModeEnum; -import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderStatusRespEnum; +import cn.iocoder.yudao.module.pay.enums.PayChannelEnum; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.PayClient; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.order.PayOrderRespDTO; +import cn.iocoder.yudao.module.pay.framework.pay.core.enums.PayOrderDisplayModeEnum; import cn.iocoder.yudao.framework.test.core.ut.BaseDbAndRedisUnitTest; import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderCreateReqDTO; import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderExportReqVO; @@ -515,7 +514,7 @@ public class PayOrderServiceTest extends BaseDbAndRedisUnitTest { // 准备参数 PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L)); PayOrderRespDTO notify = randomPojo(PayOrderRespDTO.class, - o -> o.setStatus(PayOrderStatusRespEnum.SUCCESS.getStatus())); + o -> o.setStatus(PayOrderStatusEnum.SUCCESS.getStatus())); // 调用,并断言异常 assertServiceException(() -> orderService.notifyOrder(channel, notify), @@ -532,7 +531,7 @@ public class PayOrderServiceTest extends BaseDbAndRedisUnitTest { // 准备参数 PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L)); PayOrderRespDTO notify = randomPojo(PayOrderRespDTO.class, - o -> o.setStatus(PayOrderStatusRespEnum.SUCCESS.getStatus()) + o -> o.setStatus(PayOrderStatusEnum.SUCCESS.getStatus()) .setOutTradeNo("P110")); // 调用,并断言异常 @@ -550,7 +549,7 @@ public class PayOrderServiceTest extends BaseDbAndRedisUnitTest { // 准备参数 PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L)); PayOrderRespDTO notify = randomPojo(PayOrderRespDTO.class, - o -> o.setStatus(PayOrderStatusRespEnum.SUCCESS.getStatus()) + o -> o.setStatus(PayOrderStatusEnum.SUCCESS.getStatus()) .setOutTradeNo("P110")); // 调用,并断言异常 @@ -583,7 +582,7 @@ public class PayOrderServiceTest extends BaseDbAndRedisUnitTest { // 准备参数 PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L)); PayOrderRespDTO notify = randomPojo(PayOrderRespDTO.class, - o -> o.setStatus(PayOrderStatusRespEnum.SUCCESS.getStatus()) + o -> o.setStatus(PayOrderStatusEnum.SUCCESS.getStatus()) .setOutTradeNo("P110")); // 调用,并断言异常 @@ -611,7 +610,7 @@ public class PayOrderServiceTest extends BaseDbAndRedisUnitTest { // 准备参数 PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L)); PayOrderRespDTO notify = randomPojo(PayOrderRespDTO.class, - o -> o.setStatus(PayOrderStatusRespEnum.SUCCESS.getStatus()) + o -> o.setStatus(PayOrderStatusEnum.SUCCESS.getStatus()) .setOutTradeNo("P110")); // 调用,并断言异常 @@ -641,7 +640,7 @@ public class PayOrderServiceTest extends BaseDbAndRedisUnitTest { PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L) .setFeeRate(10D)); PayOrderRespDTO notify = randomPojo(PayOrderRespDTO.class, - o -> o.setStatus(PayOrderStatusRespEnum.SUCCESS.getStatus()) + o -> o.setStatus(PayOrderStatusEnum.SUCCESS.getStatus()) .setOutTradeNo("P110")); // 调用,并断言异常 @@ -669,7 +668,7 @@ public class PayOrderServiceTest extends BaseDbAndRedisUnitTest { // 准备参数 PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L)); PayOrderRespDTO notify = randomPojo(PayOrderRespDTO.class, - o -> o.setStatus(PayOrderStatusRespEnum.CLOSED.getStatus())); + o -> o.setStatus(PayOrderStatusEnum.CLOSED.getStatus())); // 调用,并断言异常 assertServiceException(() -> orderService.notifyOrder(channel, notify), @@ -686,7 +685,7 @@ public class PayOrderServiceTest extends BaseDbAndRedisUnitTest { // 准备参数 PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L)); PayOrderRespDTO notify = randomPojo(PayOrderRespDTO.class, - o -> o.setStatus(PayOrderStatusRespEnum.CLOSED.getStatus()) + o -> o.setStatus(PayOrderStatusEnum.CLOSED.getStatus()) .setOutTradeNo("P110")); // 调用,并断言 @@ -705,7 +704,7 @@ public class PayOrderServiceTest extends BaseDbAndRedisUnitTest { // 准备参数 PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L)); PayOrderRespDTO notify = randomPojo(PayOrderRespDTO.class, - o -> o.setStatus(PayOrderStatusRespEnum.CLOSED.getStatus()) + o -> o.setStatus(PayOrderStatusEnum.CLOSED.getStatus()) .setOutTradeNo("P110")); // 调用,并断言 @@ -724,7 +723,7 @@ public class PayOrderServiceTest extends BaseDbAndRedisUnitTest { // 准备参数 PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L)); PayOrderRespDTO notify = randomPojo(PayOrderRespDTO.class, - o -> o.setStatus(PayOrderStatusRespEnum.CLOSED.getStatus()) + o -> o.setStatus(PayOrderStatusEnum.CLOSED.getStatus()) .setOutTradeNo("P110")); // 调用,并断言异常 @@ -742,7 +741,7 @@ public class PayOrderServiceTest extends BaseDbAndRedisUnitTest { // 准备参数 PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L)); PayOrderRespDTO notify = randomPojo(PayOrderRespDTO.class, - o -> o.setStatus(PayOrderStatusRespEnum.CLOSED.getStatus()) + o -> o.setStatus(PayOrderStatusEnum.CLOSED.getStatus()) .setOutTradeNo("P110")); // 调用 diff --git a/yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundServiceTest.java b/yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundServiceTest.java index 5bfbd64..87c4cdb 100755 --- a/yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundServiceTest.java +++ b/yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundServiceTest.java @@ -2,11 +2,10 @@ package cn.iocoder.yudao.module.pay.service.refund; import cn.hutool.extra.spring.SpringUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.pay.core.client.PayClient; -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.enums.channel.PayChannelEnum; -import cn.iocoder.yudao.framework.pay.core.enums.refund.PayRefundStatusRespEnum; +import cn.iocoder.yudao.module.pay.enums.PayChannelEnum; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.PayClient; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.refund.PayRefundRespDTO; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO; import cn.iocoder.yudao.framework.test.core.ut.BaseDbAndRedisUnitTest; import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundCreateReqDTO; import cn.iocoder.yudao.module.pay.controller.admin.refund.vo.PayRefundExportReqVO; @@ -464,7 +463,7 @@ public class PayRefundServiceTest extends BaseDbAndRedisUnitTest { // 准备参数 PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L).setAppId(1L)); PayRefundRespDTO refundRespDTO = randomPojo(PayRefundRespDTO.class, - o -> o.setStatus(PayRefundStatusRespEnum.SUCCESS.getStatus()).setOutRefundNo("R100")); + o -> o.setStatus(PayRefundStatusEnum.SUCCESS.getStatus()).setOutRefundNo("R100")); // 调用,并断言异常 assertServiceException(() -> refundService.notifyRefund(channel, refundRespDTO), @@ -476,7 +475,7 @@ public class PayRefundServiceTest extends BaseDbAndRedisUnitTest { // 准备参数 PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L).setAppId(1L)); PayRefundRespDTO refundRespDTO = randomPojo(PayRefundRespDTO.class, - o -> o.setStatus(PayRefundStatusRespEnum.SUCCESS.getStatus()).setOutRefundNo("R100")); + o -> o.setStatus(PayRefundStatusEnum.SUCCESS.getStatus()).setOutRefundNo("R100")); // mock 数据(refund + 已支付) PayRefundDO refund = randomPojo(PayRefundDO.class, o -> o.setAppId(1L).setNo("R100") .setStatus(PayRefundStatusEnum.SUCCESS.getStatus())); @@ -493,7 +492,7 @@ public class PayRefundServiceTest extends BaseDbAndRedisUnitTest { // 准备参数 PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L).setAppId(1L)); PayRefundRespDTO refundRespDTO = randomPojo(PayRefundRespDTO.class, - o -> o.setStatus(PayRefundStatusRespEnum.SUCCESS.getStatus()).setOutRefundNo("R100")); + o -> o.setStatus(PayRefundStatusEnum.SUCCESS.getStatus()).setOutRefundNo("R100")); // mock 数据(refund + 已支付) PayRefundDO refund = randomPojo(PayRefundDO.class, o -> o.setAppId(1L).setNo("R100") .setStatus(PayRefundStatusEnum.FAILURE.getStatus())); @@ -509,7 +508,7 @@ public class PayRefundServiceTest extends BaseDbAndRedisUnitTest { // 准备参数 PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L).setAppId(1L)); PayRefundRespDTO refundRespDTO = randomPojo(PayRefundRespDTO.class, - o -> o.setStatus(PayRefundStatusRespEnum.SUCCESS.getStatus()).setOutRefundNo("R100")); + o -> o.setStatus(PayRefundStatusEnum.SUCCESS.getStatus()).setOutRefundNo("R100")); // mock 数据(refund + 已支付) PayRefundDO refund = randomPojo(PayRefundDO.class, o -> o.setAppId(1L).setNo("R100") .setStatus(PayRefundStatusEnum.WAITING.getStatus()) @@ -530,7 +529,7 @@ public class PayRefundServiceTest extends BaseDbAndRedisUnitTest { // 准备参数 PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L).setAppId(1L)); PayRefundRespDTO refundRespDTO = randomPojo(PayRefundRespDTO.class, - o -> o.setStatus(PayRefundStatusRespEnum.SUCCESS.getStatus()).setOutRefundNo("R100")); + o -> o.setStatus(PayRefundStatusEnum.SUCCESS.getStatus()).setOutRefundNo("R100")); // mock 数据(refund + 已支付) PayRefundDO refund = randomPojo(PayRefundDO.class, o -> o.setAppId(1L).setNo("R100") .setStatus(PayRefundStatusEnum.WAITING.getStatus()) @@ -557,7 +556,7 @@ public class PayRefundServiceTest extends BaseDbAndRedisUnitTest { // 准备参数 PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L).setAppId(1L)); PayRefundRespDTO refundRespDTO = randomPojo(PayRefundRespDTO.class, - o -> o.setStatus(PayRefundStatusRespEnum.FAILURE.getStatus()).setOutRefundNo("R100")); + o -> o.setStatus(PayRefundStatusEnum.FAILURE.getStatus()).setOutRefundNo("R100")); // 调用,并断言异常 assertServiceException(() -> refundService.notifyRefund(channel, refundRespDTO), @@ -569,7 +568,7 @@ public class PayRefundServiceTest extends BaseDbAndRedisUnitTest { // 准备参数 PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L).setAppId(1L)); PayRefundRespDTO refundRespDTO = randomPojo(PayRefundRespDTO.class, - o -> o.setStatus(PayRefundStatusRespEnum.FAILURE.getStatus()).setOutRefundNo("R100")); + o -> o.setStatus(PayRefundStatusEnum.FAILURE.getStatus()).setOutRefundNo("R100")); // mock 数据(refund + 退款失败) PayRefundDO refund = randomPojo(PayRefundDO.class, o -> o.setAppId(1L).setNo("R100") .setStatus(PayRefundStatusEnum.FAILURE.getStatus())); @@ -586,7 +585,7 @@ public class PayRefundServiceTest extends BaseDbAndRedisUnitTest { // 准备参数 PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L).setAppId(1L)); PayRefundRespDTO refundRespDTO = randomPojo(PayRefundRespDTO.class, - o -> o.setStatus(PayRefundStatusRespEnum.FAILURE.getStatus()).setOutRefundNo("R100")); + o -> o.setStatus(PayRefundStatusEnum.FAILURE.getStatus()).setOutRefundNo("R100")); // mock 数据(refund + 已支付) PayRefundDO refund = randomPojo(PayRefundDO.class, o -> o.setAppId(1L).setNo("R100") .setStatus(PayRefundStatusEnum.SUCCESS.getStatus())); @@ -602,7 +601,7 @@ public class PayRefundServiceTest extends BaseDbAndRedisUnitTest { // 准备参数 PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L).setAppId(1L)); PayRefundRespDTO refundRespDTO = randomPojo(PayRefundRespDTO.class, - o -> o.setStatus(PayRefundStatusRespEnum.FAILURE.getStatus()).setOutRefundNo("R100")); + o -> o.setStatus(PayRefundStatusEnum.FAILURE.getStatus()).setOutRefundNo("R100")); // mock 数据(refund + 已支付) PayRefundDO refund = randomPojo(PayRefundDO.class, o -> o.setAppId(1L).setNo("R100") .setStatus(PayRefundStatusEnum.WAITING.getStatus()) @@ -639,17 +638,17 @@ public class PayRefundServiceTest extends BaseDbAndRedisUnitTest { @Test public void testSyncRefund_waiting() { - assertEquals(testSyncRefund_waitingOrSuccessOrFailure(PayRefundStatusRespEnum.WAITING.getStatus()), 0); + assertEquals(testSyncRefund_waitingOrSuccessOrFailure(PayRefundStatusEnum.WAITING.getStatus()), 0); } @Test public void testSyncRefund_success() { - assertEquals(testSyncRefund_waitingOrSuccessOrFailure(PayRefundStatusRespEnum.SUCCESS.getStatus()), 1); + assertEquals(testSyncRefund_waitingOrSuccessOrFailure(PayRefundStatusEnum.SUCCESS.getStatus()), 1); } @Test public void testSyncRefund_failure() { - assertEquals(testSyncRefund_waitingOrSuccessOrFailure(PayRefundStatusRespEnum.FAILURE.getStatus()), 1); + assertEquals(testSyncRefund_waitingOrSuccessOrFailure(PayRefundStatusEnum.FAILURE.getStatus()), 1); } private int testSyncRefund_waitingOrSuccessOrFailure(Integer status) { diff --git a/yudao-module-pay/yudao-spring-boot-starter-biz-pay/pom.xml b/yudao-module-pay/yudao-spring-boot-starter-biz-pay/pom.xml deleted file mode 100644 index fba532e..0000000 --- a/yudao-module-pay/yudao-spring-boot-starter-biz-pay/pom.xml +++ /dev/null @@ -1,77 +0,0 @@ - - - - cn.iocoder.boot - yudao-module-pay - ${revision} - - 4.0.0 - - yudao-spring-boot-starter-biz-pay - ${project.artifactId} - 支付拓展,接入国内多个支付渠道 - 1. 支付宝,基于官方 SDK 接入 - 2. 微信支付,基于 weixin-java-pay 接入 - - - - - cn.iocoder.boot - yudao-common - - - - - org.springframework.boot - spring-boot-starter - - - - - org.springframework.boot - spring-boot-starter-validation - - - - org.slf4j - slf4j-api - - - - com.fasterxml.jackson.core - jackson-databind - - - com.fasterxml.jackson.core - jackson-core - - - - - com.alipay.sdk - alipay-sdk-java - 4.35.79.ALL - - - org.bouncycastle - bcprov-jdk15on - - - - - com.github.binarywang - weixin-java-pay - - - - - cn.iocoder.boot - yudao-spring-boot-starter-test - test - - - - - diff --git a/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/config/YudaoPayAutoConfiguration.java b/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/config/YudaoPayAutoConfiguration.java deleted file mode 100644 index e57f4ac..0000000 --- a/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/config/YudaoPayAutoConfiguration.java +++ /dev/null @@ -1,22 +0,0 @@ -package cn.iocoder.yudao.framework.pay.config; - -import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory; -import cn.iocoder.yudao.framework.pay.core.client.impl.PayClientFactoryImpl; -import org.springframework.boot.autoconfigure.AutoConfiguration; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Bean; - -/** - * 支付配置类 - * - * @author 芋道源码 - */ -@AutoConfiguration -public class YudaoPayAutoConfiguration { - - @Bean - public PayClientFactory payClientFactory() { - return new PayClientFactoryImpl(); - } - -} diff --git a/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/PayClient.java b/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/PayClient.java deleted file mode 100644 index b5031b2..0000000 --- a/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/PayClient.java +++ /dev/null @@ -1,118 +0,0 @@ -package cn.iocoder.yudao.framework.pay.core.client; - -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.dto.transfer.PayTransferRespDTO; -import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferUnifiedReqDTO; - -import java.util.Map; - -/** - * 支付客户端,用于对接各支付渠道的 SDK,实现发起支付、退款等功能 - * - * @author 芋道源码 - */ -public interface PayClient { - - /** - * 获得渠道编号 - * - * @return 渠道编号 - */ - Long getId(); - - /** - * 获得渠道配置 - * - * @return 渠道配置 - */ - Config getConfig(); - - // ============ 支付相关 ========== - - /** - * 调用支付渠道,统一下单 - * - * @param reqDTO 下单信息 - * @return 支付订单信息 - */ - PayOrderRespDTO unifiedOrder(PayOrderUnifiedReqDTO reqDTO); - - /** - * 解析 order 回调数据 - * - * @param params HTTP 回调接口 content type 为 application/x-www-form-urlencoded 的所有参数 - * @param body HTTP 回调接口的 request body - * @param headers HTTP 回调接口的 request headers - * @return 支付订单信息 - */ - PayOrderRespDTO parseOrderNotify(Map params, String body, Map headers); - - /** - * 获得支付订单信息 - * - * @param outTradeNo 外部订单号 - * @return 支付订单信息 - */ - PayOrderRespDTO getOrder(String outTradeNo); - - // ============ 退款相关 ========== - - /** - * 调用支付渠道,进行退款 - * - * @param reqDTO 统一退款请求信息 - * @return 退款信息 - */ - PayRefundRespDTO unifiedRefund(PayRefundUnifiedReqDTO reqDTO); - - /** - * 解析 refund 回调数据 - * - * @param params HTTP 回调接口 content type 为 application/x-www-form-urlencoded 的所有参数 - * @param body HTTP 回调接口的 request body - * @param headers HTTP 回调接口的 request headers - * @return 支付订单信息 - */ - PayRefundRespDTO parseRefundNotify(Map params, String body, Map headers); - - /** - * 获得退款订单信息 - * - * @param outTradeNo 外部订单号 - * @param outRefundNo 外部退款号 - * @return 退款订单信息 - */ - PayRefundRespDTO getRefund(String outTradeNo, String outRefundNo); - - // ============ 转账相关 ========== - - /** - * 调用渠道,进行转账 - * - * @param reqDTO 统一转账请求信息 - * @return 转账信息 - */ - PayTransferRespDTO unifiedTransfer(PayTransferUnifiedReqDTO reqDTO); - - /** - * 获得转账订单信息 - * - * @param outTradeNo 外部订单号 - * @return 转账信息 - */ - PayTransferRespDTO getTransfer(String outTradeNo); - - /** - * 解析 transfer 回调数据 - * - * @param params HTTP 回调接口 content type 为 application/x-www-form-urlencoded 的所有参数 - * @param body HTTP 回调接口的 request body - * @param headers HTTP 回调接口的 request headers - * @return 转账信息 - */ - PayTransferRespDTO parseTransferNotify(Map params, String body, Map headers); - -} diff --git a/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/PayClientConfig.java b/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/PayClientConfig.java deleted file mode 100644 index 9df52cd..0000000 --- a/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/PayClientConfig.java +++ /dev/null @@ -1,27 +0,0 @@ -package cn.iocoder.yudao.framework.pay.core.client; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonTypeInfo; -import jakarta.validation.Validator; - -/** - * 支付客户端的配置,本质是支付渠道的配置 - * 每个不同的渠道,需要不同的配置,通过子类来定义 - * - * @author 芋道源码 - */ -@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) -// @JsonTypeInfo 注解的作用,Jackson 多态 -// 1. 序列化到时数据库时,增加 @class 属性。 -// 2. 反序列化到内存对象时,通过 @class 属性,可以创建出正确的类型 -@JsonIgnoreProperties(ignoreUnknown = true) // 目的:忽略未知的属性,避免反序列化失败 -public interface PayClientConfig { - - /** - * 参数校验 - * - * @param validator 校验对象 - */ - void validate(Validator validator); - -} diff --git a/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/PayClientFactory.java b/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/PayClientFactory.java deleted file mode 100644 index 934b20b..0000000 --- a/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/PayClientFactory.java +++ /dev/null @@ -1,39 +0,0 @@ -package cn.iocoder.yudao.framework.pay.core.client; - -import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum; - -/** - * 支付客户端的工厂接口 - * - * @author 芋道源码 - */ -public interface PayClientFactory { - - /** - * 获得支付客户端 - * - * @param channelId 渠道编号 - * @return 支付客户端 - */ - PayClient getPayClient(Long channelId); - - /** - * 创建支付客户端 - * - * @param channelId 渠道编号 - * @param channelCode 渠道编码 - * @param config 支付配置 - * @return 支付客户端 - */ - PayClient createOrUpdatePayClient(Long channelId, String channelCode, - Config config); - - /** - * 注册支付客户端 Class,用于模块中实现的 PayClient - * - * @param channel 支付渠道的编码的枚举 - * @param payClientClass 支付客户端 class - */ - void registerPayClientClass(PayChannelEnum channel, Class payClientClass); - -} diff --git a/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/order/PayOrderRespDTO.java b/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/order/PayOrderRespDTO.java deleted file mode 100644 index 82050a6..0000000 --- a/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/order/PayOrderRespDTO.java +++ /dev/null @@ -1,141 +0,0 @@ -package cn.iocoder.yudao.framework.pay.core.client.dto.order; - -import cn.iocoder.yudao.framework.pay.core.client.exception.PayException; -import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderDisplayModeEnum; -import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderStatusRespEnum; -import lombok.Data; - -import java.time.LocalDateTime; - -/** - * 渠道支付订单 Response DTO - * - * @author 芋道源码 - */ -@Data -public class PayOrderRespDTO { - - /** - * 支付状态 - * - * 枚举:{@link PayOrderStatusRespEnum} - */ - private Integer status; - - /** - * 外部订单号 - * - * 对应 PayOrderExtensionDO 的 no 字段 - */ - private String outTradeNo; - - /** - * 支付渠道编号 - */ - private String channelOrderNo; - /** - * 支付渠道用户编号 - */ - private String channelUserId; - - /** - * 支付成功时间 - */ - private LocalDateTime successTime; - - /** - * 原始的同步/异步通知结果 - */ - private Object rawData; - - // ========== 主动发起支付时,会返回的字段 ========== - - /** - * 展示模式 - * - * 枚举 {@link PayOrderDisplayModeEnum} 类 - */ - private String displayMode; - /** - * 展示内容 - */ - private String displayContent; - - /** - * 调用渠道的错误码 - * - * 注意:这里返回的是业务异常,而是不系统异常。 - * 如果是系统异常,则会抛出 {@link PayException} - */ - private String channelErrorCode; - /** - * 调用渠道报错时,错误信息 - */ - private String channelErrorMsg; - - public PayOrderRespDTO() { - } - - /** - * 创建【WAITING】状态的订单返回 - */ - public static PayOrderRespDTO waitingOf(String displayMode, String displayContent, - String outTradeNo, Object rawData) { - PayOrderRespDTO respDTO = new PayOrderRespDTO(); - respDTO.status = PayOrderStatusRespEnum.WAITING.getStatus(); - respDTO.displayMode = displayMode; - respDTO.displayContent = displayContent; - // 相对通用的字段 - respDTO.outTradeNo = outTradeNo; - respDTO.rawData = rawData; - return respDTO; - } - - /** - * 创建【SUCCESS】状态的订单返回 - */ - public static PayOrderRespDTO successOf(String channelOrderNo, String channelUserId, LocalDateTime successTime, - String outTradeNo, Object rawData) { - PayOrderRespDTO respDTO = new PayOrderRespDTO(); - respDTO.status = PayOrderStatusRespEnum.SUCCESS.getStatus(); - respDTO.channelOrderNo = channelOrderNo; - respDTO.channelUserId = channelUserId; - respDTO.successTime = successTime; - // 相对通用的字段 - respDTO.outTradeNo = outTradeNo; - respDTO.rawData = rawData; - return respDTO; - } - - /** - * 创建指定状态的订单返回,适合支付渠道回调时 - */ - public static PayOrderRespDTO of(Integer status, String channelOrderNo, String channelUserId, LocalDateTime successTime, - String outTradeNo, Object rawData) { - PayOrderRespDTO respDTO = new PayOrderRespDTO(); - respDTO.status = status; - respDTO.channelOrderNo = channelOrderNo; - respDTO.channelUserId = channelUserId; - respDTO.successTime = successTime; - // 相对通用的字段 - respDTO.outTradeNo = outTradeNo; - respDTO.rawData = rawData; - return respDTO; - } - - /** - * 创建【CLOSED】状态的订单返回,适合调用支付渠道失败时 - */ - public static PayOrderRespDTO closedOf(String channelErrorCode, String channelErrorMsg, - String outTradeNo, Object rawData) { - PayOrderRespDTO respDTO = new PayOrderRespDTO(); - respDTO.status = PayOrderStatusRespEnum.CLOSED.getStatus(); - respDTO.channelErrorCode = channelErrorCode; - respDTO.channelErrorMsg = channelErrorMsg; - // 相对通用的字段 - respDTO.outTradeNo = outTradeNo; - respDTO.rawData = rawData; - return respDTO; - } - -} diff --git a/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/order/PayOrderUnifiedReqDTO.java b/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/order/PayOrderUnifiedReqDTO.java deleted file mode 100644 index 15be7a7..0000000 --- a/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/order/PayOrderUnifiedReqDTO.java +++ /dev/null @@ -1,92 +0,0 @@ -package cn.iocoder.yudao.framework.pay.core.client.dto.order; - -import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderDisplayModeEnum; -import lombok.Data; -import org.hibernate.validator.constraints.Length; -import org.hibernate.validator.constraints.URL; - -import jakarta.validation.constraints.DecimalMin; -import jakarta.validation.constraints.NotEmpty; -import jakarta.validation.constraints.NotNull; -import java.time.LocalDateTime; -import java.util.Map; - -/** - * 统一下单 Request DTO - * - * @author 芋道源码 - */ -@Data -public class PayOrderUnifiedReqDTO { - - /** - * 用户 IP - */ - @NotEmpty(message = "用户 IP 不能为空") - private String userIp; - - // ========== 商户相关字段 ========== - - /** - * 外部订单号 - * - * 对应 PayOrderExtensionDO 的 no 字段 - */ - @NotEmpty(message = "外部订单编号不能为空") - private String outTradeNo; - /** - * 商品标题 - */ - @NotEmpty(message = "商品标题不能为空") - @Length(max = 32, message = "商品标题不能超过 32") - private String subject; - /** - * 商品描述信息 - */ - @Length(max = 128, message = "商品描述信息长度不能超过128") - private String body; - /** - * 支付结果的 notify 回调地址 - */ - @NotEmpty(message = "支付结果的回调地址不能为空") - @URL(message = "支付结果的 notify 回调地址必须是 URL 格式") - private String notifyUrl; - /** - * 支付结果的 return 回调地址 - */ - @URL(message = "支付结果的 return 回调地址必须是 URL 格式") - private String returnUrl; - - // ========== 订单相关字段 ========== - - /** - * 支付金额,单位:分 - */ - @NotNull(message = "支付金额不能为空") - @DecimalMin(value = "0", inclusive = false, message = "支付金额必须大于零") - private Integer price; - - /** - * 支付过期时间 - */ - @NotNull(message = "支付过期时间不能为空") - private LocalDateTime expireTime; - - // ========== 拓展参数 ========== - /** - * 支付渠道的额外参数 - * - * 例如说,微信公众号需要传递 openid 参数 - */ - private Map channelExtras; - - /** - * 展示模式 - * - * 如果不传递,则每个支付渠道使用默认的方式 - * - * 枚举 {@link PayOrderDisplayModeEnum} - */ - private String displayMode; - -} diff --git a/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/refund/PayRefundRespDTO.java b/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/refund/PayRefundRespDTO.java deleted file mode 100644 index 3184f27..0000000 --- a/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/refund/PayRefundRespDTO.java +++ /dev/null @@ -1,115 +0,0 @@ -package cn.iocoder.yudao.framework.pay.core.client.dto.refund; - -import cn.iocoder.yudao.framework.pay.core.client.exception.PayException; -import cn.iocoder.yudao.framework.pay.core.enums.refund.PayRefundStatusRespEnum; -import lombok.Data; - -import java.time.LocalDateTime; - -/** - * 渠道退款订单 Response DTO - * - * @author jason - */ -@Data -public class PayRefundRespDTO { - - /** - * 退款状态 - * - * 枚举 {@link PayRefundStatusRespEnum} - */ - private Integer status; - - /** - * 外部退款号 - * - * 对应 PayRefundDO 的 no 字段 - */ - private String outRefundNo; - - /** - * 渠道退款单号 - * - * 对应 PayRefundDO.channelRefundNo 字段 - */ - private String channelRefundNo; - - /** - * 退款成功时间 - */ - private LocalDateTime successTime; - - /** - * 原始的异步通知结果 - */ - private Object rawData; - - /** - * 调用渠道的错误码 - * - * 注意:这里返回的是业务异常,而是不系统异常。 - * 如果是系统异常,则会抛出 {@link PayException} - */ - private String channelErrorCode; - /** - * 调用渠道报错时,错误信息 - */ - private String channelErrorMsg; - - private PayRefundRespDTO() { - } - - /** - * 创建【WAITING】状态的退款返回 - */ - public static PayRefundRespDTO waitingOf(String channelRefundNo, - String outRefundNo, Object rawData) { - PayRefundRespDTO respDTO = new PayRefundRespDTO(); - respDTO.status = PayRefundStatusRespEnum.WAITING.getStatus(); - respDTO.channelRefundNo = channelRefundNo; - // 相对通用的字段 - respDTO.outRefundNo = outRefundNo; - respDTO.rawData = rawData; - return respDTO; - } - - /** - * 创建【SUCCESS】状态的退款返回 - */ - public static PayRefundRespDTO successOf(String channelRefundNo, LocalDateTime successTime, - String outRefundNo, Object rawData) { - PayRefundRespDTO respDTO = new PayRefundRespDTO(); - respDTO.status = PayRefundStatusRespEnum.SUCCESS.getStatus(); - respDTO.channelRefundNo = channelRefundNo; - respDTO.successTime = successTime; - // 相对通用的字段 - respDTO.outRefundNo = outRefundNo; - respDTO.rawData = rawData; - return respDTO; - } - - /** - * 创建【FAILURE】状态的退款返回 - */ - public static PayRefundRespDTO failureOf(String outRefundNo, Object rawData) { - return failureOf(null, null, - outRefundNo, rawData); - } - - /** - * 创建【FAILURE】状态的退款返回 - */ - public static PayRefundRespDTO failureOf(String channelErrorCode, String channelErrorMsg, - String outRefundNo, Object rawData) { - PayRefundRespDTO respDTO = new PayRefundRespDTO(); - respDTO.status = PayRefundStatusRespEnum.FAILURE.getStatus(); - respDTO.channelErrorCode = channelErrorCode; - respDTO.channelErrorMsg = channelErrorMsg; - // 相对通用的字段 - respDTO.outRefundNo = outRefundNo; - respDTO.rawData = rawData; - return respDTO; - } - -} diff --git a/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/refund/PayRefundUnifiedReqDTO.java b/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/refund/PayRefundUnifiedReqDTO.java deleted file mode 100644 index 3101633..0000000 --- a/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/refund/PayRefundUnifiedReqDTO.java +++ /dev/null @@ -1,70 +0,0 @@ -package cn.iocoder.yudao.framework.pay.core.client.dto.refund; - -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.experimental.Accessors; -import org.hibernate.validator.constraints.URL; - -import jakarta.validation.constraints.DecimalMin; -import jakarta.validation.constraints.NotEmpty; -import jakarta.validation.constraints.NotNull; - -/** - * 统一 退款 Request DTO - * - * @author jason - */ -@Accessors(chain = true) -@Builder -@NoArgsConstructor -@AllArgsConstructor -@Data -public class PayRefundUnifiedReqDTO { - - /** - * 外部订单号 - * - * 对应 PayOrderExtensionDO 的 no 字段 - */ - @NotEmpty(message = "外部订单编号不能为空") - private String outTradeNo; - - /** - * 外部退款号 - * - * 对应 PayRefundDO 的 no 字段 - */ - @NotEmpty(message = "退款请求单号不能为空") - private String outRefundNo; - - /** - * 退款原因 - */ - @NotEmpty(message = "退款原因不能为空") - private String reason; - - /** - * 支付金额,单位:分 - * - * 目前微信支付在退款的时候,必须传递该字段 - */ - @NotNull(message = "支付金额不能为空") - @DecimalMin(value = "0", inclusive = false, message = "支付金额必须大于零") - private Integer payPrice; - /** - * 退款金额,单位:分 - */ - @NotNull(message = "退款金额不能为空") - @DecimalMin(value = "0", inclusive = false, message = "支付金额必须大于零") - private Integer refundPrice; - - /** - * 退款结果的 notify 回调地址 - */ - @NotEmpty(message = "支付结果的回调地址不能为空") - @URL(message = "支付结果的 notify 回调地址必须是 URL 格式") - private String notifyUrl; - -} diff --git a/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/transfer/PayTransferRespDTO.java b/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/transfer/PayTransferRespDTO.java deleted file mode 100644 index 4a47273..0000000 --- a/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/transfer/PayTransferRespDTO.java +++ /dev/null @@ -1,116 +0,0 @@ -package cn.iocoder.yudao.framework.pay.core.client.dto.transfer; - -import cn.iocoder.yudao.framework.pay.core.enums.transfer.PayTransferStatusRespEnum; -import lombok.Data; - -import java.time.LocalDateTime; - -/** - * 统一转账 Response DTO - * - * @author jason - */ -@Data -public class PayTransferRespDTO { - - /** - * 转账状态 - * - * 关联 {@link PayTransferStatusRespEnum#getStatus()} - */ - private Integer status; - - /** - * 外部转账单号 - */ - private String outTransferNo; - - /** - * 支付渠道编号 - */ - private String channelTransferNo; - - /** - * 支付成功时间 - */ - private LocalDateTime successTime; - - /** - * 原始的返回结果 - */ - private Object rawData; - - /** - * 调用渠道的错误码 - */ - private String channelErrorCode; - /** - * 调用渠道报错时,错误信息 - */ - private String channelErrorMsg; - - /** - * 渠道 package 信息 - * - * 特殊:目前只有微信转账有这个东西!!! - * @see JSAPI 调起用户确认收款 - */ - private String channelPackageInfo; - - /** - * 创建【WAITING】状态的转账返回 - */ - public static PayTransferRespDTO waitingOf(String channelTransferNo, - String outTransferNo, Object rawData) { - PayTransferRespDTO respDTO = new PayTransferRespDTO(); - respDTO.status = PayTransferStatusRespEnum.WAITING.getStatus(); - respDTO.channelTransferNo = channelTransferNo; - respDTO.outTransferNo = outTransferNo; - respDTO.rawData = rawData; - return respDTO; - } - - /** - * 创建【IN_PROGRESS】状态的转账返回 - */ - public static PayTransferRespDTO processingOf(String channelTransferNo, - String outTransferNo, Object rawData) { - PayTransferRespDTO respDTO = new PayTransferRespDTO(); - respDTO.status = PayTransferStatusRespEnum.PROCESSING.getStatus(); - respDTO.channelTransferNo = channelTransferNo; - respDTO.outTransferNo = outTransferNo; - respDTO.rawData = rawData; - return respDTO; - } - - /** - * 创建【CLOSED】状态的转账返回 - */ - public static PayTransferRespDTO closedOf(String channelErrorCode, String channelErrorMsg, - String outTransferNo, Object rawData) { - PayTransferRespDTO respDTO = new PayTransferRespDTO(); - respDTO.status = PayTransferStatusRespEnum.CLOSED.getStatus(); - respDTO.channelErrorCode = channelErrorCode; - respDTO.channelErrorMsg = channelErrorMsg; - // 相对通用的字段 - respDTO.outTransferNo = outTransferNo; - respDTO.rawData = rawData; - return respDTO; - } - - /** - * 创建【SUCCESS】状态的转账返回 - */ - public static PayTransferRespDTO successOf(String channelTransferNo, LocalDateTime successTime, - String outTransferNo, Object rawData) { - PayTransferRespDTO respDTO = new PayTransferRespDTO(); - respDTO.status = PayTransferStatusRespEnum.SUCCESS.getStatus(); - respDTO.channelTransferNo = channelTransferNo; - respDTO.successTime = successTime; - // 相对通用的字段 - respDTO.outTransferNo = outTransferNo; - respDTO.rawData = rawData; - return respDTO; - } - -} diff --git a/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/transfer/PayTransferUnifiedReqDTO.java b/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/transfer/PayTransferUnifiedReqDTO.java deleted file mode 100644 index 1982f12..0000000 --- a/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/transfer/PayTransferUnifiedReqDTO.java +++ /dev/null @@ -1,73 +0,0 @@ -package cn.iocoder.yudao.framework.pay.core.client.dto.transfer; - -import jakarta.validation.constraints.Min; -import jakarta.validation.constraints.NotEmpty; -import jakarta.validation.constraints.NotNull; -import lombok.Data; -import org.hibernate.validator.constraints.Length; -import org.hibernate.validator.constraints.URL; - -import java.util.Map; - -/** - * 统一转账 Request DTO - * - * @author jason - */ -@Data -public class PayTransferUnifiedReqDTO { - - /** - * 用户 IP - */ - @NotEmpty(message = "用户 IP 不能为空") - private String userIp; - - /** - * 外部转账单编号 - */ - @NotEmpty(message = "外部转账单编号不能为空") - private String outTransferNo; - - /** - * 转账金额,单位:分 - */ - @NotNull(message = "转账金额不能为空") - @Min(value = 1, message = "转账金额必须大于零") - private Integer price; - - /** - * 转账标题 - */ - @NotEmpty(message = "转账标题不能为空") - @Length(max = 128, message = "转账标题不能超过 128") - private String subject; - - /** - * 收款人账号 - * - * 微信场景下:openid - * 支付宝场景下:支付宝账号 - */ - @NotEmpty(message = "收款人账号不能为空") - private String userAccount; - /** - * 收款人姓名 - */ - private String userName; - - /** - * 支付渠道的额外参数 - * - * 微信支付:sceneId 和 scene_report_infos 字段,必须传递;参考 按转账场景报备背景信息 - */ - private Map channelExtras; - - /** - * 转账结果的 notify 回调地址 - */ - @NotEmpty(message = "转账结果的回调地址不能为空") - @URL(message = "转账结果的 notify 回调地址必须是 URL 格式") - private String notifyUrl; - -} diff --git a/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/exception/PayException.java b/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/exception/PayException.java deleted file mode 100644 index 75f4c39..0000000 --- a/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/exception/PayException.java +++ /dev/null @@ -1,17 +0,0 @@ -package cn.iocoder.yudao.framework.pay.core.client.exception; - -import lombok.Data; -import lombok.EqualsAndHashCode; - -/** - * 支付系统异常 Exception - */ -@Data -@EqualsAndHashCode(callSuper = true) -public class PayException extends RuntimeException { - - public PayException(Throwable cause) { - super(cause); - } - -} diff --git a/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/AbstractPayClient.java b/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/AbstractPayClient.java deleted file mode 100644 index 3ea225b..0000000 --- a/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/AbstractPayClient.java +++ /dev/null @@ -1,251 +0,0 @@ -package cn.iocoder.yudao.framework.pay.core.client.impl; - -import cn.iocoder.yudao.framework.common.exception.ServiceException; -import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils; -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.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.dto.transfer.PayTransferRespDTO; -import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferUnifiedReqDTO; -import cn.iocoder.yudao.framework.pay.core.client.exception.PayException; -import lombok.extern.slf4j.Slf4j; - -import java.util.Map; - -import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString; - -/** - * 支付客户端的抽象类,提供模板方法,减少子类的冗余代码 - * - * @author 芋道源码 - */ -@Slf4j -public abstract class AbstractPayClient implements PayClient { - - /** - * 渠道编号 - */ - private final Long channelId; - /** - * 渠道编码 - */ - @SuppressWarnings("FieldCanBeLocal") - private final String channelCode; - /** - * 支付配置 - */ - protected Config config; - - public AbstractPayClient(Long channelId, String channelCode, Config config) { - this.channelId = channelId; - this.channelCode = channelCode; - this.config = config; - } - - /** - * 初始化 - */ - public final void init() { - doInit(); - log.debug("[init][客户端({}) 初始化完成]", getId()); - } - - /** - * 自定义初始化 - */ - protected abstract void doInit(); - - public final void refresh(Config config) { - // 判断是否更新 - if (config.equals(this.config)) { - return; - } - log.info("[refresh][客户端({})发生变化,重新初始化]", getId()); - this.config = config; - // 初始化 - this.init(); - } - - @Override - public Long getId() { - return channelId; - } - - @Override - public Config getConfig() { - return config; - } - - // ============ 支付相关 ========== - - @Override - public final PayOrderRespDTO unifiedOrder(PayOrderUnifiedReqDTO reqDTO) { - ValidationUtils.validate(reqDTO); - // 执行统一下单 - PayOrderRespDTO resp; - try { - resp = doUnifiedOrder(reqDTO); - } catch (ServiceException ex) { // 业务异常,都是实现类已经翻译,所以直接抛出即可 - throw ex; - } catch (Throwable ex) { - // 系统异常,则包装成 PayException 异常抛出 - log.error("[unifiedOrder][客户端({}) request({}) 发起支付异常]", - getId(), toJsonString(reqDTO), ex); - throw buildPayException(ex); - } - return resp; - } - - protected abstract PayOrderRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) - throws Throwable; - - @Override - public final PayOrderRespDTO parseOrderNotify(Map params, String body, Map headers) { - try { - return doParseOrderNotify(params, body, headers); - } catch (ServiceException ex) { // 业务异常,都是实现类已经翻译,所以直接抛出即可 - throw ex; - } catch (Throwable ex) { - log.error("[parseOrderNotify][客户端({}) params({}) body({}) headers({}) 解析失败]", - getId(), params, body, headers, ex); - throw buildPayException(ex); - } - } - - protected abstract PayOrderRespDTO doParseOrderNotify(Map params, String body, Map headers) - throws Throwable; - - @Override - public final PayOrderRespDTO getOrder(String outTradeNo) { - try { - return doGetOrder(outTradeNo); - } catch (ServiceException ex) { // 业务异常,都是实现类已经翻译,所以直接抛出即可 - throw ex; - } catch (Throwable ex) { - log.error("[getOrder][客户端({}) outTradeNo({}) 查询支付单异常]", - getId(), outTradeNo, ex); - throw buildPayException(ex); - } - } - - protected abstract PayOrderRespDTO doGetOrder(String outTradeNo) - throws Throwable; - - // ============ 退款相关 ========== - - @Override - public final PayRefundRespDTO unifiedRefund(PayRefundUnifiedReqDTO reqDTO) { - ValidationUtils.validate(reqDTO); - // 执行统一退款 - PayRefundRespDTO resp; - try { - resp = doUnifiedRefund(reqDTO); - } catch (ServiceException ex) { // 业务异常,都是实现类已经翻译,所以直接抛出即可 - throw ex; - } catch (Throwable ex) { - // 系统异常,则包装成 PayException 异常抛出 - log.error("[unifiedRefund][客户端({}) request({}) 发起退款异常]", - getId(), toJsonString(reqDTO), ex); - throw buildPayException(ex); - } - return resp; - } - - protected abstract PayRefundRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws Throwable; - - @Override - public final PayRefundRespDTO parseRefundNotify(Map params, String body, Map headers) { - try { - return doParseRefundNotify(params, body, headers); - } catch (ServiceException ex) { // 业务异常,都是实现类已经翻译,所以直接抛出即可 - throw ex; - } catch (Throwable ex) { - log.error("[parseRefundNotify][客户端({}) params({}) body({}) headers({}) 解析失败]", - getId(), params, body, headers, ex); - throw buildPayException(ex); - } - } - - protected abstract PayRefundRespDTO doParseRefundNotify(Map params, String body, Map headers) - throws Throwable; - - @Override - public final PayRefundRespDTO getRefund(String outTradeNo, String outRefundNo) { - try { - return doGetRefund(outTradeNo, outRefundNo); - } catch (ServiceException ex) { // 业务异常,都是实现类已经翻译,所以直接抛出即可 - throw ex; - } catch (Throwable ex) { - log.error("[getRefund][客户端({}) outTradeNo({}) outRefundNo({}) 查询退款单异常]", - getId(), outTradeNo, outRefundNo, ex); - throw buildPayException(ex); - } - } - - protected abstract PayRefundRespDTO doGetRefund(String outTradeNo, String outRefundNo) - throws Throwable; - - @Override - public final PayTransferRespDTO unifiedTransfer(PayTransferUnifiedReqDTO reqDTO) { - PayTransferRespDTO resp; - try { - resp = doUnifiedTransfer(reqDTO); - } catch (ServiceException ex) { // 业务异常,都是实现类已经翻译,所以直接抛出即可 - throw ex; - } catch (Throwable ex) { - // 系统异常,则包装成 PayException 异常抛出 - log.error("[unifiedTransfer][客户端({}) request({}) 发起转账异常]", - getId(), toJsonString(reqDTO), ex); - throw buildPayException(ex); - } - return resp; - } - - @Override - public final PayTransferRespDTO parseTransferNotify(Map params, String body, Map headers) { - try { - return doParseTransferNotify(params, body, headers); - } catch (ServiceException ex) { // 业务异常,都是实现类已经翻译,所以直接抛出即可 - throw ex; - } catch (Throwable ex) { - log.error("[doParseTransferNotify][客户端({}) params({}) body({}) headers({}) 解析失败]", - getId(), params, body, headers, ex); - throw buildPayException(ex); - } - } - - protected abstract PayTransferRespDTO doParseTransferNotify(Map params, String body, Map headers) - throws Throwable; - - @Override - public final PayTransferRespDTO getTransfer(String outTradeNo) { - try { - return doGetTransfer(outTradeNo); - } catch (ServiceException ex) { // 业务异常,都是实现类已经翻译,所以直接抛出即可 - throw ex; - } catch (Throwable ex) { - log.error("[getTransfer][客户端({}) outTradeNo({}) 查询转账单异常]", - getId(), outTradeNo, ex); - throw buildPayException(ex); - } - } - - protected abstract PayTransferRespDTO doUnifiedTransfer(PayTransferUnifiedReqDTO reqDTO) - throws Throwable; - - protected abstract PayTransferRespDTO doGetTransfer(String outTradeNo) - throws Throwable; - - // ========== 各种工具方法 ========== - - private PayException buildPayException(Throwable ex) { - if (ex instanceof PayException) { - return (PayException) ex; - } - throw new PayException(ex); - } - -} diff --git a/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/NonePayClientConfig.java b/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/NonePayClientConfig.java deleted file mode 100644 index 80581f3..0000000 --- a/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/NonePayClientConfig.java +++ /dev/null @@ -1,31 +0,0 @@ -package cn.iocoder.yudao.framework.pay.core.client.impl; - -import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig; -import lombok.Data; - -import jakarta.validation.Validator; - -/** - * 无需任何配置 PayClientConfig 实现类 - * - * @author jason - */ -@Data -public class NonePayClientConfig implements PayClientConfig { - - /** - * 配置名称 - *

- * 如果不加任何属性,JsonUtils.parseObject2 解析会报错,所以暂时加个名称 - */ - private String name; - - public NonePayClientConfig(){ - this.name = "none-config"; - } - - @Override - public void validate(Validator validator) { - // 无任何配置不需要校验 - } -} diff --git a/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/PayClientFactoryImpl.java b/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/PayClientFactoryImpl.java deleted file mode 100644 index 1a50215..0000000 --- a/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/PayClientFactoryImpl.java +++ /dev/null @@ -1,97 +0,0 @@ -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.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.*; - -/** - * 支付客户端的工厂实现类 - * - * @author 芋道源码 - */ -@Slf4j -public class PayClientFactoryImpl implements PayClientFactory { - - /** - * 支付客户端 Map - * - * key:渠道编号 - */ - private final ConcurrentMap> clients = new ConcurrentHashMap<>(); - - /** - * 支付客户端 Class Map - */ - private final Map> 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(WX_WAP, WxWapPayClient.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); - if (client == null) { - log.error("[getPayClient][渠道编号({}) 找不到客户端]", channelId); - } - return client; - } - - @Override - @SuppressWarnings("unchecked") - public PayClient createOrUpdatePayClient(Long channelId, String channelCode, - Config config) { - AbstractPayClient client = (AbstractPayClient) clients.get(channelId); - if (client == null) { - client = this.createPayClient(channelId, channelCode, config); - client.init(); - clients.put(client.getId(), client); - } else { - client.refresh(config); - } - return client; - } - - @SuppressWarnings("unchecked") - private AbstractPayClient createPayClient(Long channelId, String channelCode, - Config config) { - PayChannelEnum channelEnum = PayChannelEnum.getByCode(channelCode); - Assert.notNull(channelEnum, String.format("支付渠道(%s) 为空", channelCode)); - Class payClientClass = clientClass.get(channelEnum); - Assert.notNull(payClientClass, String.format("支付渠道(%s) Class 为空", channelCode)); - return (AbstractPayClient) ReflectUtil.newInstance(payClientClass, channelId, config); - } - -} diff --git a/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AbstractAlipayPayClient.java b/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AbstractAlipayPayClient.java deleted file mode 100644 index 7439b33..0000000 --- a/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AbstractAlipayPayClient.java +++ /dev/null @@ -1,379 +0,0 @@ -package cn.iocoder.yudao.framework.pay.core.client.impl.alipay; - -import cn.hutool.core.bean.BeanUtil; -import cn.hutool.core.date.LocalDateTimeUtil; -import cn.hutool.core.lang.Assert; -import cn.hutool.core.map.MapUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.http.HttpUtil; -import cn.iocoder.yudao.framework.common.util.json.JsonUtils; -import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; -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.dto.transfer.PayTransferRespDTO; -import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferUnifiedReqDTO; -import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient; -import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderStatusRespEnum; -import com.alipay.api.AlipayApiException; -import com.alipay.api.AlipayConfig; -import com.alipay.api.AlipayResponse; -import com.alipay.api.DefaultAlipayClient; -import com.alipay.api.domain.*; -import com.alipay.api.internal.util.AlipaySignature; -import com.alipay.api.internal.util.AntCertificationUtil; -import com.alipay.api.internal.util.codec.Base64; -import com.alipay.api.request.*; -import com.alipay.api.response.*; -import lombok.Getter; -import lombok.SneakyThrows; -import lombok.extern.slf4j.Slf4j; - -import java.nio.charset.StandardCharsets; -import java.security.cert.X509Certificate; -import java.time.LocalDateTime; -import java.util.Collections; -import java.util.Map; -import java.util.Objects; -import java.util.function.Supplier; - -import static cn.hutool.core.date.DatePattern.NORM_DATETIME_FORMATTER; -import static cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayPayClientConfig.MODE_CERTIFICATE; -import static cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayPayClientConfig.MODE_PUBLIC_KEY; - -/** - * 支付宝抽象类,实现支付宝统一的接口、以及部分实现(退款) - * - * @author jason - */ -@Slf4j -public abstract class AbstractAlipayPayClient extends AbstractPayClient { - - @Getter // 仅用于单测场景 - protected DefaultAlipayClient client; - - public AbstractAlipayPayClient(Long channelId, String channelCode, AlipayPayClientConfig config) { - super(channelId, channelCode, config); - } - - @Override - @SneakyThrows - protected void doInit() { - AlipayConfig alipayConfig = new AlipayConfig(); - BeanUtil.copyProperties(config, alipayConfig, false); - this.client = new DefaultAlipayClient(alipayConfig); - } - - // ============ 支付相关 ========== - - /** - * 构造支付关闭的 {@link PayOrderRespDTO} 对象 - * - * @return 支付关闭的 {@link PayOrderRespDTO} 对象 - */ - protected PayOrderRespDTO buildClosedPayOrderRespDTO(PayOrderUnifiedReqDTO reqDTO, AlipayResponse response) { - Assert.isFalse(response.isSuccess()); - return PayOrderRespDTO.closedOf(response.getSubCode(), response.getSubMsg(), - reqDTO.getOutTradeNo(), response); - } - - @Override - public PayOrderRespDTO doParseOrderNotify(Map params, String body, Map headers) throws Throwable { - // 1. 校验回调数据 - verifyNotifyData(params); - - // 2. 解析订单的状态 - // 额外说明:支付宝不仅仅支付成功会回调,再各种触发支付单数据变化时,都会进行回调,所以这里 status 的解析会写的比较复杂 - Map bodyObj = HttpUtil.decodeParamMap(body, StandardCharsets.UTF_8); - Integer status = parseStatus(bodyObj.get("trade_status")); - // 特殊逻辑: 支付宝没有退款成功的状态,所以,如果有退款金额,我们认为是退款成功 - if (MapUtil.getDouble(bodyObj, "refund_fee", 0D) > 0) { - status = PayOrderStatusRespEnum.REFUND.getStatus(); - } - Assert.notNull(status, (Supplier) () -> { - throw new IllegalArgumentException(StrUtil.format("body({}) 的 trade_status 不正确", body)); - }); - return PayOrderRespDTO.of(status, bodyObj.get("trade_no"), bodyObj.get("seller_id"), parseTime(params.get("gmt_payment")), - bodyObj.get("out_trade_no"), body); - } - - @Override - protected PayOrderRespDTO doGetOrder(String outTradeNo) throws Throwable { - // 1.1 构建 AlipayTradeRefundModel 请求 - AlipayTradeQueryModel model = new AlipayTradeQueryModel(); - model.setOutTradeNo(outTradeNo); - // 1.2 构建 AlipayTradeQueryRequest 请求 - AlipayTradeQueryRequest request = new AlipayTradeQueryRequest(); - request.setBizModel(model); - AlipayTradeQueryResponse response; - if (Objects.equals(config.getMode(), MODE_CERTIFICATE)) { - // 证书模式 - response = client.certificateExecute(request); - } else { - response = client.execute(request); - } - if (!response.isSuccess()) { // 不成功,例如说订单不存在 - return PayOrderRespDTO.closedOf(response.getSubCode(), response.getSubMsg(), - outTradeNo, response); - } - // 2.2 解析订单的状态 - Integer status = parseStatus(response.getTradeStatus()); - Assert.notNull(status, () -> { - throw new IllegalArgumentException(StrUtil.format("body({}) 的 trade_status 不正确", response.getBody())); - }); - return PayOrderRespDTO.of(status, response.getTradeNo(), response.getBuyerUserId(), LocalDateTimeUtil.of(response.getSendPayDate()), - outTradeNo, response); - } - - private static Integer parseStatus(String tradeStatus) { - return Objects.equals("WAIT_BUYER_PAY", tradeStatus) ? PayOrderStatusRespEnum.WAITING.getStatus() - : ObjectUtils.equalsAny(tradeStatus, "TRADE_FINISHED", "TRADE_SUCCESS") ? PayOrderStatusRespEnum.SUCCESS.getStatus() - : Objects.equals("TRADE_CLOSED", tradeStatus) ? PayOrderStatusRespEnum.CLOSED.getStatus() : null; - } - - // ============ 退款相关 ========== - - /** - * 支付宝统一的退款接口 alipay.trade.refund - * - * @param reqDTO 退款请求 request DTO - * @return 退款请求 Response - */ - @Override - protected PayRefundRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws AlipayApiException { - // 1.1 构建 AlipayTradeRefundModel 请求 - AlipayTradeRefundModel model = new AlipayTradeRefundModel(); - model.setOutTradeNo(reqDTO.getOutTradeNo()); - model.setOutRequestNo(reqDTO.getOutRefundNo()); - model.setRefundAmount(formatAmount(reqDTO.getRefundPrice())); - model.setRefundReason(reqDTO.getReason()); - // 1.2 构建 AlipayTradePayRequest 请求 - AlipayTradeRefundRequest request = new AlipayTradeRefundRequest(); - request.setBizModel(model); - - // 2.1 执行请求 - AlipayTradeRefundResponse response; - if (Objects.equals(config.getMode(), MODE_CERTIFICATE)) { // 证书模式 - response = client.certificateExecute(request); - } else { - response = client.execute(request); - } - if (!response.isSuccess()) { - // 当出现 ACQ.SYSTEM_ERROR, 退款可能成功也可能失败。 返回 WAIT 状态. 后续 job 会轮询 - if (ObjectUtils.equalsAny(response.getSubCode(), "ACQ.SYSTEM_ERROR", "SYSTEM_ERROR")) { - return PayRefundRespDTO.waitingOf(null, reqDTO.getOutRefundNo(), response); - } - return PayRefundRespDTO.failureOf(response.getSubCode(), response.getSubMsg(), reqDTO.getOutRefundNo(), response); - } - // 2.2 创建返回结果 - // 支付宝只要退款调用返回 success,就认为退款成功,不需要回调。具体可见 parseNotify 方法的说明。 - // 另外,支付宝没有退款单号,所以不用设置 - return PayRefundRespDTO.successOf(null, LocalDateTimeUtil.of(response.getGmtRefundPay()), - reqDTO.getOutRefundNo(), response); - } - - @Override - public PayRefundRespDTO doParseRefundNotify(Map params, String body, Map headers) { - // 补充说明:支付宝退款时,没有回调,这点和微信支付是不同的。并且,退款分成部分退款、和全部退款。 - // ① 部分退款:是会有回调,但是它回调的是订单状态的同步回调,不是退款订单的回调 - // ② 全部退款:Wap 支付有订单状态的同步回调,但是 PC/扫码又没有 - // 所以,这里在解析时,即使是退款导致的订单状态同步,我们也忽略不做为“退款同步”,而是订单的回调。 - // 实际上,支付宝退款只要发起成功,就可以认为退款成功,不需要等待回调。 - throw new UnsupportedOperationException("支付宝无退款回调"); - } - - @Override - protected PayRefundRespDTO doGetRefund(String outTradeNo, String outRefundNo) throws AlipayApiException { - // 1.1 构建 AlipayTradeFastpayRefundQueryModel 请求 - AlipayTradeFastpayRefundQueryModel model = new AlipayTradeFastpayRefundQueryModel(); - model.setOutTradeNo(outTradeNo); - model.setOutRequestNo(outRefundNo); - model.setQueryOptions(Collections.singletonList("gmt_refund_pay")); - // 1.2 构建 AlipayTradeFastpayRefundQueryRequest 请求 - AlipayTradeFastpayRefundQueryRequest request = new AlipayTradeFastpayRefundQueryRequest(); - request.setBizModel(model); - - // 2.1 执行请求 - AlipayTradeFastpayRefundQueryResponse response; - if (Objects.equals(config.getMode(), MODE_CERTIFICATE)) { // 证书模式 - response = client.certificateExecute(request); - } else { - response = client.execute(request); - } - if (!response.isSuccess()) { - // 明确不存在的情况,应该就是失败,可进行关闭 - if (ObjectUtils.equalsAny(response.getSubCode(), "TRADE_NOT_EXIST", "ACQ.TRADE_NOT_EXIST")) { - return PayRefundRespDTO.failureOf(outRefundNo, response); - } - // 可能存在“ACQ.SYSTEM_ERROR”系统错误等情况,所以返回 WAIT 继续等待 - return PayRefundRespDTO.waitingOf(null, outRefundNo, response); - } - // 2.2 创建返回结果 - if (Objects.equals(response.getRefundStatus(), "REFUND_SUCCESS")) { - return PayRefundRespDTO.successOf(null, LocalDateTimeUtil.of(response.getGmtRefundPay()), - outRefundNo, response); - } - return PayRefundRespDTO.waitingOf(null, outRefundNo, response); - } - - @Override - protected PayTransferRespDTO doUnifiedTransfer(PayTransferUnifiedReqDTO reqDTO) throws AlipayApiException { - // 补充说明:https://opendocs.alipay.com/open/03dcrm?pathHash=4ba3b20b - // 沙箱环境:可通过 公钥模式 或 公钥证书模式 加签进行调试 - // 生产环境:必须使用 公钥证书模式 加签请求强校验请求 - - // 1.1 构建 AlipayFundTransUniTransferModel - AlipayFundTransUniTransferModel model = new AlipayFundTransUniTransferModel(); - // ① 通用的参数 - model.setTransAmount(formatAmount(reqDTO.getPrice())); // 转账金额 - model.setOrderTitle(reqDTO.getSubject()); // 转账业务的标题,用于在支付宝用户的账单里显示。 - model.setOutBizNo(reqDTO.getOutTransferNo()); - model.setProductCode("TRANS_ACCOUNT_NO_PWD"); // 销售产品码。单笔无密转账固定为 TRANS_ACCOUNT_NO_PWD - model.setBizScene("DIRECT_TRANSFER"); // 业务场景 单笔无密转账固定为 DIRECT_TRANSFER - if (reqDTO.getChannelExtras() != null) { - model.setBusinessParams(JsonUtils.toJsonString(reqDTO.getChannelExtras())); - } - // ② 个性化的参数 - Participant payeeInfo = new Participant(); - payeeInfo.setIdentityType("ALIPAY_LOGON_ID"); // 暂时只考虑转账到支付宝,银行没有权限 https://opendocs.alipay.com/open/02byvc?scene=66dd06f5a923403393b85de68d3c0055 - payeeInfo.setIdentity(reqDTO.getUserAccount()); // 支付宝登录号 - payeeInfo.setName(reqDTO.getUserName()); // 支付宝账号姓名 - model.setPayeeInfo(payeeInfo); - // 1.2 构建 AlipayFundTransUniTransferRequest - AlipayFundTransUniTransferRequest request = new AlipayFundTransUniTransferRequest(); - request.setBizModel(model); - - // 2.1 执行请求 - AlipayFundTransUniTransferResponse response; - if (Objects.equals(config.getMode(), MODE_CERTIFICATE)) { // 证书模式 - response = client.certificateExecute(request); - } else { - response = client.execute(request); - } - if (!response.isSuccess()) { - // 当出现 SYSTEM_ERROR, 转账可能成功也可能失败。 返回 WAIT 状态. 后续 job 会轮询,或相同 outBizNo 重新发起转账 - // 发现 outBizNo 相同 两次请求参数相同. 会返回 "PAYMENT_INFO_INCONSISTENCY", 不知道哪里的问题. 暂时返回 WAIT. 后续job 会轮询 - if (ObjectUtils.equalsAny(response.getSubCode(),"PAYMENT_INFO_INCONSISTENCY", "SYSTEM_ERROR", "ACQ.SYSTEM_ERROR")) { - return PayTransferRespDTO.waitingOf(null, reqDTO.getOutTransferNo(), response); - } - return PayTransferRespDTO.closedOf(response.getSubCode(), response.getSubMsg(), - reqDTO.getOutTransferNo(), response); - } - // 2.2 处理结果 - if (ObjectUtils.equalsAny(response.getStatus(), "REFUND", "FAIL")) { // 转账到银行卡会出现 "REFUND" "FAIL" - return PayTransferRespDTO.closedOf(response.getSubCode(), response.getSubMsg(), - reqDTO.getOutTransferNo(), response); - } - if (Objects.equals(response.getStatus(), "DEALING")) { // 转账到银行卡会出现 "DEALING" 处理中 - return PayTransferRespDTO.processingOf(response.getOrderId(), reqDTO.getOutTransferNo(), response); - } - return PayTransferRespDTO.successOf(response.getOrderId(), parseTime(response.getTransDate()), - response.getOutBizNo(), response); - } - - @Override - protected PayTransferRespDTO doGetTransfer(String outTradeNo) throws Throwable { - // 1.1 构建 AlipayFundTransCommonQueryModel - AlipayFundTransCommonQueryModel model = new AlipayFundTransCommonQueryModel(); - model.setProductCode("TRANS_ACCOUNT_NO_PWD"); - model.setBizScene("DIRECT_TRANSFER"); //业务场景 - model.setOutBizNo(outTradeNo); - // 1.2 构建 AlipayFundTransCommonQueryRequest - AlipayFundTransCommonQueryRequest request = new AlipayFundTransCommonQueryRequest(); - request.setBizModel(model); - - // 2.1 执行请求 - AlipayFundTransCommonQueryResponse response; - if (Objects.equals(config.getMode(), MODE_CERTIFICATE)) { // 证书模式 - response = client.certificateExecute(request); - } else { - response = client.execute(request); - } - if (!response.isSuccess()) { - // 当出现 SYSTEM_ERROR, 转账可能成功也可能失败。 返回 WAIT 状态. 后续 job 会轮询, 或相同 outBizNo 重新发起转账 - // 当出现 ORDER_NOT_EXIST 可能是转账还在处理中,也可能是转账处理失败. 返回 WAIT 状态. 后续 job 会轮询, 或相同 outBizNo 重新发起转账 - if (ObjectUtils.equalsAny(response.getSubCode(), "ORDER_NOT_EXIST", "SYSTEM_ERROR", "ACQ.SYSTEM_ERROR")) { - return PayTransferRespDTO.waitingOf(null, outTradeNo, response); - } - return PayTransferRespDTO.closedOf(response.getSubCode(), response.getSubMsg(), - outTradeNo, response); - } - // 2.2 处理返回结果 - if (ObjectUtils.equalsAny(response.getStatus(), "REFUND", "FAIL")) { // 转账到银行卡会出现 "REFUND" "FAIL" - return PayTransferRespDTO.closedOf(response.getSubCode(), response.getSubMsg(), - outTradeNo, response); - } - if (Objects.equals(response.getStatus(), "DEALING")) { // 转账到银行卡会出现 "DEALING" 处理中 - return PayTransferRespDTO.processingOf(response.getOrderId(), outTradeNo, response); - } - return PayTransferRespDTO.successOf(response.getOrderId(), parseTime(response.getPayDate()), - response.getOutBizNo(), response); - } - - // TODO @芋艿:由于支付宝一直没触发回调,这个方法暂时没办法测试 - @Override - protected PayTransferRespDTO doParseTransferNotify(Map params, String body, Map headers) - throws Throwable { - // 1. 校验回调数据 - verifyNotifyData(params); - - // 2. 解析转账状态 - Map bodyObj = HttpUtil.decodeParamMap(body, StandardCharsets.UTF_8); - String status = bodyObj.get("status"); - String outBizNo = bodyObj.get("out_biz_no"); - String orderId = bodyObj.get("order_id"); - String payDate = bodyObj.get("pay_date"); - - // 3. 根据状态返回对应的结果 - if (Objects.equals(status, "SUCCESS")) { - return PayTransferRespDTO.successOf(orderId, parseTime(payDate), outBizNo, bodyObj); - } - if (Objects.equals(status, "DEALING")) { - return PayTransferRespDTO.processingOf(orderId, outBizNo, bodyObj); - } - if (ObjectUtils.equalsAny(status, "REFUND", "FAIL")) { - return PayTransferRespDTO.closedOf(bodyObj.get("sub_code"), bodyObj.get("sub_msg"), - outBizNo, bodyObj); - } - return PayTransferRespDTO.waitingOf(orderId, outBizNo, bodyObj); - } - - /** - * 校验回调数据 - * - * @param params 回调参数 - * @throws Throwable 验签失败时抛出异常 - */ - protected void verifyNotifyData(Map params) throws Throwable { - boolean verify; - if (Objects.equals(config.getMode(), MODE_PUBLIC_KEY)) { - verify = AlipaySignature.rsaCheckV1(params, config.getAlipayPublicKey(), - StandardCharsets.UTF_8.name(), config.getSignType()); - } else if (Objects.equals(config.getMode(), MODE_CERTIFICATE)) { - // 由于 rsaCertCheckV1 的第二个参数是 path,所以不能这么调用!!!通过阅读源码,发现可以采用如下方式! - X509Certificate cert = AntCertificationUtil.getCertFromContent(config.getAlipayPublicCertContent()); - String publicKey = Base64.encodeBase64String(cert.getEncoded()); - verify = AlipaySignature.rsaCheckV1(params, publicKey, - StandardCharsets.UTF_8.name(), config.getSignType()); - } else { - throw new IllegalArgumentException("未知的公钥类型:" + config.getMode()); - } - Assert.isTrue(verify, "验签结果不通过"); - } - - // ========== 各种工具方法 ========== - - protected String formatAmount(Integer amount) { - return String.valueOf(amount / 100.0); - } - - protected String formatTime(LocalDateTime time) { - return LocalDateTimeUtil.format(time, NORM_DATETIME_FORMATTER); - } - - protected LocalDateTime parseTime(String str) { - return LocalDateTimeUtil.parse(str, NORM_DATETIME_FORMATTER); - } - -} diff --git a/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayAppPayClient.java b/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayAppPayClient.java deleted file mode 100644 index c990e7b..0000000 --- a/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayAppPayClient.java +++ /dev/null @@ -1,61 +0,0 @@ -package cn.iocoder.yudao.framework.pay.core.client.impl.alipay; - -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.transfer.PayTransferRespDTO; -import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum; -import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderDisplayModeEnum; -import com.alipay.api.AlipayApiException; -import com.alipay.api.domain.AlipayTradeAppPayModel; -import com.alipay.api.request.AlipayTradeAppPayRequest; -import com.alipay.api.response.AlipayTradeAppPayResponse; -import lombok.extern.slf4j.Slf4j; - -/** - * 支付宝【App 支付】的 PayClient 实现类 - * - * 文档:App 支付 - * - * // TODO 芋艿:未详细测试,因为手头没 App - * - * @author 芋道源码 - */ -@Slf4j -public class AlipayAppPayClient extends AbstractAlipayPayClient { - - public AlipayAppPayClient(Long channelId, AlipayPayClientConfig config) { - super(channelId, PayChannelEnum.ALIPAY_APP.getCode(), config); - } - - @Override - public PayOrderRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) throws AlipayApiException { - // 1.1 构建 AlipayTradeAppPayModel 请求 - AlipayTradeAppPayModel model = new AlipayTradeAppPayModel(); - // ① 通用的参数 - model.setOutTradeNo(reqDTO.getOutTradeNo()); - model.setSubject(reqDTO.getSubject()); - model.setBody(reqDTO.getBody() + "test"); - model.setTotalAmount(formatAmount(reqDTO.getPrice())); - model.setTimeExpire(formatTime(reqDTO.getExpireTime())); - model.setProductCode("QUICK_MSECURITY_PAY"); // 销售产品码:无线快捷支付产品 - // ② 个性化的参数【无】 - // ③ 支付宝扫码支付只有一种展示 - String displayMode = PayOrderDisplayModeEnum.APP.getMode(); - - // 1.2 构建 AlipayTradePrecreateRequest 请求 - AlipayTradeAppPayRequest request = new AlipayTradeAppPayRequest(); - request.setBizModel(model); - request.setNotifyUrl(reqDTO.getNotifyUrl()); - request.setReturnUrl(reqDTO.getReturnUrl()); - - // 2.1 执行请求 - AlipayTradeAppPayResponse response = client.sdkExecute(request); - // 2.2 处理结果 - if (!response.isSuccess()) { - return buildClosedPayOrderRespDTO(reqDTO, response); - } - return PayOrderRespDTO.waitingOf(displayMode, response.getBody(), - reqDTO.getOutTradeNo(), response); - } - -} diff --git a/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayBarPayClient.java b/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayBarPayClient.java deleted file mode 100644 index 8bd3413..0000000 --- a/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayBarPayClient.java +++ /dev/null @@ -1,87 +0,0 @@ -package cn.iocoder.yudao.framework.pay.core.client.impl.alipay; - -import cn.hutool.core.date.LocalDateTimeUtil; -import cn.hutool.core.map.MapUtil; -import cn.hutool.core.util.StrUtil; -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.transfer.PayTransferRespDTO; -import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum; -import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderDisplayModeEnum; -import com.alipay.api.AlipayApiException; -import com.alipay.api.domain.AlipayTradePayModel; -import com.alipay.api.request.AlipayTradePayRequest; -import com.alipay.api.response.AlipayTradePayResponse; -import lombok.extern.slf4j.Slf4j; - -import java.time.LocalDateTime; -import java.util.Objects; - -import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.BAD_REQUEST; -import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception0; -import static cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayPayClientConfig.MODE_CERTIFICATE; - -/** - * 支付宝【条码支付】的 PayClient 实现类 - * - * 文档:当面付 - * - * @author 芋道源码 - */ -@Slf4j -public class AlipayBarPayClient extends AbstractAlipayPayClient { - - public AlipayBarPayClient(Long channelId, AlipayPayClientConfig config) { - super(channelId, PayChannelEnum.ALIPAY_BAR.getCode(), config); - } - - @Override - public PayOrderRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) throws AlipayApiException { - String authCode = MapUtil.getStr(reqDTO.getChannelExtras(), "auth_code"); - if (StrUtil.isEmpty(authCode)) { - throw exception0(BAD_REQUEST.getCode(), "条形码不能为空"); - } - - // 1.1 构建 AlipayTradePayModel 请求 - AlipayTradePayModel model = new AlipayTradePayModel(); - // ① 通用的参数 - model.setOutTradeNo(reqDTO.getOutTradeNo()); - model.setSubject(reqDTO.getSubject()); - model.setBody(reqDTO.getBody()); - model.setTotalAmount(formatAmount(reqDTO.getPrice())); - model.setScene("bar_code"); // 当面付条码支付场景 - // ② 个性化的参数 - model.setAuthCode(authCode); - // ③ 支付宝条码支付只有一种展示 - String displayMode = PayOrderDisplayModeEnum.BAR_CODE.getMode(); - - // 1.2 构建 AlipayTradePayRequest 请求 - AlipayTradePayRequest request = new AlipayTradePayRequest(); - request.setBizModel(model); - request.setNotifyUrl(reqDTO.getNotifyUrl()); - request.setReturnUrl(reqDTO.getReturnUrl()); - - // 2.1 执行请求 - AlipayTradePayResponse response; - if (Objects.equals(config.getMode(), MODE_CERTIFICATE)) { - // 证书模式 - response = client.certificateExecute(request); - } else { - response = client.execute(request); - } - // 2.2 处理结果 - if (!response.isSuccess()) { - return buildClosedPayOrderRespDTO(reqDTO, response); - } - if ("10000".equals(response.getCode())) { // 免密支付 - 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, "", - reqDTO.getOutTradeNo(), response); - } - -} diff --git a/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayPayClientConfig.java b/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayPayClientConfig.java deleted file mode 100644 index 9980fb7..0000000 --- a/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayPayClientConfig.java +++ /dev/null @@ -1,128 +0,0 @@ -package cn.iocoder.yudao.framework.pay.core.client.impl.alipay; - -import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils; -import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig; -import lombok.Data; - -import jakarta.validation.Validator; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; - -/** - * 支付宝的 PayClientConfig 实现类 - * 属性主要来自 {@link com.alipay.api.AlipayConfig} 的必要属性 - * - * @author 芋道源码 - */ -@Data -public class AlipayPayClientConfig implements PayClientConfig { - - /** - * 公钥类型 - 公钥模式 - */ - public static final Integer MODE_PUBLIC_KEY = 1; - /** - * 公钥类型 - 证书模式 - */ - public static final Integer MODE_CERTIFICATE = 2; - - /** - * 接口内容加密方式 - AES 加密 - */ - public static final String ENC_TYPE_AES = "AES"; - - /** - * 签名算法类型 - RSA - */ - public static final String SIGN_TYPE_DEFAULT = "RSA2"; - - /** - * 网关地址 - * - * 1. 生产环境 - * 2. 沙箱环境 - */ - @NotBlank(message = "网关地址不能为空", groups = {ModePublicKey.class, ModeCertificate.class}) - private String serverUrl; - - /** - * 开放平台上创建的应用的 ID - */ - @NotBlank(message = "开放平台上创建的应用的 ID不能为空", groups = {ModePublicKey.class, ModeCertificate.class}) - private String appId; - - /** - * 签名算法类型,推荐:RSA2 - *

- * {@link #SIGN_TYPE_DEFAULT} - */ - @NotBlank(message = "签名算法类型不能为空", groups = {ModePublicKey.class, ModeCertificate.class}) - private String signType; - - /** - * 公钥类型 - * 1. {@link #MODE_PUBLIC_KEY} 情况,privateKey + alipayPublicKey - * 2. {@link #MODE_CERTIFICATE} 情况,appCertContent + alipayPublicCertContent + rootCertContent - */ - @NotNull(message = "公钥类型不能为空", groups = {ModePublicKey.class, ModeCertificate.class}) - private Integer mode; - - // ========== 公钥模式 ========== - /** - * 商户私钥 - */ - @NotBlank(message = "商户私钥不能为空", groups = {ModePublicKey.class}) - private String privateKey; - - /** - * 支付宝公钥字符串 - */ - @NotBlank(message = "支付宝公钥字符串不能为空", groups = {ModePublicKey.class}) - private String alipayPublicKey; - - // ========== 证书模式 ========== - /** - * 指定商户公钥应用证书内容字符串 - */ - @NotBlank(message = "指定商户公钥应用证书内容不能为空", groups = {ModeCertificate.class}) - private String appCertContent; - /** - * 指定支付宝公钥证书内容字符串 - */ - @NotBlank(message = "指定支付宝公钥证书内容不能为空", groups = {ModeCertificate.class}) - private String alipayPublicCertContent; - /** - * 指定根证书内容字符串 - */ - @NotBlank(message = "指定根证书内容字符串不能为空", groups = {ModeCertificate.class}) - private String rootCertContent; - - /** - * 接口内容加密方式 - * - * 1. 如果为空,将使用无加密方式 - * 2. 如果要加密,目前支付宝只有 AES 一种加密方式 - * - * @see 支付宝开放平台 - * @see AlipayPayClientConfig#ENC_TYPE_AES - */ - private String encryptType; - - /** - * 接口内容加密的私钥 - */ - private String encryptKey; - - public interface ModePublicKey { - } - - public interface ModeCertificate { - } - - @Override - public void validate(Validator validator) { - ValidationUtils.validate(validator, this, - MODE_PUBLIC_KEY.equals(this.getMode()) ? ModePublicKey.class : ModeCertificate.class); - } - -} diff --git a/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayPcPayClient.java b/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayPcPayClient.java deleted file mode 100644 index 39cd78d..0000000 --- a/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayPcPayClient.java +++ /dev/null @@ -1,71 +0,0 @@ -package cn.iocoder.yudao.framework.pay.core.client.impl.alipay; - -import cn.hutool.core.util.ObjectUtil; -import cn.hutool.http.Method; -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.transfer.PayTransferRespDTO; -import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum; -import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderDisplayModeEnum; -import com.alipay.api.AlipayApiException; -import com.alipay.api.domain.AlipayTradePagePayModel; -import com.alipay.api.request.AlipayTradePagePayRequest; -import com.alipay.api.response.AlipayTradePagePayResponse; -import lombok.extern.slf4j.Slf4j; - -import java.util.Objects; - -/** - * 支付宝【PC 网站】的 PayClient 实现类 - * - * 文档:电脑网站支付 - * - * @author XGD - */ -@Slf4j -public class AlipayPcPayClient extends AbstractAlipayPayClient { - - public AlipayPcPayClient(Long channelId, AlipayPayClientConfig config) { - super(channelId, PayChannelEnum.ALIPAY_PC.getCode(), config); - } - - @Override - public PayOrderRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) throws AlipayApiException { - // 1.1 构建 AlipayTradePagePayModel 请求 - AlipayTradePagePayModel model = new AlipayTradePagePayModel(); - // ① 通用的参数 - model.setOutTradeNo(reqDTO.getOutTradeNo()); - model.setSubject(reqDTO.getSubject()); - model.setBody(reqDTO.getBody()); - model.setTotalAmount(formatAmount(reqDTO.getPrice())); - model.setTimeExpire(formatTime(reqDTO.getExpireTime())); - model.setProductCode("FAST_INSTANT_TRADE_PAY"); // 销售产品码. 目前 PC 支付场景下仅支持 FAST_INSTANT_TRADE_PAY - // ② 个性化的参数 - // 如果想弄更多个性化的参数,可参考 https://www.pingxx.com/api/支付渠道 extra 参数说明.html 的 alipay_pc_direct 部分进行拓展 - model.setQrPayMode("2"); // 跳转模式 - 订单码,效果参见:https://help.pingxx.com/article/1137360/ - // ③ 支付宝 PC 支付有两种展示模式:FORM、URL - String displayMode = ObjectUtil.defaultIfNull(reqDTO.getDisplayMode(), - PayOrderDisplayModeEnum.URL.getMode()); - - // 1.2 构建 AlipayTradePagePayRequest 请求 - AlipayTradePagePayRequest request = new AlipayTradePagePayRequest(); - request.setBizModel(model); - request.setNotifyUrl(reqDTO.getNotifyUrl()); - request.setReturnUrl(reqDTO.getReturnUrl()); - - // 2.1 执行请求 - AlipayTradePagePayResponse response; - if (Objects.equals(displayMode, PayOrderDisplayModeEnum.FORM.getMode())) { - response = client.pageExecute(request, Method.POST.name()); // 需要特殊使用 POST 请求 - } else { - response = client.pageExecute(request, Method.GET.name()); - } - // 2.2 处理结果 - if (!response.isSuccess()) { - return buildClosedPayOrderRespDTO(reqDTO, response); - } - return PayOrderRespDTO.waitingOf(displayMode, response.getBody(), - reqDTO.getOutTradeNo(), response); - } - -} diff --git a/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayQrPayClient.java b/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayQrPayClient.java deleted file mode 100644 index bb3ad17..0000000 --- a/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayQrPayClient.java +++ /dev/null @@ -1,66 +0,0 @@ -package cn.iocoder.yudao.framework.pay.core.client.impl.alipay; - -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.enums.channel.PayChannelEnum; -import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderDisplayModeEnum; -import com.alipay.api.AlipayApiException; -import com.alipay.api.domain.AlipayTradePrecreateModel; -import com.alipay.api.request.AlipayTradePrecreateRequest; -import com.alipay.api.response.AlipayTradePrecreateResponse; -import lombok.extern.slf4j.Slf4j; - -import java.util.Objects; - -import static cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayPayClientConfig.MODE_CERTIFICATE; - -/** - * 支付宝【扫码支付】的 PayClient 实现类 - * - * 文档:扫码支付 - * - * @author 芋道源码 - */ -@Slf4j -public class AlipayQrPayClient extends AbstractAlipayPayClient { - - public AlipayQrPayClient(Long channelId, AlipayPayClientConfig config) { - super(channelId, PayChannelEnum.ALIPAY_QR.getCode(), config); - } - - @Override - public PayOrderRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) throws AlipayApiException { - // 1.1 构建 AlipayTradePrecreateModel 请求 - AlipayTradePrecreateModel model = new AlipayTradePrecreateModel(); - // ① 通用的参数 - model.setOutTradeNo(reqDTO.getOutTradeNo()); - model.setSubject(reqDTO.getSubject()); - model.setBody(reqDTO.getBody()); - model.setTotalAmount(formatAmount(reqDTO.getPrice())); - model.setProductCode("FACE_TO_FACE_PAYMENT"); // 销售产品码. 目前扫码支付场景下仅支持 FACE_TO_FACE_PAYMENT - // ② 个性化的参数【无】 - // ③ 支付宝扫码支付只有一种展示,考虑到前端可能希望二维码扫描后,手机打开 - String displayMode = PayOrderDisplayModeEnum.QR_CODE.getMode(); - - // 1.2 构建 AlipayTradePrecreateRequest 请求 - AlipayTradePrecreateRequest request = new AlipayTradePrecreateRequest(); - request.setBizModel(model); - request.setNotifyUrl(reqDTO.getNotifyUrl()); - request.setReturnUrl(reqDTO.getReturnUrl()); - - // 2.1 执行请求 - AlipayTradePrecreateResponse response; - if (Objects.equals(config.getMode(), MODE_CERTIFICATE)) { - // 证书模式 - response = client.certificateExecute(request); - } else { - response = client.execute(request); - } - // 2.2 处理结果 - if (!response.isSuccess()) { - return buildClosedPayOrderRespDTO(reqDTO, response); - } - return PayOrderRespDTO.waitingOf(displayMode, response.getQrCode(), - reqDTO.getOutTradeNo(), response); - } -} diff --git a/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayWapPayClient.java b/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayWapPayClient.java deleted file mode 100644 index 2bc2163..0000000 --- a/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayWapPayClient.java +++ /dev/null @@ -1,59 +0,0 @@ -package cn.iocoder.yudao.framework.pay.core.client.impl.alipay; - -import cn.hutool.http.Method; -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.enums.channel.PayChannelEnum; -import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderDisplayModeEnum; -import com.alipay.api.AlipayApiException; -import com.alipay.api.domain.AlipayTradeWapPayModel; -import com.alipay.api.request.AlipayTradeWapPayRequest; -import com.alipay.api.response.AlipayTradeWapPayResponse; -import lombok.extern.slf4j.Slf4j; - -/** - * 支付宝【Wap 网站】的 PayClient 实现类 - * - * 文档:手机网站支付接口 - * - * @author 芋道源码 - */ -@Slf4j -public class AlipayWapPayClient extends AbstractAlipayPayClient { - - public AlipayWapPayClient(Long channelId, AlipayPayClientConfig config) { - super(channelId, PayChannelEnum.ALIPAY_WAP.getCode(), config); - } - - @Override - public PayOrderRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) throws AlipayApiException { - // 1.1 构建 AlipayTradeWapPayModel 请求 - AlipayTradeWapPayModel model = new AlipayTradeWapPayModel(); - // ① 通用的参数 - model.setOutTradeNo(reqDTO.getOutTradeNo()); - model.setSubject(reqDTO.getSubject()); - model.setBody(reqDTO.getBody()); - model.setTotalAmount(formatAmount(reqDTO.getPrice())); - model.setProductCode("QUICK_WAP_PAY"); // 销售产品码. 目前 Wap 支付场景下仅支持 QUICK_WAP_PAY - // ② 个性化的参数【无】 - // ③ 支付宝 Wap 支付只有一种展示:URL - String displayMode = PayOrderDisplayModeEnum.URL.getMode(); - - // 1.2 构建 AlipayTradeWapPayRequest 请求 - AlipayTradeWapPayRequest request = new AlipayTradeWapPayRequest(); - request.setBizModel(model); - request.setNotifyUrl(reqDTO.getNotifyUrl()); - request.setReturnUrl(reqDTO.getReturnUrl()); - model.setQuitUrl(reqDTO.getReturnUrl()); - model.setTimeExpire(formatTime(reqDTO.getExpireTime())); - - // 2.1 执行请求 - AlipayTradeWapPayResponse response = client.pageExecute(request, Method.GET.name()); - // 2.2 处理结果 - if (!response.isSuccess()) { - return buildClosedPayOrderRespDTO(reqDTO, response); - } - return PayOrderRespDTO.waitingOf(displayMode, response.getBody(), - reqDTO.getOutTradeNo(), response); - } -} diff --git a/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/mock/MockPayClient.java b/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/mock/MockPayClient.java deleted file mode 100644 index fc05c22..0000000 --- a/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/mock/MockPayClient.java +++ /dev/null @@ -1,84 +0,0 @@ -package cn.iocoder.yudao.framework.pay.core.client.impl.mock; - -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.dto.transfer.PayTransferRespDTO; -import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferUnifiedReqDTO; -import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient; -import cn.iocoder.yudao.framework.pay.core.client.impl.NonePayClientConfig; -import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum; - -import java.time.LocalDateTime; -import java.util.Map; - -/** - * 模拟支付的 PayClient 实现类 - * - * 模拟支付返回结果都是成功,方便大家日常流畅 - * - * @author jason - */ -public class MockPayClient extends AbstractPayClient { - - private static final String MOCK_RESP_SUCCESS_DATA = "MOCK_SUCCESS"; - - public MockPayClient(Long channelId, NonePayClientConfig config) { - super(channelId, PayChannelEnum.MOCK.getCode(), config); - } - - @Override - protected void doInit() { - } - - @Override - protected PayOrderRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) { - return PayOrderRespDTO.successOf("MOCK-P-" + reqDTO.getOutTradeNo(), "", LocalDateTime.now(), - reqDTO.getOutTradeNo(), MOCK_RESP_SUCCESS_DATA); - } - - @Override - protected PayOrderRespDTO doGetOrder(String outTradeNo) { - return PayOrderRespDTO.successOf("MOCK-P-" + outTradeNo, "", LocalDateTime.now(), - outTradeNo, MOCK_RESP_SUCCESS_DATA); - } - - @Override - protected PayRefundRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) { - return PayRefundRespDTO.successOf("MOCK-R-" + reqDTO.getOutRefundNo(), LocalDateTime.now(), - reqDTO.getOutRefundNo(), MOCK_RESP_SUCCESS_DATA); - } - - @Override - protected PayRefundRespDTO doGetRefund(String outTradeNo, String outRefundNo) { - return PayRefundRespDTO.successOf("MOCK-R-" + outRefundNo, LocalDateTime.now(), - outRefundNo, MOCK_RESP_SUCCESS_DATA); - } - - @Override - protected PayTransferRespDTO doParseTransferNotify(Map params, String body, Map headers) { - throw new UnsupportedOperationException("未实现"); - } - - @Override - protected PayRefundRespDTO doParseRefundNotify(Map params, String body, Map headers) { - throw new UnsupportedOperationException("模拟支付无退款回调"); - } - - @Override - protected PayOrderRespDTO doParseOrderNotify(Map params, String body, Map headers) { - throw new UnsupportedOperationException("模拟支付无支付回调"); - } - - @Override - protected PayTransferRespDTO doUnifiedTransfer(PayTransferUnifiedReqDTO reqDTO) { - throw new UnsupportedOperationException("待实现"); - } - - @Override - protected PayTransferRespDTO doGetTransfer(String outTradeNo) { - throw new UnsupportedOperationException("待实现"); - } - -} diff --git a/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/weixin/AbstractWxPayClient.java b/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/weixin/AbstractWxPayClient.java deleted file mode 100644 index 0bf9614..0000000 --- a/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/weixin/AbstractWxPayClient.java +++ /dev/null @@ -1,597 +0,0 @@ -package cn.iocoder.yudao.framework.pay.core.client.impl.weixin; - -import cn.hutool.core.bean.BeanUtil; -import cn.hutool.core.codec.Base64; -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.date.LocalDateTimeUtil; -import cn.hutool.core.date.TemporalAccessorUtil; -import cn.hutool.core.util.StrUtil; -import cn.iocoder.yudao.framework.common.util.io.FileUtils; -import cn.iocoder.yudao.framework.common.util.json.JsonUtils; -import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; -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.dto.transfer.PayTransferRespDTO; -import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferUnifiedReqDTO; -import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient; -import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderStatusRespEnum; -import com.github.binarywang.wxpay.bean.notify.*; -import com.github.binarywang.wxpay.bean.request.*; -import com.github.binarywang.wxpay.bean.result.*; -import com.github.binarywang.wxpay.bean.transfer.TransferBillsGetResult; -import com.github.binarywang.wxpay.bean.transfer.TransferBillsNotifyResult; -import com.github.binarywang.wxpay.bean.transfer.TransferBillsRequest; -import com.github.binarywang.wxpay.bean.transfer.TransferBillsResult; -import com.github.binarywang.wxpay.config.WxPayConfig; -import com.github.binarywang.wxpay.exception.WxPayException; -import com.github.binarywang.wxpay.service.WxPayService; -import com.github.binarywang.wxpay.service.impl.WxPayServiceImpl; -import lombok.extern.slf4j.Slf4j; - -import java.time.LocalDateTime; -import java.time.ZoneId; -import java.util.Map; -import java.util.Objects; - -import static cn.hutool.core.date.DatePattern.*; -import static cn.iocoder.yudao.framework.pay.core.client.impl.weixin.WxPayClientConfig.API_VERSION_V2; -import static cn.iocoder.yudao.framework.pay.core.client.impl.weixin.WxPayClientConfig.API_VERSION_V3; - -/** - * 微信支付抽象类,实现微信统一的接口、以及部分实现(退款) - * - * @author 芋道源码 - */ -@Slf4j -public abstract class AbstractWxPayClient extends AbstractPayClient { - - protected WxPayService client; - - public AbstractWxPayClient(Long channelId, String channelCode, WxPayClientConfig config) { - super(channelId, channelCode, config); - } - - /** - * 初始化 client 客户端 - * - * @param tradeType 交易类型 - */ - protected void doInit(String tradeType) { - // 创建 config 配置 - WxPayConfig payConfig = new WxPayConfig(); - BeanUtil.copyProperties(config, payConfig, "keyContent", "privateKeyContent", "publicKeyContent"); - payConfig.setTradeType(tradeType); - // weixin-pay-java 无法设置内容,只允许读取文件,所以这里要创建临时文件来解决 - if (Objects.equals(config.getApiVersion(), API_VERSION_V2)) { - payConfig.setKeyPath(FileUtils.createTempFile(Base64.decode(config.getKeyContent())).getPath()); - } else if (Objects.equals(config.getApiVersion(), API_VERSION_V3)) { - payConfig.setPrivateKeyPath(FileUtils.createTempFile(config.getPrivateKeyContent()).getPath()); - payConfig.setPublicKeyPath(FileUtils.createTempFile(config.getPublicKeyContent()).getPath()); - // 特殊:强制使用微信公用模式,避免灰度期间的问题!!! - payConfig.setStrictlyNeedWechatPaySerial(true); - } - - // 创建 client 客户端 - client = new WxPayServiceImpl(); - client.setConfig(payConfig); - } - - // ============ 支付相关 ========== - - @Override - protected PayOrderRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) throws Exception { - try { - switch (config.getApiVersion()) { - case API_VERSION_V2: - return doUnifiedOrderV2(reqDTO); - case API_VERSION_V3: - // TODO @芋艿:【可能是 wxjava 的 bug】参考 https://github.com/binarywang/WxJava/issues/1557 - client.getConfig().setApiV3HttpClient(null); - return doUnifiedOrderV3(reqDTO); - default: - throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion())); - } - } catch (WxPayException e) { - log.error("[doUnifiedOrder][支付({}) 发起微信支付异常", reqDTO, e); - String errorCode = getErrorCode(e); - String errorMessage = getErrorMessage(e); - return PayOrderRespDTO.closedOf(errorCode, errorMessage, - reqDTO.getOutTradeNo(), e.getXmlString()); - } - } - - /** - * 【V2】调用支付渠道,统一下单 - * - * @param reqDTO 下单信息 - * @return 各支付渠道的返回结果 - */ - protected abstract PayOrderRespDTO doUnifiedOrderV2(PayOrderUnifiedReqDTO reqDTO) - throws Exception; - - /** - * 【V3】调用支付渠道,统一下单 - * - * @param reqDTO 下单信息 - * @return 各支付渠道的返回结果 - */ - protected abstract PayOrderRespDTO doUnifiedOrderV3(PayOrderUnifiedReqDTO reqDTO) - throws WxPayException; - - /** - * 【V2】创建微信下单请求 - * - * @param reqDTO 下信息 - * @return 下单请求 - */ - protected WxPayUnifiedOrderRequest buildPayUnifiedOrderRequestV2(PayOrderUnifiedReqDTO reqDTO) { - return WxPayUnifiedOrderRequest.newBuilder() - .outTradeNo(reqDTO.getOutTradeNo()) - .body(reqDTO.getSubject()) - .detail(reqDTO.getBody()) - .totalFee(reqDTO.getPrice()) // 单位分 - .timeExpire(formatDateV2(reqDTO.getExpireTime())) - .spbillCreateIp(reqDTO.getUserIp()) - .notifyUrl(reqDTO.getNotifyUrl()) - .build(); - } - - /** - * 【V3】创建微信下单请求 - * - * @param reqDTO 下信息 - * @return 下单请求 - */ - protected WxPayUnifiedOrderV3Request buildPayUnifiedOrderRequestV3(PayOrderUnifiedReqDTO reqDTO) { - WxPayUnifiedOrderV3Request request = new WxPayUnifiedOrderV3Request(); - request.setOutTradeNo(reqDTO.getOutTradeNo()); - request.setDescription(reqDTO.getSubject()); - request.setAmount(new WxPayUnifiedOrderV3Request.Amount().setTotal(reqDTO.getPrice())); // 单位分 - request.setTimeExpire(formatDateV3(reqDTO.getExpireTime())); - request.setSceneInfo(new WxPayUnifiedOrderV3Request.SceneInfo().setPayerClientIp(reqDTO.getUserIp())); - request.setNotifyUrl(reqDTO.getNotifyUrl()); - return request; - } - - @Override - public PayOrderRespDTO doParseOrderNotify(Map params, String body, Map headers) throws WxPayException { - switch (config.getApiVersion()) { - case API_VERSION_V2: - return doParseOrderNotifyV2(body); - case API_VERSION_V3: - return doParseOrderNotifyV3(body, headers); - default: - throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion())); - } - } - - private PayOrderRespDTO doParseOrderNotifyV2(String body) throws WxPayException { - // 1. 解析回调 - WxPayOrderNotifyResult response = client.parseOrderNotifyResult(body); - // 2. 构建结果 - // V2 微信支付的回调,只有 SUCCESS 支付成功、CLOSED 支付失败两种情况,无需像支付宝一样解析的比较复杂 - Integer status = Objects.equals(response.getResultCode(), "SUCCESS") ? - PayOrderStatusRespEnum.SUCCESS.getStatus() : PayOrderStatusRespEnum.CLOSED.getStatus(); - return PayOrderRespDTO.of(status, response.getTransactionId(), response.getOpenid(), parseDateV2(response.getTimeEnd()), - response.getOutTradeNo(), body); - } - - private PayOrderRespDTO doParseOrderNotifyV3(String body, Map headers) throws WxPayException { - // 1. 解析回调 - SignatureHeader signatureHeader = getRequestHeader(headers); - WxPayNotifyV3Result response = client.parseOrderNotifyV3Result(body, signatureHeader); - WxPayNotifyV3Result.DecryptNotifyResult result = response.getResult(); - // 2. 构建结果 - Integer status = parseStatus(result.getTradeState()); - String openid = result.getPayer() != null ? result.getPayer().getOpenid() : null; - return PayOrderRespDTO.of(status, result.getTransactionId(), openid, parseDateV3(result.getSuccessTime()), - result.getOutTradeNo(), body); - } - - @Override - protected PayOrderRespDTO doGetOrder(String outTradeNo) throws Throwable { - try { - switch (config.getApiVersion()) { - case API_VERSION_V2: - return doGetOrderV2(outTradeNo); - case API_VERSION_V3: - return doGetOrderV3(outTradeNo); - default: - throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion())); - } - } catch (WxPayException e) { - if (ObjectUtils.equalsAny(e.getErrCode(), "ORDERNOTEXIST", "ORDER_NOT_EXIST")) { - String errorCode = getErrorCode(e); - String errorMessage = getErrorMessage(e); - return PayOrderRespDTO.closedOf(errorCode, errorMessage, - outTradeNo, e.getXmlString()); - } - throw e; - } - } - - private PayOrderRespDTO doGetOrderV2(String outTradeNo) throws WxPayException { - // 构建 WxPayUnifiedOrderRequest 对象 - WxPayOrderQueryRequest request = WxPayOrderQueryRequest.newBuilder() - .outTradeNo(outTradeNo).build(); - // 执行请求 - WxPayOrderQueryResult response = client.queryOrder(request); - - // 转换结果 - Integer status = parseStatus(response.getTradeState()); - return PayOrderRespDTO.of(status, response.getTransactionId(), response.getOpenid(), parseDateV2(response.getTimeEnd()), - outTradeNo, response); - } - - private PayOrderRespDTO doGetOrderV3(String outTradeNo) throws WxPayException { - fixV3HttpClientConnectionPoolShutDown(); - // 构建 WxPayUnifiedOrderRequest 对象 - WxPayOrderQueryV3Request request = new WxPayOrderQueryV3Request() - .setOutTradeNo(outTradeNo); - // 执行请求 - WxPayOrderQueryV3Result response = client.queryOrderV3(request); - - // 转换结果 - Integer status = parseStatus(response.getTradeState()); - String openid = response.getPayer() != null ? response.getPayer().getOpenid() : null; - return PayOrderRespDTO.of(status, response.getTransactionId(), openid, parseDateV3(response.getSuccessTime()), - outTradeNo, response); - } - - private static Integer parseStatus(String tradeState) { - switch (tradeState) { - case "NOTPAY": - case "USERPAYING": // 支付中,等待用户输入密码(条码支付独有) - return PayOrderStatusRespEnum.WAITING.getStatus(); - case "SUCCESS": - return PayOrderStatusRespEnum.SUCCESS.getStatus(); - case "REFUND": - return PayOrderStatusRespEnum.REFUND.getStatus(); - case "CLOSED": - case "REVOKED": // 已撤销(刷卡支付独有) - case "PAYERROR": // 支付失败(其它原因,如银行返回失败) - return PayOrderStatusRespEnum.CLOSED.getStatus(); - default: - throw new IllegalArgumentException(StrUtil.format("未知的支付状态({})", tradeState)); - } - } - - // ============ 退款相关 ========== - - @Override - protected PayRefundRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws Throwable { - try { - switch (config.getApiVersion()) { - case API_VERSION_V2: - return doUnifiedRefundV2(reqDTO); - case API_VERSION_V3: - return doUnifiedRefundV3(reqDTO); - default: - throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion())); - } - } catch (WxPayException e) { - String errorCode = getErrorCode(e); - String errorMessage = getErrorMessage(e); - return PayRefundRespDTO.failureOf(errorCode, errorMessage, - reqDTO.getOutRefundNo(), e.getXmlString()); - } - } - - private PayRefundRespDTO doUnifiedRefundV2(PayRefundUnifiedReqDTO reqDTO) throws Throwable { - // 1. 构建 WxPayRefundRequest 请求 - WxPayRefundRequest request = new WxPayRefundRequest() - .setOutTradeNo(reqDTO.getOutTradeNo()) - .setOutRefundNo(reqDTO.getOutRefundNo()) - .setRefundFee(reqDTO.getRefundPrice()) - .setRefundDesc(reqDTO.getReason()) - .setTotalFee(reqDTO.getPayPrice()) - .setNotifyUrl(reqDTO.getNotifyUrl()); - // 2.1 执行请求 - WxPayRefundResult response = client.refundV2(request); - // 2.2 创建返回结果 - if (Objects.equals("SUCCESS", response.getResultCode())) { // V2 情况下,不直接返回退款成功,而是等待异步通知 - return PayRefundRespDTO.waitingOf(response.getRefundId(), - reqDTO.getOutRefundNo(), response); - } - return PayRefundRespDTO.failureOf(reqDTO.getOutRefundNo(), response); - } - - private PayRefundRespDTO doUnifiedRefundV3(PayRefundUnifiedReqDTO reqDTO) throws Throwable { - fixV3HttpClientConnectionPoolShutDown(); - // 1. 构建 WxPayRefundRequest 请求 - WxPayRefundV3Request request = new WxPayRefundV3Request() - .setOutTradeNo(reqDTO.getOutTradeNo()) - .setOutRefundNo(reqDTO.getOutRefundNo()) - .setAmount(new WxPayRefundV3Request.Amount().setRefund(reqDTO.getRefundPrice()) - .setTotal(reqDTO.getPayPrice()).setCurrency("CNY")) - .setReason(reqDTO.getReason()) - .setNotifyUrl(reqDTO.getNotifyUrl()); - // 2.1 执行请求 - WxPayRefundV3Result response = client.refundV3(request); - // 2.2 创建返回结果 - if (Objects.equals("SUCCESS", response.getStatus())) { - return PayRefundRespDTO.successOf(response.getRefundId(), parseDateV3(response.getSuccessTime()), - reqDTO.getOutRefundNo(), response); - } - if (Objects.equals("PROCESSING", response.getStatus())) { - return PayRefundRespDTO.waitingOf(response.getRefundId(), - reqDTO.getOutRefundNo(), response); - } - return PayRefundRespDTO.failureOf(reqDTO.getOutRefundNo(), response); - } - - @Override - public PayRefundRespDTO doParseRefundNotify(Map params, String body, Map headers) throws WxPayException { - switch (config.getApiVersion()) { - case API_VERSION_V2: - return doParseRefundNotifyV2(body); - case API_VERSION_V3: - return parseRefundNotifyV3(body, headers); - default: - throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion())); - } - } - - private PayRefundRespDTO doParseRefundNotifyV2(String body) throws WxPayException { - // 1. 解析回调 - WxPayRefundNotifyResult response = client.parseRefundNotifyResult(body); - WxPayRefundNotifyResult.ReqInfo result = response.getReqInfo(); - // 2. 构建结果 - if (Objects.equals("SUCCESS", result.getRefundStatus())) { - return PayRefundRespDTO.successOf(result.getRefundId(), parseDateV2B(result.getSuccessTime()), - result.getOutRefundNo(), response); - } - return PayRefundRespDTO.failureOf(result.getOutRefundNo(), response); - } - - private PayRefundRespDTO parseRefundNotifyV3(String body, Map headers) throws WxPayException { - // 1. 解析回调 - SignatureHeader signatureHeader = getRequestHeader(headers); - WxPayRefundNotifyV3Result response = client.parseRefundNotifyV3Result(body, signatureHeader); - WxPayRefundNotifyV3Result.DecryptNotifyResult result = response.getResult(); - // 2. 构建结果 - if (Objects.equals("SUCCESS", result.getRefundStatus())) { - return PayRefundRespDTO.successOf(result.getRefundId(), parseDateV3(result.getSuccessTime()), - result.getOutRefundNo(), response); - } - return PayRefundRespDTO.failureOf(result.getOutRefundNo(), response); - } - - @Override - protected PayRefundRespDTO doGetRefund(String outTradeNo, String outRefundNo) throws WxPayException { - try { - switch (config.getApiVersion()) { - case API_VERSION_V2: - return doGetRefundV2(outTradeNo, outRefundNo); - case API_VERSION_V3: - return doGetRefundV3(outTradeNo, outRefundNo); - default: - throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion())); - } - } catch (WxPayException e) { - if (ObjectUtils.equalsAny(e.getErrCode(), "REFUNDNOTEXIST", "RESOURCE_NOT_EXISTS")) { - String errorCode = getErrorCode(e); - String errorMessage = getErrorMessage(e); - return PayRefundRespDTO.failureOf(errorCode, errorMessage, - outRefundNo, e.getXmlString()); - } - throw e; - } - } - - private PayRefundRespDTO doGetRefundV2(String outTradeNo, String outRefundNo) throws WxPayException { - // 1. 构建 WxPayRefundRequest 请求 - WxPayRefundQueryRequest request = WxPayRefundQueryRequest.newBuilder() - .outTradeNo(outTradeNo) - .outRefundNo(outRefundNo) - .build(); - // 2.1 执行请求 - WxPayRefundQueryResult response = client.refundQuery(request); - // 2.2 创建返回结果 - if (!Objects.equals("SUCCESS", response.getResultCode())) { - return PayRefundRespDTO.waitingOf(null, - outRefundNo, response); - } - WxPayRefundQueryResult.RefundRecord refund = CollUtil.findOne(response.getRefundRecords(), - record -> record.getOutRefundNo().equals(outRefundNo)); - if (refund == null) { - return PayRefundRespDTO.failureOf(outRefundNo, response); - } - switch (refund.getRefundStatus()) { - case "SUCCESS": - return PayRefundRespDTO.successOf(refund.getRefundId(), parseDateV2B(refund.getRefundSuccessTime()), - outRefundNo, response); - case "PROCESSING": - return PayRefundRespDTO.waitingOf(refund.getRefundId(), - outRefundNo, response); - case "CHANGE": // 退款到银行发现用户的卡作废或者冻结了,导致原路退款银行卡失败,资金回流到商户的现金帐号,需要商户人工干预,通过线下或者财付通转账的方式进行退款 - case "FAIL": - return PayRefundRespDTO.failureOf(outRefundNo, response); - default: - throw new IllegalArgumentException(String.format("未知的退款状态(%s)", refund.getRefundStatus())); - } - } - - private PayRefundRespDTO doGetRefundV3(String outTradeNo, String outRefundNo) throws WxPayException { - fixV3HttpClientConnectionPoolShutDown(); - // 1. 构建 WxPayRefundRequest 请求 - WxPayRefundQueryV3Request request = new WxPayRefundQueryV3Request(); - request.setOutRefundNo(outRefundNo); - // 2.1 执行请求 - WxPayRefundQueryV3Result response = client.refundQueryV3(request); - // 2.2 创建返回结果 - switch (response.getStatus()) { - case "SUCCESS": - return PayRefundRespDTO.successOf(response.getRefundId(), parseDateV3(response.getSuccessTime()), - outRefundNo, response); - case "PROCESSING": - return PayRefundRespDTO.waitingOf(response.getRefundId(), - outRefundNo, response); - case "ABNORMAL": // 退款异常 - case "CLOSED": - return PayRefundRespDTO.failureOf(outRefundNo, response); - default: - throw new IllegalArgumentException(String.format("未知的退款状态(%s)", response.getStatus())); - } - } - - @Override - protected PayTransferRespDTO doUnifiedTransfer(PayTransferUnifiedReqDTO reqDTO) throws WxPayException { - fixV3HttpClientConnectionPoolShutDown(); - // 1. 构建 TransferBillsRequest 请求 - TransferBillsRequest request = TransferBillsRequest.newBuilder() - .appid(this.config.getAppId()) - .outBillNo(reqDTO.getOutTransferNo()) - .transferAmount(reqDTO.getPrice()) - .transferRemark(reqDTO.getSubject()) - .transferSceneId(reqDTO.getChannelExtras().get("sceneId")) - .openid(reqDTO.getUserAccount()) - .userName(reqDTO.getUserName()) - .transferSceneReportInfos(JsonUtils.parseArray(reqDTO.getChannelExtras().get("sceneReportInfos"), - TransferBillsRequest.TransferSceneReportInfo.class)) - .notifyUrl(reqDTO.getNotifyUrl()) - .build(); - // 特殊:微信转账,必须 0.3 元起,才允许传入姓名 - if (reqDTO.getPrice() < 30) { - request.setUserName(null); - } - - // 2.1 执行请求 - try { - TransferBillsResult response = client.getTransferService().transferBills(request); - - // 2.2 创建返回结果 - String state = response.getState(); - if (ObjectUtils.equalsAny(state, "ACCEPTED", "PROCESSING", "WAIT_USER_CONFIRM", "TRANSFERING")) { - return PayTransferRespDTO.processingOf(response.getTransferBillNo(), response.getOutBillNo(), response) - .setChannelPackageInfo(response.getPackageInfo()); // 一般情况下,只有 WAIT_USER_CONFIRM 会有! - } - if (Objects.equals("SUCCESS", state)) { - return PayTransferRespDTO.successOf(response.getTransferBillNo(), parseDateV3(response.getCreateTime()), - response.getOutBillNo(), response); - } - return PayTransferRespDTO.closedOf(state, response.getFailReason(), - response.getOutBillNo(), response); - } catch (WxPayException e) { - log.error("[doUnifiedTransfer][转账({}) 发起微信支付异常", reqDTO, e); - String errorCode = getErrorCode(e); - String errorMessage = getErrorMessage(e); - return PayTransferRespDTO.closedOf(errorCode, errorMessage, - reqDTO.getOutTransferNo(), e.getXmlString()); - } - } - - @Override - protected PayTransferRespDTO doGetTransfer(String outTradeNo) throws WxPayException { - fixV3HttpClientConnectionPoolShutDown(); - // 1. 执行请求 - TransferBillsGetResult response = client.getTransferService().getBillsByOutBillNo(outTradeNo); - - // 2. 创建返回结果 - String state = response.getState(); - if (ObjectUtils.equalsAny(state, "ACCEPTED", "PROCESSING", "WAIT_USER_CONFIRM", "TRANSFERING")) { - return PayTransferRespDTO.processingOf(response.getTransferBillNo(), response.getOutBillNo(), response); - } - if (Objects.equals("SUCCESS", state)) { - return PayTransferRespDTO.successOf(response.getTransferBillNo(), parseDateV3(response.getUpdateTime()), - response.getOutBillNo(), response); - } - return PayTransferRespDTO.closedOf(state, response.getFailReason(), - response.getOutBillNo(), response); - } - - @Override - public PayTransferRespDTO doParseTransferNotify(Map params, String body, Map headers) throws WxPayException { - switch (config.getApiVersion()) { - case API_VERSION_V3: - return parseTransferNotifyV3(body, headers); - case API_VERSION_V2: - throw new UnsupportedOperationException("V2 版本暂不支持,建议使用 V3 版本"); - default: - throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion())); - } - } - - private PayTransferRespDTO parseTransferNotifyV3(String body, Map headers) throws WxPayException { - // 1. 解析回调 - SignatureHeader signatureHeader = getRequestHeader(headers); - TransferBillsNotifyResult response = client.getTransferService().parseTransferBillsNotifyResult(body, signatureHeader); - TransferBillsNotifyResult.DecryptNotifyResult result = response.getResult(); - - // 2. 创建返回结果 - String state = result.getState(); - if (ObjectUtils.equalsAny(state, "ACCEPTED", "PROCESSING", "WAIT_USER_CONFIRM", "TRANSFERING")) { - return PayTransferRespDTO.processingOf(result.getTransferBillNo(), result.getOutBillNo(), response); - } - if (Objects.equals("SUCCESS", state)) { - return PayTransferRespDTO.successOf(result.getTransferBillNo(), parseDateV3(result.getUpdateTime()), - result.getOutBillNo(), response); - } - return PayTransferRespDTO.closedOf(state, result.getFailReason(), - result.getOutBillNo(), response); - } - - // ========== 各种工具方法 ========== - - /** - * 组装请求头重的签名信息 - * - * @see 官方示例 - */ - private SignatureHeader getRequestHeader(Map headers) { - return SignatureHeader.builder() - .signature(headers.get("wechatpay-signature")) - .nonce(headers.get("wechatpay-nonce")) - .serial(headers.get("wechatpay-serial")) - .timeStamp(headers.get("wechatpay-timestamp")) - .build(); - } - - // TODO @芋艿:可能是 wxjava 的 bug:https://github.com/binarywang/WxJava/issues/1557 - private void fixV3HttpClientConnectionPoolShutDown() { - client.getConfig().setApiV3HttpClient(null); - } - - static String formatDateV2(LocalDateTime time) { - return TemporalAccessorUtil.format(time.atZone(ZoneId.systemDefault()), PURE_DATETIME_PATTERN); - } - - static LocalDateTime parseDateV2(String time) { - return LocalDateTimeUtil.parse(time, PURE_DATETIME_PATTERN); - } - - static LocalDateTime parseDateV2B(String time) { - return LocalDateTimeUtil.parse(time, NORM_DATETIME_PATTERN); - } - - static String formatDateV3(LocalDateTime time) { - return TemporalAccessorUtil.format(time.atZone(ZoneId.systemDefault()), UTC_WITH_XXX_OFFSET_PATTERN); - } - - static LocalDateTime parseDateV3(String time) { - return LocalDateTimeUtil.parse(time, UTC_WITH_XXX_OFFSET_PATTERN); - } - - static String getErrorCode(WxPayException e) { - if (StrUtil.isNotEmpty(e.getErrCode())) { - return e.getErrCode(); - } - if (StrUtil.isNotEmpty(e.getCustomErrorMsg())) { - return "CUSTOM_ERROR"; - } - return e.getReturnCode(); - } - - static String getErrorMessage(WxPayException e) { - if (StrUtil.isNotEmpty(e.getErrCode())) { - return e.getErrCodeDes(); - } - if (StrUtil.isNotEmpty(e.getCustomErrorMsg())) { - return e.getCustomErrorMsg(); - } - return e.getReturnMsg(); - } - -} diff --git a/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/weixin/WxAppPayClient.java b/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/weixin/WxAppPayClient.java deleted file mode 100644 index ec88aa7..0000000 --- a/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/weixin/WxAppPayClient.java +++ /dev/null @@ -1,63 +0,0 @@ -package cn.iocoder.yudao.framework.pay.core.client.impl.weixin; - -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.enums.channel.PayChannelEnum; -import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderDisplayModeEnum; -import com.github.binarywang.wxpay.bean.order.WxPayAppOrderResult; -import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderRequest; -import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderV3Request; -import com.github.binarywang.wxpay.bean.result.WxPayUnifiedOrderV3Result; -import com.github.binarywang.wxpay.bean.result.enums.TradeTypeEnum; -import com.github.binarywang.wxpay.constant.WxPayConstants; -import com.github.binarywang.wxpay.exception.WxPayException; -import lombok.extern.slf4j.Slf4j; - -import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString; - -/** - * 微信支付【App 支付】的 PayClient 实现类 - * - * 文档:App 支付 - * - * // TODO 芋艿:未详细测试,因为手头没 App - * - * @author 芋道源码 - */ -@Slf4j -public class WxAppPayClient extends AbstractWxPayClient { - - public WxAppPayClient(Long channelId, WxPayClientConfig config) { - super(channelId, PayChannelEnum.WX_APP.getCode(), config); - } - - @Override - protected void doInit() { - super.doInit(WxPayConstants.TradeType.APP); - } - - @Override - protected PayOrderRespDTO doUnifiedOrderV2(PayOrderUnifiedReqDTO reqDTO) throws WxPayException { - // 构建 WxPayUnifiedOrderRequest 对象 - WxPayUnifiedOrderRequest request = buildPayUnifiedOrderRequestV2(reqDTO); - // 执行请求 - WxPayAppOrderResult response = client.createOrder(request); - - // 转换结果 - return PayOrderRespDTO.waitingOf(PayOrderDisplayModeEnum.APP.getMode(), toJsonString(response), - reqDTO.getOutTradeNo(), response); - } - - @Override - protected PayOrderRespDTO doUnifiedOrderV3(PayOrderUnifiedReqDTO reqDTO) throws WxPayException { - // 构建 WxPayUnifiedOrderV3Request 对象 - WxPayUnifiedOrderV3Request request = buildPayUnifiedOrderRequestV3(reqDTO); - // 执行请求 - WxPayUnifiedOrderV3Result.AppResult response = client.createOrderV3(TradeTypeEnum.APP, request); - - // 转换结果 - return PayOrderRespDTO.waitingOf(PayOrderDisplayModeEnum.APP.getMode(), toJsonString(response), - reqDTO.getOutTradeNo(), response); - } - -} diff --git a/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/weixin/WxBarPayClient.java b/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/weixin/WxBarPayClient.java deleted file mode 100644 index d01b504..0000000 --- a/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/weixin/WxBarPayClient.java +++ /dev/null @@ -1,107 +0,0 @@ -package cn.iocoder.yudao.framework.pay.core.client.impl.weixin; - -import cn.hutool.core.map.MapUtil; -import cn.hutool.core.thread.ThreadUtil; -import cn.hutool.core.util.StrUtil; -import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils; -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.enums.channel.PayChannelEnum; -import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderDisplayModeEnum; -import com.github.binarywang.wxpay.bean.request.WxPayMicropayRequest; -import com.github.binarywang.wxpay.bean.result.WxPayMicropayResult; -import com.github.binarywang.wxpay.constant.WxPayConstants; -import com.github.binarywang.wxpay.exception.WxPayException; -import lombok.extern.slf4j.Slf4j; - -import java.time.Duration; -import java.time.LocalDateTime; -import java.util.concurrent.TimeUnit; - -import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.invalidParamException; -import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString; - -/** - * 微信支付【付款码支付】的 PayClient 实现类 - * - * 文档:付款码支付 - * - * @author 芋道源码 - */ -@Slf4j -public class WxBarPayClient extends AbstractWxPayClient { - - /** - * 微信付款码的过期时间 - */ - private static final Duration AUTH_CODE_EXPIRE = Duration.ofMinutes(3); - - public WxBarPayClient(Long channelId, WxPayClientConfig config) { - super(channelId, PayChannelEnum.WX_BAR.getCode(), config); - } - - @Override - protected void doInit() { - super.doInit(WxPayConstants.TradeType.MICROPAY); - } - - @Override - protected PayOrderRespDTO doUnifiedOrderV2(PayOrderUnifiedReqDTO reqDTO) throws WxPayException { - // 由于付款码需要不断轮询,所以需要在较短的时间完成支付 - LocalDateTime expireTime = LocalDateTimeUtils.addTime(AUTH_CODE_EXPIRE); - if (expireTime.isAfter(reqDTO.getExpireTime())) { - expireTime = reqDTO.getExpireTime(); - } - // 构建 WxPayMicropayRequest 对象 - WxPayMicropayRequest request = WxPayMicropayRequest.newBuilder() - .outTradeNo(reqDTO.getOutTradeNo()) - .body(reqDTO.getSubject()) - .detail(reqDTO.getBody()) - .totalFee(reqDTO.getPrice()) // 单位分 - .timeExpire(formatDateV2(expireTime)) - .spbillCreateIp(reqDTO.getUserIp()) - .authCode(getAuthCode(reqDTO)) - .build(); - // 执行请求,重试直到失败(过期),或者成功 - WxPayException lastWxPayException = null; - for (int i = 1; i < Byte.MAX_VALUE; i++) { - try { - WxPayMicropayResult response = client.micropay(request); - // 支付成功,例如说:1)用户输入了密码;2)用户免密支付 - return PayOrderRespDTO.successOf(response.getTransactionId(), response.getOpenid(), parseDateV2(response.getTimeEnd()), - response.getOutTradeNo(), response) - .setDisplayMode(PayOrderDisplayModeEnum.BAR_CODE.getMode()); - } catch (WxPayException ex) { - lastWxPayException = ex; - // 如果不满足这 3 种任一的,则直接抛出 WxPayException 异常,不仅需处理 - // 1. SYSTEMERROR:接口返回错误:请立即调用被扫订单结果查询API,查询当前订单状态,并根据订单的状态决定下一步的操作。 - // 2. USERPAYING:用户支付中,需要输入密码:等待 5 秒,然后调用被扫订单结果查询 API,查询当前订单的不同状态,决定下一步的操作。 - // 3. BANKERROR:银行系统异常:请立即调用被扫订单结果查询 API,查询当前订单的不同状态,决定下一步的操作。 - if (!StrUtil.equalsAny(ex.getErrCode(), "SYSTEMERROR", "USERPAYING", "BANKERROR")) { - throw ex; - } - // 等待 5 秒,继续下一轮重新发起支付 - log.info("[doUnifiedOrderV2][发起微信 Bar 支付第({})失败,等待下一轮重试,请求({}),响应({})]", i, - toJsonString(request), ex.getMessage()); - ThreadUtil.sleep(5, TimeUnit.SECONDS); - } - } - throw lastWxPayException; - } - - @Override - protected PayOrderRespDTO doUnifiedOrderV3(PayOrderUnifiedReqDTO reqDTO) throws WxPayException { - return doUnifiedOrderV2(reqDTO); - } - - // ========== 各种工具方法 ========== - - static String getAuthCode(PayOrderUnifiedReqDTO reqDTO) { - String authCode = MapUtil.getStr(reqDTO.getChannelExtras(), "authCode"); - if (StrUtil.isEmpty(authCode)) { - throw invalidParamException("支付请求的 authCode 不能为空!"); - } - return authCode; - } - -} diff --git a/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/weixin/WxLitePayClient.java b/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/weixin/WxLitePayClient.java deleted file mode 100644 index 9929955..0000000 --- a/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/weixin/WxLitePayClient.java +++ /dev/null @@ -1,22 +0,0 @@ -package cn.iocoder.yudao.framework.pay.core.client.impl.weixin; - -import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum; -import lombok.extern.slf4j.Slf4j; - -/** - * 微信支付【小程序】的 PayClient 实现类 - * - * 由于公众号和小程序的微信支付逻辑一致,所以直接进行继承 - * - * 文档:JSAPI 下单 - * - * @author zwy - */ -@Slf4j -public class WxLitePayClient extends WxPubPayClient { - - public WxLitePayClient(Long channelId, WxPayClientConfig config) { - super(channelId, PayChannelEnum.WX_LITE.getCode(), config); - } - -} diff --git a/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/weixin/WxNativePayClient.java b/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/weixin/WxNativePayClient.java deleted file mode 100644 index 7961ad5..0000000 --- a/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/weixin/WxNativePayClient.java +++ /dev/null @@ -1,59 +0,0 @@ -package cn.iocoder.yudao.framework.pay.core.client.impl.weixin; - -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.enums.channel.PayChannelEnum; -import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderDisplayModeEnum; -import com.github.binarywang.wxpay.bean.order.WxPayNativeOrderResult; -import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderRequest; -import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderV3Request; -import com.github.binarywang.wxpay.bean.result.enums.TradeTypeEnum; -import com.github.binarywang.wxpay.constant.WxPayConstants; -import com.github.binarywang.wxpay.exception.WxPayException; -import lombok.extern.slf4j.Slf4j; - -/** - * 微信支付【Native 二维码】的 PayClient 实现类 - * - * 文档:Native 下单 - * - * @author zwy - */ -@Slf4j -public class WxNativePayClient extends AbstractWxPayClient { - - public WxNativePayClient(Long channelId, WxPayClientConfig config) { - super(channelId, PayChannelEnum.WX_NATIVE.getCode(), config); - } - - @Override - protected void doInit() { - super.doInit(WxPayConstants.TradeType.NATIVE); - } - - @Override - protected PayOrderRespDTO doUnifiedOrderV2(PayOrderUnifiedReqDTO reqDTO) throws WxPayException { - // 构建 WxPayUnifiedOrderRequest 对象 - WxPayUnifiedOrderRequest request = buildPayUnifiedOrderRequestV2(reqDTO) - .setProductId(reqDTO.getOutTradeNo()); // V2 必须传递 productId,无需在微信配置。该参数在 V3 简化,无需传递! - // 执行请求 - WxPayNativeOrderResult response = client.createOrder(request); - - // 转换结果 - return PayOrderRespDTO.waitingOf(PayOrderDisplayModeEnum.QR_CODE.getMode(), response.getCodeUrl(), - reqDTO.getOutTradeNo(), response); - } - - @Override - protected PayOrderRespDTO doUnifiedOrderV3(PayOrderUnifiedReqDTO reqDTO) throws WxPayException { - // 构建 WxPayUnifiedOrderV3Request 对象 - WxPayUnifiedOrderV3Request request = buildPayUnifiedOrderRequestV3(reqDTO); - // 执行请求 - String response = client.createOrderV3(TradeTypeEnum.NATIVE, request); - - // 转换结果 - return PayOrderRespDTO.waitingOf(PayOrderDisplayModeEnum.QR_CODE.getMode(), response, - reqDTO.getOutTradeNo(), response); - } - -} diff --git a/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/weixin/WxPayClientConfig.java b/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/weixin/WxPayClientConfig.java deleted file mode 100644 index 273b982..0000000 --- a/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/weixin/WxPayClientConfig.java +++ /dev/null @@ -1,107 +0,0 @@ -package cn.iocoder.yudao.framework.pay.core.client.impl.weixin; - -import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils; -import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig; -import jakarta.validation.Validator; -import jakarta.validation.constraints.NotBlank; -import lombok.Data; - -/** - * 微信支付的 PayClientConfig 实现类 - * 属性主要来自 {@link com.github.binarywang.wxpay.config.WxPayConfig} 的必要属性 - * - * @author 芋道源码 - */ -@Data -public class WxPayClientConfig implements PayClientConfig { - - /** - * API 版本 - V2 - * - * V2 协议说明 - */ - public static final String API_VERSION_V2 = "v2"; - /** - * API 版本 - V3 - * - * V3 协议说明 - */ - public static final String API_VERSION_V3 = "v3"; - - /** - * 公众号或者小程序的 appid - * - * 只有公众号或小程序需要该字段 - */ - @NotBlank(message = "APPID 不能为空", groups = {V2.class, V3.class}) - private String appId; - /** - * 商户号 - */ - @NotBlank(message = "商户号不能为空", groups = {V2.class, V3.class}) - private String mchId; - /** - * API 版本 - */ - @NotBlank(message = "API 版本不能为空", groups = {V2.class, V3.class}) - private String apiVersion; - - // ========== V2 版本的参数 ========== - - /** - * 商户密钥 - */ - @NotBlank(message = "商户密钥不能为空", groups = V2.class) - private String mchKey; - /** - * apiclient_cert.p12 证书文件的对应字符串【base64 格式】 - * - * 为什么采用 base64 格式?因为 p12 读取后是二进制,需要转换成 base64 格式才好传输和存储 - */ - @NotBlank(message = "apiclient_cert.p12 不能为空", groups = V2.class) - private String keyContent; - - // ========== V3 版本的参数 ========== - /** - * apiclient_key.pem 证书文件的对应字符串 - */ - @NotBlank(message = "apiclient_key 不能为空", groups = V3.class) - private String privateKeyContent; - /** - * apiV3 密钥值 - */ - @NotBlank(message = "apiV3 密钥值不能为空", groups = V3.class) - private String apiV3Key; - /** - * 证书序列号(merchantSerialNumber) - */ - @NotBlank(message = "证书序列号不能为空", groups = V3.class) - private String certSerialNo; - - /** - * pub_key.pem 证书文件的对应字符串 - */ - @NotBlank(message = "pub_key.pem 不能为空", groups = V3.class) - private String publicKeyContent; - @NotBlank(message = "publicKeyId 不能为空", groups = V3.class) - private String publicKeyId; - - /** - * 分组校验 v2版本 - */ - public interface V2 { - } - - /** - * 分组校验 v3版本 - */ - public interface V3 { - } - - @Override - public void validate(Validator validator) { - ValidationUtils.validate(validator, this, - API_VERSION_V2.equals(this.getApiVersion()) ? V2.class : V3.class); - } - -} diff --git a/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/weixin/WxPubPayClient.java b/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/weixin/WxPubPayClient.java deleted file mode 100644 index 9f90b82..0000000 --- a/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/weixin/WxPubPayClient.java +++ /dev/null @@ -1,75 +0,0 @@ -package cn.iocoder.yudao.framework.pay.core.client.impl.weixin; - -import cn.hutool.core.map.MapUtil; -import cn.hutool.core.util.StrUtil; -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.enums.order.PayOrderDisplayModeEnum; -import com.github.binarywang.wxpay.bean.order.WxPayMpOrderResult; -import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderRequest; -import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderV3Request; -import com.github.binarywang.wxpay.bean.result.WxPayUnifiedOrderV3Result; -import com.github.binarywang.wxpay.bean.result.enums.TradeTypeEnum; -import com.github.binarywang.wxpay.constant.WxPayConstants; -import com.github.binarywang.wxpay.exception.WxPayException; -import lombok.extern.slf4j.Slf4j; - -import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.invalidParamException; -import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString; - -/** - * 微信支付(公众号)的 PayClient 实现类 - * - * 文档:JSAPI 下单 - * - * @author 芋道源码 - */ -@Slf4j -public class WxPubPayClient extends AbstractWxPayClient { - - protected WxPubPayClient(Long channelId, String channelCode, WxPayClientConfig config) { - super(channelId, channelCode, config); - } - - @Override - protected void doInit() { - super.doInit(WxPayConstants.TradeType.JSAPI); - } - - @Override - protected PayOrderRespDTO doUnifiedOrderV2(PayOrderUnifiedReqDTO reqDTO) throws WxPayException { - // 构建 WxPayUnifiedOrderRequest 对象 - WxPayUnifiedOrderRequest request = buildPayUnifiedOrderRequestV2(reqDTO) - .setOpenid(getOpenid(reqDTO)); - // 执行请求 - WxPayMpOrderResult response = client.createOrder(request); - - // 转换结果 - return PayOrderRespDTO.waitingOf(PayOrderDisplayModeEnum.APP.getMode(), toJsonString(response), - reqDTO.getOutTradeNo(), response); - } - - @Override - protected PayOrderRespDTO doUnifiedOrderV3(PayOrderUnifiedReqDTO reqDTO) throws WxPayException { - // 构建 WxPayUnifiedOrderRequest 对象 - WxPayUnifiedOrderV3Request request = buildPayUnifiedOrderRequestV3(reqDTO) - .setPayer(new WxPayUnifiedOrderV3Request.Payer().setOpenid(getOpenid(reqDTO))); - // 执行请求 - WxPayUnifiedOrderV3Result.JsapiResult response = client.createOrderV3(TradeTypeEnum.JSAPI, request); - - // 转换结果 - return PayOrderRespDTO.waitingOf(PayOrderDisplayModeEnum.APP.getMode(), toJsonString(response), - reqDTO.getOutTradeNo(), response); - } - - // ========== 各种工具方法 ========== - - static String getOpenid(PayOrderUnifiedReqDTO reqDTO) { - String openid = MapUtil.getStr(reqDTO.getChannelExtras(), "openid"); - if (StrUtil.isEmpty(openid)) { - throw invalidParamException("支付请求的 openid 不能为空!"); - } - return openid; - } - -} diff --git a/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/weixin/WxWapPayClient.java b/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/weixin/WxWapPayClient.java deleted file mode 100644 index d7bd996..0000000 --- a/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/weixin/WxWapPayClient.java +++ /dev/null @@ -1,62 +0,0 @@ -package cn.iocoder.yudao.framework.pay.core.client.impl.weixin; - -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.enums.channel.PayChannelEnum; -import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderDisplayModeEnum; -import com.github.binarywang.wxpay.bean.order.WxPayMwebOrderResult; -import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderRequest; -import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderV3Request; -import com.github.binarywang.wxpay.bean.result.enums.TradeTypeEnum; -import com.github.binarywang.wxpay.constant.WxPayConstants; -import com.github.binarywang.wxpay.exception.WxPayException; -import lombok.extern.slf4j.Slf4j; - -/** - * 微信支付(H5 网页)的 PayClient 实现类 - * - * 文档:H5下单API - * - * @author YYQ - */ -@Slf4j -public class WxWapPayClient extends AbstractWxPayClient { - - public WxWapPayClient(Long channelId, WxPayClientConfig config) { - super(channelId, PayChannelEnum.WX_WAP.getCode(), config); - } - - protected WxWapPayClient(Long channelId, String channelCode, WxPayClientConfig config) { - super(channelId, channelCode, config); - } - - @Override - protected void doInit() { - super.doInit(WxPayConstants.TradeType.MWEB); - } - - @Override - protected PayOrderRespDTO doUnifiedOrderV2(PayOrderUnifiedReqDTO reqDTO) throws WxPayException { - // 构建 WxPayUnifiedOrderRequest 对象 - WxPayUnifiedOrderRequest request = buildPayUnifiedOrderRequestV2(reqDTO); - // 执行请求 - WxPayMwebOrderResult response = client.createOrder(request); - - // 转换结果 - return PayOrderRespDTO.waitingOf(PayOrderDisplayModeEnum.URL.getMode(), response.getMwebUrl(), - reqDTO.getOutTradeNo(), response); - } - - @Override - protected PayOrderRespDTO doUnifiedOrderV3(PayOrderUnifiedReqDTO reqDTO) throws WxPayException { - // 构建 WxPayUnifiedOrderRequest 对象 - WxPayUnifiedOrderV3Request request = buildPayUnifiedOrderRequestV3(reqDTO); - // 执行请求 - String response = client.createOrderV3(TradeTypeEnum.H5, request); - - // 转换结果 - return PayOrderRespDTO.waitingOf(PayOrderDisplayModeEnum.URL.getMode(), response, - reqDTO.getOutTradeNo(), response); - } - -} diff --git a/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/enums/channel/PayChannelEnum.java b/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/enums/channel/PayChannelEnum.java deleted file mode 100644 index 925fb45..0000000 --- a/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/enums/channel/PayChannelEnum.java +++ /dev/null @@ -1,69 +0,0 @@ -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.weixin.WxPayClientConfig; -import lombok.AllArgsConstructor; -import lombok.Getter; - -/** - * 支付渠道的编码的枚举 - * - * @author 芋道源码 - */ -@Getter -@AllArgsConstructor -public enum PayChannelEnum { - - WX_PUB("wx_pub", "微信 JSAPI 支付", WxPayClientConfig.class), // 公众号网页 - WX_LITE("wx_lite", "微信小程序支付", WxPayClientConfig.class), - WX_APP("wx_app", "微信 App 支付", WxPayClientConfig.class), - WX_NATIVE("wx_native", "微信 Native 支付", WxPayClientConfig.class), - WX_WAP("wx_wap", "微信 Wap 网站支付", WxPayClientConfig.class), // H5 网页 - WX_BAR("wx_bar", "微信付款码支付", WxPayClientConfig.class), - - ALIPAY_PC("alipay_pc", "支付宝 PC 网站支付", AlipayPayClientConfig.class), - ALIPAY_WAP("alipay_wap", "支付宝 Wap 网站支付", AlipayPayClientConfig.class), - ALIPAY_APP("alipay_app", "支付宝App 支付", AlipayPayClientConfig.class), - ALIPAY_QR("alipay_qr", "支付宝扫码支付", AlipayPayClientConfig.class), - ALIPAY_BAR("alipay_bar", "支付宝条码支付", AlipayPayClientConfig.class), - MOCK("mock", "模拟支付", NonePayClientConfig.class), - - WALLET("wallet", "钱包支付", NonePayClientConfig.class); - - /** - * 编码 - * - * 参考 支付渠道属性值 - */ - private final String code; - /** - * 名字 - */ - private final String name; - - /** - * 配置类 - */ - private final Class configClass; - - /** - * 微信支付 - */ - public static final String WECHAT = "WECHAT"; - - /** - * 支付宝支付 - */ - public static final String ALIPAY = "ALIPAY"; - - public static PayChannelEnum getByCode(String code) { - return ArrayUtil.firstMatch(o -> o.getCode().equals(code), values()); - } - - public static boolean isAlipay(String channelCode) { - return channelCode != null && channelCode.startsWith("alipay"); - } -} diff --git a/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/enums/order/PayOrderDisplayModeEnum.java b/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/enums/order/PayOrderDisplayModeEnum.java deleted file mode 100644 index 129c406..0000000 --- a/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/enums/order/PayOrderDisplayModeEnum.java +++ /dev/null @@ -1,29 +0,0 @@ -package cn.iocoder.yudao.framework.pay.core.enums.order; - -import lombok.AllArgsConstructor; -import lombok.Getter; - -/** - * 支付 UI 展示模式 - * - * @author 芋道源码 - */ -@Getter -@AllArgsConstructor -public enum PayOrderDisplayModeEnum { - - URL("url"), // Redirect 跳转链接的方式 - IFRAME("iframe"), // IFrame 内嵌链接的方式【目前暂时用不到】 - FORM("form"), // HTML 表单提交 - QR_CODE("qr_code"), // 二维码的文字内容 - QR_CODE_URL("qr_code_url"), // 二维码的图片链接 - BAR_CODE("bar_code"), // 条形码 - APP("app"), // 应用:Android、iOS、微信小程序、微信公众号等,需要做自定义处理的 - ; - - /** - * 展示模式 - */ - private final String mode; - -} diff --git a/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/enums/order/PayOrderStatusRespEnum.java b/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/enums/order/PayOrderStatusRespEnum.java deleted file mode 100644 index eac381c..0000000 --- a/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/enums/order/PayOrderStatusRespEnum.java +++ /dev/null @@ -1,56 +0,0 @@ -package cn.iocoder.yudao.framework.pay.core.enums.order; - -import lombok.AllArgsConstructor; -import lombok.Getter; - -import java.util.Objects; - -/** - * 渠道的支付状态枚举 - * - * @author 芋道源码 - */ -@Getter -@AllArgsConstructor -public enum PayOrderStatusRespEnum { - - WAITING(0, "未支付"), - SUCCESS(10, "支付成功"), - REFUND(20, "已退款"), - CLOSED(30, "支付关闭"), - ; - - private final Integer status; - private final String name; - - /** - * 判断是否支付成功 - * - * @param status 状态 - * @return 是否支付成功 - */ - public static boolean isSuccess(Integer status) { - return Objects.equals(status, SUCCESS.getStatus()); - } - - /** - * 判断是否已退款 - * - * @param status 状态 - * @return 是否支付成功 - */ - public static boolean isRefund(Integer status) { - return Objects.equals(status, REFUND.getStatus()); - } - - /** - * 判断是否支付关闭 - * - * @param status 状态 - * @return 是否支付关闭 - */ - public static boolean isClosed(Integer status) { - return Objects.equals(status, CLOSED.getStatus()); - } - -} diff --git a/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/enums/refund/PayRefundStatusRespEnum.java b/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/enums/refund/PayRefundStatusRespEnum.java deleted file mode 100644 index 8ad61a6..0000000 --- a/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/enums/refund/PayRefundStatusRespEnum.java +++ /dev/null @@ -1,32 +0,0 @@ -package cn.iocoder.yudao.framework.pay.core.enums.refund; - -import lombok.AllArgsConstructor; -import lombok.Getter; - -import java.util.Objects; - -/** - * 渠道的退款状态枚举 - * - * @author jason - */ -@Getter -@AllArgsConstructor -public enum PayRefundStatusRespEnum { - - WAITING(0, "等待退款"), - SUCCESS(10, "退款成功"), - FAILURE(20, "退款失败"); - - private final Integer status; - private final String name; - - public static boolean isSuccess(Integer status) { - return Objects.equals(status, SUCCESS.getStatus()); - } - - public static boolean isFailure(Integer status) { - return Objects.equals(status, FAILURE.getStatus()); - } - -} diff --git a/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/enums/transfer/PayTransferStatusRespEnum.java b/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/enums/transfer/PayTransferStatusRespEnum.java deleted file mode 100644 index 2731aab..0000000 --- a/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/enums/transfer/PayTransferStatusRespEnum.java +++ /dev/null @@ -1,37 +0,0 @@ -package cn.iocoder.yudao.framework.pay.core.enums.transfer; - -import lombok.AllArgsConstructor; -import lombok.Getter; - -import java.util.Objects; - -/** - * 渠道的转账状态枚举 - * - * @author jason - */ -@Getter -@AllArgsConstructor -public enum PayTransferStatusRespEnum { - - WAITING(0, "等待转账"), - PROCESSING(5, "转账进行中"), - SUCCESS(10, "转账成功"), - CLOSED(20, "转账关闭"); - - private final Integer status; - private final String name; - - public static boolean isSuccess(Integer status) { - return Objects.equals(status, SUCCESS.getStatus()); - } - - public static boolean isClosed(Integer status) { - return Objects.equals(status, CLOSED.getStatus()); - } - - public static boolean isProcessing(Integer status) { - return Objects.equals(status, PROCESSING.getStatus()); - } - -} diff --git a/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports deleted file mode 100644 index f2a8bf1..0000000 --- a/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ /dev/null @@ -1 +0,0 @@ -cn.iocoder.yudao.framework.pay.config.YudaoPayAutoConfiguration \ No newline at end of file diff --git a/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/test/java/cn/iocoder/yudao/framework/pay/core/client/impl/PayClientFactoryImplIntegrationTest.java b/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/test/java/cn/iocoder/yudao/framework/pay/core/client/impl/PayClientFactoryImplIntegrationTest.java deleted file mode 100644 index 37c9e90..0000000 --- a/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/test/java/cn/iocoder/yudao/framework/pay/core/client/impl/PayClientFactoryImplIntegrationTest.java +++ /dev/null @@ -1,133 +0,0 @@ -package cn.iocoder.yudao.framework.pay.core.client.impl; - -import cn.hutool.core.io.IoUtil; -import cn.hutool.core.util.RandomUtil; -import cn.iocoder.yudao.framework.pay.core.client.PayClient; -import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO; -import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayPayClientConfig; -import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayQrPayClient; -import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayWapPayClient; -import cn.iocoder.yudao.framework.pay.core.client.impl.weixin.WxPayClientConfig; -import cn.iocoder.yudao.framework.pay.core.client.impl.weixin.WxPubPayClient; -import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; - -import java.io.FileInputStream; -import java.io.FileNotFoundException; - -/** - * {@link PayClientFactoryImpl} 的集成测试 - * - * @author 芋道源码 - */ -@Disabled -public class PayClientFactoryImplIntegrationTest { - - private static final String SERVER_URL_SANDBOX = "https://openapi.alipaydev.com/gateway.do"; - - private final PayClientFactoryImpl payClientFactory = new PayClientFactoryImpl(); - - /** - * {@link WxPubPayClient} 的 V2 版本 - */ - @Test - public void testCreatePayClient_WX_PUB_V2() { - // 创建配置 - WxPayClientConfig config = new WxPayClientConfig(); - config.setAppId("wx041349c6f39b268b"); - config.setMchId("1545083881"); - config.setApiVersion(WxPayClientConfig.API_VERSION_V2); - config.setMchKey("0alL64UDQdlCwiKZ73ib7ypaIjMns06p"); - // 创建客户端 - Long channelId = RandomUtil.randomLong(); - payClientFactory.createOrUpdatePayClient(channelId, PayChannelEnum.WX_PUB.getCode(), config); - PayClient client = payClientFactory.getPayClient(channelId); - // 发起支付 - PayOrderUnifiedReqDTO reqDTO = buildPayOrderUnifiedReqDTO(); -// CommonResult result = client.unifiedOrder(reqDTO); -// System.out.println(result); - } - - /** - * {@link WxPubPayClient} 的 V3 版本 - */ - @Test - public void testCreatePayClient_WX_PUB_V3() throws FileNotFoundException { - // 创建配置 - WxPayClientConfig config = new WxPayClientConfig(); - config.setAppId("wx041349c6f39b268b"); - config.setMchId("1545083881"); - config.setApiVersion(WxPayClientConfig.API_VERSION_V3); - config.setPrivateKeyContent(IoUtil.readUtf8(new FileInputStream("/Users/yunai/Downloads/wx_pay/apiclient_key.pem"))); -// config.setPrivateCertContent(IoUtil.readUtf8(new FileInputStream("/Users/yunai/Downloads/wx_pay/apiclient_cert.pem"))); - config.setApiV3Key("joerVi8y5DJ3o4ttA0o1uH47Xz1u2Ase"); - // 创建客户端 - Long channelId = RandomUtil.randomLong(); - payClientFactory.createOrUpdatePayClient(channelId, PayChannelEnum.WX_PUB.getCode(), config); - PayClient client = payClientFactory.getPayClient(channelId); - // 发起支付 - PayOrderUnifiedReqDTO reqDTO = buildPayOrderUnifiedReqDTO(); -// CommonResult result = client.unifiedOrder(reqDTO); -// System.out.println(result); - } - - /** - * {@link AlipayQrPayClient} - */ - @Test - @SuppressWarnings("unchecked") - public void testCreatePayClient_ALIPAY_QR() { - // 创建配置 - AlipayPayClientConfig config = new AlipayPayClientConfig(); - config.setAppId("2021000118634035"); - config.setServerUrl(SERVER_URL_SANDBOX); - config.setSignType(AlipayPayClientConfig.SIGN_TYPE_DEFAULT); - config.setPrivateKey("MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCHsEV1cDupwJv890x84qbppUtRIfhaKSwSVN0thCcsDCaAsGR5MZslDkO8NCT9V4r2SVXjyY7eJUZlZd1M0C8T01Tg4UOx5LUbic0O3A1uJMy6V1n9IyYwbAW3AEZhBd5bSbPgrqvmv3NeWSTQT6Anxnllf+2iDH6zyA2fPl7cYyQtbZoDJQFGqr4F+cGh2R6akzRKNoBkAeMYwoY6es2lX8sJxCVPWUmxNUoL3tScwlSpd7Bxw0q9c/X01jMwuQ0+Va358zgFiGERTE6yD01eu40OBDXOYO3z++y+TAYHlQQ2toMO63trepo88X3xV3R44/1DH+k2pAm2IF5ixiLrAgMBAAECggEAPx3SoXcseaD7rmcGcE0p4SMfbsUDdkUSmBBbtfF0GzwnqNLkWa+mgE0rWt9SmXngTQH97vByAYmLPl1s3G82ht1V7Sk7yQMe74lhFllr8eEyTjeVx3dTK1EEM4TwN+936DTXdFsr4TELJEcJJdD0KaxcCcfBLRDs2wnitEFZ9N+GoZybVmY8w0e0MI7PLObUZ2l0X4RurQnfG9ZxjXjC7PkeMVv7cGGylpNFi3BbvkRhdhLPDC2E6wqnr9e7zk+hiENivAezXrtxtwKovzCtnWJ1r0IO14Rh47H509Ic0wFnj+o5YyUL4LdmpL7yaaH6fM7zcSLFjNZPHvZCKPwYcQKBgQDQFho98QvnL8ex4v6cry4VitGpjSXm1qP3vmMQk4rTsn8iPWtcxPjqGEqOQJjdi4Mi0VZKQOLFwlH0kl95wNrD/isJ4O1yeYfX7YAXApzHqYNINzM79HemO3Yx1qLMW3okRFJ9pPRzbQ9qkTpsaegsmyX316zOBhzGRYjKbutTYwKBgQCm7phr9XdFW5Vh+XR90mVs483nrLmMiDKg7YKxSLJ8amiDjzPejCn7i95Hah08P+2MIZLIPbh2VLacczR6ltRRzN5bg5etFuqSgfkuHyxpoDmpjbe08+Q2h8JBYqcC5Nhv1AKU4iOUhVLHo/FBAQliMcGc/J3eiYTFC7EsNx382QKBgClb20doe7cttgFTXswBvaUmfFm45kmla924B7SpvrQpDD/f+VDtDZRp05fGmxuduSjYdtA3aVtpLiTwWu22OUUvZZqHDGruYOO4Hvdz23mL5b4ayqImCwoNU4bAZIc9v18p/UNf3/55NNE3oGcf/bev9rH2OjCQ4nM+Ktwhg8CFAoGACSgvbkShzUkv0ZcIf9ppu+ZnJh1AdGgINvGwaJ8vQ0nm/8h8NOoFZ4oNoGc+wU5Ubops7dUM6FjPR5e+OjdJ4E7Xp7d5O4J1TaIZlCEbo5OpdhaTDDcQvrkFu+Z4eN0qzj+YAKjDAOOrXc4tbr5q0FsgXscwtcNfaBuzFVTUrUkCgYEAwzPnMNhWG3zOWLUs2QFA2GP4Y+J8cpUYfj6pbKKzeLwyG9qBwF1NJpN8m+q9q7V9P2LY+9Lp9e1mGsGeqt5HMEA3P6vIpcqLJLqE/4PBLLRzfccTcmqb1m71+erxTRhHBRkGS+I7dZEb3olQfnS1Y1tpMBxiwYwR3LW4oXuJwj8="); - config.setAlipayPublicKey("MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnq90KnF4dTnlzzmxpujbI05OYqi5WxAS6cL0gnZFv2gK51HExF8v/BaP7P979PhFMgWTqmOOI+Dtno5s+yD09XTY1WkshbLk6i4g2Xlr8fyW9ODnkU88RI2w9UdPhQU4cPPwBNlrsYhKkVK2OxwM3kFqjoBBY0CZoZCsSQ3LDH5WeZqPArlsS6xa2zqJBuuoKjMrdpELl3eXSjP8K54eDJCbeetCZNKWLL3DPahTPB7LZikfYmslb0QUvCgGapD0xkS7eVq70NaL1G57MWABs4tbfWgxike4Daj3EfUrzIVspQxj7w8HEj9WozJPgL88kSJSits0pqD3n5r8HSuseQIDAQAB"); - // 创建客户端 - Long channelId = RandomUtil.randomLong(); - payClientFactory.createOrUpdatePayClient(channelId, PayChannelEnum.ALIPAY_QR.getCode(), config); - PayClient client = payClientFactory.getPayClient(channelId); - // 发起支付 - PayOrderUnifiedReqDTO reqDTO = buildPayOrderUnifiedReqDTO(); - reqDTO.setNotifyUrl("http://yunai.natapp1.cc/admin-api/pay/notify/callback/18"); // TODO @tina: 这里改成你的 natapp 回调地址 -// CommonResult result = (CommonResult) client.unifiedOrder(reqDTO); -// System.out.println(JsonUtils.toJsonString(result)); -// System.out.println(result.getData().getQrCode()); - } - - /** - * {@link AlipayWapPayClient} - */ - @Test - public void testCreatePayClient_ALIPAY_WAP() { - // 创建配置 - AlipayPayClientConfig config = new AlipayPayClientConfig(); - config.setAppId("2021000118634035"); - config.setServerUrl(SERVER_URL_SANDBOX); - config.setSignType(AlipayPayClientConfig.SIGN_TYPE_DEFAULT); - config.setPrivateKey("MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCHsEV1cDupwJv890x84qbppUtRIfhaKSwSVN0thCcsDCaAsGR5MZslDkO8NCT9V4r2SVXjyY7eJUZlZd1M0C8T01Tg4UOx5LUbic0O3A1uJMy6V1n9IyYwbAW3AEZhBd5bSbPgrqvmv3NeWSTQT6Anxnllf+2iDH6zyA2fPl7cYyQtbZoDJQFGqr4F+cGh2R6akzRKNoBkAeMYwoY6es2lX8sJxCVPWUmxNUoL3tScwlSpd7Bxw0q9c/X01jMwuQ0+Va358zgFiGERTE6yD01eu40OBDXOYO3z++y+TAYHlQQ2toMO63trepo88X3xV3R44/1DH+k2pAm2IF5ixiLrAgMBAAECggEAPx3SoXcseaD7rmcGcE0p4SMfbsUDdkUSmBBbtfF0GzwnqNLkWa+mgE0rWt9SmXngTQH97vByAYmLPl1s3G82ht1V7Sk7yQMe74lhFllr8eEyTjeVx3dTK1EEM4TwN+936DTXdFsr4TELJEcJJdD0KaxcCcfBLRDs2wnitEFZ9N+GoZybVmY8w0e0MI7PLObUZ2l0X4RurQnfG9ZxjXjC7PkeMVv7cGGylpNFi3BbvkRhdhLPDC2E6wqnr9e7zk+hiENivAezXrtxtwKovzCtnWJ1r0IO14Rh47H509Ic0wFnj+o5YyUL4LdmpL7yaaH6fM7zcSLFjNZPHvZCKPwYcQKBgQDQFho98QvnL8ex4v6cry4VitGpjSXm1qP3vmMQk4rTsn8iPWtcxPjqGEqOQJjdi4Mi0VZKQOLFwlH0kl95wNrD/isJ4O1yeYfX7YAXApzHqYNINzM79HemO3Yx1qLMW3okRFJ9pPRzbQ9qkTpsaegsmyX316zOBhzGRYjKbutTYwKBgQCm7phr9XdFW5Vh+XR90mVs483nrLmMiDKg7YKxSLJ8amiDjzPejCn7i95Hah08P+2MIZLIPbh2VLacczR6ltRRzN5bg5etFuqSgfkuHyxpoDmpjbe08+Q2h8JBYqcC5Nhv1AKU4iOUhVLHo/FBAQliMcGc/J3eiYTFC7EsNx382QKBgClb20doe7cttgFTXswBvaUmfFm45kmla924B7SpvrQpDD/f+VDtDZRp05fGmxuduSjYdtA3aVtpLiTwWu22OUUvZZqHDGruYOO4Hvdz23mL5b4ayqImCwoNU4bAZIc9v18p/UNf3/55NNE3oGcf/bev9rH2OjCQ4nM+Ktwhg8CFAoGACSgvbkShzUkv0ZcIf9ppu+ZnJh1AdGgINvGwaJ8vQ0nm/8h8NOoFZ4oNoGc+wU5Ubops7dUM6FjPR5e+OjdJ4E7Xp7d5O4J1TaIZlCEbo5OpdhaTDDcQvrkFu+Z4eN0qzj+YAKjDAOOrXc4tbr5q0FsgXscwtcNfaBuzFVTUrUkCgYEAwzPnMNhWG3zOWLUs2QFA2GP4Y+J8cpUYfj6pbKKzeLwyG9qBwF1NJpN8m+q9q7V9P2LY+9Lp9e1mGsGeqt5HMEA3P6vIpcqLJLqE/4PBLLRzfccTcmqb1m71+erxTRhHBRkGS+I7dZEb3olQfnS1Y1tpMBxiwYwR3LW4oXuJwj8="); - config.setAlipayPublicKey("MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnq90KnF4dTnlzzmxpujbI05OYqi5WxAS6cL0gnZFv2gK51HExF8v/BaP7P979PhFMgWTqmOOI+Dtno5s+yD09XTY1WkshbLk6i4g2Xlr8fyW9ODnkU88RI2w9UdPhQU4cPPwBNlrsYhKkVK2OxwM3kFqjoBBY0CZoZCsSQ3LDH5WeZqPArlsS6xa2zqJBuuoKjMrdpELl3eXSjP8K54eDJCbeetCZNKWLL3DPahTPB7LZikfYmslb0QUvCgGapD0xkS7eVq70NaL1G57MWABs4tbfWgxike4Daj3EfUrzIVspQxj7w8HEj9WozJPgL88kSJSits0pqD3n5r8HSuseQIDAQAB"); - // 创建客户端 - Long channelId = RandomUtil.randomLong(); - payClientFactory.createOrUpdatePayClient(channelId, PayChannelEnum.ALIPAY_WAP.getCode(), config); - PayClient client = payClientFactory.getPayClient(channelId); - // 发起支付 - PayOrderUnifiedReqDTO reqDTO = buildPayOrderUnifiedReqDTO(); -// CommonResult result = client.unifiedOrder(reqDTO); -// System.out.println(JsonUtils.toJsonString(result)); - } - - private static PayOrderUnifiedReqDTO buildPayOrderUnifiedReqDTO() { - PayOrderUnifiedReqDTO reqDTO = new PayOrderUnifiedReqDTO(); - reqDTO.setPrice(123); - reqDTO.setSubject("IPhone 13"); - reqDTO.setBody("biubiubiu"); - reqDTO.setOutTradeNo(String.valueOf(System.currentTimeMillis())); - reqDTO.setUserIp("127.0.0.1"); - reqDTO.setNotifyUrl("http://127.0.0.1:8080"); - return reqDTO; - } - -} diff --git a/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/test/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AbstractAlipayClientTest.java b/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/test/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AbstractAlipayClientTest.java deleted file mode 100644 index 7c45b6a..0000000 --- a/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/test/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AbstractAlipayClientTest.java +++ /dev/null @@ -1,221 +0,0 @@ -package cn.iocoder.yudao.framework.pay.core.client.impl.alipay; - -import cn.hutool.core.date.LocalDateTimeUtil; -import cn.hutool.core.util.RandomUtil; -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; -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.exception.PayException; -import cn.iocoder.yudao.framework.pay.core.enums.refund.PayRefundStatusRespEnum; -import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; -import com.alipay.api.AlipayApiException; -import com.alipay.api.DefaultAlipayClient; -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; -import org.mockito.ArgumentMatcher; -import org.mockito.Mock; - -import jakarta.validation.ConstraintViolationException; -import java.util.Date; - -import static cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayPayClientConfig.MODE_PUBLIC_KEY; -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*; -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.argThat; -import static org.mockito.Mockito.when; - -/** - * 支付宝 Client 的测试基类 - * - * @author jason - */ -public abstract class AbstractAlipayClientTest extends BaseMockitoUnitTest { - - 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; - - /** - * 子类需要实现该方法. 设置 client 的具体实现 - */ - @BeforeEach - public abstract void setUp(); - - @Test - @DisplayName("支付宝 Client 初始化") - public void testDoInit() { - // 调用 - client.doInit(); - // 断言 - DefaultAlipayClient realClient = client.getClient(); - assertNotSame(defaultAlipayClient, realClient); - assertInstanceOf(DefaultSigner.class, realClient.getSigner()); - assertEquals(config.getPrivateKey(), ((DefaultSigner) realClient.getSigner()).getPrivateKey()); - } - - @Test - @DisplayName("支付宝 Client 统一退款:成功") - public void testUnifiedRefund_success() throws AlipayApiException { - // mock 方法 - String notifyUrl = randomURL(); - Date refundTime = randomDate(); - String outRefundNo = randomString(); - String outTradeNo = randomString(); - Integer refundAmount = randomInteger(); - AlipayTradeRefundResponse response = randomPojo(AlipayTradeRefundResponse.class, o -> { - o.setSubCode(""); - o.setGmtRefundPay(refundTime); - }); - when(defaultAlipayClient.execute(argThat((ArgumentMatcher) request -> { - assertInstanceOf(AlipayTradeRefundModel.class, request.getBizModel()); - AlipayTradeRefundModel bizModel = (AlipayTradeRefundModel) request.getBizModel(); - assertEquals(outRefundNo, bizModel.getOutRequestNo()); - assertEquals(outTradeNo, bizModel.getOutTradeNo()); - assertEquals(String.valueOf(refundAmount / 100.0), bizModel.getRefundAmount()); - return true; - }))).thenReturn(response); - // 准备请求参数 - PayRefundUnifiedReqDTO refundReqDTO = randomPojo(PayRefundUnifiedReqDTO.class, o -> { - o.setOutRefundNo(outRefundNo); - o.setOutTradeNo(outTradeNo); - 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()); - assertSame(response, resp.getRawData()); - assertNull(resp.getChannelErrorCode()); - assertNull(resp.getChannelErrorMsg()); - } - - @Test - @DisplayName("支付宝 Client 统一退款:渠道返回失败") - public void test_unified_refund_channel_failed() throws AlipayApiException { - // mock 方法 - String notifyUrl = randomURL(); - String subCode = randomString(); - String subMsg = randomString(); - AlipayTradeRefundResponse response = randomPojo(AlipayTradeRefundResponse.class, o -> { - o.setSubCode(subCode); - o.setSubMsg(subMsg); - }); - when(defaultAlipayClient.execute(argThat((ArgumentMatcher) request -> { - assertInstanceOf(AlipayTradeRefundModel.class, request.getBizModel()); - return true; - }))).thenReturn(response); - // 准备请求参数 - String outRefundNo = randomString(); - String outTradeNo = randomString(); - PayRefundUnifiedReqDTO refundReqDTO = randomPojo(PayRefundUnifiedReqDTO.class, o -> { - o.setOutRefundNo(outRefundNo); - 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()); - } - - @Test - @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 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 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 testUnifiedOrder_paramInvalidate() { - // 准备请求参数 - String outTradeNo = randomString(); - String notifyUrl = randomURL(); - PayOrderUnifiedReqDTO reqDTO = randomPojo(PayOrderUnifiedReqDTO.class, o -> { - o.setOutTradeNo(outTradeNo); - o.setNotifyUrl(notifyUrl); - }); - - // 调用,并断言 - assertThrows(ConstraintViolationException.class, () -> client.unifiedOrder(reqDTO)); - } - - protected PayOrderUnifiedReqDTO buildOrderUnifiedReqDTO(String notifyUrl, String outTradeNo, Integer price) { - return randomPojo(PayOrderUnifiedReqDTO.class, o -> { - o.setOutTradeNo(outTradeNo); - o.setNotifyUrl(notifyUrl); - o.setPrice(price); - o.setSubject(RandomUtil.randomString(32)); - o.setBody(RandomUtil.randomString(32)); - }); - } - -} diff --git a/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/test/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayBarPayClientTest.java b/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/test/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayBarPayClientTest.java deleted file mode 100644 index 47f1008..0000000 --- a/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/test/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayBarPayClientTest.java +++ /dev/null @@ -1,170 +0,0 @@ -package cn.iocoder.yudao.framework.pay.core.client.impl.alipay; - -import cn.hutool.core.date.LocalDateTimeUtil; -import cn.iocoder.yudao.framework.common.exception.ServiceException; -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.enums.order.PayOrderDisplayModeEnum; -import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderStatusRespEnum; -import com.alipay.api.AlipayApiException; -import com.alipay.api.domain.AlipayTradePayModel; -import com.alipay.api.request.AlipayTradePayRequest; -import com.alipay.api.response.AlipayTradePayResponse; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.mockito.ArgumentMatcher; -import org.mockito.InjectMocks; - -import java.util.Date; -import java.util.HashMap; -import java.util.Map; - -import static cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderStatusRespEnum.CLOSED; -import static cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderStatusRespEnum.WAITING; -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*; -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.argThat; -import static org.mockito.Mockito.when; - -/** - * {@link AlipayBarPayClient} 单元测试 - * - * @author jason - */ -public class AlipayBarPayClientTest extends AbstractAlipayClientTest { - - @InjectMocks - private AlipayBarPayClient client = new AlipayBarPayClient(randomLongId(), config); - - @Override - @BeforeEach - public void setUp() { - setClient(client); - } - - @Test - @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("")); - when(defaultAlipayClient.execute(argThat((ArgumentMatcher) request -> { - assertInstanceOf(AlipayTradePayModel.class, request.getBizModel()); - assertEquals(notifyUrl, request.getNotifyUrl()); - AlipayTradePayModel model = (AlipayTradePayModel) request.getBizModel(); - assertEquals(outTradeNo, model.getOutTradeNo()); - assertEquals(String.valueOf(price / 100.0), model.getTotalAmount()); - assertEquals(authCode, model.getAuthCode()); - return true; - }))).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(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 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"); - o.setOutTradeNo(outTradeNo); - o.setTradeNo(channelNo); - o.setBuyerUserId(channelUserId); - o.setGmtPayment(payTime); - }); - 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); - // 断言 - assertEquals(PayOrderStatusRespEnum.SUCCESS.getStatus(), resp.getStatus()); - assertEquals(outTradeNo, resp.getOutTradeNo()); - 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 testUnifiedOrder_emptyAuthCode() { - // 准备参数 - PayOrderUnifiedReqDTO reqDTO = buildOrderUnifiedReqDTO(randomURL(), randomString(), randomInteger()); - - // 调用,并断言 - assertThrows(ServiceException.class, () -> client.unifiedOrder(reqDTO)); - } - - @Test - @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); - }); - 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()); - } - -} diff --git a/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/test/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayPcPayClientTest.java b/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/test/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayPcPayClientTest.java deleted file mode 100644 index d78caf2..0000000 --- a/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/test/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayPcPayClientTest.java +++ /dev/null @@ -1,131 +0,0 @@ -package cn.iocoder.yudao.framework.pay.core.client.impl.alipay; - -import cn.hutool.http.Method; -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.enums.order.PayOrderDisplayModeEnum; -import com.alipay.api.AlipayApiException; -import com.alipay.api.request.AlipayTradePagePayRequest; -import com.alipay.api.response.AlipayTradePagePayResponse; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.mockito.ArgumentMatcher; -import org.mockito.InjectMocks; - -import static cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderStatusRespEnum.CLOSED; -import static cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderStatusRespEnum.WAITING; -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*; -import static org.junit.jupiter.api.Assertions.*; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.mockito.ArgumentMatchers.argThat; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.when; - -/** - * {@link AlipayPcPayClient} 单元测试 - * - * @author jason - */ -public class AlipayPcPayClientTest extends AbstractAlipayClientTest { - - @InjectMocks - private AlipayPcPayClient client = new AlipayPcPayClient(randomLongId(), config); - - @Override - @BeforeEach - public void setUp() { - setClient(client); - } - - @Test - @DisplayName("支付宝 PC 网站支付:URL Display Mode 下单成功") - public void testUnifiedOrder_urlSuccess() throws AlipayApiException { - // mock 方法 - String notifyUrl = randomURL(); - AlipayTradePagePayResponse response = randomPojo(AlipayTradePagePayResponse.class, o -> o.setSubCode("")); - when(defaultAlipayClient.pageExecute(argThat((ArgumentMatcher) request -> true), - eq(Method.GET.name()))).thenReturn(response); - // 准备请求参数 - String outTradeNo = randomString(); - Integer price = randomInteger(); - PayOrderUnifiedReqDTO reqDTO = buildOrderUnifiedReqDTO(notifyUrl, outTradeNo, price); - reqDTO.setDisplayMode(null); - - // 调用 - PayOrderRespDTO resp = client.unifiedOrder(reqDTO); - // 断言 - assertEquals(WAITING.getStatus(), resp.getStatus()); - assertEquals(outTradeNo, resp.getOutTradeNo()); - 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("支付宝 PC 网站支付:Form Display Mode 下单成功") - public void testUnifiedOrder_formSuccess() throws AlipayApiException { - // mock 方法 - String notifyUrl = randomURL(); - AlipayTradePagePayResponse response = randomPojo(AlipayTradePagePayResponse.class, o -> o.setSubCode("")); - when(defaultAlipayClient.pageExecute(argThat((ArgumentMatcher) request -> true), - eq(Method.POST.name()))).thenReturn(response); - // 准备请求参数 - String outTradeNo = randomString(); - Integer price = randomInteger(); - PayOrderUnifiedReqDTO reqDTO = buildOrderUnifiedReqDTO(notifyUrl, outTradeNo, price); - reqDTO.setDisplayMode(PayOrderDisplayModeEnum.FORM.getMode()); - - // 调用 - PayOrderRespDTO resp = client.unifiedOrder(reqDTO); - // 断言 - assertEquals(WAITING.getStatus(), resp.getStatus()); - assertEquals(outTradeNo, resp.getOutTradeNo()); - assertNull(resp.getChannelOrderNo()); - assertNull(resp.getChannelUserId()); - assertNull(resp.getSuccessTime()); - assertEquals(PayOrderDisplayModeEnum.FORM.getMode(), resp.getDisplayMode()); - assertEquals(response.getBody(), resp.getDisplayContent()); - assertSame(response, resp.getRawData()); - assertNull(resp.getChannelErrorCode()); - assertNull(resp.getChannelErrorMsg()); - } - - @Test - @DisplayName("支付宝 PC 网站支付:渠道返回失败") - public void testUnifiedOrder_channelFailed() throws AlipayApiException { - // mock 方法 - String subCode = randomString(); - String subMsg = randomString(); - AlipayTradePagePayResponse response = randomPojo(AlipayTradePagePayResponse.class, o -> { - o.setSubCode(subCode); - o.setSubMsg(subMsg); - }); - when(defaultAlipayClient.pageExecute(argThat((ArgumentMatcher) request -> true), - eq(Method.GET.name()))).thenReturn(response); - // 准备请求参数 - String outTradeNo = randomString(); - PayOrderUnifiedReqDTO reqDTO = buildOrderUnifiedReqDTO(randomURL(), outTradeNo, randomInteger()); - reqDTO.setDisplayMode(PayOrderDisplayModeEnum.URL.getMode()); - - // 调用 - 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()); - } - -} diff --git a/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/test/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayQrPayClientTest.java b/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/test/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayQrPayClientTest.java deleted file mode 100644 index c7e1eb3..0000000 --- a/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/test/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayQrPayClientTest.java +++ /dev/null @@ -1,147 +0,0 @@ -package cn.iocoder.yudao.framework.pay.core.client.impl.alipay; - -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; -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.exception.PayException; -import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderDisplayModeEnum; -import com.alipay.api.AlipayApiException; -import com.alipay.api.request.AlipayTradePrecreateRequest; -import com.alipay.api.response.AlipayTradePrecreateResponse; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.mockito.ArgumentMatcher; -import org.mockito.InjectMocks; - -import static cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderStatusRespEnum.CLOSED; -import static cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderStatusRespEnum.WAITING; -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*; -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.argThat; -import static org.mockito.Mockito.when; - -/** - * {@link AlipayQrPayClient} 单元测试 - * - * @author jason - */ -public class AlipayQrPayClientTest extends AbstractAlipayClientTest { - - @InjectMocks - private AlipayQrPayClient client = new AlipayQrPayClient(randomLongId(), config); - - @BeforeEach - public void setUp() { - setClient(client); - } - - @Test - @DisplayName("支付宝扫描支付:下单成功") - public void testUnifiedOrder_success() throws AlipayApiException { - // mock 方法 - String notifyUrl = randomURL(); - String qrCode = randomString(); - Integer price = randomInteger(); - AlipayTradePrecreateResponse response = randomPojo(AlipayTradePrecreateResponse.class, o -> { - o.setQrCode(qrCode); - o.setSubCode(""); - }); - when(defaultAlipayClient.execute(argThat((ArgumentMatcher) request -> { - assertEquals(notifyUrl, request.getNotifyUrl()); - return true; - }))).thenReturn(response); - // 准备请求参数 - String outTradeNo = randomString(); - PayOrderUnifiedReqDTO reqDTO = buildOrderUnifiedReqDTO(notifyUrl, outTradeNo, price); - - // 调用 - PayOrderRespDTO resp = client.unifiedOrder(reqDTO); - // 断言 - assertEquals(WAITING.getStatus(), resp.getStatus()); - assertEquals(outTradeNo, resp.getOutTradeNo()); - 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 testUnifiedOrder_channelFailed() throws AlipayApiException { - // mock 方法 - String notifyUrl = randomURL(); - String subCode = randomString(); - String subMsg = randomString(); - Integer price = randomInteger(); - AlipayTradePrecreateResponse response = randomPojo(AlipayTradePrecreateResponse.class, o -> { - o.setSubCode(subCode); - o.setSubMsg(subMsg); - }); - // mock - when(defaultAlipayClient.execute(argThat((ArgumentMatcher) request -> { - assertEquals(notifyUrl, request.getNotifyUrl()); - return true; - }))).thenReturn(response); - // 准备请求参数 - 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()); - } - - @Test - @DisplayName("支付宝扫描支付, 抛出系统异常") - public void testUnifiedOrder_throwPayException() throws AlipayApiException { - // mock 方法 - String outTradeNo = randomString(); - String notifyUrl = randomURL(); - Integer price = randomInteger(); - when(defaultAlipayClient.execute(argThat((ArgumentMatcher) 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 testUnifiedOrder_throwServiceException() throws AlipayApiException { - // mock 方法 - String outTradeNo = randomString(); - String notifyUrl = randomURL(); - Integer price = randomInteger(); - when(defaultAlipayClient.execute(argThat((ArgumentMatcher) 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)); - } - -} diff --git a/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/test/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayWapPayClientTest.java b/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/test/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayWapPayClientTest.java deleted file mode 100644 index af0c717..0000000 --- a/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/test/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayWapPayClientTest.java +++ /dev/null @@ -1,111 +0,0 @@ -package cn.iocoder.yudao.framework.pay.core.client.impl.alipay; - -import cn.hutool.http.Method; -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.enums.order.PayOrderDisplayModeEnum; -import com.alipay.api.AlipayApiException; -import com.alipay.api.domain.AlipayTradeWapPayModel; -import com.alipay.api.request.AlipayTradeWapPayRequest; -import com.alipay.api.response.AlipayTradeWapPayResponse; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.mockito.ArgumentMatcher; -import org.mockito.InjectMocks; - -import static cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderStatusRespEnum.CLOSED; -import static cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderStatusRespEnum.WAITING; -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*; -import static org.junit.jupiter.api.Assertions.*; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.ArgumentMatchers.argThat; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.when; - -/** - * {@link AlipayWapPayClient} 单元测试 - * - * @author jason - */ -public class AlipayWapPayClientTest extends AbstractAlipayClientTest { - - /** - * 支付宝 H5 支付 Client - */ - @InjectMocks - private AlipayWapPayClient client = new AlipayWapPayClient(randomLongId(), config); - - @BeforeEach - public void setUp() { - setClient(client); - } - - @Test - @DisplayName("支付宝 H5 支付:下单成功") - public void testUnifiedOrder_success() throws AlipayApiException { - // mock 方法 - String h5Body = randomString(); - Integer price = randomInteger(); - AlipayTradeWapPayResponse response = randomPojo(AlipayTradeWapPayResponse.class, o -> { - o.setSubCode(""); - o.setBody(h5Body); - }); - String notifyUrl = randomURL(); - when(defaultAlipayClient.pageExecute(argThat((ArgumentMatcher) request -> { - assertInstanceOf(AlipayTradeWapPayModel.class, request.getBizModel()); - AlipayTradeWapPayModel bizModel = (AlipayTradeWapPayModel) request.getBizModel(); - assertEquals(String.valueOf(price / 100.0), bizModel.getTotalAmount()); - 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(outTradeNo, resp.getOutTradeNo()); - 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 支付:渠道返回失败") - 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); - }); - when(defaultAlipayClient.pageExecute(argThat((ArgumentMatcher) request -> true), - eq(Method.GET.name()))).thenReturn(response); - 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()); - } - -} diff --git a/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/test/java/cn/iocoder/yudao/framework/pay/core/client/impl/weixin/WxBarPayClientIntegrationTest.java b/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/test/java/cn/iocoder/yudao/framework/pay/core/client/impl/weixin/WxBarPayClientIntegrationTest.java deleted file mode 100644 index 9af11ac..0000000 --- a/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/test/java/cn/iocoder/yudao/framework/pay/core/client/impl/weixin/WxBarPayClientIntegrationTest.java +++ /dev/null @@ -1,123 +0,0 @@ -package cn.iocoder.yudao.framework.pay.core.client.impl.weixin; - -import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils; -import cn.iocoder.yudao.framework.common.util.json.JsonUtils; -import com.github.binarywang.wxpay.bean.notify.WxPayRefundNotifyResult; -import com.github.binarywang.wxpay.bean.request.WxPayMicropayRequest; -import com.github.binarywang.wxpay.bean.request.WxPayRefundRequest; -import com.github.binarywang.wxpay.bean.request.WxPayRefundV3Request; -import com.github.binarywang.wxpay.bean.result.WxPayMicropayResult; -import com.github.binarywang.wxpay.bean.result.WxPayRefundResult; -import com.github.binarywang.wxpay.bean.result.WxPayRefundV3Result; -import com.github.binarywang.wxpay.config.WxPayConfig; -import com.github.binarywang.wxpay.exception.WxPayException; -import com.github.binarywang.wxpay.service.WxPayService; -import com.github.binarywang.wxpay.service.impl.WxPayServiceImpl; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; - -import java.time.Duration; - -import static cn.iocoder.yudao.framework.pay.core.client.impl.weixin.AbstractWxPayClient.formatDateV2; - -/** - * {@link WxBarPayClient} 的集成测试,用于快速调试微信条码支付 - * - * @author 芋道源码 - */ -@Disabled -public class WxBarPayClientIntegrationTest { - - @Test - public void testPayV2() throws WxPayException { - // 创建 config 配置 - WxPayConfig config = buildWxPayConfigV2(); - // 创建 WxPayService 客户端 - WxPayService client = new WxPayServiceImpl(); - client.setConfig(config); - - // 执行发起支付 - WxPayMicropayRequest request = WxPayMicropayRequest.newBuilder() - .outTradeNo(String.valueOf(System.currentTimeMillis())) - .body("测试支付-body") - .detail("测试支付-detail") - .totalFee(1) // 单位分 - .timeExpire(formatDateV2(LocalDateTimeUtils.addTime(Duration.ofMinutes(2)))) - .spbillCreateIp("127.0.0.1") - .authCode("134298744426278497") - .build(); - System.out.println("========= request =========="); - System.out.println(JsonUtils.toJsonPrettyString(request)); - WxPayMicropayResult response = client.micropay(request); - System.out.println("========= response =========="); - System.out.println(JsonUtils.toJsonPrettyString(response)); - } - - @Test - public void testParseRefundNotifyV2() throws WxPayException { - // 创建 config 配置 - WxPayConfig config = buildWxPayConfigV2(); - // 创建 WxPayService 客户端 - WxPayService client = new WxPayServiceImpl(); - client.setConfig(config); - - // 执行解析 - String xml = "SUCCESS"; - WxPayRefundNotifyResult response = client.parseRefundNotifyResult(xml); - System.out.println(response.getReqInfo()); - } - - @Test - public void testRefundV2() throws WxPayException { - // 创建 config 配置 - WxPayConfig config = buildWxPayConfigV2(); - // 创建 WxPayService 客户端 - WxPayService client = new WxPayServiceImpl(); - client.setConfig(config); - - // 执行发起退款 - WxPayRefundRequest request = new WxPayRefundRequest() - .setOutTradeNo("1689545667276") - .setOutRefundNo(String.valueOf(System.currentTimeMillis())) - .setRefundFee(1) - .setRefundDesc("就是想退了") - .setTotalFee(1); - System.out.println("========= request =========="); - System.out.println(JsonUtils.toJsonPrettyString(request)); - WxPayRefundResult response = client.refund(request); - System.out.println("========= response =========="); - System.out.println(JsonUtils.toJsonPrettyString(response)); - } - - @Test - public void testRefundV3() throws WxPayException { - // 创建 config 配置 - WxPayConfig config = buildWxPayConfigV2(); - // 创建 WxPayService 客户端 - WxPayService client = new WxPayServiceImpl(); - client.setConfig(config); - - // 执行发起退款 - WxPayRefundV3Request request = new WxPayRefundV3Request() - .setOutTradeNo("1689506325635") - .setOutRefundNo(String.valueOf(System.currentTimeMillis())) - .setAmount(new WxPayRefundV3Request.Amount().setTotal(1).setRefund(1).setCurrency("CNY")) - .setReason("就是想退了"); - System.out.println("========= request =========="); - System.out.println(JsonUtils.toJsonPrettyString(request)); - WxPayRefundV3Result response = client.refundV3(request); - System.out.println("========= response =========="); - System.out.println(JsonUtils.toJsonPrettyString(response)); - } - - private WxPayConfig buildWxPayConfigV2() { - WxPayConfig config = new WxPayConfig(); - config.setAppId("wx62056c0d5e8db250"); - config.setMchId("1545083881"); - config.setMchKey("dS1ngeN63JLr3NRbvPH9AJy3MyUxZdim"); -// config.setSignType(WxPayConstants.SignType.MD5); - config.setKeyPath("/Users/yunai/Downloads/wx_pay/apiclient_cert.p12"); - return config; - } - -} diff --git a/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/test/java/cn/iocoder/yudao/framework/pay/core/client/impl/weixin/WxNativePayClientIntegrationTest.java b/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/test/java/cn/iocoder/yudao/framework/pay/core/client/impl/weixin/WxNativePayClientIntegrationTest.java deleted file mode 100644 index 5e73601..0000000 --- a/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/test/java/cn/iocoder/yudao/framework/pay/core/client/impl/weixin/WxNativePayClientIntegrationTest.java +++ /dev/null @@ -1,83 +0,0 @@ -package cn.iocoder.yudao.framework.pay.core.client.impl.weixin; - -import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils; -import cn.iocoder.yudao.framework.common.util.json.JsonUtils; -import com.github.binarywang.wxpay.bean.request.WxPayRefundV3Request; -import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderV3Request; -import com.github.binarywang.wxpay.bean.result.WxPayRefundV3Result; -import com.github.binarywang.wxpay.bean.result.enums.TradeTypeEnum; -import com.github.binarywang.wxpay.config.WxPayConfig; -import com.github.binarywang.wxpay.exception.WxPayException; -import com.github.binarywang.wxpay.service.WxPayService; -import com.github.binarywang.wxpay.service.impl.WxPayServiceImpl; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; - -import java.time.Duration; - -import static cn.iocoder.yudao.framework.pay.core.client.impl.weixin.AbstractWxPayClient.formatDateV3; - -/** - * {@link WxNativePayClient} 的集成测试,用于快速调试微信扫码支付 - * - * @author 芋道源码 - */ -@Disabled -public class WxNativePayClientIntegrationTest { - - @Test - public void testPayV3() throws WxPayException { - // 创建 config 配置 - WxPayConfig config = buildWxPayConfigV3(); - // 创建 WxPayService 客户端 - WxPayService client = new WxPayServiceImpl(); - client.setConfig(config); - - // 执行发起支付 - WxPayUnifiedOrderV3Request request = new WxPayUnifiedOrderV3Request() - .setOutTradeNo(String.valueOf(System.currentTimeMillis())) - .setDescription("测试支付-body") - .setAmount(new WxPayUnifiedOrderV3Request.Amount().setTotal(1)) // 单位分 - .setTimeExpire(formatDateV3(LocalDateTimeUtils.addTime(Duration.ofMinutes(2)))) - .setSceneInfo(new WxPayUnifiedOrderV3Request.SceneInfo().setPayerClientIp("127.0.0.1")) - .setNotifyUrl("http://127.0.0.1:48080"); - System.out.println("========= request =========="); - System.out.println(JsonUtils.toJsonPrettyString(request)); - String response = client.createOrderV3(TradeTypeEnum.NATIVE, request); - System.out.println("========= response =========="); - System.out.println(JsonUtils.toJsonPrettyString(response)); - } - - @Test - public void testRefundV3() throws WxPayException { - // 创建 config 配置 - WxPayConfig config = buildWxPayConfigV3(); - // 创建 WxPayService 客户端 - WxPayService client = new WxPayServiceImpl(); - client.setConfig(config); - - // 执行发起退款 - WxPayRefundV3Request request = new WxPayRefundV3Request() - .setOutTradeNo("1689545729695") - .setOutRefundNo(String.valueOf(System.currentTimeMillis())) - .setAmount(new WxPayRefundV3Request.Amount().setTotal(1).setRefund(1).setCurrency("CNY")) - .setReason("就是想退了"); - System.out.println("========= request =========="); - System.out.println(JsonUtils.toJsonPrettyString(request)); - WxPayRefundV3Result response = client.refundV3(request); - System.out.println("========= response =========="); - System.out.println(JsonUtils.toJsonPrettyString(response)); - } - - private WxPayConfig buildWxPayConfigV3() { - WxPayConfig config = new WxPayConfig(); - config.setAppId("wx62056c0d5e8db250"); - config.setMchId("1545083881"); - config.setApiV3Key("459arNsYHl1mgkiO6H9ZH5KkhFXSxaA4"); -// config.setCertSerialNo(serialNo); - config.setPrivateCertPath("/Users/yunai/Downloads/wx_pay/apiclient_cert.pem"); - config.setPrivateKeyPath("/Users/yunai/Downloads/wx_pay/apiclient_key.pem"); - return config; - } - -} -- libgit2 0.21.4